From fa880775ab0d5a8d540972f7b6800fad1af16b75 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 27 Apr 2015 22:13:23 +0200 Subject: ASoC: Add helper functions bias level management Currently drivers are responsible for managing the bias_level field of their DAPM context. The DAPM state itself is managed by the DAPM core though and the core has certain expectations on how and when the bias_level field should be updated. If drivers don't adhere to these undefined behavior can occur. This patch adds a few helper functions for manipulating the DAPM context state, each function with a description on when it should be used and what its effects are. This will also help us to move more of the bias_level management from drivers to the DAPM core. For convenience also add snd_soc_codec_* wrappers around these helpers. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'include/sound/soc-dapm.h') diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 0bc83647d3fa..70216d20e897 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -444,6 +444,9 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( struct snd_kcontrol *kcontrol); +int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level); + /* dapm widget types */ enum snd_soc_dapm_type { snd_soc_dapm_input = 0, /* input pin */ @@ -623,4 +626,35 @@ struct snd_soc_dapm_stats { int neighbour_checks; }; +/** + * snd_soc_dapm_init_bias_level() - Initialize DAPM bias level + * @dapm: The DAPM context to initialize + * @level: The DAPM level to initialize to + * + * This function only sets the driver internal state of the DAPM level and will + * not modify the state of the device. Hence it should not be used during normal + * operation, but only to synchronize the internal state to the device state. + * E.g. during driver probe to set the DAPM level to the one corresponding with + * the power-on reset state of the device. + * + * To change the DAPM state of the device use snd_soc_dapm_set_bias_level(). + */ +static inline void snd_soc_dapm_init_bias_level( + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + dapm->bias_level = level; +} + +/** + * snd_soc_dapm_get_bias_level() - Get current DAPM bias level + * @dapm: The context for which to get the bias level + * + * Returns: The current bias level of the passed DAPM context. + */ +static inline enum snd_soc_bias_level snd_soc_dapm_get_bias_level( + struct snd_soc_dapm_context *dapm) +{ + return dapm->bias_level; +} + #endif -- cgit v1.2.3-55-g7522 From d714f97c5b8c4c5da56b89a7289acb3f12ef7abb Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 1 May 2015 18:02:43 +0200 Subject: ASoC: dapm: Add demux support A demux is conceptually similar to a mux. Where a mux has multiple input and one output and selects one of the inputs to be connected to the output, the demux has one input and multiple outputs and selects one of the outputs to which the input gets connected. This similarity makes it straight forward to support them in DAPM using the existing mux support, we only need to swap sinks and sources when initially setting up the paths. The only slightly tricky part is that there can only be one control per path. Since mixers/muxes are at the sink of a path and a demux is at the source and both types want a control it is not possible to directly connect a demux output to a mixer/mux input. The patch adds some sanity checks to make sure that this does not happen. Drivers who want to model hardware which directly connects a demux output to a mixer/mux input can do this by inserting a dummy widget between the two. E.g.: { "Dummy", "Demux Control", "Demux" }, { "Mixer", "Mixer Control", "Dummy" }, Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 5 +++ sound/soc/soc-dapm.c | 112 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 102 insertions(+), 15 deletions(-) (limited to 'include/sound/soc-dapm.h') diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 70216d20e897..96c5e0ec81d1 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -107,6 +107,10 @@ struct device; { .id = snd_soc_dapm_mux, .name = wname, \ SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \ .kcontrol_news = wcontrols, .num_kcontrols = 1} +#define SND_SOC_DAPM_DEMUX(wname, wreg, wshift, winvert, wcontrols) \ +{ .id = snd_soc_dapm_demux, .name = wname, \ + SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \ + .kcontrol_news = wcontrols, .num_kcontrols = 1} /* Simplified versions of above macros, assuming wncontrols = ARRAY_SIZE(wcontrols) */ #define SOC_PGA_ARRAY(wname, wreg, wshift, winvert,\ @@ -452,6 +456,7 @@ enum snd_soc_dapm_type { snd_soc_dapm_input = 0, /* input pin */ snd_soc_dapm_output, /* output pin */ snd_soc_dapm_mux, /* selects 1 analog signal from many inputs */ + snd_soc_dapm_demux, /* connects the input to one of multiple outputs */ snd_soc_dapm_mixer, /* mixes several analog signals together */ snd_soc_dapm_mixer_named_ctl, /* mixer with named controls */ snd_soc_dapm_pga, /* programmable gain/attenuation (volume) */ diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 5c159f4f8097..a2e5f2278caa 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -70,6 +70,7 @@ static int dapm_up_seq[] = { [snd_soc_dapm_aif_out] = 4, [snd_soc_dapm_mic] = 5, [snd_soc_dapm_mux] = 6, + [snd_soc_dapm_demux] = 6, [snd_soc_dapm_dac] = 7, [snd_soc_dapm_switch] = 8, [snd_soc_dapm_mixer] = 8, @@ -100,6 +101,7 @@ static int dapm_down_seq[] = { [snd_soc_dapm_mic] = 7, [snd_soc_dapm_micbias] = 8, [snd_soc_dapm_mux] = 9, + [snd_soc_dapm_demux] = 9, [snd_soc_dapm_aif_in] = 10, [snd_soc_dapm_aif_out] = 10, [snd_soc_dapm_dai_in] = 10, @@ -356,6 +358,7 @@ static int dapm_kcontrol_data_alloc(struct snd_soc_dapm_widget *widget, } } break; + case snd_soc_dapm_demux: case snd_soc_dapm_mux: e = (struct soc_enum *)kcontrol->private_value; @@ -639,9 +642,10 @@ out: /* connect mux widget to its interconnecting audio paths */ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, - struct snd_soc_dapm_path *path, const char *control_name) + struct snd_soc_dapm_path *path, const char *control_name, + struct snd_soc_dapm_widget *w) { - const struct snd_kcontrol_new *kcontrol = &path->sink->kcontrol_news[0]; + const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0]; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, item; int i; @@ -781,6 +785,7 @@ static int dapm_create_or_share_mixmux_kcontrol(struct snd_soc_dapm_widget *w, wname_in_long_name = false; kcname_in_long_name = true; break; + case snd_soc_dapm_demux: case snd_soc_dapm_mux: wname_in_long_name = true; kcname_in_long_name = false; @@ -886,17 +891,32 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w) { struct snd_soc_dapm_context *dapm = w->dapm; struct snd_soc_dapm_path *path; + struct list_head *paths; + const char *type; int ret; + switch (w->id) { + case snd_soc_dapm_mux: + paths = &w->sources; + type = "mux"; + break; + case snd_soc_dapm_demux: + paths = &w->sinks; + type = "demux"; + break; + default: + return -EINVAL; + } + if (w->num_kcontrols != 1) { dev_err(dapm->dev, - "ASoC: mux %s has incorrect number of controls\n", + "ASoC: %s %s has incorrect number of controls\n", type, w->name); return -EINVAL; } - if (list_empty(&w->sources)) { - dev_err(dapm->dev, "ASoC: mux %s has no paths\n", w->name); + if (list_empty(paths)) { + dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name); return -EINVAL; } @@ -904,9 +924,16 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w) if (ret < 0) return ret; - list_for_each_entry(path, &w->sources, list_sink) { - if (path->name) - dapm_kcontrol_add_path(w->kcontrols[0], path); + if (w->id == snd_soc_dapm_mux) { + list_for_each_entry(path, &w->sources, list_sink) { + if (path->name) + dapm_kcontrol_add_path(w->kcontrols[0], path); + } + } else { + list_for_each_entry(path, &w->sinks, list_source) { + if (path->name) + dapm_kcontrol_add_path(w->kcontrols[0], path); + } } return 0; @@ -2414,6 +2441,50 @@ static void dapm_update_widget_flags(struct snd_soc_dapm_widget *w) } } +static int snd_soc_dapm_check_dynamic_path(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink, + const char *control) +{ + bool dynamic_source = false; + bool dynamic_sink = false; + + if (!control) + return 0; + + switch (source->id) { + case snd_soc_dapm_demux: + dynamic_source = true; + break; + default: + break; + } + + switch (sink->id) { + case snd_soc_dapm_mux: + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + dynamic_sink = true; + break; + default: + break; + } + + if (dynamic_source && dynamic_sink) { + dev_err(dapm->dev, + "Direct connection between demux and mixer/mux not supported for path %s -> [%s] -> %s\n", + source->name, control, sink->name); + return -EINVAL; + } else if (!dynamic_source && !dynamic_sink) { + dev_err(dapm->dev, + "Control not supported for path %s -> [%s] -> %s\n", + source->name, control, sink->name); + return -EINVAL; + } + + return 0; +} + static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink, const char *control, @@ -2444,6 +2515,10 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, return -EINVAL; } + ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control); + if (ret) + return ret; + path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL); if (!path) return -ENOMEM; @@ -2463,10 +2538,19 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, if (control == NULL) { path->connect = 1; } else { - /* connect dynamic paths */ + switch (wsource->id) { + case snd_soc_dapm_demux: + ret = dapm_connect_mux(dapm, path, control, wsource); + if (ret) + goto err; + break; + default: + break; + } + switch (wsink->id) { case snd_soc_dapm_mux: - ret = dapm_connect_mux(dapm, path, control); + ret = dapm_connect_mux(dapm, path, control, wsink); if (ret != 0) goto err; break; @@ -2478,11 +2562,7 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, goto err; break; default: - dev_err(dapm->dev, - "Control not supported for path %s -> [%s] -> %s\n", - wsource->name, control, wsink->name); - ret = -EINVAL; - goto err; + break; } } @@ -2815,6 +2895,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) dapm_new_mixer(w); break; case snd_soc_dapm_mux: + case snd_soc_dapm_demux: dapm_new_mux(w); break; case snd_soc_dapm_pga: @@ -3219,6 +3300,7 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, w->power_check = dapm_always_on_check_power; break; case snd_soc_dapm_mux: + case snd_soc_dapm_demux: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: -- cgit v1.2.3-55-g7522 From 45a110a1377d9f7afbbf53e351b72cf813ac426e Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:50:30 +0100 Subject: ASoC: dapm: Add cache to speed up adding of routes Some CODECs have a significant number of DAPM routes and for each route, when it is added to the card, the entire card widget list must be searched. When adding routes it is very likely, however, that adjacent routes will require adjacent widgets. For example all the routes for a mux are likely added in a block and the sink widget will be the same each time and it is also quite likely that the source widgets are sequential located in the widget list. This patch adds a cache to the DAPM context, this cache will hold the source and sink widgets from the last call to snd_soc_dapm_add_route for that context. A small search of the widget list will be made from those points for both the sink and source. Currently this search only checks both the last widget and the one adjacent to it. On wm8280 which has approximately 500 widgets and 30000 routes (one of the largest CODECs in mainline), the number of paths that hit the cache is 24000, which significantly improves probe time. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 7 +++++++ sound/soc/soc-dapm.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) (limited to 'include/sound/soc-dapm.h') diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 96c5e0ec81d1..b9170e2bc5ab 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -593,6 +593,10 @@ struct snd_soc_dapm_update { int val; }; +struct snd_soc_dapm_wcache { + struct snd_soc_dapm_widget *widget; +}; + /* DAPM context */ struct snd_soc_dapm_context { enum snd_soc_bias_level bias_level; @@ -614,6 +618,9 @@ struct snd_soc_dapm_context { int (*set_bias_level)(struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); + struct snd_soc_dapm_wcache path_sink_cache; + struct snd_soc_dapm_wcache path_source_cache; + #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_dapm; #endif diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 63748526d630..10fb7087c405 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -572,6 +572,35 @@ static void soc_dapm_async_complete(struct snd_soc_dapm_context *dapm) snd_soc_component_async_complete(dapm->component); } +static struct snd_soc_dapm_widget * +dapm_wcache_lookup(struct snd_soc_dapm_wcache *wcache, const char *name) +{ + struct snd_soc_dapm_widget *w = wcache->widget; + struct list_head *wlist; + const int depth = 2; + int i = 0; + + if (w) { + wlist = &w->dapm->card->widgets; + + list_for_each_entry_from(w, wlist, list) { + if (!strcmp(name, w->name)) + return w; + + if (++i == depth) + break; + } + } + + return NULL; +} + +static inline void dapm_wcache_update(struct snd_soc_dapm_wcache *wcache, + struct snd_soc_dapm_widget *w) +{ + wcache->widget = w; +} + /** * snd_soc_dapm_force_bias_level() - Sets the DAPM bias level * @dapm: The DAPM context for which to set the level @@ -2610,6 +2639,12 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, source = route->source; } + wsource = dapm_wcache_lookup(&dapm->path_source_cache, source); + wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink); + + if (wsink && wsource) + goto skip_search; + /* * find src and dest widgets over all widgets but favor a widget from * current DAPM context @@ -2650,6 +2685,10 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, return -ENODEV; } +skip_search: + dapm_wcache_update(&dapm->path_sink_cache, wsink); + dapm_wcache_update(&dapm->path_source_cache, wsource); + ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control, route->connected); if (ret) -- cgit v1.2.3-55-g7522