Devicetree
 help / color / mirror / Atom feed
* [PATCH v3 0/2] ASoC: Add support for AN7581 PCM driver
@ 2026-10-09 11:13 Christian Marangi
  2026-10-09 11:13 ` [PATCH v3 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
  2026-10-09 11:13 ` [PATCH v3 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
  0 siblings, 2 replies; 5+ messages in thread
From: Christian Marangi @ 2026-10-09 11:13 UTC (permalink / raw)
  To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Philipp Zabel, linux-sound, devicetree, linux-kernel

The Airoha AN7581 SoC provides a PCM interface for VoIP applications,
supporting playback and capture at sample rates of 8 kHz and 16 kHz.
The driver configures the PCM bit clock to 2.048 MHz and supports
8-bit and 16-bit samples, with up to 32 channels.

Add YAML and driver. This has been verified with loopback
and confirmed by Airoha with ZSI voice chip.

Changes v3:
- Out of RFC
- Add Documentation patch
- Drop SLIC testing code
- Add MAINTAINERS entry
- Follow normal pattern for commit title
- Improve commit description

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

Christian Marangi (2):
  ASoC: dt-bindings: airoha: Add AN7581 PCM controller
  ASoC: airoha: Add support for AN7581 PCM driver

 .../bindings/sound/airoha,an7581-pcm.yaml     |  52 +
 MAINTAINERS                                   |   7 +
 sound/soc/Kconfig                             |   1 +
 sound/soc/Makefile                            |   1 +
 sound/soc/airoha/Kconfig                      |  10 +
 sound/soc/airoha/Makefile                     |   3 +
 sound/soc/airoha/an7581-pcm.c                 | 910 ++++++++++++++++++
 7 files changed, 984 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml
 create mode 100644 sound/soc/airoha/Kconfig
 create mode 100644 sound/soc/airoha/Makefile
 create mode 100644 sound/soc/airoha/an7581-pcm.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller
  2026-10-09 11:13 [PATCH v3 0/2] ASoC: Add support for AN7581 PCM driver Christian Marangi
@ 2026-10-09 11:13 ` Christian Marangi
  2026-10-09 15:41   ` Rob Herring (Arm)
  2026-10-09 11:13 ` [PATCH v3 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
  1 sibling, 1 reply; 5+ messages in thread
From: Christian Marangi @ 2026-10-09 11:13 UTC (permalink / raw)
  To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Philipp Zabel, linux-sound, devicetree, linux-kernel

The Airoha AN7581 SoC provides a PCM interface for VoIP applications,
supporting playback and capture at sample rates of 8 kHz and 16 kHz.
The driver configures the PCM bit clock to 2.048 MHz and supports
8-bit and 16-bit samples, with up to 32 channels.

Document the related compatible and required interrupt/reset and provide an
example for it.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 .../bindings/sound/airoha,an7581-pcm.yaml     | 52 +++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml

diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml
new file mode 100644
index 000000000000..e73236453b88
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/airoha,an7581-pcm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Airoha PCM controller for AN7581 SoC
+
+maintainers:
+  - Christian Marangi <ansuelsmth@gmail.com>
+
+properties:
+  compatible:
+    const: airoha,an7581-pcm
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+  reset-names:
+    const: pcm1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - resets
+  - reset-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/reset/airoha,en7581-reset.h>
+
+    pcm@1fbd0000 {
+        compatible = "airoha,an7581-pcm";
+        reg = <0x1fbd0000 0x10000>;
+
+        interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
+        resets = <&scuclk EN7581_PCM1_RST>;
+        reset-names = "pcm1";
+    };
+
+...
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 2/2] ASoC: airoha: Add support for AN7581 PCM driver
  2026-10-09 11:13 [PATCH v3 0/2] ASoC: Add support for AN7581 PCM driver Christian Marangi
  2026-10-09 11:13 ` [PATCH v3 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
@ 2026-10-09 11:13 ` Christian Marangi
  2026-10-09 11:33   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Christian Marangi @ 2026-10-09 11:13 UTC (permalink / raw)
  To: Christian Marangi, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Philipp Zabel, linux-sound, devicetree, linux-kernel

The Airoha AN7581 SoC provides a PCM interface for VoIP applications,
supporting playback and capture at sample rates of 8 kHz and 16 kHz.
The driver configures the PCM bit clock to 2.048 MHz and supports
8-bit and 16-bit samples, with up to 32 channels.

Add an ASoC platform driver for the AN7581 PCM controller configured for
ZSI attached voice chip.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 MAINTAINERS                   |   7 +
 sound/soc/Kconfig             |   1 +
 sound/soc/Makefile            |   1 +
 sound/soc/airoha/Kconfig      |  10 +
 sound/soc/airoha/Makefile     |   3 +
 sound/soc/airoha/an7581-pcm.c | 910 ++++++++++++++++++++++++++++++++++
 6 files changed, 932 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/MAINTAINERS b/MAINTAINERS
index 5c57cbb68060..6d81a7c5d672 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -776,6 +776,13 @@ F:	Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml
 F:	Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml
 F:	sound/soc/mediatek/an7581/*
 
+AIROHA PCM CONTROLLER DRIVER
+M:	Christian Marangi <ansuelsmth@gmail.com>
+L:	linux-sound@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml
+F:	sound/soc/airoha/*
+
 AIROHA SPI SNFI DRIVER
 M:	Lorenzo Bianconi <lorenzo@kernel.org>
 M:	Ray Liu <ray.liu@airoha.com>
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..51e5264fe801
--- /dev/null
+++ b/sound/soc/airoha/an7581-pcm.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha ALSA SoC PCM platform driver for AN7581
+ *
+ * Copyright (C) 2026 Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#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;
+
+	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_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] ASoC: airoha: Add support for AN7581 PCM driver
  2026-10-09 11:13 ` [PATCH v3 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
@ 2026-10-09 11:33   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-10-09 11:33 UTC (permalink / raw)
  To: Christian Marangi; +Cc: conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider:
- [High] sound: airoha: UAF of DMA descriptors due to devm cleanup ordering
- [High] sound: airoha: probe fails due to IRQF_ONESHOT without thread_fn
- [High] soc: airoha: stream hangs and skips descriptors on underrun/overrun
- [High] sound: airoha: missing interrupt acknowledgement causes IRQ storm
- [High] ASoC: airoha: missing dma_set_coherent_mask() causes DMA address truncation
- [High] sound: airoha: hardware and software pointers desync after XRUN
- [High] alsa: airoha: PCM hardware is configured for non-interleaved data but advertises interleaved
- [High] sound: an7581-pcm: premature IRQ handler exit hangs stream on overrun/underrun
- [High] an7581-pcm: use-after-free of runtime in IRQ handler due to missing synchronize_irq()
- [Medium] sound: airoha: priv->users refcount leak locks hardware configuration
- [Medium] sound: airoha: unbounded tx_pos/rx_pos causes ALSA pointer discontinuity
- [Medium] sound: airoha: data race on tx_pos and tx_desc_idx in prepare()
- [Medium] sound: airoha: device_node_to_regmap() on non-syscon node leaks memory
- [Medium] sound: airoha: missing DMA memory barriers for descriptor status
- [Medium] irq: unconditional IRQ_HANDLED breaks spurious interrupt detection
--

commit 89efa182514eefdcd30c073a692953c0c449d4d3
Author: Christian Marangi <ansuelsmth@gmail.com>
Subject: ASoC: airoha: Add support for AN7581 PCM driver

This commit adds a new ALSA SoC platform driver for the Airoha AN7581 PCM
controller, supporting playback and capture at sample rates of 8 kHz and
16 kHz with up to 32 channels.

> diff --git a/sound/soc/airoha/an7581-pcm.c b/sound/soc/airoha/an7581-pcm.c
> index 0000000000000..51e5264fe801a
> --- /dev/null
> +++ b/sound/soc/airoha/an7581-pcm.c
> @@ -189,11 +189,9 @@ struct an7581_pcm_priv {
>  	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,

[Severity: High]
Does advertising SNDRV_PCM_INFO_INTERLEAVED conflict with the hardware
configuration? In hw_params, the driver sets AN7581_PCM_DESC_CHBF_OFFSET to
the size of a single channel's period, matching a non-interleaved memory
layout. ALSA applications will supply interleaved data, which the hardware
DMA may misinterpret, outputting the wrong samples to the wrong channels.

>  	.formats		= SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE,

[ ... ]

> @@ -259,10 +259,9 @@ static void an7581_pcm_fe_shutdown(struct snd_pcm_substream *substream,
>  		regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
>  				  AN7581_PCM_RXDMA_ENABLE);
>  	}
>  
>  	priv->stream[substream->stream] = NULL;
>  
>  	usleep_range(10000, 20000);

[Severity: High]
Is it possible for a delayed IRQ handler to access a freed runtime? The
driver detaches the stream pointer here but does not wait for concurrent
handlers to finish with synchronize_irq(). A handler executing on another
CPU could have cached the substream pointer and might dereference
substream->runtime after the ALSA core has freed it.

>  	mutex_lock(&priv->user_lock);
>  
>  	if (priv->users)
>  		priv->users--;

[Severity: Medium]
Is there a reference leak for priv->users if userspace calls hw_params
multiple times for the same stream before it starts? Each call to hw_params
increments priv->users, but shutdown only decrements it once. This can lock
the stream configuration by preventing AN7581_PCM_CFG_VALID from clearing,
causing subsequent hw_params calls to fail full-duplex constraints.

[ ... ]

> @@ -274,10 +273,8 @@ 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;

[Severity: Medium]
Is there a data race when resetting tx_pos and tx_desc_idx here? These
variables are modified without acquiring priv->desc_lock. Because ALSA
trigger(STOP) does not call synchronize_irq(), an IRQ handler might still be
executing on another CPU when prepare is called, leading to state corruption.

[Severity: High]
Could the hardware and software pointers desync after an XRUN? During stream
prepare, the software index is reset to 0, but the hardware DMA base address
is only written in hw_params. If prepare is called to recover from an XRUN,
the hardware will resume from its previous descriptor index while the
software handler expects it at index 0, potentially causing a stall.

>  		desc = priv->tx_descs;
>  		num_desc = AN7581_PCM_TX_DESCRIPTORS;

[ ... ]

> @@ -291,10 +288,8 @@ static int an7581_pcm_fe_prepare(struct snd_pcm_substream *substream,
>  	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;

[Severity: High]
Will runtime->dma_addr be truncated on 64-bit systems? Since
dma_set_coherent_mask() is not called in probe, coherent allocations might
return a 64-bit physical address on ARM64, which would be silently truncated
when assigned to the 32-bit desc->dma_addr field here.

>  		desc++;
>  	}

[ ... ]

> @@ -455,10 +450,8 @@ static int an7581_pcm_fe_hw_params(struct snd_pcm_substream *substream,
>  	}
>  
>  	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);

[Severity: High]
Could priv->tx_dma and priv->rx_dma be truncated when written? regmap_write
expects an unsigned int value, which will truncate a 64-bit dma_addr_t to
32 bits on a 64-bit platform.

[ ... ]

> @@ -546,12 +539,9 @@ static snd_pcm_uframes_t an7581_pcm_pointer(struct snd_soc_component *component,
>  
>  	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;

[Severity: Medium]
Will this modulo operation produce a discontinuous jump when the counters
overflow? tx_pos and rx_pos indefinitely increment by the period size in
bytes. When the 32-bit counter wraps around UINT_MAX to 0, the modulo
operation produces a jump because UINT_MAX + 1 is not typically a multiple
of buffer_size. This can confuse the ALSA core and lead to an XRUN.

[ ... ]

> @@ -661,10 +651,8 @@ static irqreturn_t an7581_pcm_irq_handler(int irq, void *data)
>  	struct an7581_pcm_priv *priv = data;
>  	unsigned long flags;
>  	u32 status, isr = 0;
>  
>  	regmap_read(priv->map, AN7581_PCM_ISR, &isr);

[Severity: High]
Are pending interrupts left active here? The interrupt status is read to
identify the source, but it is never explicitly written back to clear the
status in AN7581_PCM_ISR. Since the DT binding specifies a level-triggered
interrupt, omitting the write-back can cause a severe interrupt storm.

>  
>  	/* Scan descriptor on receiving RX desc update interrupt */
>  	if (isr & AN7581_PCM_IMR_RDESC_UPDATE_INT) {

[ ... ]

> @@ -681,10 +669,8 @@ static irqreturn_t an7581_pcm_irq_handler(int irq, void *data)
>  		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;

[Severity: Medium]
Are DMA memory barriers missing here? The CPU checks descriptor ownership
without a subsequent dma_rmb(), which might allow speculative reads of stale
buffer data.

>  			if ((status & AN7581_PCM_DESC_STATUS_OWNERSHIP) ==
>  			    AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA) {

[ ... ]

> @@ -704,10 +690,8 @@ static irqreturn_t an7581_pcm_irq_handler(int irq, void *data)
>  			spin_unlock_irqrestore(&priv->desc_lock, flags);
>  
>  			snd_pcm_period_elapsed(substream);
>  
>  			desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA;

[Severity: Medium]
Is a dma_wmb() missing before handing ownership back to the DMA? Without it,
the CPU might write the ownership status back to RAM before earlier writes
are visible to the device.

>  
>  			if (snd_pcm_capture_hw_avail(runtime) < runtime->period_size)
>  				goto handle_tx;

[Severity: High]
Does exiting early here stall the stream on underrun or overrun? If the
hardware available frames drop below period_size, this early exit aborts
the loop. Remaining completed descriptors are abandoned with CPU ownership,
and the DMA polling demand register is not written, permanently hanging the
DMA engine.

>  
>  			rx_poll = true;

[ ... ]

> @@ -767,11 +751,9 @@ static irqreturn_t an7581_pcm_irq_handler(int irq, void *data)
>  			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;

[Severity: Medium]
Is it safe to return IRQ_HANDLED unconditionally on early exit paths? The
kernel's spurious interrupt detection mechanism relies on handlers returning
IRQ_NONE when they did not service any interrupt. Blindly returning
IRQ_HANDLED prevents the core from disabling a stuck interrupt line.

>  
>  			tx_poll = true;
>  		}

[ ... ]

> @@ -787,14 +769,11 @@ static int an7581_pcm_probe(struct platform_device *pdev)
>  		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);

[Severity: Medium]
Does calling device_node_to_regmap on a non-syscon node leak memory? This
API is intended for persistent syscon devices, registering the regmap in a
global static list without a cleanup function. If the driver is unloaded,
the regmap structure and MMIO mapping will remain allocated forever.

>  
>  	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);

[Severity: High]
Will this probe fail unconditionally? devm_request_threaded_irq is called
with IRQF_ONESHOT and a NULL thread_fn. According to core IRQ management,
IRQF_ONESHOT requires a threaded handler, and omitting it causes
request_threaded_irq to return -EINVAL and trigger a WARN_ON_ONCE.

>  	if (ret)
>  		return ret;

[ ... ]

> @@ -814,11 +793,8 @@ static int an7581_pcm_probe(struct platform_device *pdev)
>  	reset_control_deassert(priv->reset);
>  	usleep_range(5000, 10000);
>  
>  	ret = dma_set_mask(dev, DMA_BIT_MASK(32));

[Severity: High]
Should this use dma_set_mask_and_coherent instead? The streaming mask is
restricted to 32 bits, but the coherent mask retains its default. On 64-bit
platforms, dma_alloc_coherent might return addresses above 4GB, which are
then silently truncated when assigned to the 32-bit dma_addr fields.

>  	if (ret)
>  		return ret;

[ ... ]

> @@ -862,11 +838,8 @@ static void an7581_pcm_remove(struct platform_device *pdev)
>  			  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);
>  }

[Severity: High]
Does freeing the DMA descriptors explicitly here cause a use-after-free?
The driver registers its ALSA components and sound card using managed
devm_snd_soc_register_component and devm_snd_soc_register_card in probe.
Because devres callbacks execute after remove returns, the ALSA components
remain registered and accessible to user-space while and immediately after
this memory is freed. A concurrent ALSA callback or an in-flight IRQ could
dereference the freed descriptor arrays.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261009111334.23522-1-ansuelsmth@gmail.com?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller
  2026-10-09 11:13 ` [PATCH v3 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
@ 2026-10-09 15:41   ` Rob Herring (Arm)
  0 siblings, 0 replies; 5+ messages in thread
From: Rob Herring (Arm) @ 2026-10-09 15:41 UTC (permalink / raw)
  To: Christian Marangi
  Cc: Jaroslav Kysela, Krzysztof Kozlowski, Mark Brown, Liam Girdwood,
	Philipp Zabel, Takashi Iwai, linux-kernel, devicetree,
	linux-sound, Conor Dooley


On Fri, 09 Oct 2026 13:13:31 +0200, Christian Marangi wrote:
> The Airoha AN7581 SoC provides a PCM interface for VoIP applications,
> supporting playback and capture at sample rates of 8 kHz and 16 kHz.
> The driver configures the PCM bit clock to 2.048 MHz and supports
> 8-bit and 16-bit samples, with up to 32 channels.
> 
> Document the related compatible and required interrupt/reset and provide an
> example for it.
> 
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> ---
>  .../bindings/sound/airoha,an7581-pcm.yaml     | 52 +++++++++++++++++++
>  1 file changed, 52 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-pcm.yaml
> 

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-09 15:41 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09 11:13 [PATCH v3 0/2] ASoC: Add support for AN7581 PCM driver Christian Marangi
2026-10-09 11:13 ` [PATCH v3 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
2026-10-09 15:41   ` Rob Herring (Arm)
2026-10-09 11:13 ` [PATCH v3 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
2026-10-09 11:33   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox