* [PATCH v4 0/2] ASoC: Add support for AN7581 PCM driver
@ 2026-10-12 11:54 Christian Marangi
2026-10-12 11:54 ` [PATCH v4 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
2026-10-12 11:54 ` [PATCH v4 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
0 siblings, 2 replies; 4+ messages in thread
From: Christian Marangi @ 2026-10-12 11:54 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 v4:
- Add review by tag for DT patch
- Address shashiko bot comments
- Better protext tx pos
- Set DMA mask for coherent DMA
- Use more managed resource
- Increase users in startup
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 | 904 ++++++++++++++++++
7 files changed, 978 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] 4+ messages in thread
* [PATCH v4 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller
2026-10-12 11:54 [PATCH v4 0/2] ASoC: Add support for AN7581 PCM driver Christian Marangi
@ 2026-10-12 11:54 ` Christian Marangi
2026-10-12 11:54 ` [PATCH v4 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
1 sibling, 0 replies; 4+ messages in thread
From: Christian Marangi @ 2026-10-12 11:54 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>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../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] 4+ messages in thread
* [PATCH v4 2/2] ASoC: airoha: Add support for AN7581 PCM driver
2026-10-12 11:54 [PATCH v4 0/2] ASoC: Add support for AN7581 PCM driver Christian Marangi
2026-10-12 11:54 ` [PATCH v4 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
@ 2026-10-12 11:54 ` Christian Marangi
2026-10-12 12:12 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Christian Marangi @ 2026-10-12 11:54 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 | 904 ++++++++++++++++++++++++++++++++++
6 files changed, 926 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 ecd1461eb707..f1dd6fa2aec2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -775,6 +775,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..568c11dc7e6b
--- /dev/null
+++ b/sound/soc/airoha/an7581-pcm.c
@@ -0,0 +1,904 @@
+// 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;
+
+ 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");
+
+ /* 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);
+ }
+
+ priv->users++;
+
+ mutex_unlock(&priv->user_lock);
+
+ 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;
+ unsigned long flags;
+ int num_desc, i;
+
+ spin_lock_irqsave(&priv->desc_lock, flags);
+ 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;
+ }
+ spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+ 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 <= 1 &&
+ (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 <= 1) {
+ /* 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 <= 1) {
+ /* 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 <= 1) {
+ /* 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;
+
+ 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_new = 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 const struct regmap_config an7581_pcm_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+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;
+ void __iomem *base;
+ 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);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->map = devm_regmap_init_mmio(dev, base,
+ &an7581_pcm_regmap_config);
+ 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_coherent_mask(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ priv->tx_descs = dmam_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 = dmam_alloc_coherent(dev, sizeof(struct an7581_pcm_desc) *
+ AN7581_PCM_RX_DESCRIPTORS, &priv->rx_dma,
+ GFP_KERNEL);
+ if (!priv->rx_descs)
+ return -ENOMEM;
+
+ 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)
+ return dev_err_probe(dev, ret, "Cannot register PCM component\n");
+
+ ret = devm_snd_soc_register_component(dev,
+ &an7581_pcm_dai_component,
+ an7581_pcm_dai_driver,
+ ARRAY_SIZE(an7581_pcm_dai_driver));
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot register PCM DAI component\n");
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret)
+ return dev_err_probe(dev, ret, "%s snd_soc_register_card fail\n", __func__);
+
+ return 0;
+}
+
+static void an7581_pcm_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &an7581_pcm_card;
+ 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);
+}
+
+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] 4+ messages in thread
* Re: [PATCH v4 2/2] ASoC: airoha: Add support for AN7581 PCM driver
2026-10-12 11:54 ` [PATCH v4 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
@ 2026-10-12 12:12 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-12 12:12 UTC (permalink / raw)
To: Christian Marangi; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [High] ASoC: airoha: an7581_pcm_fe_hw_params logic error permanently rejects first stream
- [High] ASoC: airoha: probe unconditionally fails due to IRQF_ONESHOT without thread_fn
- [High] ASoC: airoha: unbounded tx_pos/rx_pos accumulation breaks ALSA pointer tracking
- [High] ASoC: airoha: missing return on constraint failure in startup corrupts state
- [High] ASoC: airoha: Use-After-Free of DMA descriptors on driver unbind due to devres ordering
- [High] ASoC: airoha: Use-After-Free of ALSA runtime due to missing IRQ synchronization in shutdown
- [High] sound: airoha: missing interrupt status acknowledgment causes IRQ storm
- [High] sound: soc: airoha: inverted users check in hw_params breaks all streams
- [High] sound: soc: airoha: early exit in IRQ handler stalls stream
- [Medium] ASoC: airoha: premature return in IRQ handler stalls DMA on underrun
- [Medium] ALSA: airoha: missing endianness conversion for hardware DMA descriptors
- [Medium] ASoC: airoha: incorrect PCM interleaved format advertised for non-interleaved hardware
--
commit d46acbcb8f477a3db86ab3f295bc95f3a191a323
Author: Christian Marangi <ansuelsmth@gmail.com>
ASoC: airoha: Add support for AN7581 PCM driver
The Airoha AN7581 SoC provides a PCM interface for VoIP applications,
supporting playback and capture at sample rates of 8 kHz and 16 kHz.
Add an ASoC platform driver for the AN7581 PCM controller configured for
ZSI attached voice chip.
diff --git a/sound/soc/airoha/an7581-pcm.c b/sound/soc/airoha/an7581-pcm.c
new file mode 100644
index 0000000000000..568c11dc7e6b9
--- /dev/null
+++ b/sound/soc/airoha/an7581-pcm.c
@@ -0,0 +1,904 @@
+[ ... ]
> +struct an7581_pcm_desc {
> + u32 status;
> + u32 channel_mask;
> + u32 dma_addr;
> +} __packed;
[Severity: Medium]
Will this work correctly on big-endian architectures? Since the descriptors are
shared with the hardware DMA, using native u32 instead of explicit __le32 means
the data will be written and read backwards if the kernel is compiled for a
Big Endian target. Should we use __le32 and explicit cpu_to_le32() conversions?
> +
> +struct an7581_pcm_priv {
[ ... ]
> +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,
[Severity: Medium]
Is it correct to advertise SNDRV_PCM_INFO_INTERLEAVED here?
Later in an7581_pcm_fe_hw_params(), the hardware is configured with a
per-channel memory offset, indicating that the hardware expects a
non-interleaved layout:
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 userspace writes interleaved samples as advertised, might the hardware
misinterpret the audio data?
[ ... ]
> +static int an7581_pcm_fe_startup(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
[ ... ]
> + 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");
> +
> + /* Cache stream substream for IRQ handling */
> + priv->stream[substream->stream] = substream;
[Severity: High]
Does this code inadvertently continue execution and modify driver state when
snd_pcm_hw_constraint_integer() fails? By missing a return statement, the
function proceeds to cache the substream pointer and increment priv->users
before returning the error code. Because the startup failed, ALSA will abort
and never call shutdown, permanently leaking the users count and leaving a
dangling substream pointer in priv->stream that could cause a Use-After-Free
during the next interrupt.
[ ... ]
> +static void an7581_pcm_fe_shutdown(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
[ ... ]
> + priv->stream[substream->stream] = NULL;
> +
> + usleep_range(10000, 20000);
[Severity: High]
Is it possible for a concurrent interrupt handler to execute here?
By clearing the stream pointer without calling synchronize_irq(), an already
executing handler on another CPU might have already passed the NULL check. Once
this function returns, ALSA will free the substream runtime. If the delayed IRQ
handler then resumes, could it dereference the freed runtime pointer?
[ ... ]
> +static int an7581_pcm_fe_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
[ ... ]
> + mutex_lock(&priv->user_lock);
> + if (priv->users <= 1 &&
> + (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;
> + }
[Severity: High]
Does this inverted logic unconditionally reject the first stream?
Since an7581_pcm_fe_startup() increments priv->users, it will be exactly 1 for
the first stream. The condition priv->users <= 1 evaluates to true, and since
the uninitialized priv->current_rate is 0, it will mismatch the requested rate.
This causes the driver to return -EBUSY immediately, preventing hardware setup.
Should this check be for priv->users > 1 instead?
[ ... ]
> +static snd_pcm_uframes_t an7581_pcm_pointer(struct snd_soc_component *component,
> + struct snd_pcm_substream *substream)
> +{
[ ... ]
> + 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: High]
Could the continuously accumulating tx_pos and rx_pos cause a discontinuous jump
when the 32-bit integers wrap? After accumulating 4GB (which wraps to 0), the
value is almost certainly not an exact multiple of the ALSA buffer_size.
Applying modulo here will cause the ALSA frame pointer to jump backwards
unexpectedly, triggering an XRUN and halting the audio stream. Should
the byte counters be bounded or wrapped explicitly at the buffer boundary
instead?
[ ... ]
> +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);
[Severity: High]
Does this handler leave the interrupt asserted? After reading the
AN7581_PCM_ISR register, there is no corresponding regmap_write() to clear the
asserted status bits. Without a write-1-to-clear acknowledgment, the interrupt
line may remain asserted, potentially leading to an unrecoverable interrupt
storm.
> +
> + /* Scan descriptor on receiving RX desc update interrupt */
> + if (isr & AN7581_PCM_IMR_RDESC_UPDATE_INT) {
[ ... ]
> + 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) {
[ ... ]
> + desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA;
> +
> + if (snd_pcm_playback_hw_avail(runtime) < runtime->period_size)
> + return IRQ_HANDLED;
> +
> + tx_poll = true;
> + }
[Severity: High]
Could these early exits abandon completed descriptors and stall the stream?
The loop iterates over descriptors that the hardware has already completed.
If the driver returns early here (or jumps to handle_tx) due to an ALSA
underrun, it breaks out of the loop and skips processing the rest of the
completed descriptors. This leaves them permanently owned by the CPU, stalls
the DMA, and misses the wakeup write to the polling register. Because ALSA
core already handles overruns and underruns safely, shouldn't the driver
always process all completed descriptors without aborting the loop?
[ ... ]
> +static int an7581_pcm_probe(struct platform_device *pdev)
> +{
[ ... ]
> + ret = devm_request_threaded_irq(dev, irq, an7581_pcm_irq_handler,
> + NULL, IRQF_ONESHOT, dev_name(dev), priv);
> + if (ret)
> + return ret;
[Severity: High]
Is it valid to pass IRQF_ONESHOT with a NULL thread_fn? The core IRQ subsystem
rejects this combination because it doesn't make sense to keep the interrupt
masked for a thread that doesn't exist. This will cause
devm_request_threaded_irq() to unconditionally return -EINVAL, causing probe
to fail every time.
> +
> + spin_lock_init(&priv->desc_lock);
> + mutex_init(&priv->user_lock);
> +
[ ... ]
> + priv->tx_descs = dmam_alloc_coherent(dev, sizeof(struct an7581_pcm_desc) *
> + AN7581_PCM_TX_DESCRIPTORS, &priv->tx_dma,
> + GFP_KERNEL);
[Severity: High]
Could this allocation order lead to a Use-After-Free during driver unbind?
Since devres frees resources in reverse order (LIFO), priv->tx_descs and
priv->rx_descs will be freed before the IRQ is released. Because
an7581_pcm_remove() doesn't call synchronize_irq(), an executing interrupt
handler might access the DMA descriptors after devres has already freed them.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261012115408.87066-1-ansuelsmth@gmail.com?part=2
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-12 12:12 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 11:54 [PATCH v4 0/2] ASoC: Add support for AN7581 PCM driver Christian Marangi
2026-10-12 11:54 ` [PATCH v4 1/2] ASoC: dt-bindings: airoha: Add AN7581 PCM controller Christian Marangi
2026-10-12 11:54 ` [PATCH v4 2/2] ASoC: airoha: Add support for AN7581 PCM driver Christian Marangi
2026-10-12 12:12 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox