From 39ed68c8cd3aff417603a95d0594308598b9f469 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 27 Apr 2015 22:13:22 +0200 Subject: ASoC: Add helper function getting CODEC's DAPM context The DAPM context in the snd_soc_codec struct is redundant and scheduled to be replaced by the DAPM context in the snd_soc_component struct. This patch introduces a new helper function snd_soc_codec_get_dapm() which should be used for getting the DAPM context for a CODEC rather then directly accessing the dapm field. Once there are no more direct users of the dapm field left it is possible to transparently switch all drivers to the component DAPM context by updating snd_soc_codec_get_dapm() function. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- include/sound/soc.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/sound/soc.h b/include/sound/soc.h index fcb312b3f258..2f742009da4b 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -807,7 +807,7 @@ struct snd_soc_codec { /* component */ struct snd_soc_component component; - /* dapm */ + /* Don't access this directly, use snd_soc_codec_get_dapm() */ struct snd_soc_dapm_context dapm; #ifdef CONFIG_DEBUG_FS @@ -1269,6 +1269,18 @@ static inline struct snd_soc_dapm_context *snd_soc_component_get_dapm( return component->dapm_ptr; } +/** + * snd_soc_codec_get_dapm() - Returns the DAPM context for the CODEC + * @codec: The CODEC for which to get the DAPM context + * + * Note: Use this function instead of directly accessing the CODEC's dapm field + */ +static inline struct snd_soc_dapm_context *snd_soc_codec_get_dapm( + struct snd_soc_codec *codec) +{ + return &codec->dapm; +} + /** * snd_soc_dapm_kcontrol_codec() - Returns the codec associated to a kcontrol * @kcontrol: The kcontrol -- cgit v1.2.3-70-g09d2 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 ++++++++++++++++++++++++++++++++++ include/sound/soc.h | 40 ++++++++++++++++++++++++++++++++++++++++ sound/soc/soc-dapm.c | 35 +++++++++++++++++++++++++++++++---- 3 files changed, 105 insertions(+), 4 deletions(-) (limited to 'include') 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 diff --git a/include/sound/soc.h b/include/sound/soc.h index 2f742009da4b..7781bfe85c5d 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1281,6 +1281,46 @@ static inline struct snd_soc_dapm_context *snd_soc_codec_get_dapm( return &codec->dapm; } +/** + * snd_soc_dapm_init_bias_level() - Initialize CODEC DAPM bias level + * @dapm: The CODEC for which to initialize the DAPM bias level + * @level: The DAPM level to initialize to + * + * Initializes the CODEC DAPM bias level. See snd_soc_dapm_init_bias_level(). + */ +static inline void snd_soc_codec_init_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + snd_soc_dapm_init_bias_level(snd_soc_codec_get_dapm(codec), level); +} + +/** + * snd_soc_dapm_get_bias_level() - Get current CODEC DAPM bias level + * @codec: The CODEC for which to get the DAPM bias level + * + * Returns: The current DAPM bias level of the CODEC. + */ +static inline enum snd_soc_bias_level snd_soc_codec_get_bias_level( + struct snd_soc_codec *codec) +{ + return snd_soc_dapm_get_bias_level(snd_soc_codec_get_dapm(codec)); +} + +/** + * snd_soc_codec_force_bias_level() - Set the CODEC DAPM bias level + * @codec: The CODEC for which to set the level + * @level: The level to set to + * + * Forces the CODEC bias level to a specific state. See + * snd_soc_dapm_force_bias_level(). + */ +static inline int snd_soc_codec_force_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + return snd_soc_dapm_force_bias_level(snd_soc_codec_get_dapm(codec), + level); +} + /** * snd_soc_dapm_kcontrol_codec() - Returns the codec associated to a kcontrol * @kcontrol: The kcontrol diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index defe0f0082b5..b24782b50809 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -525,6 +525,35 @@ static void soc_dapm_async_complete(struct snd_soc_dapm_context *dapm) snd_soc_component_async_complete(dapm->component); } +/** + * snd_soc_dapm_force_bias_level() - Sets the DAPM bias level + * @dapm: The DAPM context for which to set the level + * @level: The level to set + * + * Forces the DAPM bias level to a specific state. It will call the bias level + * callback of DAPM context with the specified level. This will even happen if + * the context is already at the same level. Furthermore it will not go through + * the normal bias level sequencing, meaning any intermediate states between the + * current and the target state will not be entered. + * + * Note that the change in bias level is only temporary and the next time + * snd_soc_dapm_sync() is called the state will be set to the level as + * determined by the DAPM core. The function is mainly intended to be used to + * used during probe or resume from suspend to power up the device so + * initialization can be done, before the DAPM core takes over. + */ +int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + int ret = 0; + + if (dapm->set_bias_level) + ret = dapm->set_bias_level(dapm, level); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_force_bias_level); + /** * snd_soc_dapm_set_bias_level - set the bias level for the system * @dapm: DAPM context @@ -547,10 +576,8 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, if (ret != 0) goto out; - if (dapm->set_bias_level) - ret = dapm->set_bias_level(dapm, level); - else if (!card || dapm != &card->dapm) - dapm->bias_level = level; + if (!card || dapm != &card->dapm) + ret = snd_soc_dapm_force_bias_level(dapm, level); if (ret != 0) goto out; -- cgit v1.2.3-70-g09d2 From 5967cb3d87802908fe5ab96aa0b417606bf4ca3b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 1 May 2015 12:37:23 +0100 Subject: ASoC: Correct typo in SOC_VALUE_ENUM_SINGLE macro xnitmes is clearly intended to be xnitems, but all other macros just refer to this as xitems, so change it to that. Signed-off-by: Charles Keepax Reviewed-by: Lars-Peter Clausen Tested-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- include/sound/soc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/sound/soc.h b/include/sound/soc.h index 7781bfe85c5d..b257a09a98d1 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -190,8 +190,8 @@ #define SOC_VALUE_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xitems, xtexts, xvalues) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ .mask = xmask, .items = xitems, .texts = xtexts, .values = xvalues} -#define SOC_VALUE_ENUM_SINGLE(xreg, xshift, xmask, xnitmes, xtexts, xvalues) \ - SOC_VALUE_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xnitmes, xtexts, xvalues) +#define SOC_VALUE_ENUM_SINGLE(xreg, xshift, xmask, xitems, xtexts, xvalues) \ + SOC_VALUE_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xitems, xtexts, xvalues) #define SOC_ENUM_SINGLE_VIRT(xitems, xtexts) \ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, xitems, xtexts) #define SOC_ENUM(xname, xenum) \ -- cgit v1.2.3-70-g09d2 From 561ed680b764b288feeb74a24e1d9fb3da98ec7b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 1 May 2015 12:37:26 +0100 Subject: ASoC: dapm: Add support for autodisable mux controls Commit 57295073b6ac ("ASoC: dapm: Implement mixer input auto-disable") added support for autodisable controls, controls whose values are only written to the hardware when their respective widgets are powered up. But it only added support for controls based on the mixer abstraction. This patch add support for mux controls (DAPM controls based on the enum abstraction) to be auto-disabled as well. As each mux can only have a single control, there is no need to tie the autodisable widget to the inputs (as is done for the mixer controls) it can be tided directly to the mux widget itself. Note that it is assumed that the first entry in a autodisable mux control will always represent the off state for the mux and is what the mux will be set to whilst it is disabled. Signed-off-by: Charles Keepax Reviewed-by: Lars-Peter Clausen Tested-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- include/sound/soc.h | 10 +++++++ sound/soc/soc-dapm.c | 78 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/include/sound/soc.h b/include/sound/soc.h index b257a09a98d1..2f2e59e1513e 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -192,6 +192,10 @@ .mask = xmask, .items = xitems, .texts = xtexts, .values = xvalues} #define SOC_VALUE_ENUM_SINGLE(xreg, xshift, xmask, xitems, xtexts, xvalues) \ SOC_VALUE_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xitems, xtexts, xvalues) +#define SOC_VALUE_ENUM_SINGLE_AUTODISABLE(xreg, xshift, xmask, xitems, xtexts, xvalues) \ +{ .reg = xreg, .shift_l = xshift, .shift_r = xshift, \ + .mask = xmask, .items = xitems, .texts = xtexts, \ + .values = xvalues, .autodisable = 1} #define SOC_ENUM_SINGLE_VIRT(xitems, xtexts) \ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, xitems, xtexts) #define SOC_ENUM(xname, xenum) \ @@ -312,6 +316,11 @@ ARRAY_SIZE(xtexts), xtexts, xvalues) #define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \ SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues) + +#define SOC_VALUE_ENUM_SINGLE_AUTODISABLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \ + const struct soc_enum name = SOC_VALUE_ENUM_SINGLE_AUTODISABLE(xreg, \ + xshift, xmask, ARRAY_SIZE(xtexts), xtexts, xvalues) + #define SOC_ENUM_SINGLE_VIRT_DECL(name, xtexts) \ const struct soc_enum name = SOC_ENUM_SINGLE_VIRT(ARRAY_SIZE(xtexts), xtexts) @@ -1188,6 +1197,7 @@ struct soc_enum { unsigned int mask; const char * const *texts; const unsigned int *values; + unsigned int autodisable:1; }; /** diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a0d97f89eb75..79e6cf4b7de1 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -308,6 +308,7 @@ static int dapm_kcontrol_data_alloc(struct snd_soc_dapm_widget *widget, { struct dapm_kcontrol_data *data; struct soc_mixer_control *mc; + struct soc_enum *e; const char *name; int ret; @@ -355,6 +356,41 @@ static int dapm_kcontrol_data_alloc(struct snd_soc_dapm_widget *widget, } } break; + case snd_soc_dapm_mux: + e = (struct soc_enum *)kcontrol->private_value; + + if (e->autodisable) { + struct snd_soc_dapm_widget template; + + name = kasprintf(GFP_KERNEL, "%s %s", kcontrol->id.name, + "Autodisable"); + if (!name) { + ret = -ENOMEM; + goto err_data; + } + + memset(&template, 0, sizeof(template)); + template.reg = e->reg; + template.mask = e->mask << e->shift_l; + template.shift = e->shift_l; + template.off_val = snd_soc_enum_item_to_val(e, 0); + template.on_val = template.off_val; + template.id = snd_soc_dapm_kcontrol; + template.name = name; + + data->value = template.on_val; + + data->widget = snd_soc_dapm_new_control(widget->dapm, + &template); + if (!data->widget) { + ret = -ENOMEM; + goto err_name; + } + + snd_soc_dapm_add_path(widget->dapm, data->widget, + widget, NULL, NULL); + } + break; default: break; } @@ -418,11 +454,6 @@ static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol, struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); list_add_tail(&path->list_kcontrol, &data->paths); - - if (data->widget) { - snd_soc_dapm_add_path(data->widget->dapm, data->widget, - path->source, NULL, NULL); - } } static bool dapm_kcontrol_is_powered(const struct snd_kcontrol *kcontrol) @@ -820,6 +851,7 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w) { int i, ret; struct snd_soc_dapm_path *path; + struct dapm_kcontrol_data *data; /* add kcontrol */ for (i = 0; i < w->num_kcontrols; i++) { @@ -829,16 +861,20 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w) if (path->name != (char *)w->kcontrol_news[i].name) continue; - if (w->kcontrols[i]) { - dapm_kcontrol_add_path(w->kcontrols[i], path); - continue; + if (!w->kcontrols[i]) { + ret = dapm_create_or_share_mixmux_kcontrol(w, i); + if (ret < 0) + return ret; } - ret = dapm_create_or_share_mixmux_kcontrol(w, i); - if (ret < 0) - return ret; - dapm_kcontrol_add_path(w->kcontrols[i], path); + + data = snd_kcontrol_chip(w->kcontrols[i]); + if (data->widget) + snd_soc_dapm_add_path(data->widget->dapm, + data->widget, + path->source, + NULL, NULL); } } @@ -2945,16 +2981,19 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int reg_val, val; - if (e->reg != SND_SOC_NOPM) { + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + if (e->reg != SND_SOC_NOPM && dapm_kcontrol_is_powered(kcontrol)) { int ret = soc_dapm_read(dapm, e->reg, ®_val); if (ret) return ret; } else { reg_val = dapm_kcontrol_get_value(kcontrol); } + mutex_unlock(&card->dapm_mutex); val = (reg_val >> e->shift_l) & e->mask; ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val); @@ -2984,7 +3023,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, struct snd_soc_card *card = dapm->card; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; - unsigned int val, change; + unsigned int val, change, reg_change = 0; unsigned int mask; struct snd_soc_dapm_update update; int ret = 0; @@ -3003,19 +3042,20 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + change = dapm_kcontrol_set_value(kcontrol, val); + if (e->reg != SND_SOC_NOPM) - change = soc_dapm_test_bits(dapm, e->reg, mask, val); - else - change = dapm_kcontrol_set_value(kcontrol, val); + reg_change = soc_dapm_test_bits(dapm, e->reg, mask, val); - if (change) { - if (e->reg != SND_SOC_NOPM) { + if (change || reg_change) { + if (reg_change) { update.kcontrol = kcontrol; update.reg = e->reg; update.mask = mask; update.val = val; card->update = &update; } + change |= reg_change; ret = soc_dapm_mux_update_power(card, kcontrol, item[0], e); -- cgit v1.2.3-70-g09d2 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') 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-70-g09d2 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') 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-70-g09d2