[alsa-devel] [RFC PATCH 0/4] Initial prototype of DAPM sample rate domains
Here is a first pass at adding dapm sample rate domain support. Things are still pretty rough in quite a few places and I expect some major refactoring before they are ready to merge, but things are far enough along for people to look at the approach I am taking to the problem. And should provide enough for discussion at the mini-conference, although hopefully I will get some more done next week as well.
The basic high level summary is two new concepts are added to ASoC, one being a sample rate domain and the other being a domain group. The domain groups represent widgets that are limited by hardware restrictions to always run at the same sample rate as each other. Sample rate domains represent a slot on a device for a sample rate. Both of these are currently limited to be within the scope of a single component. The core will track which groups are connected together, then as a group is powered up it will be allocated a domain based on the other groups it is connected to. Domain groups are implemented as a new type of DAPM widget and the domains themselves are a new thing which devices add through their component drivers.
As we have previously discussed my intention would be that we also add bridges between domains although I haven't started work on that yet. The current code is limited to within a single component and I think that the bridges will allow better support for propagating rates between components as well as obviously for SRCs. Currently the SRCs on wm5110 are just being handled as points where the domain groups don't connect to another peer. This allows them to function but doesn't accurately convey things like the limitations which input rates support which output rates on the ISRC.
Thanks, Charles
Charles Keepax (4): ASoC: dapm: Add support for hw_free on CODEC to CODEC links ASoC: dapm: Add support for a rate domain widget ASoC: domain: Add sample rate domain support ASoC: arizona: Add rate domain support
include/sound/soc-dapm.h | 14 ++ include/sound/soc-domain.h | 98 +++++++++++ include/sound/soc.h | 8 + sound/soc/Makefile | 2 +- sound/soc/codecs/arizona.c | 255 ++++++++++++++++++++++------ sound/soc/codecs/arizona.h | 76 ++++++++- sound/soc/codecs/wm5110.c | 414 ++++++++++++++++++++++++++++++++++----------- sound/soc/soc-core.c | 8 + sound/soc/soc-dapm.c | 57 ++++++- sound/soc/soc-domain.c | 412 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1183 insertions(+), 161 deletions(-) create mode 100644 include/sound/soc-domain.h create mode 100644 sound/soc/soc-domain.c
Currently, on power down for a CODEC to CODEC DAI link we only call digital_mute and shutdown. Provide a little more flexibility for drivers by adding a call to hw_free as well.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com --- sound/soc/soc-dapm.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8c5b065c88806..a5178845065b3 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3737,25 +3737,30 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, ret = 0; }
+ substream.stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv;
+ if (source->driver->ops->hw_free) + source->driver->ops->hw_free(&substream, + source); + source->active--; - if (source->driver->ops->shutdown) { - substream.stream = SNDRV_PCM_STREAM_CAPTURE; + if (source->driver->ops->shutdown) source->driver->ops->shutdown(&substream, source); - } }
+ substream.stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv;
+ if (sink->driver->ops->hw_free) + sink->driver->ops->hw_free(&substream, sink); + sink->active--; - if (sink->driver->ops->shutdown) { - substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + if (sink->driver->ops->shutdown) sink->driver->ops->shutdown(&substream, sink); - } } break;
The patch
ASoC: dapm: Add support for hw_free on CODEC to CODEC links
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 3c01b0e129e9486c8004e43eba3a70de7393f645 Mon Sep 17 00:00:00 2001
From: Charles Keepax ckeepax@opensource.cirrus.com Date: Thu, 11 Oct 2018 17:28:28 +0100 Subject: [PATCH] ASoC: dapm: Add support for hw_free on CODEC to CODEC links
Currently, on power down for a CODEC to CODEC DAI link we only call digital_mute and shutdown. Provide a little more flexibility for drivers by adding a call to hw_free as well.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/soc-dapm.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8c5b065c8880..a5178845065b 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3737,25 +3737,30 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, ret = 0; }
+ substream.stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv;
+ if (source->driver->ops->hw_free) + source->driver->ops->hw_free(&substream, + source); + source->active--; - if (source->driver->ops->shutdown) { - substream.stream = SNDRV_PCM_STREAM_CAPTURE; + if (source->driver->ops->shutdown) source->driver->ops->shutdown(&substream, source); - } }
+ substream.stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv;
+ if (sink->driver->ops->hw_free) + sink->driver->ops->hw_free(&substream, sink); + sink->active--; - if (sink->driver->ops->shutdown) { - substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + if (sink->driver->ops->shutdown) sink->driver->ops->shutdown(&substream, sink); - } } break;
The patch
ASoC: dapm: Add support for hw_free on CODEC to CODEC links
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 3c01b0e129e9486c8004e43eba3a70de7393f645 Mon Sep 17 00:00:00 2001
From: Charles Keepax ckeepax@opensource.cirrus.com Date: Thu, 11 Oct 2018 17:28:28 +0100 Subject: [PATCH] ASoC: dapm: Add support for hw_free on CODEC to CODEC links
Currently, on power down for a CODEC to CODEC DAI link we only call digital_mute and shutdown. Provide a little more flexibility for drivers by adding a call to hw_free as well.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/soc-dapm.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8c5b065c8880..a5178845065b 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3737,25 +3737,30 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, ret = 0; }
+ substream.stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv;
+ if (source->driver->ops->hw_free) + source->driver->ops->hw_free(&substream, + source); + source->active--; - if (source->driver->ops->shutdown) { - substream.stream = SNDRV_PCM_STREAM_CAPTURE; + if (source->driver->ops->shutdown) source->driver->ops->shutdown(&substream, source); - } }
+ substream.stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv;
+ if (sink->driver->ops->hw_free) + sink->driver->ops->hw_free(&substream, sink); + sink->active--; - if (sink->driver->ops->shutdown) { - substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + if (sink->driver->ops->shutdown) sink->driver->ops->shutdown(&substream, sink); - } } break;
Some CODECs can support multiple sample rates internally but frequently groups of functionality will be force to the run on the same sample rate. For example, perhaps all the DACs are required to run at a single rate. Utimately, it would be ideal if the sample rates could be propagated through from the top level DAI links to these groups of widgets. This should allow more complex use-cases involving multiple sample rates to be connected and will also allow detection of errors where blocks with incompatible sample rates are connected.
A first step in this process is to provide a mechanism that documents which blocks are tidied together with respect to sample rate and track when they are in use. This is acheived by adding a supply like widget that represents a group of widgets that are bound to a single sample rate. This domain widget should be connected to all inputs and outputs to the group of widgets, that way it will power up whenever anything in that group of widgets does.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com --- include/sound/soc-dapm.h | 4 ++++ sound/soc/soc-dapm.c | 5 +++++ 2 files changed, 9 insertions(+)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index bd8163f151cb8..c0ef27b2d4b22 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -273,6 +273,9 @@ struct device; .reg = SND_SOC_NOPM, .event = dapm_pinctrl_event, \ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }
+#define SND_SOC_DAPM_RATE(wname, wreg, wshift, winvert, wops, wpriv) \ +{ .id = snd_soc_dapm_rate, .name = wname, \ + SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) }
/* dapm kcontrol types */ @@ -519,6 +522,7 @@ enum snd_soc_dapm_type { snd_soc_dapm_asrc, /* DSP/CODEC ASRC component */ snd_soc_dapm_encoder, /* FW/SW audio encoder component */ snd_soc_dapm_decoder, /* FW/SW audio decoder component */ + snd_soc_dapm_rate, /* Rate group */ };
enum snd_soc_dapm_subclass { diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a5178845065b3..7e3858d1e81dc 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -68,6 +68,7 @@ static int dapm_up_seq[] = { [snd_soc_dapm_regulator_supply] = 1, [snd_soc_dapm_pinctrl] = 1, [snd_soc_dapm_clock_supply] = 1, + [snd_soc_dapm_rate] = 2, [snd_soc_dapm_supply] = 2, [snd_soc_dapm_micbias] = 3, [snd_soc_dapm_dai_link] = 2, @@ -115,6 +116,7 @@ static int dapm_down_seq[] = { [snd_soc_dapm_dai_out] = 10, [snd_soc_dapm_dai_link] = 11, [snd_soc_dapm_supply] = 12, + [snd_soc_dapm_rate] = 12, [snd_soc_dapm_clock_supply] = 13, [snd_soc_dapm_pinctrl] = 13, [snd_soc_dapm_regulator_supply] = 13, @@ -1912,6 +1914,7 @@ static int dapm_power_widgets(struct snd_soc_card *card, int event) case snd_soc_dapm_vmid: break; case snd_soc_dapm_supply: + case snd_soc_dapm_rate: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_pinctrl: case snd_soc_dapm_clock_supply: @@ -2326,6 +2329,7 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *cmpnt, case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: case snd_soc_dapm_supply: + case snd_soc_dapm_rate: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_pinctrl: case snd_soc_dapm_clock_supply: @@ -3522,6 +3526,7 @@ snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_supply: + case snd_soc_dapm_rate: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_pinctrl: case snd_soc_dapm_clock_supply:
The rate domain widgets allowed tracking of which hardware blocks are physically bound to the same sample rate. The next step is to follow which blocks are connected together as two directly connected blocks should also run at the same rate, even though the hardware may provide independent settings for them.
To acheive this two new concepts are introduced to ASoC, a rate domain and a rate domain group. The rate domain group corresponds to the rate domain widgets previously added to DAPM. And the domains correspond to actual sample rates.
The rate domain groups internally track which other groups they are connected to. These lists of peer groups are updated as DAPM routes are connected/disconnected and form a collection of graphs tracking which domain groups are connected. Note that these graphs are significantly smaller than the DAPM graph itself.
When a domain group's corresponding widget is powered up then the group must locate an actual domain to attach to. Firstly, the group will walk its peer graph, should it find it is attached to widgets that require certain domains it will limit the choice to those. For example if a widget is connected into a graph that is already powered up then it will find the only suitable domain is the one being currently used by the groups in the graph.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com ---
Thinking about trying to split this into two patches perhaps one to add the tracking of the rate domain groups connecting together and then a second patch to add the actual domains.
Thanks, Charles
include/sound/soc-dapm.h | 12 +- include/sound/soc-domain.h | 98 +++++++++++ include/sound/soc.h | 8 + sound/soc/Makefile | 2 +- sound/soc/soc-core.c | 8 + sound/soc/soc-dapm.c | 35 ++++ sound/soc/soc-domain.c | 412 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 573 insertions(+), 2 deletions(-) create mode 100644 include/sound/soc-domain.h create mode 100644 sound/soc/soc-domain.c
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index c0ef27b2d4b22..c736b1d3e4931 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -275,7 +275,12 @@ struct device;
#define SND_SOC_DAPM_RATE(wname, wreg, wshift, winvert, wops, wpriv) \ { .id = snd_soc_dapm_rate, .name = wname, \ - SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) } + SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \ + .event = snd_soc_domain_event, \ + .event_flags = SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_PRE_PMU | \ + SND_SOC_DAPM_POST_PMD, \ + .priv = (&(struct snd_soc_domain_group_driver){ \ + .name = wname, .ops = wops, .private_data = wpriv}),}
/* dapm kcontrol types */ @@ -410,6 +415,9 @@ int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card); void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
+int snd_soc_dapm_connect_domains(struct snd_soc_dapm_context *dapm, + const char * const a, const char * const b); + /* dapm path setup */ int snd_soc_dapm_new_widgets(struct snd_soc_card *card); void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm); @@ -629,6 +637,8 @@ struct snd_soc_dapm_widget { int endpoints[2];
struct clk *clk; + + struct snd_soc_domain_group *dgroup; };
struct snd_soc_dapm_update { diff --git a/include/sound/soc-domain.h b/include/sound/soc-domain.h new file mode 100644 index 0000000000000..94e1c1ae9ff00 --- /dev/null +++ b/include/sound/soc-domain.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ASoC Sample Rate Domain Support + * + * Copyright (c) 2018 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + * + * Author: Charles Keepax ckeepax@opensource.cirrus.com + */ + +#ifndef LINUX_SND_SOC_DOMAIN_H +#define LINUX_SND_SOC_DOMAIN_H + +#define SND_SOC_DOMAIN_CURRENT -1 + +struct snd_soc_domain; +struct snd_soc_domain_group; + +struct snd_soc_domain_ops { + int (*set_rate)(struct snd_soc_domain *domain, int rate); + int (*get_rate)(struct snd_soc_domain *domain); +}; + +struct snd_soc_domain_driver { + const char * const name; + + const struct snd_soc_domain_ops *ops; + + void *private_data; +}; + +struct snd_soc_domain { + const struct snd_soc_domain_driver *driver; + struct snd_soc_component *component; + + /* TODO: Probably should be a snd_pcm_hw_params */ + int rate; + + int active_groups; +}; + +struct snd_soc_domain_group_ops { + int (*set_domain)(struct snd_soc_domain_group *group, int dom); + + int (*mask_domains)(struct snd_soc_domain_group *group, + unsigned long *domain_mask); + /* optional */ + int (*pick_domain)(struct snd_soc_domain_group *group, + const unsigned long *domain_mask); +}; + +struct snd_soc_domain_group_driver { + const char * const name; + + const struct snd_soc_domain_group_ops *ops; + + void *private_data; +}; + +struct snd_soc_domain_group { + const struct snd_soc_domain_group_driver *driver; + struct snd_soc_component *component; + + int domain_index; + int attach_count; + + struct list_head peers; + + unsigned int walking:1; + unsigned int power:1; +}; + +int devm_snd_soc_domain_init(struct snd_soc_component *component); + +struct snd_soc_domain_group * +devm_snd_soc_domain_group_new(struct snd_soc_component *component, + const struct snd_soc_domain_group_driver *drv); + +struct snd_soc_domain *snd_soc_domain_get(struct snd_soc_domain_group *group, + int index); +bool snd_soc_domain_active(struct snd_soc_domain *domain); +int snd_soc_domain_get_rate(struct snd_soc_domain *domain); + +int snd_soc_domain_set_rate(struct snd_soc_domain_group *group, int rate); + +/* TODO: API to force a particular domain onto a group? */ +int snd_soc_domain_attach(struct snd_soc_domain_group *group); +int snd_soc_domain_detach(struct snd_soc_domain_group *group); + +int snd_soc_domain_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event); + +int snd_soc_domain_connect_widgets(struct snd_soc_dapm_widget *a, + struct snd_soc_dapm_widget *b, + bool connect); + +#endif diff --git a/include/sound/soc.h b/include/sound/soc.h index f1dab1f4b194d..475843a17ebd4 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -413,6 +413,7 @@ struct snd_soc_jack_pin; #include <sound/soc-dapm.h> #include <sound/soc-dpcm.h> #include <sound/soc-topology.h> +#include <sound/soc-domain.h>
struct snd_soc_jack_gpio;
@@ -763,6 +764,9 @@ struct snd_soc_component_driver { const struct snd_soc_dapm_route *dapm_routes; unsigned int num_dapm_routes;
+ const struct snd_soc_domain_driver *domains; + unsigned int num_domains; + int (*probe)(struct snd_soc_component *); void (*remove)(struct snd_soc_component *); int (*suspend)(struct snd_soc_component *); @@ -838,6 +842,9 @@ struct snd_soc_component { struct list_head dai_list; int num_dai;
+ struct snd_soc_domain *domains; + int num_domains; + struct regmap *regmap; int val_bytes;
@@ -1036,6 +1043,7 @@ struct snd_soc_card {
struct mutex mutex; struct mutex dapm_mutex; + struct mutex domain_mutex;
bool instantiated; bool topology_shortname_created; diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 62a5f87c3cfc4..185f51aa963a2 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o -snd-soc-core-objs += soc-pcm.o soc-io.o soc-devres.o soc-ops.o +snd-soc-core-objs += soc-pcm.o soc-io.o soc-devres.o soc-ops.o soc-domain.o snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o
ifneq ($(CONFIG_SND_SOC_TOPOLOGY),) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 62e8e36062df0..4623adb27543b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1345,6 +1345,13 @@ static int soc_probe_component(struct snd_soc_card *card, } }
+ ret = devm_snd_soc_domain_init(component); + if (ret < 0) { + dev_err(component->dev, "Failed to initialise domains: %d\n", + ret); + goto err_probe; + } + if (component->driver->controls) snd_soc_add_component_controls(component, component->driver->controls, @@ -2739,6 +2746,7 @@ int snd_soc_register_card(struct snd_soc_card *card) card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); + mutex_init(&card->domain_mutex);
return snd_soc_bind_card(card); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7e3858d1e81dc..a3f01626fda76 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2179,6 +2179,9 @@ static void soc_dapm_connect_path(struct snd_soc_dapm_path *path, if (path->connect == connect) return;
+ /* TODO: Need to handle routes that are already connected */ + snd_soc_domain_connect_widgets(path->source, path->sink, connect); + path->connect = connect; dapm_mark_dirty(path->source, reason); dapm_mark_dirty(path->sink, reason); @@ -2685,6 +2688,14 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, if (wsource->is_supply || wsink->is_supply) path->is_supply = 1;
+ switch (wsource->id) { + case snd_soc_dapm_rate: + wsink->dgroup = wsource->dgroup; + break; + default: + break; + } + /* connect static paths */ if (control == NULL) { path->connect = 1; @@ -3463,6 +3474,14 @@ snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, goto request_failed; } break; + case snd_soc_dapm_rate: + w->dgroup = devm_snd_soc_domain_group_new(dapm->component, + w->priv); + if (IS_ERR(w->dgroup)) { + ret = PTR_ERR(w->dgroup); + goto request_failed; + } + break; default: break; } @@ -4566,6 +4585,22 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card) SND_SOC_BIAS_OFF); }
+int snd_soc_dapm_connect_domains(struct snd_soc_dapm_context *dapm, + const char * const a, const char * const b) +{ + struct snd_soc_dapm_widget *wa, *wb; + + wa = dapm_find_widget(dapm, a, false); + if (!wa) + return -ENODEV; + wb = dapm_find_widget(dapm, b, false); + if (!wb) + return -ENODEV; + + return snd_soc_domain_connect_widgets(wa, wb, true); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_connect_domains); + /* Module information */ MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); diff --git a/sound/soc/soc-domain.c b/sound/soc/soc-domain.c new file mode 100644 index 0000000000000..01914d5971601 --- /dev/null +++ b/sound/soc/soc-domain.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Sample Rate Domain Support + * + * Copyright (c) 2018 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + * + * Author: Charles Keepax ckeepax@opensource.cirrus.com + */ + +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <sound/soc.h> + +struct domain_group_peer { + struct list_head list; + int link_count; + struct snd_soc_domain_group *group; +}; + +static inline void domain_mutex_lock(struct snd_soc_component *component) +{ + mutex_lock(&component->card->domain_mutex); +} + +static inline void domain_mutex_unlock(struct snd_soc_component *component) +{ + mutex_unlock(&component->card->domain_mutex); +} + +static inline void domain_mutex_assert_held(struct snd_soc_component *component) +{ + lockdep_assert_held(&component->card->domain_mutex); +} + +int devm_snd_soc_domain_init(struct snd_soc_component *component) +{ + int i; + + if (!component->driver->num_domains) + return 0; + + component->num_domains = component->driver->num_domains; + component->domains = devm_kcalloc(component->card->dev, + component->num_domains, + sizeof(*component->domains), + GFP_KERNEL); + if (!component->domains) + return -ENOMEM; + + for (i = 0; i < component->num_domains; i++) { + component->domains[i].component = component; + component->domains[i].driver = &component->driver->domains[i]; + } + + return 0; +} +EXPORT_SYMBOL_GPL(devm_snd_soc_domain_init); + +struct snd_soc_domain_group * +devm_snd_soc_domain_group_new(struct snd_soc_component *component, + const struct snd_soc_domain_group_driver *driver) +{ + struct snd_soc_domain_group *group; + + group = devm_kzalloc(component->card->dev, sizeof(*group), GFP_KERNEL); + if (!group) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&group->peers); + + group->component = component; + group->driver = driver; + + return group; +} +EXPORT_SYMBOL_GPL(devm_snd_soc_domain_group_new); + +struct snd_soc_domain *snd_soc_domain_get(struct snd_soc_domain_group *group, + int index) +{ + int ndomains = group->component->num_domains; + + domain_mutex_assert_held(group->component); + + if (index == SND_SOC_DOMAIN_CURRENT) + index = group->domain_index; + + if (index < 0 || index >= ndomains) + return NULL; + + return &group->component->domains[index]; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_get); + +bool snd_soc_domain_active(struct snd_soc_domain *domain) +{ + bool active; + + domain_mutex_assert_held(domain->component); + + active = !!domain->active_groups; + + return active; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_active); + +int snd_soc_domain_get_rate(struct snd_soc_domain *domain) +{ + domain_mutex_assert_held(domain->component); + + return domain->rate; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_get_rate); + +int snd_soc_domain_set_rate(struct snd_soc_domain_group *group, int rate) +{ + struct snd_soc_domain *domain; + int ret = -ENODEV; + + domain_mutex_lock(group->component); + + domain = snd_soc_domain_get(group, SND_SOC_DOMAIN_CURRENT); + if (domain) { + domain->rate = rate; + ret = domain->driver->ops->set_rate(domain, rate); + } + + domain_mutex_unlock(group->component); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_set_rate); + +static struct snd_soc_domain_group * +group_walk(struct snd_soc_domain_group *group, bool local, + bool (*cond)(struct snd_soc_domain_group *g, void *c), void *cookie) +{ + struct domain_group_peer *link; + struct snd_soc_domain_group *target = NULL; + + domain_mutex_assert_held(group->component); + + if (group->walking) + return NULL; + + dev_vdbg(group->component->dev, "Walking %s\n", group->driver->name); + + if (cond(group, cookie)) + return group; + + group->walking = true; + list_for_each_entry(link, &group->peers, list) { + if (!link->group->power) + continue; + + if (local && link->group->component != group->component) + continue; + + target = group_walk(link->group, local, cond, cookie); + if (target) + break; + } + group->walking = false; + + return target; +} + +static bool group_mask(struct snd_soc_domain_group *group, void *cookie) +{ + unsigned long *mask = cookie; + + if (group->attach_count) + *mask &= 1 << group->domain_index; + else if (group->driver->ops->mask_domains) + group->driver->ops->mask_domains(group, mask); + + return false; +} + +static int group_pick(struct snd_soc_domain_group *group, + const unsigned long *domain_mask) +{ + int ndomains = group->component->num_domains; + int i; + + domain_mutex_assert_held(group->component); + + for_each_set_bit(i, domain_mask, ndomains) { + struct snd_soc_domain *domain = &group->component->domains[i]; + + if (!snd_soc_domain_active(domain)) + return i; + } + + return find_first_bit(domain_mask, ndomains); +} + +int snd_soc_domain_attach(struct snd_soc_domain_group *group) +{ + int ret = 0; + + domain_mutex_lock(group->component); + + dev_dbg(group->component->dev, "Attaching domain to %s: %d\n", + group->driver->name, group->attach_count); + + if (!group->attach_count) { + const struct snd_soc_domain_group_ops *ops = group->driver->ops; + unsigned long dom_map = ~0UL; + struct snd_soc_domain *domain; + + group_walk(group, true, group_mask, &dom_map); + + if (ops->pick_domain) + group->domain_index = ops->pick_domain(group, &dom_map); + else + group->domain_index = group_pick(group, &dom_map); + + domain = snd_soc_domain_get(group, SND_SOC_DOMAIN_CURRENT); + if (!domain) { + dev_err(group->component->dev, + "No suitable domain to attach for %s\n", + group->driver->name); + ret = -ENODEV; + goto error; + } + + dev_dbg(group->component->dev, "Apply domain %s to %s\n", + domain->driver->name, group->driver->name); + + ret = ops->set_domain(group, group->domain_index); + if (ret) + goto error; + + domain->active_groups++; + } + + group->attach_count++; + +error: + domain_mutex_unlock(group->component); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_attach); + +int snd_soc_domain_detach(struct snd_soc_domain_group *group) +{ + int ret = 0; + + domain_mutex_lock(group->component); + + dev_dbg(group->component->dev, "Detaching domain from %s: %d\n", + group->driver->name, group->attach_count); + + if (!group->attach_count) { + dev_err(group->component->dev, "Unbalanced detach on %s\n", + group->driver->name); + ret = -EPERM; + } else { + struct snd_soc_domain *domain; + + domain = snd_soc_domain_get(group, SND_SOC_DOMAIN_CURRENT); + if (!domain) { + dev_err(group->component->dev, + "Group %s has missing domain\n", + group->driver->name); + ret = -ENODEV; + goto error; + } + + domain->active_groups--; + group->attach_count--; + } + +error: + domain_mutex_unlock(group->component); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_detach); + +int snd_soc_domain_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + switch (event) { + case SND_SOC_DAPM_WILL_PMU: + w->dgroup->power = true; + return 0; + case SND_SOC_DAPM_PRE_PMU: + return snd_soc_domain_attach(w->dgroup); + case SND_SOC_DAPM_POST_PMD: + w->dgroup->power = false; + return snd_soc_domain_detach(w->dgroup); + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(snd_soc_domain_event); + +static struct domain_group_peer * +group_peer_find(struct snd_soc_domain_group *group, + struct snd_soc_domain_group *peer) +{ + struct domain_group_peer *link; + + domain_mutex_assert_held(group->component); + + list_for_each_entry(link, &group->peers, list) { + if (link->group == peer) + return link; + } + + return NULL; +} + +static int group_peer_new(struct snd_soc_domain_group *group, + struct snd_soc_domain_group *peer) +{ + struct domain_group_peer *link; + + domain_mutex_lock(group->component); + + link = group_peer_find(group, peer); + if (!link) { + dev_dbg(group->component->dev, "New peer: %s -> %s\n", + group->driver->name, peer->driver->name); + + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) + return -ENOMEM; + + INIT_LIST_HEAD(&link->list); + link->group = peer; + + list_add_tail(&link->list, &group->peers); + } + + link->link_count++; + + domain_mutex_unlock(group->component); + + return 0; +} + +static int group_peer_delete(struct snd_soc_domain_group *group, + struct snd_soc_domain_group *peer) +{ + struct domain_group_peer *link; + int ret = 0; + + domain_mutex_lock(group->component); + + link = group_peer_find(group, peer); + if (!link) { + dev_err(group->component->dev, + "Delete on invalid peer: %s -> %s\n", + group->driver->name, peer->driver->name); + ret = -ENOENT; + goto error; + } + + link->link_count--; + if (!link->link_count) { + dev_dbg(group->component->dev, "Delete peer: %s -> %s\n", + group->driver->name, peer->driver->name); + + list_del(&link->list); + kfree(link); + } + +error: + domain_mutex_unlock(group->component); + + return ret; +} + +int snd_soc_domain_connect_widgets(struct snd_soc_dapm_widget *a, + struct snd_soc_dapm_widget *b, + bool connect) +{ + int (*op)(struct snd_soc_domain_group *group, + struct snd_soc_domain_group *peer); + int ret; + + if (!a->dgroup || !b->dgroup) + return 0; + + dev_dbg(a->dapm->dev, "%s %s,%s - %s,%s\n", + connect ? "Connecting" : "Disconnecting", + a->name, a->dgroup->driver->name, + b->name, b->dgroup->driver->name); + + if (connect) + op = group_peer_new; + else + op = group_peer_delete; + + ret = op(a->dgroup, b->dgroup); + if (ret) + return ret; + + ret = op(b->dgroup, a->dgroup); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_domain_connect_widgets);
Move to using sample rate domains on the Arizona CODECs. Rate domain group widgets are added and these are connected to all the widgets they supply.
arizona_hw_params_rate is updated significantly, it now manually attached the AIF sample rate domain group to a domain at the rate requested by the params. This ensures that any widgets powering up that are connected to the audio interface will be placed on the same sample rate domain.
Whilst the SLIMbus technically supports a different sample rate on each channel the driver currently only supports sample rates for capture and playback on each DAI, as such the SLIMBus channels that are grouped onto a single DAI are manually connected together as peers.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com ---
This one is obviously not quite finished yet, the other CODECs in Arizona need updated and the macros that are duplicated will obviously just be updated in that case.
Thanks, Charles
sound/soc/codecs/arizona.c | 255 ++++++++++++++++++++++------ sound/soc/codecs/arizona.h | 76 ++++++++- sound/soc/codecs/wm5110.c | 414 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 591 insertions(+), 154 deletions(-)
diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 5727ea079ad7a..37ce772691739 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -633,12 +633,12 @@ const char *arizona_sample_rate_val_to_name(unsigned int rate_val) EXPORT_SYMBOL_GPL(arizona_sample_rate_val_to_name);
const char * const arizona_rate_text[ARIZONA_RATE_ENUM_SIZE] = { - "SYNCCLK rate", "8kHz", "16kHz", "ASYNCCLK rate", + "SYNCCLK rate", "8kHz", "16kHz", "ASYNCCLK rate", "ASYNCCLK rate 2", }; EXPORT_SYMBOL_GPL(arizona_rate_text);
const unsigned int arizona_rate_val[ARIZONA_RATE_ENUM_SIZE] = { - 0, 1, 2, 8, + 0, 1, 2, 8, 9, }; EXPORT_SYMBOL_GPL(arizona_rate_val);
@@ -1681,40 +1681,15 @@ static int arizona_hw_params_rate(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct arizona_priv *priv = snd_soc_component_get_drvdata(component); struct arizona_dai_priv *dai_priv = &priv->dai[dai->id - 1]; - int base = dai->driver->base; - int i, sr_val, ret; - - /* - * We will need to be more flexible than this in future, - * currently we use a single sample rate for SYSCLK. - */ - for (i = 0; i < ARRAY_SIZE(arizona_sr_vals); i++) - if (arizona_sr_vals[i] == params_rate(params)) - break; - if (i == ARRAY_SIZE(arizona_sr_vals)) { - arizona_aif_err(dai, "Unsupported sample rate %dHz\n", - params_rate(params)); - return -EINVAL; - } - sr_val = i; - - switch (priv->arizona->type) { - case WM5102: - case WM8997: - if (arizona_sr_vals[sr_val] >= 88200) - ret = arizona_dvfs_up(component, ARIZONA_DVFS_SR1_RQ); - else - ret = arizona_dvfs_down(component, ARIZONA_DVFS_SR1_RQ); + int ret; + struct snd_soc_domain_group *dgrp;
- if (ret) { - arizona_aif_err(dai, "Failed to change DVFS %d\n", ret); - return ret; - } - break; - default: - break; - } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dgrp = dai->playback_widget->dgroup; + else + dgrp = dai->capture_widget->dgroup;
+ /* TODO: This should be handled on power up of the OUT_RATE widget */ switch (dai_priv->clk) { case ARIZONA_CLK_SYSCLK: switch (priv->arizona->type) { @@ -1725,31 +1700,38 @@ static int arizona_hw_params_rate(struct snd_pcm_substream *substream, default: break; } - - snd_soc_component_update_bits(component, ARIZONA_SAMPLE_RATE_1, - ARIZONA_SAMPLE_RATE_1_MASK, - sr_val); - if (base) - snd_soc_component_update_bits(component, - base + ARIZONA_AIF_RATE_CTRL, - ARIZONA_AIF1_RATE_MASK, 0); break; case ARIZONA_CLK_ASYNCCLK: - snd_soc_component_update_bits(component, - ARIZONA_ASYNC_SAMPLE_RATE_1, - ARIZONA_ASYNC_SAMPLE_RATE_1_MASK, - sr_val); - if (base) - snd_soc_component_update_bits(component, - base + ARIZONA_AIF_RATE_CTRL, - ARIZONA_AIF1_RATE_MASK, - 8 << ARIZONA_AIF1_RATE_SHIFT); break; default: arizona_aif_err(dai, "Invalid clock %d\n", dai_priv->clk); return -EINVAL; }
+ /* TODO: Needs updated to handle multiple calls of hw_params */ + ret = snd_soc_domain_attach(dgrp); + if (ret) + return ret; + + ret = snd_soc_domain_set_rate(dgrp, params_rate(params)); + if (ret) + return ret; + + return 0; +} + +static int arizona_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_domain_group *dgrp; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dgrp = dai->playback_widget->dgroup; + else + dgrp = dai->capture_widget->dgroup; + + snd_soc_domain_detach(dgrp); + return 0; }
@@ -2029,6 +2011,7 @@ const struct snd_soc_dai_ops arizona_dai_ops = { .set_fmt = arizona_set_fmt, .set_tdm_slot = arizona_set_tdm_slot, .hw_params = arizona_hw_params, + .hw_free = arizona_hw_free, .set_sysclk = arizona_dai_set_sysclk, .set_tristate = arizona_set_tristate, }; @@ -2858,6 +2841,176 @@ int arizona_of_get_audio_pdata(struct arizona *arizona) } EXPORT_SYMBOL_GPL(arizona_of_get_audio_pdata);
+static int arizona_set_rate(struct snd_soc_domain *dom, int rate) +{ + struct snd_soc_component *component = dom->component; + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + const struct arizona_rate_dom_priv *dpriv = dom->driver->private_data; + int i, ret; + + dev_dbg(arizona->dev, "Set %s to %d Hz\n", dom->driver->name, rate); + + switch (arizona->type) { + case WM5102: + case WM8997: + if (rate >= 88200) + ret = arizona_dvfs_up(component, dpriv->dvfs_mask); + else + ret = arizona_dvfs_down(component, dpriv->dvfs_mask); + + if (ret) { + dev_err(arizona->dev, + "Failed to change DVFS for %s: %d\n", + dom->driver->name, ret); + return ret; + } + break; + default: + break; + } + + for (i = 0; i < ARRAY_SIZE(arizona_sr_vals); i++) { + if (arizona_sr_vals[i] == rate) + break; + } + + if (i == ARRAY_SIZE(arizona_sr_vals)) { + dev_err(arizona->dev, "Invalid sample rate: %d Hz\n", rate); + return -EINVAL; + } + + return regmap_update_bits(arizona->regmap, dpriv->reg, + ARIZONA_SAMPLE_RATE_1_MASK, i); +} + +static int arizona_get_rate(struct snd_soc_domain *dom) +{ + struct snd_soc_component *component = dom->component; + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + const struct arizona_rate_dom_priv *dpriv = dom->driver->private_data; + unsigned int rate; + int ret; + + ret = regmap_read(arizona->regmap, dpriv->reg, &rate); + if (ret) + return ret; + + rate &= ARIZONA_SAMPLE_RATE_1_MASK; + + if (rate >= ARRAY_SIZE(arizona_sr_vals)) { + dev_err(arizona->dev, "Read bad sample rate: 0x%x\n", rate); + return -EINVAL; + } + + rate = arizona_sr_vals[rate]; + + dev_dbg(arizona->dev, "Got %u Hz for %s\n", rate, dom->driver->name); + + return (int)rate; +} + +static const struct snd_soc_domain_ops arizona_dom_ops = { + .set_rate = arizona_set_rate, + .get_rate = arizona_get_rate, +}; + +const struct snd_soc_domain_driver arizona_rate_domains[ARIZONA_RATE_ENUM_SIZE] = { + { + .name = "Sample Rate 1", + .ops = &arizona_dom_ops, + .private_data = &(struct arizona_rate_dom_priv){ + .reg = ARIZONA_SAMPLE_RATE_1, + .val = 0, + .dvfs_mask = ARIZONA_DVFS_SR1_RQ, + }, + }, + { + .name = "Sample Rate 2", + .ops = &arizona_dom_ops, + .private_data = &(struct arizona_rate_dom_priv){ + .reg = ARIZONA_SAMPLE_RATE_2, + .val = 1, + .dvfs_mask = ARIZONA_DVFS_SR2_RQ, + }, + }, + { + .name = "Sample Rate 3", + .ops = &arizona_dom_ops, + .private_data = &(struct arizona_rate_dom_priv){ + .reg = ARIZONA_SAMPLE_RATE_3, + .val = 2, + .dvfs_mask = ARIZONA_DVFS_SR3_RQ, + }, + }, + { + .name = "Async Sample Rate 1", + .ops = &arizona_dom_ops, + .private_data = &(struct arizona_rate_dom_priv){ + .reg = ARIZONA_ASYNC_SAMPLE_RATE_1, + .val = 8, + .dvfs_mask = ARIZONA_DVFS_ASR1_RQ, + }, + }, + { + .name = "Async Sample Rate 2", + .ops = &arizona_dom_ops, + .private_data = &(struct arizona_rate_dom_priv){ + .reg = ARIZONA_ASYNC_SAMPLE_RATE_2, + .val = 9, + .dvfs_mask = ARIZONA_DVFS_ASR2_RQ, + }, + }, +}; + +int arizona_set_domain(struct snd_soc_domain_group *dgrp, int dom) +{ + struct arizona *arizona = dev_get_drvdata(dgrp->component->dev->parent); + const struct arizona_rate_grp_priv *gpriv = dgrp->driver->private_data; + const struct arizona_rate_dom_priv *dpriv = arizona_rate_domains[dom].private_data; + + return regmap_update_bits(arizona->regmap, gpriv->reg, gpriv->mask, + dpriv->val << gpriv->shift); +} + +int arizona_mask_domain(struct snd_soc_domain_group *dgrp, unsigned long *mask) +{ + const struct arizona_rate_grp_priv *gpriv = dgrp->driver->private_data; + struct snd_soc_component *component = dgrp->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + unsigned long supported = 0; + + switch (gpriv->reg) { + case ARIZONA_AIF1_RATE_CTRL: + case ARIZONA_AIF2_RATE_CTRL: + case ARIZONA_AIF3_RATE_CTRL: + switch (priv->dai[ARIZONA_AIF1_RATE_CTRL - gpriv->reg].clk) { + case ARIZONA_CLK_ASYNCCLK: + supported |= 0x18; + break; + default: + supported |= 0x7; + break; + } + break; + default: + if (gpriv->sync) + supported |= 0x7; + if (gpriv->async) + supported |= 0x18; + break; + } + + *mask &= supported; + + return 0; +} + +const struct snd_soc_domain_group_ops arizona_dgrp_ops = { + .set_domain = arizona_set_domain, + .mask_domains = arizona_mask_domain, +}; +EXPORT_SYMBOL_GPL(arizona_dgrp_ops); + MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support"); MODULE_AUTHOR("Mark Brown broonie@opensource.wolfsonmicro.com"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index e3ccee5627c6b..734a90648224f 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -63,6 +63,10 @@ #define ARIZONA_MAX_ADSP 4
#define ARIZONA_DVFS_SR1_RQ 0x001 +#define ARIZONA_DVFS_SR2_RQ 0x002 +#define ARIZONA_DVFS_SR3_RQ 0x004 +#define ARIZONA_DVFS_ASR1_RQ 0x008 +#define ARIZONA_DVFS_ASR2_RQ 0x010 #define ARIZONA_DVFS_ADSP1_RQ 0x100
/* Notifier events */ @@ -189,6 +193,26 @@ extern unsigned int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS]; ARIZONA_MIXER_INPUT_ROUTES(name " Input 3"), \ ARIZONA_MIXER_INPUT_ROUTES(name " Input 4")
+#define ARIZONA_MUX_ROUTES_R(widget, name, rate) \ + { widget, NULL, name " Input" }, \ + { name " Input", NULL, rate }, \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input") + +#define ARIZONA_MIXER_ROUTES_R(widget, name, rate) \ + { widget, NULL, name " Mixer" }, \ + { name " Mixer", NULL, name " Input 1" }, \ + { name " Mixer", NULL, name " Input 2" }, \ + { name " Mixer", NULL, name " Input 3" }, \ + { name " Mixer", NULL, name " Input 4" }, \ + { name " Input 1", NULL, rate }, \ + { name " Input 2", NULL, rate }, \ + { name " Input 3", NULL, rate }, \ + { name " Input 4", NULL, rate }, \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 1"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 2"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 3"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 4") + #define ARIZONA_DSP_ROUTES(name) \ { name, NULL, name " Preloader"}, \ { name " Preloader", NULL, "SYSCLK" }, \ @@ -208,6 +232,32 @@ extern unsigned int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS]; ARIZONA_MIXER_ROUTES(name, name "L"), \ ARIZONA_MIXER_ROUTES(name, name "R")
+#define ARIZONA_DSP_ROUTES_R(name, rate) \ + { name, NULL, rate }, \ + { name, NULL, name " Preloader"}, \ + { name " Preloader", NULL, "SYSCLK" }, \ + { name " Preload", NULL, name " Preloader"}, \ + { name, NULL, name " Aux 1" }, \ + { name, NULL, name " Aux 2" }, \ + { name, NULL, name " Aux 3" }, \ + { name, NULL, name " Aux 4" }, \ + { name, NULL, name " Aux 5" }, \ + { name, NULL, name " Aux 6" }, \ + { name " Aux 1", NULL, rate }, \ + { name " Aux 2", NULL, rate }, \ + { name " Aux 3", NULL, rate }, \ + { name " Aux 4", NULL, rate }, \ + { name " Aux 5", NULL, rate }, \ + { name " Aux 6", NULL, rate }, \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 1"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 2"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 3"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 4"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 5"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 6"), \ + ARIZONA_MIXER_ROUTES_R(name, name "L", rate), \ + ARIZONA_MIXER_ROUTES_R(name, name "R", rate) + #define ARIZONA_EQ_CONTROL(xname, xbase) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ @@ -222,7 +272,7 @@ extern unsigned int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS]; ((unsigned long)&(struct soc_bytes) { .base = xbase, \ .num_regs = 1 }) }
-#define ARIZONA_RATE_ENUM_SIZE 4 +#define ARIZONA_RATE_ENUM_SIZE 5 #define ARIZONA_SAMPLE_RATE_ENUM_SIZE 14
extern const char * const arizona_rate_text[ARIZONA_RATE_ENUM_SIZE]; @@ -354,4 +404,28 @@ static inline int arizona_unregister_notifier(struct snd_soc_component *componen
int arizona_of_get_audio_pdata(struct arizona *arizona);
+struct arizona_rate_dom_priv { + unsigned int reg; + unsigned int val; + int dvfs_mask; +}; + +struct arizona_rate_grp_priv { + unsigned int reg; + unsigned int mask; + unsigned int shift; + + bool sync; + bool async; +}; + +#define ARIZONA_RATE_WIDGET(rname, rsync, rasync, rreg, rmask) \ + SND_SOC_DAPM_RATE(rname, SND_SOC_NOPM, 0, 0, &arizona_dgrp_ops, \ + (&(struct arizona_rate_grp_priv){ .reg = rreg, \ + .mask = rmask##_MASK, .shift = rmask##_SHIFT, \ + .sync = rsync, .async = rasync})) + +extern const struct snd_soc_domain_group_ops arizona_dgrp_ops; +extern const struct snd_soc_domain_driver arizona_rate_domains[ARIZONA_RATE_ENUM_SIZE]; + #endif diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index b0789a03d699b..930092370735e 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -1092,6 +1092,88 @@ static const struct snd_kcontrol_new wm5110_output_anc_src[] = { };
static const struct snd_soc_dapm_widget wm5110_dapm_widgets[] = { +ARIZONA_RATE_WIDGET("In Rate", 1, 0, ARIZONA_INPUT_RATE, ARIZONA_IN_RATE), + +ARIZONA_RATE_WIDGET("Out Rate", 1, 0, ARIZONA_OUTPUT_RATE_1, ARIZONA_OUT_RATE), +ARIZONA_RATE_WIDGET("PWM Rate", 1, 0, ARIZONA_PWM_DRIVE_1, ARIZONA_PWM_RATE), + +ARIZONA_RATE_WIDGET("Tone Rate", 1, 1, ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE_RATE), +ARIZONA_RATE_WIDGET("Noise Rate", 1, 1, ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_RATE), +ARIZONA_RATE_WIDGET("Haptics Rate", 1, 0, ARIZONA_HAPTICS_CONTROL_1, + ARIZONA_HAP_RATE), +ARIZONA_RATE_WIDGET("FX Rate", 1, 1, ARIZONA_FX_CTRL1, ARIZONA_FX_RATE), + +ARIZONA_RATE_WIDGET("AIF1 Rate", 1, 1, ARIZONA_AIF1_RATE_CTRL, + ARIZONA_AIF1_RATE), +ARIZONA_RATE_WIDGET("AIF2 Rate", 1, 1, ARIZONA_AIF2_RATE_CTRL, + ARIZONA_AIF2_RATE), +ARIZONA_RATE_WIDGET("AIF3 Rate", 1, 1, ARIZONA_AIF3_RATE_CTRL, + ARIZONA_AIF3_RATE), + +ARIZONA_RATE_WIDGET("ASRC Rate", 1, 0, ARIZONA_ASRC_RATE1, ARIZONA_ASRC_RATE1), +ARIZONA_RATE_WIDGET("ASRC Async Rate", 0, 1, ARIZONA_ASRC_RATE2, + ARIZONA_ASRC_RATE2), + +ARIZONA_RATE_WIDGET("Mic Mute Rate", 1, 1, ARIZONA_MIC_NOISE_MIX_CONTROL_1, + ARIZONA_MICMUTE_RATE), + +ARIZONA_RATE_WIDGET("ISRC1 FSH", 1, 1, ARIZONA_ISRC_1_CTRL_1, + ARIZONA_ISRC1_FSH), +ARIZONA_RATE_WIDGET("ISRC1 FSL", 1, 1, ARIZONA_ISRC_1_CTRL_2, + ARIZONA_ISRC1_FSL), +ARIZONA_RATE_WIDGET("ISRC2 FSH", 1, 1, ARIZONA_ISRC_2_CTRL_1, + ARIZONA_ISRC2_FSH), +ARIZONA_RATE_WIDGET("ISRC2 FSL", 1, 1, ARIZONA_ISRC_2_CTRL_2, + ARIZONA_ISRC2_FSL), +ARIZONA_RATE_WIDGET("ISRC3 FSH", 1, 1, ARIZONA_ISRC_3_CTRL_1, + ARIZONA_ISRC3_FSH), +ARIZONA_RATE_WIDGET("ISRC3 FSL", 1, 1, ARIZONA_ISRC_3_CTRL_2, + ARIZONA_ISRC3_FSL), + +ARIZONA_RATE_WIDGET("DSP1 Rate", 1, 1, ARIZONA_DSP1_CONTROL_1, + ARIZONA_DSP1_RATE), +ARIZONA_RATE_WIDGET("DSP2 Rate", 1, 1, ARIZONA_DSP2_CONTROL_1, + ARIZONA_DSP1_RATE), +ARIZONA_RATE_WIDGET("DSP3 Rate", 1, 1, ARIZONA_DSP3_CONTROL_1, + ARIZONA_DSP1_RATE), +ARIZONA_RATE_WIDGET("DSP4 Rate", 1, 1, ARIZONA_DSP4_CONTROL_1, + ARIZONA_DSP1_RATE), + +ARIZONA_RATE_WIDGET("SLIMRX1 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_1, + ARIZONA_SLIMRX1_RATE), +ARIZONA_RATE_WIDGET("SLIMRX2 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_1, + ARIZONA_SLIMRX2_RATE), +ARIZONA_RATE_WIDGET("SLIMRX3 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_2, + ARIZONA_SLIMRX3_RATE), +ARIZONA_RATE_WIDGET("SLIMRX4 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_2, + ARIZONA_SLIMRX4_RATE), +ARIZONA_RATE_WIDGET("SLIMRX5 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_3, + ARIZONA_SLIMRX5_RATE), +ARIZONA_RATE_WIDGET("SLIMRX6 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_3, + ARIZONA_SLIMRX6_RATE), +ARIZONA_RATE_WIDGET("SLIMRX7 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_4, + ARIZONA_SLIMRX7_RATE), +ARIZONA_RATE_WIDGET("SLIMRX8 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_4, + ARIZONA_SLIMRX8_RATE), +ARIZONA_RATE_WIDGET("SLIMTX1 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_5, + ARIZONA_SLIMTX1_RATE), +ARIZONA_RATE_WIDGET("SLIMTX2 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_5, + ARIZONA_SLIMTX2_RATE), +ARIZONA_RATE_WIDGET("SLIMTX3 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_6, + ARIZONA_SLIMTX3_RATE), +ARIZONA_RATE_WIDGET("SLIMTX4 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_6, + ARIZONA_SLIMTX4_RATE), +ARIZONA_RATE_WIDGET("SLIMTX5 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_7, + ARIZONA_SLIMTX5_RATE), +ARIZONA_RATE_WIDGET("SLIMTX6 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_7, + ARIZONA_SLIMTX6_RATE), +ARIZONA_RATE_WIDGET("SLIMTX7 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_8, + ARIZONA_SLIMTX7_RATE), +ARIZONA_RATE_WIDGET("SLIMTX8 Rate", 1, 1, ARIZONA_SLIMBUS_RATES_8, + ARIZONA_SLIMTX8_RATE), + SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT, 0, wm5110_sysclk_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), @@ -1728,6 +1810,119 @@ SND_SOC_DAPM_OUTPUT("MICSUPP"), { name, "DSP4.6", "DSP4" }
static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { + { "IN1L PGA", NULL, "In Rate" }, + { "IN1R PGA", NULL, "In Rate" }, + { "IN2L PGA", NULL, "In Rate" }, + { "IN2R PGA", NULL, "In Rate" }, + { "IN3L PGA", NULL, "In Rate" }, + { "IN3R PGA", NULL, "In Rate" }, + { "IN4L PGA", NULL, "In Rate" }, + { "IN4R PGA", NULL, "In Rate" }, + + { "AIF1 Playback", NULL, "AIF1 Rate" }, + { "AIF1 Capture", NULL, "AIF1 Rate" }, + { "AIF1RX1", NULL, "AIF1 Rate" }, + { "AIF1RX2", NULL, "AIF1 Rate" }, + { "AIF1RX3", NULL, "AIF1 Rate" }, + { "AIF1RX4", NULL, "AIF1 Rate" }, + { "AIF1RX5", NULL, "AIF1 Rate" }, + { "AIF1RX6", NULL, "AIF1 Rate" }, + { "AIF1RX7", NULL, "AIF1 Rate" }, + { "AIF1RX8", NULL, "AIF1 Rate" }, + + { "AIF2 Playback", NULL, "AIF2 Rate" }, + { "AIF2 Capture", NULL, "AIF2 Rate" }, + { "AIF2RX1", NULL, "AIF2 Rate" }, + { "AIF2RX2", NULL, "AIF2 Rate" }, + { "AIF2RX3", NULL, "AIF2 Rate" }, + { "AIF2RX4", NULL, "AIF2 Rate" }, + { "AIF2RX5", NULL, "AIF2 Rate" }, + { "AIF2RX6", NULL, "AIF2 Rate" }, + + { "AIF3 Playback", NULL, "AIF3 Rate" }, + { "AIF3 Capture", NULL, "AIF3 Rate" }, + { "AIF3RX1", NULL, "AIF3 Rate" }, + { "AIF3RX2", NULL, "AIF3 Rate" }, + + { "EQ1", NULL, "FX Rate" }, + { "EQ2", NULL, "FX Rate" }, + { "EQ3", NULL, "FX Rate" }, + { "EQ4", NULL, "FX Rate" }, + + { "DRC1L", NULL, "FX Rate" }, + { "DRC1R", NULL, "FX Rate" }, + { "DRC2L", NULL, "FX Rate" }, + { "DRC2R", NULL, "FX Rate" }, + + { "LHPF1", NULL, "FX Rate" }, + { "LHPF2", NULL, "FX Rate" }, + { "LHPF3", NULL, "FX Rate" }, + { "LHPF4", NULL, "FX Rate" }, + + { "PWM1 Driver", NULL, "PWM Rate" }, + { "PWM2 Driver", NULL, "PWM Rate" }, + + { "HAPTICS", NULL, "Haptics Rate" }, + + { "Noise Generator", NULL, "Noise Rate" }, + { "Tone Generator 1", NULL, "Tone Rate" }, + { "Tone Generator 2", NULL, "Tone Rate" }, + + { "Mic Mute Mixer", NULL, "Mic Mute Rate" }, + + { "ASRC1L", NULL, "ASRC Async Rate" }, + { "ASRC1R", NULL, "ASRC Async Rate" }, + { "ASRC2L", NULL, "ASRC Rate" }, + { "ASRC2R", NULL, "ASRC Rate" }, + + { "ISRC1INT1", NULL, "ISRC1 FSH" }, + { "ISRC1INT2", NULL, "ISRC1 FSH" }, + { "ISRC1INT3", NULL, "ISRC1 FSH" }, + { "ISRC1INT4", NULL, "ISRC1 FSH" }, + + { "ISRC1DEC1", NULL, "ISRC1 FSL" }, + { "ISRC1DEC2", NULL, "ISRC1 FSL" }, + { "ISRC1DEC3", NULL, "ISRC1 FSL" }, + { "ISRC1DEC4", NULL, "ISRC1 FSL" }, + + { "ISRC2INT1", NULL, "ISRC2 FSH" }, + { "ISRC2INT2", NULL, "ISRC2 FSH" }, + { "ISRC2INT3", NULL, "ISRC2 FSH" }, + { "ISRC2INT4", NULL, "ISRC2 FSH" }, + + { "ISRC2DEC1", NULL, "ISRC2 FSL" }, + { "ISRC2DEC2", NULL, "ISRC2 FSL" }, + { "ISRC2DEC3", NULL, "ISRC2 FSL" }, + { "ISRC2DEC4", NULL, "ISRC2 FSL" }, + + { "ISRC3INT1", NULL, "ISRC3 FSH" }, + { "ISRC3INT2", NULL, "ISRC3 FSH" }, + { "ISRC3INT3", NULL, "ISRC3 FSH" }, + { "ISRC3INT4", NULL, "ISRC3 FSH" }, + + { "ISRC3DEC1", NULL, "ISRC3 FSL" }, + { "ISRC3DEC2", NULL, "ISRC3 FSL" }, + { "ISRC3DEC3", NULL, "ISRC3 FSL" }, + { "ISRC3DEC4", NULL, "ISRC3 FSL" }, + + { "SLIMRX1", NULL, "SLIMRX1 Rate" }, + { "SLIMRX2", NULL, "SLIMRX2 Rate" }, + { "SLIMRX3", NULL, "SLIMRX3 Rate" }, + { "SLIMRX4", NULL, "SLIMRX4 Rate" }, + { "SLIMRX5", NULL, "SLIMRX5 Rate" }, + { "SLIMRX6", NULL, "SLIMRX6 Rate" }, + { "SLIMRX7", NULL, "SLIMRX7 Rate" }, + { "SLIMRX8", NULL, "SLIMRX8 Rate" }, + + { "Slim1 Playback", NULL, "SLIMRX1 Rate" }, + { "Slim1 Capture", NULL, "SLIMTX1 Rate" }, + + { "Slim2 Playback", NULL, "SLIMRX5 Rate" }, + { "Slim2 Capture", NULL, "SLIMTX5 Rate" }, + + { "Slim3 Playback", NULL, "SLIMRX7 Rate" }, + { "Slim3 Capture", NULL, "SLIMTX7 Rate" }, + { "AIF2 Capture", NULL, "DBVDD2" }, { "AIF2 Playback", NULL, "DBVDD2" },
@@ -1878,108 +2073,108 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { { "IN4L PGA", NULL, "IN4L" }, { "IN4R PGA", NULL, "IN4R" },
- ARIZONA_MIXER_ROUTES("OUT1L", "HPOUT1L"), - ARIZONA_MIXER_ROUTES("OUT1R", "HPOUT1R"), - ARIZONA_MIXER_ROUTES("OUT2L", "HPOUT2L"), - ARIZONA_MIXER_ROUTES("OUT2R", "HPOUT2R"), - ARIZONA_MIXER_ROUTES("OUT3L", "HPOUT3L"), - ARIZONA_MIXER_ROUTES("OUT3R", "HPOUT3R"), - - ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUTL"), - ARIZONA_MIXER_ROUTES("OUT4R", "SPKOUTR"), - ARIZONA_MIXER_ROUTES("OUT5L", "SPKDAT1L"), - ARIZONA_MIXER_ROUTES("OUT5R", "SPKDAT1R"), - ARIZONA_MIXER_ROUTES("OUT6L", "SPKDAT2L"), - ARIZONA_MIXER_ROUTES("OUT6R", "SPKDAT2R"), - - ARIZONA_MIXER_ROUTES("PWM1 Driver", "PWM1"), - ARIZONA_MIXER_ROUTES("PWM2 Driver", "PWM2"), - - ARIZONA_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), - ARIZONA_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), - ARIZONA_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), - ARIZONA_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), - ARIZONA_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), - ARIZONA_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), - ARIZONA_MIXER_ROUTES("AIF1TX7", "AIF1TX7"), - ARIZONA_MIXER_ROUTES("AIF1TX8", "AIF1TX8"), - - ARIZONA_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), - ARIZONA_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), - ARIZONA_MIXER_ROUTES("AIF2TX3", "AIF2TX3"), - ARIZONA_MIXER_ROUTES("AIF2TX4", "AIF2TX4"), - ARIZONA_MIXER_ROUTES("AIF2TX5", "AIF2TX5"), - ARIZONA_MIXER_ROUTES("AIF2TX6", "AIF2TX6"), - - ARIZONA_MIXER_ROUTES("AIF3TX1", "AIF3TX1"), - ARIZONA_MIXER_ROUTES("AIF3TX2", "AIF3TX2"), - - ARIZONA_MIXER_ROUTES("SLIMTX1", "SLIMTX1"), - ARIZONA_MIXER_ROUTES("SLIMTX2", "SLIMTX2"), - ARIZONA_MIXER_ROUTES("SLIMTX3", "SLIMTX3"), - ARIZONA_MIXER_ROUTES("SLIMTX4", "SLIMTX4"), - ARIZONA_MIXER_ROUTES("SLIMTX5", "SLIMTX5"), - ARIZONA_MIXER_ROUTES("SLIMTX6", "SLIMTX6"), - ARIZONA_MIXER_ROUTES("SLIMTX7", "SLIMTX7"), - ARIZONA_MIXER_ROUTES("SLIMTX8", "SLIMTX8"), - - ARIZONA_MIXER_ROUTES("EQ1", "EQ1"), - ARIZONA_MIXER_ROUTES("EQ2", "EQ2"), - ARIZONA_MIXER_ROUTES("EQ3", "EQ3"), - ARIZONA_MIXER_ROUTES("EQ4", "EQ4"), - - ARIZONA_MIXER_ROUTES("DRC1L", "DRC1L"), - ARIZONA_MIXER_ROUTES("DRC1R", "DRC1R"), - ARIZONA_MIXER_ROUTES("DRC2L", "DRC2L"), - ARIZONA_MIXER_ROUTES("DRC2R", "DRC2R"), - - ARIZONA_MIXER_ROUTES("LHPF1", "LHPF1"), - ARIZONA_MIXER_ROUTES("LHPF2", "LHPF2"), - ARIZONA_MIXER_ROUTES("LHPF3", "LHPF3"), - ARIZONA_MIXER_ROUTES("LHPF4", "LHPF4"), - - ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Noise"), - ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Mic"), - - ARIZONA_MUX_ROUTES("ASRC1L", "ASRC1L"), - ARIZONA_MUX_ROUTES("ASRC1R", "ASRC1R"), - ARIZONA_MUX_ROUTES("ASRC2L", "ASRC2L"), - ARIZONA_MUX_ROUTES("ASRC2R", "ASRC2R"), - - ARIZONA_DSP_ROUTES("DSP1"), - ARIZONA_DSP_ROUTES("DSP2"), - ARIZONA_DSP_ROUTES("DSP3"), - ARIZONA_DSP_ROUTES("DSP4"), - - ARIZONA_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), - ARIZONA_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), - ARIZONA_MUX_ROUTES("ISRC1INT3", "ISRC1INT3"), - ARIZONA_MUX_ROUTES("ISRC1INT4", "ISRC1INT4"), - - ARIZONA_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), - ARIZONA_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), - ARIZONA_MUX_ROUTES("ISRC1DEC3", "ISRC1DEC3"), - ARIZONA_MUX_ROUTES("ISRC1DEC4", "ISRC1DEC4"), - - ARIZONA_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), - ARIZONA_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), - ARIZONA_MUX_ROUTES("ISRC2INT3", "ISRC2INT3"), - ARIZONA_MUX_ROUTES("ISRC2INT4", "ISRC2INT4"), - - ARIZONA_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), - ARIZONA_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), - ARIZONA_MUX_ROUTES("ISRC2DEC3", "ISRC2DEC3"), - ARIZONA_MUX_ROUTES("ISRC2DEC4", "ISRC2DEC4"), - - ARIZONA_MUX_ROUTES("ISRC3INT1", "ISRC3INT1"), - ARIZONA_MUX_ROUTES("ISRC3INT2", "ISRC3INT2"), - ARIZONA_MUX_ROUTES("ISRC3INT3", "ISRC3INT3"), - ARIZONA_MUX_ROUTES("ISRC3INT4", "ISRC3INT4"), - - ARIZONA_MUX_ROUTES("ISRC3DEC1", "ISRC3DEC1"), - ARIZONA_MUX_ROUTES("ISRC3DEC2", "ISRC3DEC2"), - ARIZONA_MUX_ROUTES("ISRC3DEC3", "ISRC3DEC3"), - ARIZONA_MUX_ROUTES("ISRC3DEC4", "ISRC3DEC4"), + ARIZONA_MIXER_ROUTES_R("OUT1L", "HPOUT1L", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT1R", "HPOUT1R", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT2L", "HPOUT2L", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT2R", "HPOUT2R", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT3L", "HPOUT3L", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT3R", "HPOUT3R", "Out Rate"), + + ARIZONA_MIXER_ROUTES_R("OUT4L", "SPKOUTL", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT4R", "SPKOUTR", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT5L", "SPKDAT1L", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT5R", "SPKDAT1R", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT6L", "SPKDAT2L", "Out Rate"), + ARIZONA_MIXER_ROUTES_R("OUT6R", "SPKDAT2R", "Out Rate"), + + ARIZONA_MIXER_ROUTES_R("PWM1 Driver", "PWM1", "PWM Rate"), + ARIZONA_MIXER_ROUTES_R("PWM2 Driver", "PWM2", "PWM Rate"), + + ARIZONA_MIXER_ROUTES_R("AIF1TX1", "AIF1TX1", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX2", "AIF1TX2", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX3", "AIF1TX3", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX4", "AIF1TX4", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX5", "AIF1TX5", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX6", "AIF1TX6", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX7", "AIF1TX7", "AIF1 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF1TX8", "AIF1TX8", "AIF1 Rate"), + + ARIZONA_MIXER_ROUTES_R("AIF2TX1", "AIF2TX1", "AIF2 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF2TX2", "AIF2TX2", "AIF2 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF2TX3", "AIF2TX3", "AIF2 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF2TX4", "AIF2TX4", "AIF2 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF2TX5", "AIF2TX5", "AIF2 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF2TX6", "AIF2TX6", "AIF2 Rate"), + + ARIZONA_MIXER_ROUTES_R("AIF3TX1", "AIF3TX1", "AIF3 Rate"), + ARIZONA_MIXER_ROUTES_R("AIF3TX2", "AIF3TX2", "AIF3 Rate"), + + ARIZONA_MIXER_ROUTES_R("SLIMTX1", "SLIMTX1", "SLIMTX1 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX2", "SLIMTX2", "SLIMTX2 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX3", "SLIMTX3", "SLIMTX3 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX4", "SLIMTX4", "SLIMTX4 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX5", "SLIMTX5", "SLIMTX5 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX6", "SLIMTX6", "SLIMTX6 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX7", "SLIMTX7", "SLIMTX7 Rate"), + ARIZONA_MIXER_ROUTES_R("SLIMTX8", "SLIMTX8", "SLIMTX8 Rate"), + + ARIZONA_MIXER_ROUTES_R("EQ1", "EQ1", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("EQ2", "EQ2", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("EQ3", "EQ3", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("EQ4", "EQ4", "FX Rate"), + + ARIZONA_MIXER_ROUTES_R("DRC1L", "DRC1L", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("DRC1R", "DRC1R", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("DRC2L", "DRC2L", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("DRC2R", "DRC2R", "FX Rate"), + + ARIZONA_MIXER_ROUTES_R("LHPF1", "LHPF1", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("LHPF2", "LHPF2", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("LHPF3", "LHPF3", "FX Rate"), + ARIZONA_MIXER_ROUTES_R("LHPF4", "LHPF4", "FX Rate"), + + ARIZONA_MIXER_ROUTES_R("Mic Mute Mixer", "Noise", "Mic Mute Rate"), + ARIZONA_MIXER_ROUTES_R("Mic Mute Mixer", "Mic", "Mic Mute Rate"), + + ARIZONA_MUX_ROUTES_R("ASRC1L", "ASRC1L", "ASRC Rate"), + ARIZONA_MUX_ROUTES_R("ASRC1R", "ASRC1R", "ASRC Rate"), + ARIZONA_MUX_ROUTES_R("ASRC2L", "ASRC2L", "ASRC Async Rate"), + ARIZONA_MUX_ROUTES_R("ASRC2R", "ASRC2R", "ASRC Async Rate"), + + ARIZONA_DSP_ROUTES_R("DSP1", "DSP1 Rate"), + ARIZONA_DSP_ROUTES_R("DSP2", "DSP2 Rate"), + ARIZONA_DSP_ROUTES_R("DSP3", "DSP3 Rate"), + ARIZONA_DSP_ROUTES_R("DSP4", "DSP4 Rate"), + + ARIZONA_MUX_ROUTES_R("ISRC1INT1", "ISRC1INT1", "ISRC1 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC1INT2", "ISRC1INT2", "ISRC1 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC1INT3", "ISRC1INT3", "ISRC1 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC1INT4", "ISRC1INT4", "ISRC1 FSL"), + + ARIZONA_MUX_ROUTES_R("ISRC1DEC1", "ISRC1DEC1", "ISRC1 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC1DEC2", "ISRC1DEC2", "ISRC1 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC1DEC3", "ISRC1DEC3", "ISRC1 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC1DEC4", "ISRC1DEC4", "ISRC1 FSH"), + + ARIZONA_MUX_ROUTES_R("ISRC2INT1", "ISRC2INT1", "ISRC2 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC2INT2", "ISRC2INT2", "ISRC2 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC2INT3", "ISRC2INT3", "ISRC2 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC2INT4", "ISRC2INT4", "ISRC2 FSL"), + + ARIZONA_MUX_ROUTES_R("ISRC2DEC1", "ISRC2DEC1", "ISRC2 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC2DEC2", "ISRC2DEC2", "ISRC2 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC2DEC3", "ISRC2DEC3", "ISRC2 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC2DEC4", "ISRC2DEC4", "ISRC2 FSH"), + + ARIZONA_MUX_ROUTES_R("ISRC3INT1", "ISRC3INT1", "ISRC3 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC3INT2", "ISRC3INT2", "ISRC3 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC3INT3", "ISRC3INT3", "ISRC3 FSL"), + ARIZONA_MUX_ROUTES_R("ISRC3INT4", "ISRC3INT4", "ISRC3 FSL"), + + ARIZONA_MUX_ROUTES_R("ISRC3DEC1", "ISRC3DEC1", "ISRC3 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC3DEC2", "ISRC3DEC2", "ISRC3 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC3DEC3", "ISRC3DEC3", "ISRC3 FSH"), + ARIZONA_MUX_ROUTES_R("ISRC3DEC4", "ISRC3DEC4", "ISRC3 FSH"),
{ "AEC Loopback", "HPOUT1L", "OUT1L" }, { "AEC Loopback", "HPOUT1R", "OUT1R" }, @@ -2319,6 +2514,19 @@ static int wm5110_component_probe(struct snd_soc_component *component)
snd_soc_component_disable_pin(component, "HAPTICS");
+ snd_soc_dapm_connect_domains(dapm, "SLIMRX1 Rate", "SLIMRX2 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMRX1 Rate", "SLIMRX3 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMRX1 Rate", "SLIMRX4 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMTX1 Rate", "SLIMTX2 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMTX1 Rate", "SLIMTX3 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMTX1 Rate", "SLIMTX4 Rate"); + + snd_soc_dapm_connect_domains(dapm, "SLIMRX5 Rate", "SLIMRX6 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMTX5 Rate", "SLIMTX6 Rate"); + + snd_soc_dapm_connect_domains(dapm, "SLIMRX7 Rate", "SLIMRX8 Rate"); + snd_soc_dapm_connect_domains(dapm, "SLIMTX7 Rate", "SLIMTX8 Rate"); + return 0;
err_adsp2_codec_probe: @@ -2379,6 +2587,8 @@ static const struct snd_soc_component_driver soc_component_dev_wm5110 = { .num_dapm_widgets = ARRAY_SIZE(wm5110_dapm_widgets), .dapm_routes = wm5110_dapm_routes, .num_dapm_routes = ARRAY_SIZE(wm5110_dapm_routes), + .domains = arizona_rate_domains, + .num_domains = ARRAY_SIZE(arizona_rate_domains), .use_pmdown_time = 1, .endianness = 1, .non_legacy_dai_naming = 1,
On 10/11/18 11:28 AM, Charles Keepax wrote:
Here is a first pass at adding dapm sample rate domain support. Things are still pretty rough in quite a few places and I expect some major refactoring before they are ready to merge, but things are far enough along for people to look at the approach I am taking to the problem. And should provide enough for discussion at the mini-conference, although hopefully I will get some more done next week as well.
Interesting, too bad I couldn't be in Edinburgh this year.
The basic high level summary is two new concepts are added to ASoC, one being a sample rate domain and the other being a domain group. The domain groups represent widgets that are limited by hardware restrictions to always run at the same sample rate as each other. Sample rate domains represent a slot on a device for a sample rate. Both of these are currently limited to be within the scope of a single component. The core will track which groups are connected together, then as a group is powered up it will be allocated a domain based on the other groups it is connected to. Domain groups are implemented as a new type of DAPM widget and the domains themselves are a new thing which devices add through their component drivers.
As we have previously discussed my intention would be that we also add bridges between domains although I haven't started work on that yet. The current code is limited to within a single component and I think that the bridges will allow better support for propagating rates between components as well as obviously for SRCs. Currently the SRCs on wm5110 are just being handled as points where the domain groups don't connect to another peer. This allows them to function but doesn't accurately convey things like the limitations which input rates support which output rates on the ISRC.
My feedback would be: why limit the domains to sample rate definition? we can also have additional criteria such as number of channels, channel maps and bit depth - maybe also audio/non-audio for compressed stuff. It'd be interesting to start with a non-scalar descriptor from day1. To some extent we'd also want something similar to .info definitions, with domains defined with masks to allow for flexibility in the conversions and easier bridge implementations. e.g. it'd be interesting to have a domain definition supporting more than one sampling frequency (8, 16, 48kHz) with the bridge doing the relevant conversion to adapt to the connected domain (fixed 48kHz DAI for example)
Thanks for starting the discussion!
-Pierre
Thanks, Charles
Charles Keepax (4): ASoC: dapm: Add support for hw_free on CODEC to CODEC links ASoC: dapm: Add support for a rate domain widget ASoC: domain: Add sample rate domain support ASoC: arizona: Add rate domain support
include/sound/soc-dapm.h | 14 ++ include/sound/soc-domain.h | 98 +++++++++++ include/sound/soc.h | 8 + sound/soc/Makefile | 2 +- sound/soc/codecs/arizona.c | 255 ++++++++++++++++++++++------ sound/soc/codecs/arizona.h | 76 ++++++++- sound/soc/codecs/wm5110.c | 414 ++++++++++++++++++++++++++++++++++----------- sound/soc/soc-core.c | 8 + sound/soc/soc-dapm.c | 57 ++++++- sound/soc/soc-domain.c | 412 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1183 insertions(+), 161 deletions(-) create mode 100644 include/sound/soc-domain.h create mode 100644 sound/soc/soc-domain.c
On Mon, Oct 22, 2018 at 09:27:13AM -0500, Pierre-Louis Bossart wrote:
On 10/11/18 11:28 AM, Charles Keepax wrote: My feedback would be: why limit the domains to sample rate definition? we can also have additional criteria such as number of channels, channel maps and bit depth - maybe also audio/non-audio for compressed stuff. It'd be interesting to start with a non-scalar descriptor from day1. To some extent we'd also want something similar to .info definitions, with domains defined with masks to allow for flexibility in the conversions and easier bridge implementations. e.g. it'd be interesting to have a domain definition supporting more than one sampling frequency (8, 16, 48kHz) with the bridge doing the relevant conversion to adapt to the connected domain (fixed 48kHz DAI for example)
Thanks for starting the discussion!
I don't think there really is anything about the way I am approaching the code that will limit this to just sample rates, it's more that is the part I need at the moment so is what I am implementing. I have basically a "domain" structure, that gets assigned as the groups power up, this is in no way specific to rates. We can just extend that struct to have other properties and add new ops on the domains as people discover additional features they require. Currently at that point in the code you will see a TODO which says to consider using a hw_params struct instead of just an int for the rate. I think based on your comments and others at the conference I will definitely be switching to that in the next spin. Although implementing the additional callbacks etc. is not something I can find sensible use-cases for on our devices, so someone else might need to pick up that part.
Thanks, Charles
participants (3)
-
Charles Keepax
-
Mark Brown
-
Pierre-Louis Bossart