lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20251003015521.2244-1-ansuelsmth@gmail.com>
Date: Fri,  3 Oct 2025 03:54:42 +0200
From: Christian Marangi <ansuelsmth@...il.com>
To: Jaroslav Kysela <perex@...ex.cz>,
	Takashi Iwai <tiwai@...e.com>,
	Philipp Zabel <p.zabel@...gutronix.de>,
	Christian Marangi <ansuelsmth@...il.com>,
	linux-kernel@...r.kernel.org,
	linux-sound@...r.kernel.org,
	upstream@...oha.com
Subject: [RFC PATCH] sound: airoha: add support for AN7581 PCM driver

The Airoha AN7581 SoC have alternative Sound Card that expose PCM OPs
dedicated for VoIP application.

Signed-off-by: Christian Marangi <ansuelsmth@...il.com>
---
 sound/Kconfig             |   2 +
 sound/Makefile            |   2 +-
 sound/airoha/Kconfig      |   9 +
 sound/airoha/Makefile     |   3 +
 sound/airoha/an7581-pcm.c | 637 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 652 insertions(+), 1 deletion(-)
 create mode 100644 sound/airoha/Kconfig
 create mode 100644 sound/airoha/Makefile
 create mode 100644 sound/airoha/an7581-pcm.c

diff --git a/sound/Kconfig b/sound/Kconfig
index 8b40205394fe..7886c2460e86 100644
--- a/sound/Kconfig
+++ b/sound/Kconfig
@@ -63,6 +63,8 @@ source "sound/ppc/Kconfig"
 
 source "sound/ac97/Kconfig"
 
+source "sound/airoha/Kconfig"
+
 source "sound/aoa/Kconfig"
 
 source "sound/arm/Kconfig"
diff --git a/sound/Makefile b/sound/Makefile
index 5942311a4232..85008bb22b7e 100644
--- a/sound/Makefile
+++ b/sound/Makefile
@@ -6,7 +6,7 @@ obj-$(CONFIG_SOUND) += soundcore.o
 obj-$(CONFIG_DMASOUND) += oss/dmasound/
 obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
 	firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ \
-	virtio/
+	virtio/ airoha/
 obj-$(CONFIG_SND_AOA) += aoa/
 
 # This one must be compilable even if sound is configured out
diff --git a/sound/airoha/Kconfig b/sound/airoha/Kconfig
new file mode 100644
index 000000000000..fe226fedd1a4
--- /dev/null
+++ b/sound/airoha/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SND_AN7581_PCM
+	tristate "PCM support for Airoha AN7581 chip"
+	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/airoha/Makefile b/sound/airoha/Makefile
new file mode 100644
index 000000000000..039b8853d5a2
--- /dev/null
+++ b/sound/airoha/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_SND_AN7581_PCM) += an7581-pcm.o
diff --git a/sound/airoha/an7581-pcm.c b/sound/airoha/an7581-pcm.c
new file mode 100644
index 000000000000..bf1fc447aed4
--- /dev/null
+++ b/sound/airoha/an7581-pcm.c
@@ -0,0 +1,637 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha ALSA SoC PCM platform driver for AN7581
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define AN7581_PCM_PICR				0x0
+#define   AN7581_PCM_CFG_VALID			BIT(26)
+#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(0)
+#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) / 2 < 8 ? AN7581_PCM_PTTSCR0 : AN7581_PCM_PTTSCR4)
+#define   AN7581_PCM_TXTS_BW(chan)		((chan) % 2 ? AN7581_PCM_TXTS0_BW : AN7581_PCM_TXTS1_BW)
+#define     AN7581_PCM_TXTS_BW_8BIT(chan)	0x0
+#define     AN7581_PCM_TXTS_BW_16BIT(chan)	AN7581_PCM_TXTS_BW(chan)
+#define   AN7581_PCM_TXTS_START(chan)		((chan) % 2 ? AN7581_PCM_TXTS0_START : AN7581_PCM_TXTS1_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) / 2 < 8 ? AN7581_PCM_PRTSCR0 : AN7581_PCM_PRTSCR4)
+#define   AN7581_PCM_RXTS_BW(chan)		((chan) % 2 ? AN7581_PCM_RXTS0_BW : AN7581_PCM_RXTS1_BW)
+#define     AN7581_PCM_RXTS_BW_8BIT(chan)	0x0
+#define     AN7581_PCM_RXTS_BW_16BIT(chan)	AN7581_PCM_RXTS_BW(chan)
+#define   AN7581_PCM_RXTS_START(chan)		((chan) % 2 ? AN7581_PCM_RXTS0_START : AN7581_PCM_RXTS1_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_SAMPLE_SIZE			80
+#define AN7581_PCM_BUFFER_SIZE			160
+
+#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;
+	dma_addr_t buf;
+};
+
+enum an7581_pcm_direction {
+	AN7581_PCM_PLAYBACK,
+	AN7581_PCM_CAPTURE,
+};
+
+enum an7581_pcm_bitwidth {
+	AN7581_PCM_BITWIDTH_8BIT = 8,
+	AN7581_PCM_BITWIDTH_16BIT = 16,
+};
+
+struct an7581_pcm_priv {
+	struct regmap *map;
+	struct reset_control *reset;
+
+	struct snd_pcm_substream *playback_stream;
+	struct snd_pcm_substream *capture_stream;
+
+	/* Protect descriptor idx */
+	spinlock_t desc_lock;
+
+	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;
+};
+
+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,
+	.buffer_bytes_max	= AN7581_PCM_BUFFER_SIZE,
+	.periods_min		= AN7581_PCM_SAMPLE_SIZE,
+	.periods_max		= AN7581_PCM_BUFFER_SIZE / AN7581_PCM_SAMPLE_SIZE,
+	.channels_min		= 1,
+	.channels_max		= AN7581_PCM_MAX_CHANNELS,
+};
+
+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;
+
+		if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+			mask = AN7581_PCM_TXTS_BW(chan) |
+			       AN7581_PCM_TXTS_START(chan);
+
+			if (bit_width == 8)
+				val = AN7581_PCM_TXTS_BW_8BIT(chan);
+			else
+				val = AN7581_PCM_TXTS_BW_16BIT(chan);
+			val |= bit_counter << __bf_shf(AN7581_PCM_TXTS_START(chan));
+		} else {
+			mask = AN7581_PCM_RXTS_BW(chan) |
+			       AN7581_PCM_RXTS_START(chan);
+
+			if (bit_width == 8)
+				val = AN7581_PCM_RXTS_BW_8BIT(chan);
+			else
+				val = AN7581_PCM_RXTS_BW_16BIT(chan);
+			val |= bit_counter << __bf_shf(AN7581_PCM_RXTS_START(chan));
+		}
+
+		regmap_update_bits(priv->map, AN7581_PCM_PTTSCR(chan),
+				   mask, val);
+
+		bit_counter += bit_width;
+	}
+
+	return 0;
+}
+
+static int an7581_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct an7581_pcm_priv *priv = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		priv->playback_stream = substream;
+	else
+		priv->capture_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_END_INT |
+				  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_END_INT |
+				  AN7581_PCM_IMR_RDESC_UPDATE_INT);
+		regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+				  AN7581_PCM_RXDMA_ENABLE);
+	}
+
+	usleep_range(10, 20);
+
+	regmap_clear_bits(priv->map, AN7581_PCM_PICR,
+			  AN7581_PCM_CFG_VALID);
+
+	usleep_range(5000, 10000);
+
+	/* Enable channels */
+	regmap_update_bits(priv->map, AN7581_PCM_DCHENR,
+			   AN7581_PCM_CHAN_EN,
+			   FIELD_PREP(AN7581_PCM_CHAN_EN,
+				      BIT(AN7581_PCM_MAX_CHANNELS) - 1));
+
+	/* Enable interrupt */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		regmap_set_bits(priv->map, AN7581_PCM_IMR,
+				AN7581_PCM_IMR_TDESC_END_INT |
+				AN7581_PCM_IMR_TDESC_UPDATE_INT);
+	} else {
+		regmap_set_bits(priv->map, AN7581_PCM_IMR,
+				AN7581_PCM_IMR_RDESC_END_INT |
+				AN7581_PCM_IMR_RDESC_UPDATE_INT);
+	}
+
+	snd_soc_set_runtime_hwparams(substream, &an7581_pcm_hardware);
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return 0;
+}
+
+static int an7581_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct an7581_pcm_priv *priv = snd_pcm_substream_chip(substream);
+
+	/* 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_END_INT |
+				  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_END_INT |
+				  AN7581_PCM_IMR_RDESC_UPDATE_INT);
+		regmap_clear_bits(priv->map, AN7581_PCM_TRDCR,
+				  AN7581_PCM_RXDMA_ENABLE);
+	}
+
+	usleep_range(10, 20);
+
+	regmap_clear_bits(priv->map, AN7581_PCM_PICR,
+			  AN7581_PCM_CFG_VALID);
+
+	return 0;
+}
+
+static int an7581_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int an7581_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct an7581_pcm_priv *priv = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	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;
+	}
+
+	/* Setup timeslot */
+	an7581_pcm_setup_time_slot(priv, substream->stream, params_width(params));
+
+	/* 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, AN7581_PCM_TX_DESCRIPTORS)));
+
+	/* FIXME: Confirm this actually set buffer size */
+	regmap_update_bits(priv->map, AN7581_PCM_CHBFOSR,
+			   AN7581_PCM_DESC_CHBF_OFFSET,
+			   FIELD_PREP(AN7581_PCM_DESC_CHBF_OFFSET,
+				      AN7581_PCM_BUFFER_SIZE));
+
+	/* Configure each RX descriptor 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->buf = runtime->dma_addr + (i * params_period_bytes(params));
+		desc->status = AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA |
+				  FIELD_PREP(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE,
+					     AN7581_PCM_SAMPLE_SIZE);
+		desc->channel_mask = BIT(AN7581_PCM_MAX_CHANNELS) - 1;
+
+		desc++;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		regmap_set_bits(priv->map, AN7581_PCM_TRDCR,
+				AN7581_PCM_TXDMA_ENABLE);
+	else
+		regmap_set_bits(priv->map, AN7581_PCM_TRDCR,
+				AN7581_PCM_RXDMA_ENABLE);
+
+	/* Signal PCM configuration is now valid */
+	regmap_set_bits(priv->map, AN7581_PCM_PICR,
+			AN7581_PCM_CFG_VALID);
+
+	return 0;
+}
+
+static int an7581_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct an7581_pcm_priv *priv = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/* Trigger PCM to POLL new descriptor */
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			rcu_assign_pointer(priv->playback_stream, substream);
+
+			spin_lock_irqsave(&priv->desc_lock, flags);
+			priv->rx_desc_idx = 0;
+			spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+			regmap_set_bits(priv->map, AN7581_PCM_TPDR,
+					AN7581_PCM_TX_POLLING);
+		} else {
+			rcu_assign_pointer(priv->capture_stream, substream);
+
+			spin_lock_irqsave(&priv->desc_lock, flags);
+			priv->tx_desc_idx = 0;
+			spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+			regmap_set_bits(priv->map, AN7581_PCM_RPDR,
+					AN7581_PCM_RX_POLLING);
+		}
+
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			rcu_assign_pointer(priv->playback_stream, NULL);
+		else
+			rcu_assign_pointer(priv->capture_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t an7581_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct an7581_pcm_priv *priv = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&priv->desc_lock, flags);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		idx = priv->tx_desc_idx;
+	else
+		idx = priv->rx_desc_idx;
+
+	spin_unlock_irqrestore(&priv->desc_lock, flags);
+
+	return idx;
+}
+
+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 isr;
+	int i;
+
+	regmap_read(priv->map, AN7581_PCM_ISR, &isr);
+
+	/* Scan descriptor on receiving RX desc update interrupt */
+	if (isr & AN7581_PCM_IMR_RDESC_UPDATE_INT) {
+		spin_lock_irqsave(&priv->desc_lock, flags);
+
+		for (i = 0; i < AN7581_PCM_RX_DESCRIPTORS; i++) {
+			if (FIELD_GET(AN7581_PCM_DESC_STATUS_OWNERSHIP,
+				      priv->rx_descs[i].status) ==
+			    AN7581_PCM_DESC_STATUS_OWNERSHIP_CPU)
+				priv->rx_desc_idx++;
+		}
+
+		spin_unlock_irqrestore(&priv->desc_lock, flags);
+	}
+
+	/* Scan descriptor on receiving TX desc update interrupt */
+	if (isr & AN7581_PCM_IMR_TDESC_UPDATE_INT) {
+		spin_lock_irqsave(&priv->desc_lock, flags);
+
+		for (i = 0; i < AN7581_PCM_TX_DESCRIPTORS; i++) {
+			if (FIELD_GET(AN7581_PCM_DESC_STATUS_OWNERSHIP,
+				      priv->tx_descs[i].status) ==
+			    AN7581_PCM_DESC_STATUS_OWNERSHIP_CPU)
+				priv->tx_desc_idx++;
+		}
+
+		spin_unlock_irqrestore(&priv->desc_lock, flags);
+	}
+
+	/* Signal elapsed if we are finished handling TX/RX frame */
+	if (isr & AN7581_PCM_IMR_RDESC_END_INT ||
+	    isr & AN7581_PCM_IMR_TDESC_END_INT) {
+		rcu_read_lock();
+
+		if (isr & AN7581_PCM_IMR_TDESC_END_INT) {
+			substream = rcu_dereference(priv->playback_stream);
+
+			if (substream && snd_pcm_running(substream))
+				snd_pcm_period_elapsed(substream);
+		}
+
+		if (isr & AN7581_PCM_IMR_RDESC_END_INT) {
+			substream = rcu_dereference(priv->capture_stream);
+
+			if (substream && snd_pcm_running(substream))
+				snd_pcm_period_elapsed(substream);
+		}
+
+		rcu_read_unlock();
+	}
+
+	return IRQ_HANDLED;
+}
+
+static const struct snd_pcm_ops an7581_pcm_ops = {
+	.open		= an7581_pcm_open,
+	.close		= an7581_pcm_close,
+	.prepare	= an7581_pcm_prepare,
+	.hw_params	= an7581_pcm_hw_params,
+	.trigger	= an7581_pcm_trigger,
+	.pointer	= an7581_pcm_pointer,
+};
+
+static int an7581_pcm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct an7581_pcm_priv *priv;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	int irq;
+	int ret;
+
+	ret = snd_devm_card_new(dev, 1, "AN7581 PCM SOUND",
+				THIS_MODULE, 0, &card);
+	if (ret)
+		return ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	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);
+
+	ret = snd_pcm_new(card, "AN7581 PCM", 0,
+			  1, /* 1 playback substreams */
+			  1, /* 1 capture substream */
+			  &pcm);
+	if (ret)
+		return ret;
+
+	/* Reset the PCM interface */
+	reset_control_assert(priv->reset);
+	usleep_range(5000, 10000);
+	reset_control_deassert(priv->reset);
+	usleep_range(5000, 10000);
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&an7581_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&an7581_pcm_ops);
+	pcm->private_data = priv;
+
+	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;
+	}
+
+	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, NULL,
+				       AN7581_PCM_BUFFER_SIZE * AN7581_PCM_TX_DESCRIPTORS,
+				       0);
+
+	strscpy(card->driver, "an7581-pcm", sizeof(card->driver));
+	strscpy(card->shortname, "AN7581 PCM SOUND", sizeof(card->shortname));
+	strscpy(card->longname, "Airoha AN7581 PCM Dedicated Sound Card",
+		sizeof(card->longname));
+
+	ret = snd_card_register(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 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,
+};
+module_platform_driver(an7581_pcm_driver);
+
+MODULE_DESCRIPTION("Airoha SoC PCM platform driver for ALSA AN7581");
+MODULE_LICENSE("GPL");
-- 
2.51.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ