[alsa-devel] [PATCH 1/5] ASoC: TWL4030: Enable/disable voice digital filters
From: Misael Lopez Cruz x0052729@ti.com
Enable TWL4030 VTXL/VTXR and VRX digital filters for uplink and downlink paths, respectively.
This patch also corrects voice 8/16kHz mode selection bit (SEL_16K) of CODEC_MODE register.
Signed-off-by: Misael Lopez Cruz x0052729@ti.com Acked-by: Peter Ujfalusi peter.ujfalusi@nokia.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/twl4030.c | 37 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/twl4030.h | 2 +- 2 files changed, 38 insertions(+), 1 deletions(-)
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index eaf91ab..e4d683d 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1629,6 +1629,28 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; }
+/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R + * (VTXL, VTXR) for uplink has to be enabled/disabled. */ +static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(codec, TWL4030_REG_OPTION, reg); +} + static int twl4030_voice_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1665,6 +1687,17 @@ static int twl4030_voice_startup(struct snd_pcm_substream *substream, return 0; }
+static void twl4030_voice_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* Enable voice digital filters */ + twl4030_voice_enable(codec, substream->stream, 0); +} + static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -1673,6 +1706,9 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; u8 old_mode, mode;
+ /* Enable voice digital filters */ + twl4030_voice_enable(codec, substream->stream, 1); + /* bit rate */ old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & ~(TWL4030_CODECPDZ); @@ -1780,6 +1816,7 @@ static struct snd_soc_dai_ops twl4030_dai_ops = {
static struct snd_soc_dai_ops twl4030_dai_voice_ops = { .startup = twl4030_voice_startup, + .shutdown = twl4030_voice_shutdown, .hw_params = twl4030_voice_hw_params, .set_sysclk = twl4030_voice_set_dai_sysclk, .set_fmt = twl4030_voice_set_dai_fmt, diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 3441115..9668bdf 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -110,7 +110,7 @@ #define TWL4030_APLL_RATE_44100 0x90 #define TWL4030_APLL_RATE_48000 0xA0 #define TWL4030_APLL_RATE_96000 0xE0 -#define TWL4030_SEL_16K 0x04 +#define TWL4030_SEL_16K 0x08 #define TWL4030_CODECPDZ 0x02 #define TWL4030_OPT_MODE 0x01 #define TWL4030_OPTION_1 (1 << 0)
DAPM has always applied any changes to the power state of widgets as soon as it has determined that they are required. Instead of doing this store all the changes that are required on lists of widgets to power up and down, then iterate over those lists and apply the changes. This changes the sequence in which changes are implemented, doing all power downs before power ups and always using the up/down sequences (previously they were only used when changes were due to DAC/ADC power events). The error handling is also changed so that we continue attempting to power widgets if some changes fail.
The main benefit of this is to allow future changes to do optimisations over the whole power sequence and to reduce the number of walks of the widget graph required to check the power status of widgets.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 3 ++ include/sound/soc.h | 2 + sound/soc/soc-dapm.c | 81 +++++++++++++++++++++++++++++++-------------- 3 files changed, 61 insertions(+), 25 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 533f9f2..b3f789d 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -385,6 +385,9 @@ struct snd_soc_dapm_widget { /* widget input and outputs */ struct list_head sources; struct list_head sinks; + + /* used during DAPM updates */ + struct list_head power_list; };
#endif diff --git a/include/sound/soc.h b/include/sound/soc.h index 6ab80bf..8309ce8 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -372,6 +372,8 @@ struct snd_soc_codec { enum snd_soc_bias_level bias_level; enum snd_soc_bias_level suspend_bias_level; struct delayed_work delayed_work; + struct list_head up_list; + struct list_head down_list;
/* codec DAI's */ struct snd_soc_dai *dai; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7847f80..04ef841 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -658,7 +658,7 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) static int dapm_power_widget(struct snd_soc_codec *codec, int event, struct snd_soc_dapm_widget *w) { - int power, ret; + int ret;
switch (w->id) { case snd_soc_dapm_pre: @@ -696,18 +696,8 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, return 0;
default: - break; + return dapm_generic_apply_power(w); } - - if (!w->power_check) - return 0; - - power = w->power_check(w); - if (w->power == power) - return 0; - w->power = power; - - return dapm_generic_apply_power(w); }
/* @@ -722,27 +712,68 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { struct snd_soc_dapm_widget *w; - int i, c = 1, *seq = NULL, ret = 0; - - /* do we have a sequenced stream event */ - if (event == SND_SOC_DAPM_STREAM_START) { - c = ARRAY_SIZE(dapm_up_seq); - seq = dapm_up_seq; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - c = ARRAY_SIZE(dapm_down_seq); - seq = dapm_down_seq; + int ret = 0; + int i, power; + + INIT_LIST_HEAD(&codec->up_list); + INIT_LIST_HEAD(&codec->down_list); + + /* Check which widgets we need to power and store them in + * lists indicating if they should be powered up or down. + */ + list_for_each_entry(w, &codec->dapm_widgets, list) { + switch (w->id) { + case snd_soc_dapm_pre: + list_add_tail(&codec->down_list, &w->power_list); + break; + case snd_soc_dapm_post: + list_add_tail(&codec->up_list, &w->power_list); + break; + + default: + if (!w->power_check) + continue; + + power = w->power_check(w); + if (w->power == power) + continue; + + if (power) + list_add_tail(&w->power_list, &codec->up_list); + else + list_add_tail(&w->power_list, + &codec->down_list); + + w->power = power; + break; + } }
- for (i = 0; i < c; i++) { - list_for_each_entry(w, &codec->dapm_widgets, list) { + /* Power down widgets first; try to avoid amplifying pops. */ + for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { + list_for_each_entry(w, &codec->down_list, power_list) { + /* is widget in stream order */ + if (w->id != dapm_down_seq[i]) + continue; + + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power down %s: %d\n", + w->name, ret); + } + }
+ /* Now power up. */ + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) { + list_for_each_entry(w, &codec->up_list, power_list) { /* is widget in stream order */ - if (seq && seq[i] && w->id != seq[i]) + if (w->id != dapm_up_seq[i]) continue;
ret = dapm_power_widget(codec, event, w); if (ret != 0) - return ret; + pr_err("Failed to power up %s: %d\n", + w->name, ret); } }
sysfs is so standard these days there's no point.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/soc-dapm.c | 10 +--------- 1 files changed, 1 insertions(+), 9 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 04ef841..d130602 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -67,10 +67,6 @@ static int dapm_down_seq[] = { snd_soc_dapm_post };
-static int dapm_status = 1; -module_param(dapm_status, int, 0); -MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries"); - static void pop_wait(u32 pop_time) { if (pop_time) @@ -974,16 +970,12 @@ static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
int snd_soc_dapm_sys_add(struct device *dev) { - if (!dapm_status) - return 0; return device_create_file(dev, &dev_attr_dapm_widget); }
static void snd_soc_dapm_sys_remove(struct device *dev) { - if (dapm_status) { - device_remove_file(dev, &dev_attr_dapm_widget); - } + device_remove_file(dev, &dev_attr_dapm_widget); }
/* free all dapm widgets and resources */
Rather than managing the bias level of the system based on if there is an active audio stream manage it based on there being an active DAPM widget. This simplifies the code a little, moving the power handling into one place, and improves audio performance for bypass paths when no playbacks or captures are active.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 2 - include/sound/soc.h | 1 + sound/soc/soc-core.c | 61 ++++++------------------------------ sound/soc/soc-dapm.c | 78 +++++++++++++++++++++++++++++++-------------- 4 files changed, 65 insertions(+), 77 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index b3f789d..ec8a45f 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -279,8 +279,6 @@ int snd_soc_dapm_add_routes(struct snd_soc_codec *codec, /* dapm events */ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, char *stream, int event); -int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, - enum snd_soc_bias_level level);
/* dapm sys fs - used by the core */ int snd_soc_dapm_sys_add(struct device *dev); diff --git a/include/sound/soc.h b/include/sound/soc.h index 8309ce8..2af3213 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -339,6 +339,7 @@ struct snd_soc_codec { struct module *owner; struct mutex mutex; struct device *dev; + struct snd_soc_device *socdev;
struct list_head list;
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c0e7066..4aa8e2d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -299,7 +299,6 @@ static void close_delayed_work(struct work_struct *work) { struct snd_soc_card *card = container_of(work, struct snd_soc_card, delayed_work.work); - struct snd_soc_device *socdev = card->socdev; struct snd_soc_codec *codec = card->codec; struct snd_soc_dai *codec_dai; int i; @@ -315,27 +314,10 @@ static void close_delayed_work(struct work_struct *work)
/* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { - - /* Reduce power if no longer active */ - if (codec->active == 0) { - pr_debug("pop wq D1 %s %s\n", codec->name, - codec_dai->playback.stream_name); - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_PREPARE); - } - codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP); - - /* Fall into standby if no longer active */ - if (codec->active == 0) { - pr_debug("pop wq D3 %s %s\n", codec->name, - codec_dai->playback.stream_name); - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_STANDBY); - } } } mutex_unlock(&pcm_mutex); @@ -399,10 +381,6 @@ static int soc_codec_close(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); - - if (codec->active == 0 && codec_dai->pop_wait == 0) - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_STANDBY); }
mutex_unlock(&pcm_mutex); @@ -467,36 +445,16 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) cancel_delayed_work(&card->delayed_work); }
- /* do we need to power up codec */ - if (codec->bias_level != SND_SOC_BIAS_ON) { - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_PREPARE); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dapm_stream_event(codec, - codec_dai->playback.stream_name, - SND_SOC_DAPM_STREAM_START); - else - snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, - SND_SOC_DAPM_STREAM_START); - - snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON); - snd_soc_dai_digital_mute(codec_dai, 0); - - } else { - /* codec already powered - power on widgets */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dapm_stream_event(codec, - codec_dai->playback.stream_name, - SND_SOC_DAPM_STREAM_START); - else - snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, - SND_SOC_DAPM_STREAM_START); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_stream_event(codec, + codec_dai->playback.stream_name, + SND_SOC_DAPM_STREAM_START); + else + snd_soc_dapm_stream_event(codec, + codec_dai->capture.stream_name, + SND_SOC_DAPM_STREAM_START);
- snd_soc_dai_digital_mute(codec_dai, 0); - } + snd_soc_dai_digital_mute(codec_dai, 0);
out: mutex_unlock(&pcm_mutex); @@ -1372,6 +1330,7 @@ int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid) return ret; }
+ codec->socdev = socdev; codec->card->dev = socdev->dev; codec->card->private_data = codec; strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver)); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index d130602..4ca5e56 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -94,6 +94,30 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL); }
+/** + * snd_soc_dapm_set_bias_level - set the bias level for the system + * @socdev: audio device + * @level: level to configure + * + * Configure the bias (power) levels for the SoC audio device. + * + * Returns 0 for success else error. + */ +static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, + enum snd_soc_bias_level level) +{ + struct snd_soc_card *card = socdev->card; + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + if (card->set_bias_level) + ret = card->set_bias_level(card, level); + if (ret == 0 && codec->set_bias_level) + ret = codec->set_bias_level(codec, level); + + return ret; +} + /* set up initial codec paths */ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, struct snd_soc_dapm_path *p, int i) @@ -707,9 +731,11 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, */ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { + struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; int ret = 0; int i, power; + int sys_power = 0;
INIT_LIST_HEAD(&codec->up_list); INIT_LIST_HEAD(&codec->down_list); @@ -731,6 +757,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue;
power = w->power_check(w); + if (power) + sys_power = 1; + if (w->power == power) continue;
@@ -745,6 +774,15 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } }
+ /* If we're changing to all on or all off then prepare */ + if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) || + (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_PREPARE); + if (ret != 0) + pr_err("Failed to prepare bias: %d\n", ret); + } + /* Power down widgets first; try to avoid amplifying pops. */ for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { list_for_each_entry(w, &codec->down_list, power_list) { @@ -773,6 +811,22 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } }
+ /* If we just powered the last thing off drop to standby bias */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_STANDBY); + if (ret != 0) + pr_err("Failed to apply standby bias: %d\n", ret); + } + + /* If we just powered up then move to active bias */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_ON); + if (ret != 0) + pr_err("Failed to apply active bias: %d\n", ret); + } + return 0; }
@@ -1721,30 +1775,6 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
/** - * snd_soc_dapm_set_bias_level - set the bias level for the system - * @socdev: audio device - * @level: level to configure - * - * Configure the bias (power) levels for the SoC audio device. - * - * Returns 0 for success else error. - */ -int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, - enum snd_soc_bias_level level) -{ - struct snd_soc_card *card = socdev->card; - struct snd_soc_codec *codec = socdev->card->codec; - int ret = 0; - - if (card->set_bias_level) - ret = card->set_bias_level(card, level); - if (ret == 0 && codec->set_bias_level) - ret = codec->set_bias_level(codec, level); - - return ret; -} - -/** * snd_soc_dapm_enable_pin - enable pin. * @codec: SoC codec * @pin: pin name
A standard way of making sure we know when the bias level changes.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/soc-dapm.c | 18 ++++++++++++++++++ 1 files changed, 18 insertions(+), 0 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 4ca5e56..39a63f9 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -110,6 +110,24 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, struct snd_soc_codec *codec = socdev->card->codec; int ret = 0;
+ switch (level) { + case SND_SOC_BIAS_ON: + dev_dbg(socdev->dev, "Setting full bias\n"); + break; + case SND_SOC_BIAS_PREPARE: + dev_dbg(socdev->dev, "Setting bias prepare\n"); + break; + case SND_SOC_BIAS_STANDBY: + dev_dbg(socdev->dev, "Setting standby bias\n"); + break; + case SND_SOC_BIAS_OFF: + dev_dbg(socdev->dev, "Setting bias off\n"); + break; + default: + dev_err(socdev->dev, "Setting invalid bias %d\n", level); + return -EINVAL; + } + if (card->set_bias_level) ret = card->set_bias_level(card, level); if (ret == 0 && codec->set_bias_level)
participants (1)
-
Mark Brown