From: Christian Marangi <ansuelsmth@gmail.com>
To: Liam Girdwood <lgirdwood@gmail.com>,
Mark Brown <broonie@kernel.org>, Jaroslav Kysela <perex@perex.cz>,
Takashi Iwai <tiwai@suse.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
Christian Marangi <ansuelsmth@gmail.com>,
linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org
Subject: [RFC PATCH v2] sound: airoha: add support for AN7581 PCM driver
Date: Wed, 30 Sep 2026 09:07:15 +0200 [thread overview]
Message-ID: <20260930070717.47487-1-ansuelsmth@gmail.com> (raw)
The Airoha AN7581 SoC have alternative Sound Card that expose PCM OPs
dedicated for VoIP application.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
This is posted as RFC waiting for Airoha to verify
correct functionality with ZSI or SLIC Voip chip.
Some register/compatible related stuff will be dropped
in the final version.
Driver has been verified by enabling internal loopback
mode.
Changes v2:
- Rentirely rework IRQ handling
- Fix problem for timeslow configuration
- Better support duplex mode
- Better handle descriptor tracking
- Move to Soc directory
- General code fixup and cleanup
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/airoha/Kconfig | 10 +
sound/soc/airoha/Makefile | 3 +
sound/soc/airoha/an7581-pcm.c | 926 ++++++++++++++++++++++++++++++++++
5 files changed, 941 insertions(+)
create mode 100644 sound/soc/airoha/Kconfig
create mode 100644 sound/soc/airoha/Makefile
create mode 100644 sound/soc/airoha/an7581-pcm.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index edfdcbf734fe..27fc13e11b77 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -103,6 +103,7 @@ config SND_SOC_USB
# All the supported SoCs
source "sound/soc/adi/Kconfig"
+source "sound/soc/airoha/Kconfig"
source "sound/soc/amd/Kconfig"
source "sound/soc/apple/Kconfig"
source "sound/soc/atmel/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 21d8406767fc..ea581930bb3d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC) += codecs/
obj-$(CONFIG_SND_SOC) += generic/
obj-$(CONFIG_SND_SOC) += apple/
obj-$(CONFIG_SND_SOC) += adi/
+obj-$(CONFIG_SND_SOC) += airoha/
obj-$(CONFIG_SND_SOC) += amd/
obj-$(CONFIG_SND_SOC) += atmel/
obj-$(CONFIG_SND_SOC) += au1x/
diff --git a/sound/soc/airoha/Kconfig b/sound/soc/airoha/Kconfig
new file mode 100644
index 000000000000..20c9211430c9
--- /dev/null
+++ b/sound/soc/airoha/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SND_SOC_AN7581_PCM
+ tristate "PCM support for Airoha AN7581 chip"
+ depends on ARCH_AIROHA || COMPILE_TEST
+ help
+ This adds PCM driver for Airoha AN7581 boards
+ that can be used with other codecs.
+ Select Y if you have such device.
+ If unsure select "N".
diff --git a/sound/soc/airoha/Makefile b/sound/soc/airoha/Makefile
new file mode 100644
index 000000000000..2522fe705d9a
--- /dev/null
+++ b/sound/soc/airoha/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_SND_SOC_AN7581_PCM) += an7581-pcm.o
diff --git a/sound/soc/airoha/an7581-pcm.c b/sound/soc/airoha/an7581-pcm.c
new file mode 100644
index 000000000000..11b27ec57d42
--- /dev/null
+++ b/sound/soc/airoha/an7581-pcm.c
@@ -0,0 +1,926 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha ALSA SoC PCM platform driver for AN7581
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/pinctrl/consumer.h>
+#include <sound/soc.h>
+
+#define AN7581_PCM_PICR 0x0
+#define AN7581_PCM_CFG_VALID BIT(26)
+#define AN7581_PCM_LPBK_ENABLE BIT(25)
+#define AN7581_PCM_SOFT_RESET BIT(24)
+#define AN7581_PCM_FRAME_COUNT GENMASK(22, 18)
+#define AN7581_PCM_BIT_ORDER BIT(16)
+#define AN7581_PCM_ONE_BIT_DELAY BIT(12)
+#define AN7581_PCM_FS_EDGE BIT(10)
+#define AN7581_PCM_FS_LEN GENMASK(9, 8)
+#define AN7581_PCM_FS_LEN_8 FIELD_PREP_CONST(AN7581_PCM_FS_LEN, 0x2)
+#define AN7581_PCM_FS_LEN_16 FIELD_PREP_CONST(AN7581_PCM_FS_LEN, 0x3)
+#define AN7581_PCM_SAMPLE_CLK BIT(4)
+#define AN7581_PCM_SAMPLE_CLK_8KHZ FIELD_PREP_CONST(AN7581_PCM_SAMPLE_CLK, 0x0)
+#define AN7581_PCM_SAMPLE_CLK_16KHZ FIELD_PREP_CONST(AN7581_PCM_SAMPLE_CLK, 0x1)
+#define AN7581_PCM_BIT_CLK GENMASK(3, 1)
+#define AN7581_PCM_BIT_CLK_256KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x0)
+#define AN7581_PCM_BIT_CLK_512KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x1)
+#define AN7581_PCM_BIT_CLK_1024KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x2)
+#define AN7581_PCM_BIT_CLK_2048HZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x3)
+#define AN7581_PCM_BIT_CLK_4096KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x4)
+#define AN7581_PCM_BIT_CLK_8192KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x5)
+#define AN7581_PCM_BIT_MSTSLV_MODE BIT(0)
+#define AN7581_PCM_BIT_MSTSLV_MODE_MASTER FIELD_PREP_CONST(AN7581_PCM_BIT_MSTSLV_MODE, 0x0)
+#define AN7581_PCM_BIT_MSTSLV_MODE_SLAVE FIELD_PREP_CONST(AN7581_PCM_BIT_MSTSLV_MODE, 0x1)
+/* Same bitmap for all TX timeslot reg */
+#define AN7581_PCM_PTTSCR0 0x4
+#define AN7581_PCM_TXTS1_BW BIT(28)
+#define AN7581_PCM_TXTS1_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_TXTS1_BW, 0x0)
+#define AN7581_PCM_TXTS1_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_TXTS1_BW, 0x1)
+#define AN7581_PCM_TXTS1_START GENMASK(25, 16)
+#define AN7581_PCM_TXTS0_BW BIT(12)
+#define AN7581_PCM_TXTS0_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_TXTS0_BW, 0x0)
+#define AN7581_PCM_TXTS0_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_TXTS0_BW, 0x1)
+#define AN7581_PCM_TXTS0_START GENMASK(9, 0)
+#define AN7581_PCM_PTTSCR1 0x8
+#define AN7581_PCM_PTTSCR2 0xc
+#define AN7581_PCM_PTTSCR3 0x10
+/* Same bitmap for all RX timeslot reg */
+#define AN7581_PCM_PRTSCR0 0x14
+#define AN7581_PCM_RXTS1_BW BIT(28)
+#define AN7581_PCM_RXTS1_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_RXTS1_BW, 0x0)
+#define AN7581_PCM_RXTS1_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_RXTS1_BW, 0x1)
+#define AN7581_PCM_RXTS1_START GENMASK(25, 16)
+#define AN7581_PCM_RXTS0_BW BIT(12)
+#define AN7581_PCM_RXTS0_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_RXTS0_BW, 0x0)
+#define AN7581_PCM_RXTS0_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_RXTS0_BW, 0x1)
+#define AN7581_PCM_RXTS0_START GENMASK(9, 0)
+#define AN7581_PCM_PRTSCR1 0x18
+#define AN7581_PCM_PRTSCR2 0x1c
+#define AN7581_PCM_PRTSCR3 0x20
+#define AN7581_PCM_ISR 0x24
+#define AN7581_PCM_IMR 0x28
+#define AN7581_PCM_IMR_ISI2_INT BIT(16)
+#define AN7581_PCM_IMR_ZSI2_INT BIT(15)
+#define AN7581_PCM_IMR_SLIC_INT BIT(14)
+#define AN7581_PCM_IMR_SFC_INT BIT(13)
+#define AN7581_PCM_IMR_ISI1_INT BIT(12)
+#define AN7581_PCM_IMR_ZSI1_INT BIT(11)
+#define AN7581_PCM_IMR_HUNT_OVERTIME_INT BIT(10)
+#define AN7581_PCM_IMR_HUNT_ERR_INT BIT(9)
+#define AN7581_PCM_IMR_AHB_BUS_ERR_INT BIT(8)
+#define AN7581_PCM_IMR_RBUF_OVERRUN_INT BIT(7)
+#define AN7581_PCM_IMR_TBUF_UNDERRUN_INT BIT(6)
+#define AN7581_PCM_IMR_RDESC_END_INT BIT(5)
+#define AN7581_PCM_IMR_TDESC_END_INT BIT(4)
+#define AN7581_PCM_IMR_RDESC_UPDATE_INT BIT(3)
+#define AN7581_PCM_IMR_TDESC_UPDATE_INT BIT(2)
+#define AN7581_PCM_IMR_TXRX_FB_INT BIT(0)
+#define AN7581_PCM_TPDR 0x2c
+#define AN7581_PCM_TX_POLLING BIT(0)
+#define AN7581_PCM_RPDR 0x30
+#define AN7581_PCM_RX_POLLING BIT(0)
+#define AN7581_PCM_TDRBAR 0x34
+#define AN7581_TDESC_BASE GENMASK(31, 0)
+#define AN7581_PCM_RDRBAR 0x38
+#define AN7581_RDESC_BASE GENMASK(31, 0)
+#define AN7581_PCM_TRDRSR 0x3c
+#define AN7581_DESC_OFFSET GENMASK(7, 4)
+#define AN7581_DESC_SIZE GENMASK(3, 0)
+#define AN7581_PCM_TRDCR 0x40
+#define AN7581_PCM_DMA_POLICY GENMASK(3, 2)
+#define AN7581_PCM_DMA_POLICY_RR FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x0)
+#define AN7581_PCM_DMA_POLICY_RSVD FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x1)
+#define AN7581_PCM_DMA_POLICY_TX_PRIORITY FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x2)
+#define AN7581_PCM_DMA_POLICY_RX_PRIORITY FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x3)
+#define AN7581_PCM_RXDMA_ENABLE BIT(1)
+#define AN7581_PCM_TXDMA_ENABLE BIT(0)
+#define AN7581_PCM_PTTSCR4 0x48
+#define AN7581_PCM_PTTSCR5 0x4c
+#define AN7581_PCM_PTTSCR6 0x50
+#define AN7581_PCM_PTTSCR7 0x54
+#define AN7581_PCM_PTTSCR8 0x58
+#define AN7581_PCM_PTTSCR9 0x5c
+#define AN7581_PCM_PTTSCR10 0x60
+#define AN7581_PCM_PTTSCR11 0x64
+#define AN7581_PCM_PTTSCR12 0x68
+#define AN7581_PCM_PTTSCR13 0x6c
+#define AN7581_PCM_PTTSCR14 0x70
+#define AN7581_PCM_PTTSCR15 0x74
+#define AN7581_PCM_PTTSCR(chan) ((chan) < 8 ? \
+ (AN7581_PCM_PTTSCR0 + ((chan) / 2) * 4) : \
+ (AN7581_PCM_PTTSCR4 + (((chan) - 8) / 2) * 4))
+#define AN7581_PCM_TXTS_BW(chan) ((chan) % 2 ? AN7581_PCM_TXTS1_BW : AN7581_PCM_TXTS0_BW)
+#define AN7581_PCM_TXTS_BW_8BIT 0x0
+#define AN7581_PCM_TXTS_BW_16BIT(chan) AN7581_PCM_TXTS_BW(chan)
+#define AN7581_PCM_TXTS_START(chan) ((chan) % 2 ? AN7581_PCM_TXTS1_START : AN7581_PCM_TXTS0_START)
+#define AN7581_PCM_PRTSCR4 0x78
+#define AN7581_PCM_PRTSCR5 0x7c
+#define AN7581_PCM_PRTSCR6 0x80
+#define AN7581_PCM_PRTSCR7 0x84
+#define AN7581_PCM_PRTSCR8 0x88
+#define AN7581_PCM_PRTSCR9 0x8c
+#define AN7581_PCM_PRTSCR10 0x90
+#define AN7581_PCM_PRTSCR11 0x94
+#define AN7581_PCM_PRTSCR12 0x98
+#define AN7581_PCM_PRTSCR13 0x9c
+#define AN7581_PCM_PRTSCR14 0xa0
+#define AN7581_PCM_PRTSCR15 0xa4
+#define AN7581_PCM_PRTSCR(chan) ((chan) < 8 ? \
+ (AN7581_PCM_PRTSCR0 + ((chan) / 2) * 4) : \
+ (AN7581_PCM_PRTSCR4 + (((chan) - 8) / 2) * 4))
+#define AN7581_PCM_RXTS_BW(chan) ((chan) % 2 ? AN7581_PCM_RXTS1_BW : AN7581_PCM_RXTS0_BW)
+#define AN7581_PCM_RXTS_BW_8BIT 0x0
+#define AN7581_PCM_RXTS_BW_16BIT(chan) AN7581_PCM_RXTS_BW(chan)
+#define AN7581_PCM_RXTS_START(chan) ((chan) % 2 ? AN7581_PCM_RXTS1_START : AN7581_PCM_RXTS0_START)
+#define AN7581_PCM_CHBFOSR 0xa8
+#define AN7581_PCM_DESC_CHBF_OFFSET GENMASK(15, 0)
+#define AN7581_PCM_DCHENR 0xac
+/* Each channel correspond to a bit.
+ * Example:
+ * BIT(0) -> chan 0
+ * BIT(1) -> chan 1
+ * ...
+ *
+ * Notice that it's expected the channel are sequential
+ * aka it's not possible to have empty channel in between.
+ * (example BIT(0) | BIT(2))
+ */
+#define AN7581_PCM_CHAN_EN GENMASK(31, 0)
+
+#define AN7581_PCM_MAX_CHANNELS 32
+#define AN7581_PCM_TX_DESCRIPTORS 15
+#define AN7581_PCM_RX_DESCRIPTORS 15
+#define AN7581_PCM_DESCRIPTORS (AN7581_PCM_TX_DESCRIPTORS + AN7581_PCM_RX_DESCRIPTORS)
+#define AN7581_PCM_FRAMES_PER_DESC 80
+#define AN7581_PCM_MAX_SAMPLE_BYTES 2
+#define AN7581_PCM_MAX_PERIOD_BYTES (AN7581_PCM_FRAMES_PER_DESC * \
+ AN7581_PCM_MAX_CHANNELS * \
+ AN7581_PCM_MAX_SAMPLE_BYTES)
+#define AN7581_PCM_MAX_BUFFER_BYTES (AN7581_PCM_MAX_PERIOD_BYTES * \
+ AN7581_PCM_TX_DESCRIPTORS)
+
+#define AN7581_PCM_DESC_STATUS_OWNERSHIP BIT(31)
+#define AN7581_PCM_DESC_STATUS_OWNERSHIP_CPU FIELD_PREP_CONST(AN7581_PCM_DESC_STATUS_OWNERSHIP, 0x0)
+#define AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA FIELD_PREP_CONST(AN7581_PCM_DESC_STATUS_OWNERSHIP, 0x1)
+#define AN7581_PCM_DESC_STATUS_SAMPLE_SIZE GENMASK(9, 0)
+
+struct an7581_pcm_desc {
+ u32 status;
+ u32 channel_mask;
+ u32 dma_addr;
+} __packed;
+
+struct an7581_pcm_priv {
+ struct regmap *map;
+ struct reset_control *reset;
+
+ /* Cached current stream for IRQ */
+ struct snd_pcm_substream *stream[SNDRV_PCM_STREAM_LAST + 1];
+
+ /* Protect descriptor idx */
+ spinlock_t desc_lock;
+ /* Protect configuration shared by playback and capture. */
+ struct mutex user_lock;
+ unsigned int users;
+ unsigned int current_rate;
+ unsigned int current_channels;
+
+ struct an7581_pcm_desc *tx_descs;
+ int tx_desc_idx;
+ dma_addr_t tx_dma;
+
+ struct an7581_pcm_desc *rx_descs;
+ int rx_desc_idx;
+ dma_addr_t rx_dma;
+
+ unsigned int tx_pos;
+ unsigned int rx_pos;
+};
+
+static const struct snd_pcm_hardware an7581_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE,
+
+ .period_bytes_min = AN7581_PCM_FRAMES_PER_DESC,
+ .period_bytes_max = AN7581_PCM_MAX_PERIOD_BYTES,
+
+ .buffer_bytes_max = AN7581_PCM_MAX_BUFFER_BYTES,
+
+ .periods_min = AN7581_PCM_TX_DESCRIPTORS,
+ .periods_max = AN7581_PCM_TX_DESCRIPTORS,
+};
+
+static int an7581_pcm_setup_time_slot(struct an7581_pcm_priv *priv,
+ int direction, int bit_width)
+{
+ int bit_counter = 0;
+ int chan;
+
+ for (chan = 0; chan < AN7581_PCM_MAX_CHANNELS; chan++) {
+ u32 mask, val;
+ u32 reg;
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg = AN7581_PCM_PTTSCR(chan);
+ mask = AN7581_PCM_TXTS_BW(chan) |
+ AN7581_PCM_TXTS_START(chan);
+
+ if (bit_width == 8)
+ val = AN7581_PCM_TXTS_BW_8BIT;
+ else
+ val = AN7581_PCM_TXTS_BW_16BIT(chan);
+ val |= bit_counter << __bf_shf(AN7581_PCM_TXTS_START(chan));
+ } else {
+ reg = AN7581_PCM_PRTSCR(chan);
+ mask = AN7581_PCM_RXTS_BW(chan) |
+ AN7581_PCM_RXTS_START(chan);
+
+ if (bit_width == 8)
+ val = AN7581_PCM_RXTS_BW_8BIT;
+ else
+ val = AN7581_PCM_RXTS_BW_16BIT(chan);
+ val |= bit_counter << __bf_shf(AN7581_PCM_RXTS_START(chan));
+ }
+
+ regmap_update_bits(priv->map, reg, mask, val);
+
+ bit_counter += bit_width;
+ }
+
+ return 0;
+}
+
+static int an7581_pcm_fe_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ /* Cache stream substream for IRQ handling */
+ priv->stream[substream->stream] = substream;
+
+ /* On open mask interrupt and disable DMA */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_TDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_TXDMA_ENABLE);
+ } else {
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_RDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_RXDMA_ENABLE);
+ }
+
+ usleep_range(10000, 20000);
+
+ mutex_lock(&priv->user_lock);
+ if (!priv->users) {
+ regmap_clear_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_CFG_VALID);
+
+ usleep_range(5000, 10000);
+ }
+ mutex_unlock(&priv->user_lock);
+
+ snd_soc_set_runtime_hwparams(substream, &an7581_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ AN7581_PCM_FRAMES_PER_DESC,
+ AN7581_PCM_FRAMES_PER_DESC);
+ if (ret < 0) {
+ dev_err(rtd->card->dev, "failed to constrain period to 80 frames\n");
+ return ret;
+ }
+
+ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ dev_err(rtd->card->dev, "snd_pcm_hw_constraint_integer failed\n");
+
+ return ret;
+}
+
+static void an7581_pcm_fe_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+
+ /* On close mask interrupt and disable DMA */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_TDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_TXDMA_ENABLE);
+ } else {
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_RDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_RXDMA_ENABLE);
+ }
+
+ priv->stream[substream->stream] = NULL;
+
+ usleep_range(10000, 20000);
+
+ mutex_lock(&priv->user_lock);
+
+ if (priv->users)
+ priv->users--;
+
+ if (!priv->users)
+ regmap_clear_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_CFG_VALID);
+
+ mutex_unlock(&priv->user_lock);
+}
+
+static int an7581_pcm_fe_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct an7581_pcm_desc *desc;
+ unsigned int period_bytes;
+ int num_desc, i;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ priv->tx_pos = 0;
+ priv->tx_desc_idx = 0;
+ desc = priv->tx_descs;
+ num_desc = AN7581_PCM_TX_DESCRIPTORS;
+ } else {
+ priv->rx_pos = 0;
+ priv->rx_desc_idx = 0;
+ desc = priv->rx_descs;
+ num_desc = AN7581_PCM_RX_DESCRIPTORS;
+ }
+
+ period_bytes = frames_to_bytes(runtime, AN7581_PCM_FRAMES_PER_DESC);
+ for (i = 0; i < num_desc; i++) {
+ desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA;
+ desc->dma_addr = runtime->dma_addr + i * period_bytes;
+ desc++;
+ }
+
+ return 0;
+}
+
+static int an7581_pcm_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ int bit_width = params_width(params);
+ struct an7581_pcm_desc *desc;
+ int num_desc, i;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ desc = priv->tx_descs;
+ num_desc = AN7581_PCM_TX_DESCRIPTORS;
+ } else {
+ desc = priv->rx_descs;
+ num_desc = AN7581_PCM_RX_DESCRIPTORS;
+ }
+
+ mutex_lock(&priv->user_lock);
+ if (priv->users &&
+ (priv->current_rate != rate ||
+ priv->current_channels != channels)) {
+ dev_err(rtd->card->dev, "playback/capture rates/channels must match in full-duplex mode\n");
+ mutex_unlock(&priv->user_lock);
+ return -EBUSY;
+ }
+
+ if (!priv->users) {
+ /* one bit delay for ZSI */
+ regmap_set_bits(priv->map, AN7581_PCM_PICR, AN7581_PCM_ONE_BIT_DELAY);
+
+ regmap_update_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_BIT_MSTSLV_MODE,
+ AN7581_PCM_BIT_MSTSLV_MODE_MASTER);
+
+ regmap_update_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_BIT_CLK,
+ AN7581_PCM_BIT_CLK_2048HZ);
+
+ regmap_update_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_SAMPLE_CLK,
+ (rate == 16000 ? AN7581_PCM_SAMPLE_CLK_16KHZ :
+ AN7581_PCM_SAMPLE_CLK_8KHZ));
+
+ regmap_update_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_FS_LEN,
+ AN7581_PCM_FS_LEN_16);
+
+ regmap_set_bits(priv->map, AN7581_PCM_PICR, AN7581_PCM_BIT_ORDER);
+
+ regmap_clear_bits(priv->map, AN7581_PCM_PICR, AN7581_PCM_FS_EDGE);
+ }
+
+ /* Setup timeslot */
+ an7581_pcm_setup_time_slot(priv, substream->stream, bit_width);
+
+ if (!priv->users) {
+ /* Setup Descriptor size and number */
+ regmap_update_bits(priv->map, AN7581_PCM_TRDRSR,
+ AN7581_DESC_OFFSET | AN7581_DESC_SIZE,
+ FIELD_PREP(AN7581_DESC_OFFSET, sizeof(*desc) / sizeof(u32)) |
+ FIELD_PREP(AN7581_DESC_SIZE, num_desc));
+
+ regmap_update_bits(priv->map, AN7581_PCM_CHBFOSR,
+ AN7581_PCM_DESC_CHBF_OFFSET,
+ FIELD_PREP(AN7581_PCM_DESC_CHBF_OFFSET,
+ AN7581_PCM_FRAMES_PER_DESC * bit_width / 8));
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regmap_write(priv->map, AN7581_PCM_TDRBAR, priv->tx_dma);
+ else
+ regmap_write(priv->map, AN7581_PCM_RDRBAR, priv->rx_dma);
+
+ /* Configure descriptors for DMA ownership and sample size */
+ for (i = 0; i < num_desc; i++) {
+ /* Assign to each descriptor the dma_addr + offset of period bytes */
+ desc[i].status = FIELD_PREP(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE,
+ AN7581_PCM_FRAMES_PER_DESC);
+
+ desc[i].channel_mask = GENMASK(channels - 1, 0);
+ }
+
+ if (!priv->users) {
+ /* Enable channels */
+ regmap_update_bits(priv->map, AN7581_PCM_DCHENR,
+ AN7581_PCM_CHAN_EN,
+ FIELD_PREP(AN7581_PCM_CHAN_EN, GENMASK(channels - 1, 0)));
+
+ /* Signal PCM configuration is now valid */
+ regmap_set_bits(priv->map, AN7581_PCM_PICR,
+ AN7581_PCM_CFG_VALID);
+ }
+
+ priv->current_rate = rate;
+ priv->current_channels = channels;
+ priv->users++;
+
+ mutex_unlock(&priv->user_lock);
+
+ return 0;
+}
+
+static int an7581_pcm_fe_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ /* Trigger PCM to POLL new descriptor */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_set_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_TXDMA_ENABLE);
+
+ regmap_set_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_TDESC_UPDATE_INT);
+
+ regmap_write(priv->map, AN7581_PCM_TPDR,
+ AN7581_PCM_TX_POLLING);
+ } else {
+ regmap_set_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_RXDMA_ENABLE);
+
+ regmap_set_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_RDESC_UPDATE_INT);
+
+ regmap_write(priv->map, AN7581_PCM_RPDR,
+ AN7581_PCM_RX_POLLING);
+ }
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_TDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_TXDMA_ENABLE);
+ } else {
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_RDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_RXDMA_ENABLE);
+ }
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops an7581_pcm_fe_ops = {
+ .startup = an7581_pcm_fe_startup,
+ .shutdown = an7581_pcm_fe_shutdown,
+ .hw_params = an7581_pcm_fe_hw_params,
+ .prepare = an7581_pcm_fe_prepare,
+ .trigger = an7581_pcm_fe_trigger,
+};
+
+static struct snd_soc_dai_driver an7581_pcm_dai_driver[] = {
+ {
+ .name = "DLPCM",
+ .id = 0,
+ .playback = {
+ .stream_name = "DLPCM",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE,
+ },
+ .ops = &an7581_pcm_fe_ops,
+ },
+ {
+ .name = "ULPCM",
+ .id = 1,
+ .capture = {
+ .stream_name = "ULPCM",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE,
+ },
+ .ops = &an7581_pcm_fe_ops,
+ },
+};
+
+static const struct snd_soc_component_driver an7581_pcm_dai_component = {
+ .name = "an7581-pcm-dai",
+};
+
+static snd_pcm_uframes_t an7581_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_card *card = component->card;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct an7581_pcm_priv *priv;
+ unsigned long flags;
+ unsigned int pos;
+
+ priv = snd_soc_card_get_drvdata(card);
+
+ spin_lock_irqsave(&priv->desc_lock, flags);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ pos = bytes_to_frames(runtime, priv->tx_pos);
+ else
+ pos = bytes_to_frames(runtime, priv->rx_pos);
+ spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+ return pos % runtime->buffer_size;
+}
+
+static int an7581_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev,
+ 0,
+ AN7581_PCM_MAX_BUFFER_BYTES);
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver an7581_pcm_platform = {
+ .name = "an7581-pcm-audio",
+ .pointer = an7581_pcm_pointer,
+ .pcm_construct = an7581_pcm_new,
+};
+
+SND_SOC_DAILINK_DEFS(playback,
+ DAILINK_COMP_ARRAY(COMP_CPU("DLPCM")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(capture,
+ DAILINK_COMP_ARRAY(COMP_CPU("ULPCM")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link an7581_pcm_dai_links[] = {
+ /* FE */
+ {
+ .name = "an7581-playback",
+ .stream_name = "an7581-playback",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST},
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(playback),
+ },
+ {
+ .name = "an7581-capture",
+ .stream_name = "an7581-capture",
+ .trigger = {SND_SOC_DPCM_TRIGGER_POST,
+ SND_SOC_DPCM_TRIGGER_POST},
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(capture),
+ },
+};
+
+static struct snd_soc_card an7581_pcm_card = {
+ .name = "an7581-pcm",
+ .owner = THIS_MODULE,
+ .dai_link = an7581_pcm_dai_links,
+ .num_links = ARRAY_SIZE(an7581_pcm_dai_links),
+};
+
+static irqreturn_t an7581_pcm_irq_handler(int irq, void *data)
+{
+ struct snd_pcm_substream *substream;
+ struct an7581_pcm_priv *priv = data;
+ unsigned long flags;
+ u32 status, isr = 0;
+
+ regmap_read(priv->map, AN7581_PCM_ISR, &isr);
+
+ /* Scan descriptor on receiving RX desc update interrupt */
+ if (isr & AN7581_PCM_IMR_RDESC_UPDATE_INT) {
+ struct snd_pcm_runtime *runtime;
+ struct an7581_pcm_desc *desc;
+ unsigned int sample_size;
+ bool rx_poll = false;
+ int i;
+
+ substream = priv->stream[SNDRV_PCM_STREAM_CAPTURE];
+ if (!substream)
+ goto handle_tx;
+
+ runtime = substream->runtime;
+
+ for (i = 0; i < AN7581_PCM_RX_DESCRIPTORS; i++) {
+ spin_lock_irqsave(&priv->desc_lock, flags);
+
+ desc = &priv->rx_descs[priv->rx_desc_idx];
+ status = desc->status;
+ if ((status & AN7581_PCM_DESC_STATUS_OWNERSHIP) ==
+ AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA) {
+ spin_unlock_irqrestore(&priv->desc_lock,
+ flags);
+ break;
+ }
+
+ sample_size = FIELD_GET(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE,
+ status);
+
+ priv->rx_pos += frames_to_bytes(runtime, sample_size);
+ priv->rx_desc_idx++;
+ if (priv->rx_desc_idx == AN7581_PCM_RX_DESCRIPTORS)
+ priv->rx_desc_idx = 0;
+
+ spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+ snd_pcm_period_elapsed(substream);
+
+ desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA;
+
+ if (snd_pcm_capture_hw_avail(runtime) < runtime->period_size)
+ goto handle_tx;
+
+ rx_poll = true;
+ }
+
+ if (rx_poll)
+ regmap_write(priv->map, AN7581_PCM_RPDR, AN7581_PCM_RX_POLLING);
+ }
+
+handle_tx:
+ /* Scan descriptor on receiving TX desc update interrupt */
+ if (isr & AN7581_PCM_IMR_TDESC_UPDATE_INT) {
+ struct snd_pcm_runtime *runtime;
+ struct an7581_pcm_desc *desc;
+ unsigned int sample_size;
+ bool tx_poll = false;
+ int i;
+
+ substream = priv->stream[SNDRV_PCM_STREAM_PLAYBACK];
+ if (!substream)
+ return IRQ_HANDLED;
+
+ runtime = substream->runtime;
+
+ for (i = 0; i < AN7581_PCM_TX_DESCRIPTORS; i++) {
+ spin_lock_irqsave(&priv->desc_lock, flags);
+
+ desc = &priv->tx_descs[priv->tx_desc_idx];
+ status = desc->status;
+ if ((status & AN7581_PCM_DESC_STATUS_OWNERSHIP) ==
+ AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA) {
+ spin_unlock_irqrestore(&priv->desc_lock,
+ flags);
+ break;
+ }
+
+ sample_size = FIELD_GET(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE,
+ status);
+
+ priv->tx_pos += frames_to_bytes(runtime, sample_size);
+ priv->tx_desc_idx++;
+ if (priv->tx_desc_idx == AN7581_PCM_TX_DESCRIPTORS)
+ priv->tx_desc_idx = 0;
+
+ spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+ snd_pcm_period_elapsed(substream);
+
+ desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA;
+
+ if (snd_pcm_playback_hw_avail(runtime) < runtime->period_size)
+ return IRQ_HANDLED;
+
+ tx_poll = true;
+ }
+
+ if (tx_poll)
+ regmap_write(priv->map, AN7581_PCM_TPDR,
+ AN7581_PCM_TX_POLLING);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int an7581_pcm_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &an7581_pcm_card;
+ struct snd_soc_dai_link *dai_link;
+ struct device *dev = &pdev->dev;
+ struct an7581_pcm_priv *priv;
+ const char *connection_type;
+ int irq;
+ int ret;
+ int i;
+
+ struct regmap *chip_scu;
+
+ chip_scu = syscon_regmap_lookup_by_compatible("airoha,en7581-chip-scu");
+
+ /* set clock source to ZSI and enable fractional divider */
+ regmap_set_bits(chip_scu, 0x1d0, BIT(10) | BIT(0));
+
+ if (!device_property_read_string(dev, "airoha,connection-type",
+ &connection_type)) {
+ if (!strcmp(connection_type, "slic")) {
+ struct pinctrl *pinctrl;
+
+ pinctrl = devm_pinctrl_get_select(dev, "pcm_spi");
+ if (IS_ERR(pinctrl))
+ dev_err(dev, "Failed to setup PCM SPI pinctrl\n");
+ }
+ }
+
+ card->dev = dev;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ snd_soc_card_set_drvdata(card, priv);
+
+ priv->map = device_node_to_regmap(dev->of_node);
+ if (IS_ERR(priv->map))
+ return PTR_ERR(priv->map);
+
+ priv->reset = devm_reset_control_get_exclusive(dev, "pcm1");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ irq = of_irq_get(dev_of_node(dev), 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev, irq, an7581_pcm_irq_handler,
+ NULL, IRQF_ONESHOT, dev_name(dev), priv);
+ if (ret)
+ return ret;
+
+ spin_lock_init(&priv->desc_lock);
+ mutex_init(&priv->user_lock);
+
+ /* Reset the PCM interface */
+ reset_control_assert(priv->reset);
+ usleep_range(5000, 10000);
+ reset_control_deassert(priv->reset);
+ usleep_range(5000, 10000);
+
+ ret = dma_set_mask(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ priv->tx_descs = dma_alloc_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_TX_DESCRIPTORS, &priv->tx_dma,
+ GFP_KERNEL);
+ if (!priv->tx_descs)
+ return -ENOMEM;
+
+ priv->rx_descs = dma_alloc_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_RX_DESCRIPTORS, &priv->rx_dma,
+ GFP_KERNEL);
+ if (!priv->rx_descs) {
+ ret = -ENOMEM;
+ goto err_free_tx;
+ }
+
+ for_each_card_prelinks(card, i, dai_link)
+ dai_link->platforms->of_node = dev->of_node;
+
+ ret = devm_snd_soc_register_component(dev,
+ &an7581_pcm_platform,
+ NULL, 0);
+ if (ret) {
+ dev_err_probe(dev, ret, "Cannot register PCM component\n");
+ goto err_free_rx;
+ }
+
+ ret = devm_snd_soc_register_component(dev,
+ &an7581_pcm_dai_component,
+ an7581_pcm_dai_driver,
+ ARRAY_SIZE(an7581_pcm_dai_driver));
+ if (ret) {
+ dev_err_probe(dev, ret, "Cannot register PCM DAI component\n");
+ goto err_free_rx;
+ }
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret) {
+ dev_err_probe(dev, ret, "%s snd_soc_register_card fail\n", __func__);
+ goto err_free_rx;
+ }
+
+ return 0;
+
+err_free_rx:
+ dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_RX_DESCRIPTORS, priv->rx_descs, priv->rx_dma);
+err_free_tx:
+ dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_TX_DESCRIPTORS, priv->tx_descs, priv->tx_dma);
+
+ return ret;
+}
+
+static void an7581_pcm_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &an7581_pcm_card;
+ struct device *dev = &pdev->dev;
+ struct an7581_pcm_priv *priv;
+
+ priv = snd_soc_card_get_drvdata(card);
+ regmap_clear_bits(priv->map, AN7581_PCM_IMR,
+ AN7581_PCM_IMR_TDESC_UPDATE_INT |
+ AN7581_PCM_IMR_RDESC_UPDATE_INT);
+ regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+ AN7581_PCM_TXDMA_ENABLE |
+ AN7581_PCM_RXDMA_ENABLE);
+
+ dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_RX_DESCRIPTORS, priv->rx_descs, priv->rx_dma);
+ dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_TX_DESCRIPTORS, priv->tx_descs, priv->tx_dma);
+}
+
+static const struct of_device_id an7581_pcm_dt_match[] = {
+ { .compatible = "airoha,an7581-pcm" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an7581_pcm_dt_match);
+
+static struct platform_driver an7581_pcm_driver = {
+ .driver = {
+ .name = "an7581-pcm",
+ .of_match_table = an7581_pcm_dt_match,
+ },
+ .probe = an7581_pcm_probe,
+ .remove = an7581_pcm_remove,
+};
+module_platform_driver(an7581_pcm_driver);
+
+MODULE_DESCRIPTION("Airoha SoC PCM platform driver for ALSA AN7581");
+MODULE_LICENSE("GPL");
--
2.55.0
next reply other threads:[~2026-09-30 7:07 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 7:07 Christian Marangi [this message]
2026-09-30 11:07 ` Mark Brown
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260930070717.47487-1-ansuelsmth@gmail.com \
--to=ansuelsmth@gmail.com \
--cc=broonie@kernel.org \
--cc=lgirdwood@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=p.zabel@pengutronix.de \
--cc=perex@perex.cz \
--cc=tiwai@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®