[PATCH 4/4] ASoC: amd: acp: Improve support for speaker power events

Marian Postevca
2023-03-20T20:53:50Z

In order to reduce the audible pops when speaker or headphones
are activated or disabled we need to delay the switching of the
GPIOs.
We need to also disable/enable the speaker/headphones GPIOs when
the audio stream is stopped/started. To avoid race conditions
between the speaker power event callback and the trigger callback
we use a ring buffer to save the events that we need to process
in the delayed work callback.

Signed-off-by: Marian Postevca <posteuca@mutex.one>
---
sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 154 +++++++++++++++++-
1 file changed, 149 insertions(+), 5 deletions(-)

diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c
index 138a7c12669b..033c94e91928 100644
--- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c
+++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c
@@ -20,8 +20,18 @@
#include <linux/io.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
+#include <linux/circ_buf.h>
+
#include "../acp-mach.h"

+#define RB_SIZE 32
+
+#define circ_count(circ) \
+ (CIRC_CNT((circ)->head, (circ)->tail, RB_SIZE))
+
+#define circ_space(circ) \
+ (CIRC_SPACE((circ)->head, (circ)->tail, RB_SIZE))
+
#define get_mach_priv(card) ((struct acp3x_es83xx_private *)((acp_get_drvdata(card))->mach_priv))

/* mclk-div-by-2 + terminating entry */
@@ -32,14 +42,35 @@
#define ES83XX_ENABLE_DMIC BIT(4)
#define ES83XX_48_MHZ_MCLK BIT(5)

+enum {
+ SPEAKER_ON = 0,
+ SPEAKER_OFF,
+ SPEAKER_SUSPEND,
+ SPEAKER_RESUME,
+ SPEAKER_MAX
+};
+
+const char *msg[SPEAKER_MAX] = {
+ "SPEAKER_ON",
+ "SPEAKER_OFF",
+ "SPEAKER_SUSPEND",
+ "SPEAKER_RESUME"
+};
+
struct acp3x_es83xx_private {
unsigned long quirk;
+ bool speaker_on;
+ bool stream_suspended;
struct snd_soc_component *codec;
struct device *codec_dev;
struct gpio_desc *gpio_speakers, *gpio_headphone;
struct acpi_gpio_params enable_spk_gpio, enable_hp_gpio;
struct acpi_gpio_mapping gpio_mapping[3];
struct snd_soc_dapm_route mic_map[2];
+ struct delayed_work jack_work;
+ struct mutex rb_lock;
+ struct circ_buf gpio_rb;
+ u8 gpio_events_buf[RB_SIZE];
};

static const unsigned int rates[] = {
@@ -86,6 +117,107 @@ static void acp3x_es83xx_set_gpios_values(struct acp3x_es83xx_private *priv,
gpiod_set_value_cansleep(priv->gpio_headphone, headphone);
}

+static void acp3x_es83xx_rb_insert_evt(struct circ_buf *rb, u8 val)
+{
+ u8 *buf = rb->buf;
+
+ if (circ_space(rb) == 0) {
+ /* make some space by dropping the oldest entry, we are more
+ * interested in the last event
+ */
+ rb->tail = (rb->tail + 1) & (RB_SIZE - 1);
+ }
+ buf[rb->head] = val;
+ rb->head = (rb->head + 1) & (RB_SIZE - 1);
+}
+
+static int acp3x_es83xx_rb_remove_evt(struct circ_buf *rb)
+{
+ u8 *buf = rb->buf;
+ int rc = -1;
+
+ if (circ_count(rb)) {
+ rc = buf[rb->tail];
+ rb->tail = (rb->tail + 1) & (RB_SIZE - 1);
+ }
+ return rc;
+}
+
+static void acp3x_es83xx_jack_events(struct work_struct *work)
+{
+ struct acp3x_es83xx_private *priv =
+ container_of(work, struct acp3x_es83xx_private, jack_work.work);
+ int evt;
+
+ mutex_lock(&priv->rb_lock);
+ do {
+ evt = acp3x_es83xx_rb_remove_evt(&priv->gpio_rb);
+ dev_dbg(priv->codec_dev, "jack event, %s\n", msg[evt]);
+ switch (evt) {
+ case SPEAKER_SUSPEND:
+ acp3x_es83xx_set_gpios_values(priv, 0, 0);
+ break;
+ case SPEAKER_RESUME:
+ acp3x_es83xx_set_gpios_values(priv, priv->speaker_on, !priv->speaker_on);
+ break;
+ case SPEAKER_ON:
+ priv->speaker_on = true;
+ acp3x_es83xx_set_gpios_values(priv, 1, 0);
+ break;
+ case SPEAKER_OFF:
+ priv->speaker_on = false;
+ acp3x_es83xx_set_gpios_values(priv, 0, 1);
+ break;
+ }
+ } while (evt != -1);
+ mutex_unlock(&priv->rb_lock);
+}
+
+static int acp3x_es83xx_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct acp3x_es83xx_private *priv = get_mach_priv(card);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (substream->stream == 0) {
+ dev_dbg(priv->codec_dev, "trigger start/release/resume, activating GPIOs\n");
+ mutex_lock(&priv->rb_lock);
+ if (priv->stream_suspended) {
+ priv->stream_suspended = false;
+ acp3x_es83xx_rb_insert_evt(&priv->gpio_rb, SPEAKER_RESUME);
+ mutex_unlock(&priv->rb_lock);
+ queue_delayed_work(system_wq, &priv->jack_work,
+ msecs_to_jiffies(1));
+ } else {
+ mutex_unlock(&priv->rb_lock);
+ }
+ }
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (substream->stream == 0) {
+ dev_dbg(priv->codec_dev, "trigger pause/suspend/stop deactivating GPIOs\n");
+ mutex_lock(&priv->rb_lock);
+ priv->stream_suspended = true;
+ acp3x_es83xx_rb_insert_evt(&priv->gpio_rb, SPEAKER_SUSPEND);
+ mutex_unlock(&priv->rb_lock);
+ queue_delayed_work(system_wq, &priv->jack_work, msecs_to_jiffies(1));
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int acp3x_es83xx_create_swnode(struct device *codec_dev)
{
struct property_entry props[MAX_NO_PROPS] = {};
@@ -212,12 +344,17 @@ static int acp3x_es83xx_speaker_power_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct acp3x_es83xx_private *priv = get_mach_priv(w->dapm->card);
+ u8 val;
+
+ val = SND_SOC_DAPM_EVENT_ON(event) ? SPEAKER_ON : SPEAKER_OFF;

- dev_dbg(priv->codec_dev, "speaker power event: %d\n", event);
- if (SND_SOC_DAPM_EVENT_ON(event))
- acp3x_es83xx_set_gpios_values(priv, 1, 0);
- else
- acp3x_es83xx_set_gpios_values(priv, 0, 1);
+ dev_dbg(priv->codec_dev, "speaker power event = %s\n", msg[val]);
+
+ mutex_lock(&priv->rb_lock);
+ acp3x_es83xx_rb_insert_evt(&priv->gpio_rb, val);
+ mutex_unlock(&priv->rb_lock);
+
+ queue_delayed_work(system_wq, &priv->jack_work, msecs_to_jiffies(70));

return 0;
}
@@ -353,6 +490,7 @@ static int acp3x_es83xx_init(struct snd_soc_pcm_runtime *runtime)

static const struct snd_soc_ops acp3x_es83xx_ops = {
.startup = acp3x_es83xx_codec_startup,
+ .trigger = acp3x_es83xx_trigger,
};

@@ -452,6 +590,12 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card)
priv->codec_dev = codec_dev;
priv->quirk = (unsigned long)dmi_id->driver_data;
acp_drvdata->mach_priv = priv;
+
+ priv->gpio_rb.buf = priv->gpio_events_buf;
+ priv->gpio_rb.head = priv->gpio_rb.tail = 0;
+ mutex_init(&priv->rb_lock);
+
+ INIT_DELAYED_WORK(&priv->jack_work, acp3x_es83xx_jack_events);
dev_info(dev, "successfully probed the sound card\n");
} else {
ret = -ENODEV;
--
2.39.1