linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
@ 2023-06-15 12:27 YingKun Meng
  2023-06-15 13:58 ` Mark Brown
  2023-06-18 20:11 ` Harshit Mogalapalli
  0 siblings, 2 replies; 10+ messages in thread
From: YingKun Meng @ 2023-06-15 12:27 UTC (permalink / raw)
  To: broonie, lgirdwood
  Cc: krzysztof.kozlowski, linux-kernel, alsa-devel, loongarch,
	loongson-kernel, mengyingkun

From: Yingkun Meng <mengyingkun@loongson.cn>

Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
it is a PCI device with two private DMA controllers, one for playback,
the other for capture.

The driver supports the use of DTS or ACPI to describe device resources.

Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
---
changes v3 -> v4
  Add __packed attribute for loongson_dma_desc struct.

sound/soc/Kconfig                     |   1 +
 sound/soc/Makefile                    |   1 +
 sound/soc/loongson/Kconfig            |  16 ++
 sound/soc/loongson/Makefile           |   4 +
 sound/soc/loongson/loongson_dma.c     | 350 ++++++++++++++++++++++++++
 sound/soc/loongson/loongson_dma.h     |  16 ++
 sound/soc/loongson/loongson_i2s.c     | 269 ++++++++++++++++++++
 sound/soc/loongson/loongson_i2s.h     |  71 ++++++
 sound/soc/loongson/loongson_i2s_pci.c | 171 +++++++++++++
 9 files changed, 899 insertions(+)
 create mode 100644 sound/soc/loongson/Kconfig
 create mode 100644 sound/soc/loongson/Makefile
 create mode 100644 sound/soc/loongson/loongson_dma.c
 create mode 100644 sound/soc/loongson/loongson_dma.h
 create mode 100644 sound/soc/loongson/loongson_i2s.c
 create mode 100644 sound/soc/loongson/loongson_i2s.h
 create mode 100644 sound/soc/loongson/loongson_i2s_pci.c

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 4b6e5a802880..bfa9622e1ab1 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -79,6 +79,7 @@ source "sound/soc/google/Kconfig"
 source "sound/soc/hisilicon/Kconfig"
 source "sound/soc/jz4740/Kconfig"
 source "sound/soc/kirkwood/Kconfig"
+source "sound/soc/loongson/Kconfig"
 source "sound/soc/img/Kconfig"
 source "sound/soc/intel/Kconfig"
 source "sound/soc/mediatek/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 9d9b228e4508..8376fdb217ed 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC)	+= fsl/
 obj-$(CONFIG_SND_SOC)	+= google/
 obj-$(CONFIG_SND_SOC)	+= hisilicon/
 obj-$(CONFIG_SND_SOC)	+= jz4740/
+obj-$(CONFIG_SND_SOC)	+= loongson/
 obj-$(CONFIG_SND_SOC)	+= img/
 obj-$(CONFIG_SND_SOC)	+= intel/
 obj-$(CONFIG_SND_SOC)	+= mediatek/
diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig
new file mode 100644
index 000000000000..4478ac91e402
--- /dev/null
+++ b/sound/soc/loongson/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+menu "SoC Audio for Loongson CPUs"
+	depends on LOONGARCH || COMPILE_TEST
+
+config SND_SOC_LOONGSON_I2S_PCI
+	tristate "Loongson I2S-PCI Device Driver"
+	select REGMAP_MMIO
+	depends on PCI
+	help
+	  Say Y or M if you want to add support for I2S driver for
+	  Loongson I2S controller.
+
+	  The controller is found in loongson bridge chips or SoCs,
+	  and work as a PCI device.
+
+endmenu
diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile
new file mode 100644
index 000000000000..099af7989103
--- /dev/null
+++ b/sound/soc/loongson/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+#Platform Support
+snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o loongson_dma.o
+obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
diff --git a/sound/soc/loongson/loongson_dma.c b/sound/soc/loongson/loongson_dma.c
new file mode 100644
index 000000000000..65b6719e61c5
--- /dev/null
+++ b/sound/soc/loongson/loongson_dma.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Loongson ALSA SoC Platform (DMA) driver
+//
+// Copyright (C) 2023 Loongson Technology Corporation Limited
+// Author: Yingkun Meng <mengyingkun@loongson.cn>
+//
+
+#include <linux/module.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "loongson_i2s.h"
+
+/* DMA dma_order Register */
+#define DMA_ORDER_STOP          (1 << 4) /* DMA stop */
+#define DMA_ORDER_START         (1 << 3) /* DMA start */
+#define DMA_ORDER_ASK_VALID     (1 << 2) /* DMA ask valid flag */
+#define DMA_ORDER_AXI_UNCO      (1 << 1) /* Uncache access */
+#define DMA_ORDER_ADDR_64       (1 << 0) /* 64bits address support */
+
+#define DMA_ORDER_ASK_MASK      (~0x1fUL) /* Ask addr mask */
+#define DMA_ORDER_CTRL_MASK     (0x0fUL)  /* Control mask  */
+
+/*
+ * DMA registers descriptor.
+ */
+struct loongson_dma_desc {
+	u32 order;		/* Next descriptor address register */
+	u32 saddr;		/* Source address register */
+	u32 daddr;		/* Device address register */
+	u32 length;		/* Total length register */
+	u32 step_length;	/* Memory stride register */
+	u32 step_times;		/* Repeat time register */
+	u32 cmd;		/* Command register */
+	u32 stats;		/* Status register */
+	u32 order_hi;		/* Next descriptor high address register */
+	u32 saddr_hi;		/* High source address register */
+	u32 res[6];		/* Reserved */
+} __packed;
+
+struct loongson_runtime_data {
+	struct loongson_dma_data *dma_data;
+
+	struct loongson_dma_desc *dma_desc_arr;
+	dma_addr_t dma_desc_arr_phy;
+	int dma_desc_arr_size;
+
+	struct loongson_dma_desc *dma_pos_desc;
+	dma_addr_t dma_pos_desc_phy;
+};
+
+static const struct snd_pcm_hardware ls_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_RESUME |
+		SNDRV_PCM_INFO_PAUSE,
+	.formats = (SNDRV_PCM_FMTBIT_S8 |
+		SNDRV_PCM_FMTBIT_S16_LE |
+		SNDRV_PCM_FMTBIT_S20_3LE |
+		SNDRV_PCM_FMTBIT_S24_LE),
+	.period_bytes_min = 128,
+	.period_bytes_max = 128 * 1024,
+	.periods_min = 1,
+	.periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc),
+	.buffer_bytes_max = 1024 * 1024,
+};
+
+static struct
+loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd)
+{
+	void __iomem *order_reg = prtd->dma_data->order_addr;
+	u64 val;
+
+	val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
+	val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
+	val |= DMA_ORDER_ASK_VALID;
+	writeq(val, order_reg);
+
+	while (readl(order_reg) & DMA_ORDER_ASK_VALID)
+		udelay(2);
+
+	return prtd->dma_pos_desc;
+}
+
+static int loongson_pcm_trigger(struct snd_soc_component *component,
+				struct snd_pcm_substream *substream, int cmd)
+{
+	struct loongson_runtime_data *prtd = substream->runtime->private_data;
+	struct device *dev = substream->pcm->card->dev;
+	void __iomem *order_reg = prtd->dma_data->order_addr;
+	u64 val;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
+		if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
+			val |= DMA_ORDER_ADDR_64;
+		else
+			val &= ~DMA_ORDER_ADDR_64;
+		val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
+		val |= DMA_ORDER_START;
+		writeq(val, order_reg);
+
+		while ((readl(order_reg) & DMA_ORDER_START))
+			udelay(2);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dma_desc_save(prtd);
+
+		/* dma stop */
+		val = readq(order_reg) | DMA_ORDER_STOP;
+		writeq(val, order_reg);
+		udelay(1000);
+
+		break;
+	default:
+		dev_err(dev, "Invalid pcm trigger operation\n");
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int loongson_pcm_hw_params(struct snd_soc_component *component,
+				  struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct device *dev = substream->pcm->card->dev;
+	struct loongson_runtime_data *prtd = runtime->private_data;
+	size_t buf_len = params_buffer_bytes(params);
+	size_t period_len = params_period_bytes(params);
+	dma_addr_t order_addr, mem_addr;
+	struct loongson_dma_desc *desc;
+	u32 num_periods;
+	int i;
+
+	if (buf_len % period_len) {
+		dev_err(dev, "buf len not multiply of period len\n");
+		return -EINVAL;
+	}
+
+	num_periods = buf_len / period_len;
+	if (!num_periods || num_periods > prtd->dma_desc_arr_size) {
+		dev_err(dev, "dma data too small or too big\n");
+		return -EINVAL;
+	}
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = buf_len;
+
+	/* initialize dma descriptor array */
+	mem_addr = runtime->dma_addr;
+	order_addr = prtd->dma_desc_arr_phy;
+	for (i = 0; i < num_periods; i++) {
+		desc = &prtd->dma_desc_arr[i];
+
+		/* next descriptor physical address */
+		order_addr += sizeof(*desc);
+		desc->order = lower_32_bits(order_addr | BIT(0));
+		desc->order_hi = upper_32_bits(order_addr);
+
+		desc->saddr = lower_32_bits(mem_addr);
+		desc->saddr_hi = upper_32_bits(mem_addr);
+		desc->daddr = prtd->dma_data->dev_addr;
+
+		desc->cmd = BIT(0);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			desc->cmd |= BIT(12);
+
+		desc->length = period_len >> 2;
+		desc->step_length = 0;
+		desc->step_times = 1;
+
+		mem_addr += period_len;
+	}
+	desc = &prtd->dma_desc_arr[num_periods - 1];
+	desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0));
+	desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy);
+
+	/* init position descriptor */
+	*prtd->dma_pos_desc = *prtd->dma_desc_arr;
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+loongson_pcm_pointer(struct snd_soc_component *component,
+		     struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loongson_runtime_data *prtd = runtime->private_data;
+	struct loongson_dma_desc *desc;
+	snd_pcm_uframes_t x;
+	u64 addr;
+
+	desc = dma_desc_save(prtd);
+	addr = ((u64)desc->saddr_hi << 32) | desc->saddr;
+
+	x = bytes_to_frames(runtime, addr - runtime->dma_addr);
+	if (x == runtime->buffer_size)
+		x = 0;
+	return x;
+}
+
+static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid)
+{
+	struct snd_pcm_substream *substream = devid;
+
+	snd_pcm_period_elapsed(substream);
+	return IRQ_HANDLED;
+}
+
+static int loongson_pcm_open(struct snd_soc_component *component,
+			     struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_card *card = substream->pcm->card;
+	struct loongson_runtime_data *prtd;
+	struct loongson_dma_data *dma_data;
+	int ret;
+
+	/*
+	 * For mysterious reasons (and despite what the manual says)
+	 * playback samples are lost if the DMA count is not a multiple
+	 * of the DMA burst size.  Let's add a rule to enforce that.
+	 */
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
+	snd_pcm_hw_constraint_integer(substream->runtime,
+				      SNDRV_PCM_HW_PARAM_PERIODS);
+	snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware);
+
+	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+	if (!prtd)
+		return -ENOMEM;
+
+	prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE,
+						&prtd->dma_desc_arr_phy,
+						GFP_KERNEL);
+	if (!prtd->dma_desc_arr) {
+		ret = -ENOMEM;
+		goto desc_err;
+	}
+	prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr);
+
+	prtd->dma_pos_desc = dma_alloc_coherent(card->dev,
+						sizeof(*prtd->dma_pos_desc),
+						&prtd->dma_pos_desc_phy,
+						GFP_KERNEL);
+	if (!prtd->dma_pos_desc) {
+		ret = -ENOMEM;
+		goto pos_err;
+	}
+
+	dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+	prtd->dma_data = dma_data;
+
+	substream->runtime->private_data = prtd;
+
+	return 0;
+pos_err:
+	dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
+			  prtd->dma_desc_arr_phy);
+desc_err:
+	kfree(prtd);
+
+	return ret;
+}
+
+static int loongson_pcm_close(struct snd_soc_component *component,
+			      struct snd_pcm_substream *substream)
+{
+	struct snd_card *card = substream->pcm->card;
+	struct loongson_runtime_data *prtd = substream->runtime->private_data;
+
+	dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
+			  prtd->dma_desc_arr_phy);
+
+	dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc),
+			  prtd->dma_pos_desc, prtd->dma_pos_desc_phy);
+
+	kfree(prtd);
+	return 0;
+}
+
+static int loongson_pcm_mmap(struct snd_soc_component *component,
+			     struct snd_pcm_substream *substream,
+			     struct vm_area_struct *vma)
+{
+	return remap_pfn_range(vma, vma->vm_start,
+			substream->dma_buffer.addr >> PAGE_SHIFT,
+			vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static int loongson_pcm_new(struct snd_soc_component *component,
+			    struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_card *card = rtd->card->snd_card;
+	struct snd_pcm_substream *substream;
+	struct loongson_dma_data *dma_data;
+	unsigned int i;
+	int ret;
+
+	for_each_pcm_streams(i) {
+		substream = rtd->pcm->streams[i].substream;
+		if (!substream)
+			continue;
+
+		dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0),
+						    substream);
+		ret = devm_request_irq(card->dev, dma_data->irq,
+				       loongson_pcm_dma_irq,
+				       IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME,
+				       substream);
+		if (ret < 0) {
+			dev_err(card->dev, "request irq for DMA failed\n");
+			return ret;
+		}
+	}
+
+	return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
+					    card->dev,
+					    ls_pcm_hardware.buffer_bytes_max);
+}
+
+const struct snd_soc_component_driver loongson_i2s_component = {
+	.name		= LS_I2S_DRVNAME,
+	.open		= loongson_pcm_open,
+	.close		= loongson_pcm_close,
+	.hw_params	= loongson_pcm_hw_params,
+	.trigger	= loongson_pcm_trigger,
+	.pointer	= loongson_pcm_pointer,
+	.mmap		= loongson_pcm_mmap,
+	.pcm_construct	= loongson_pcm_new,
+};
diff --git a/sound/soc/loongson/loongson_dma.h b/sound/soc/loongson/loongson_dma.h
new file mode 100644
index 000000000000..073ee8c0c046
--- /dev/null
+++ b/sound/soc/loongson/loongson_dma.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA ASoC interface for the Loongson platform
+ *
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ * Author: Yingkun Meng <mengyingkun@loongson.cn>
+ */
+
+#ifndef _LOONGSON_DMA_H
+#define _LOONGSON_DMA_H
+
+#include <sound/soc.h>
+
+extern const struct snd_soc_component_driver loongson_i2s_component;
+
+#endif
diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c
new file mode 100644
index 000000000000..35d34568be79
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Common functions for loongson I2S controller driver
+//
+// Copyright (C) 2023 Loongson Technology Corporation Limited.
+// Author: Yingkun Meng <mengyingkun@loongson.cn>
+//
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <sound/soc.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include "loongson_i2s.h"
+
+#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+			SNDRV_PCM_FMTBIT_S16_LE | \
+			SNDRV_PCM_FMTBIT_S20_3LE | \
+			SNDRV_PCM_FMTBIT_S24_LE)
+
+static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN,
+					   I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN);
+		else
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN,
+					   I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0);
+		else
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int loongson_i2s_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	u32 clk_rate = i2s->clk_rate;
+	u32 sysclk = i2s->sysclk;
+	u32 bits = params_width(params);
+	u32 chans = params_channels(params);
+	u32 fs = params_rate(params);
+	u32 bclk_ratio, mclk_ratio;
+	u32 mclk_ratio_frac;
+	u32 val = 0;
+
+	switch (i2s->rev_id) {
+	case 0:
+		bclk_ratio = DIV_ROUND_CLOSEST(clk_rate,
+					       (bits * chans * fs * 2)) - 1;
+		mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1;
+
+		/* According to 2k1000LA user manual, set bits == depth */
+		val |= (bits << 24);
+		val |= (bits << 16);
+		val |= (bclk_ratio << 8);
+		val |= mclk_ratio;
+		regmap_write(i2s->regmap, LS_I2S_CFG, val);
+
+		break;
+	case 1:
+		bclk_ratio = DIV_ROUND_CLOSEST(sysclk,
+					       (bits * chans * fs * 2)) - 1;
+		mclk_ratio = clk_rate / sysclk;
+		mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16),
+						    sysclk) - (mclk_ratio << 16);
+
+		regmap_read(i2s->regmap, LS_I2S_CFG, &val);
+		val |= (bits << 24);
+		val |= (bclk_ratio << 8);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val |= (bits << 16);
+		else
+			val |= bits;
+		regmap_write(i2s->regmap, LS_I2S_CFG, val);
+
+		val = (mclk_ratio_frac << 16) | mclk_ratio;
+		regmap_write(i2s->regmap, LS_I2S_CFG1, val);
+
+		break;
+	default:
+		dev_err(i2s->dev, "I2S revision invalid\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+				       unsigned int freq, int dir)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+	i2s->sysclk = freq;
+
+	return 0;
+}
+
+static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	u32 val;
+	int ret;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB,
+				   I2S_CTRL_MSB);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_BC_FC:
+		break;
+	case SND_SOC_DAIFMT_BP_FC:
+		/* Enable master mode */
+		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
+				   I2S_CTRL_MASTER);
+		if (i2s->rev_id == 1) {
+			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
+						LS_I2S_CTRL, val,
+						val & I2S_CTRL_CLK_READY,
+						10, 500000);
+			if (ret < 0)
+				dev_warn(dai->dev, "wait BCLK ready timeout\n");
+		}
+		break;
+	case SND_SOC_DAIFMT_BC_FP:
+		/* Enable MCLK */
+		if (i2s->rev_id == 1) {
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_MCLK_EN,
+					   I2S_CTRL_MCLK_EN);
+			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
+						LS_I2S_CTRL, val,
+						val & I2S_CTRL_MCLK_READY,
+						10, 500000);
+			if (ret < 0)
+				dev_warn(dai->dev, "wait MCLK ready timeout\n");
+		}
+		break;
+	case SND_SOC_DAIFMT_BP_FP:
+		/* Enable MCLK */
+		if (i2s->rev_id == 1) {
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_MCLK_EN,
+					   I2S_CTRL_MCLK_EN);
+			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
+						LS_I2S_CTRL, val,
+						val & I2S_CTRL_MCLK_READY,
+						10, 500000);
+			if (ret < 0)
+				dev_warn(dai->dev, "wait MCLK ready timeout\n");
+		}
+
+		/* Enable master mode */
+		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
+				   I2S_CTRL_MASTER);
+		if (i2s->rev_id == 1) {
+			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
+						LS_I2S_CTRL, val,
+						val & I2S_CTRL_CLK_READY,
+						10, 500000);
+			if (ret < 0)
+				dev_warn(dai->dev, "wait BCLK ready timeout\n");
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
+	.trigger	= loongson_i2s_trigger,
+	.hw_params	= loongson_i2s_hw_params,
+	.set_sysclk	= loongson_i2s_set_dai_sysclk,
+	.set_fmt	= loongson_i2s_set_fmt,
+};
+
+static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
+
+	snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
+				  &i2s->capture_dma_data);
+	snd_soc_dai_set_drvdata(cpu_dai, i2s);
+
+	return 0;
+}
+
+struct snd_soc_dai_driver loongson_i2s_dai = {
+	.name = "loongson-i2s",
+	.probe = loongson_i2s_dai_probe,
+	.playback = {
+		.stream_name = "CPU-Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = LOONGSON_I2S_FORMATS,
+	},
+	.capture = {
+		.stream_name = "CPU-Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = LOONGSON_I2S_FORMATS,
+	},
+	.ops = &loongson_i2s_dai_ops,
+	.symmetric_rate = 1,
+};
+
+static int i2s_suspend(struct device *dev)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(dev);
+
+	regcache_cache_only(i2s->regmap, true);
+
+	return 0;
+}
+
+static int i2s_resume(struct device *dev)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(dev);
+	int ret;
+
+	regcache_cache_only(i2s->regmap, false);
+	regcache_mark_dirty(i2s->regmap);
+	ret = regcache_sync(i2s->regmap);
+
+	return ret;
+}
+
+const struct dev_pm_ops loongson_i2s_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
+};
diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h
new file mode 100644
index 000000000000..52788f6a94ad
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA I2S interface for the Loongson platform
+ *
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ * Author: Yingkun Meng <mengyingkun@loongson.cn>
+ */
+
+#ifndef _LOONGSON_I2S_H
+#define _LOONGSON_I2S_H
+
+#include <linux/regmap.h>
+#include <sound/dmaengine_pcm.h>
+
+/* I2S Common Registers */
+#define LS_I2S_VER	0x00 /* I2S Version */
+#define LS_I2S_CFG	0x04 /* I2S Config */
+#define LS_I2S_CTRL	0x08 /* I2S Control */
+#define LS_I2S_RX_DATA	0x0C /* I2S DMA RX Address */
+#define LS_I2S_TX_DATA	0x10 /* I2S DMA TX Address */
+
+/* 2K2000 I2S Specify Registers */
+#define LS_I2S_CFG1	0x14 /* I2S Config1 */
+
+/* 7A2000 I2S Specify Registers */
+#define LS_I2S_TX_ORDER	0x100 /* TX DMA Order */
+#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */
+
+/* Loongson I2S Control Register */
+#define I2S_CTRL_MCLK_READY	(1 << 16) /* MCLK ready */
+#define I2S_CTRL_MASTER		(1 << 15) /* Master mode */
+#define I2S_CTRL_MSB		(1 << 14) /* MSB bit order */
+#define I2S_CTRL_RX_EN		(1 << 13) /* RX enable */
+#define I2S_CTRL_TX_EN		(1 << 12) /* TX enable */
+#define I2S_CTRL_RX_DMA_EN	(1 << 11) /* DMA RX enable */
+#define I2S_CTRL_CLK_READY	(1 << 8)  /* BCLK ready */
+#define I2S_CTRL_TX_DMA_EN	(1 << 7)  /* DMA TX enable */
+#define I2S_CTRL_RESET		(1 << 4)  /* Controller soft reset */
+#define I2S_CTRL_MCLK_EN	(1 << 3)  /* Enable MCLK */
+#define I2S_CTRL_RX_INT_EN	(1 << 1)  /* RX interrupt enable */
+#define I2S_CTRL_TX_INT_EN	(1 << 0)  /* TX interrupt enable */
+
+#define LS_I2S_DRVNAME		"loongson-i2s"
+
+struct loongson_dma_data {
+	dma_addr_t dev_addr;		/* device physical address for DMA */
+	void __iomem *order_addr;	/* DMA order register */
+	u32 irq;			/* DMA irq */
+};
+
+struct loongson_i2s {
+	struct device *dev;
+	union {
+		struct snd_dmaengine_dai_dma_data playback_dma_data;
+		struct loongson_dma_data tx_dma_data;
+	};
+	union {
+		struct snd_dmaengine_dai_dma_data capture_dma_data;
+		struct loongson_dma_data rx_dma_data;
+	};
+	struct regmap *regmap;
+	void __iomem *reg_base;
+	u32 rev_id;
+	u32 clk_rate;
+	u32 sysclk;
+};
+
+extern const struct dev_pm_ops loongson_i2s_pm;
+extern struct snd_soc_dai_driver loongson_i2s_dai;
+
+#endif
diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c
new file mode 100644
index 000000000000..6dcfb17d3276
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s_pci.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// loongson_i2s_pci.c -- Loongson I2S controller driver
+//
+// Copyright (C) 2023 Loongson Technology Corporation Limited
+// Author: Yingkun Meng <mengyingkun@loongson.cn>
+//
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/acpi.h>
+#include <linux/pci.h>
+#include <sound/soc.h>
+#include "loongson_i2s.h"
+#include "loongson_dma.h"
+
+static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case LS_I2S_CFG:
+	case LS_I2S_CTRL:
+	case LS_I2S_RX_DATA:
+	case LS_I2S_TX_DATA:
+	case LS_I2S_CFG1:
+		return true;
+	default:
+		return false;
+	};
+}
+
+static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case LS_I2S_VER:
+	case LS_I2S_CFG:
+	case LS_I2S_CTRL:
+	case LS_I2S_RX_DATA:
+	case LS_I2S_TX_DATA:
+	case LS_I2S_CFG1:
+		return true;
+	default:
+		return false;
+	};
+}
+
+static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case LS_I2S_CFG:
+	case LS_I2S_CTRL:
+	case LS_I2S_RX_DATA:
+	case LS_I2S_TX_DATA:
+	case LS_I2S_CFG1:
+		return true;
+	default:
+		return false;
+	};
+}
+
+static const struct regmap_config loongson_i2s_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = LS_I2S_CFG1,
+	.writeable_reg = loongson_i2s_wr_reg,
+	.readable_reg = loongson_i2s_rd_reg,
+	.volatile_reg = loongson_i2s_volatile_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+static int loongson_i2s_pci_probe(struct pci_dev *pdev,
+				  const struct pci_device_id *pid)
+{
+	const struct fwnode_handle *fwnode = pdev->dev.fwnode;
+	struct loongson_dma_data *tx_data, *rx_data;
+	struct loongson_i2s *i2s;
+	int ret;
+
+	if (pcim_enable_device(pdev)) {
+		dev_err(&pdev->dev, "pci_enable_device failed\n");
+		return -ENODEV;
+	}
+
+	i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
+	if (!i2s)
+		return -ENOMEM;
+
+	i2s->rev_id = pdev->revision;
+	i2s->dev = &pdev->dev;
+	pci_set_drvdata(pdev, i2s);
+
+	ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev));
+	if (ret < 0) {
+		dev_err(&pdev->dev, "iomap_regions failed\n");
+		return ret;
+	}
+	i2s->reg_base = pcim_iomap_table(pdev)[0];
+	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
+					    &loongson_i2s_regmap_config);
+	if (IS_ERR(i2s->regmap)) {
+		dev_err(&pdev->dev, "regmap_init_mmio failed\n");
+		return PTR_ERR(i2s->regmap);
+	}
+
+	tx_data = &i2s->tx_dma_data;
+	rx_data = &i2s->rx_dma_data;
+
+	tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA;
+	tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER;
+
+	rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA;
+	rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
+
+	tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
+	if (tx_data->irq < 0) {
+		dev_err(&pdev->dev, "dma tx irq invalid\n");
+		return tx_data->irq;
+	}
+
+	rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
+	if (rx_data->irq < 0) {
+		dev_err(&pdev->dev, "dma rx irq invalid\n");
+		return rx_data->irq;
+	}
+
+	device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate);
+	if (!i2s->clk_rate) {
+		dev_err(&pdev->dev, "clock-frequency property invalid\n");
+		return -EINVAL;
+	}
+
+	dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+
+	if (i2s->rev_id == 1) {
+		regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET);
+		udelay(200);
+	}
+
+	ret = devm_snd_soc_register_component(&pdev->dev,
+					      &loongson_i2s_component,
+					      &loongson_i2s_dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "register DAI failed %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct pci_device_id loongson_i2s_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
+
+static struct pci_driver loongson_i2s_driver = {
+	.name = "loongson-i2s-pci",
+	.id_table = loongson_i2s_ids,
+	.probe = loongson_i2s_pci_probe,
+	.driver = {
+		.owner = THIS_MODULE,
+		.pm = pm_sleep_ptr(&loongson_i2s_pm),
+	},
+};
+module_pci_driver(loongson_i2s_driver);
+
+MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
-- 
2.33.0


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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-15 12:27 [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller YingKun Meng
@ 2023-06-15 13:58 ` Mark Brown
  2023-06-16  1:12   ` Yingkun Meng
  2023-06-18 20:11 ` Harshit Mogalapalli
  1 sibling, 1 reply; 10+ messages in thread
From: Mark Brown @ 2023-06-15 13:58 UTC (permalink / raw)
  To: lgirdwood, YingKun Meng
  Cc: krzysztof.kozlowski, linux-kernel, alsa-devel, loongarch,
	loongson-kernel

On Thu, 15 Jun 2023 20:27:18 +0800, YingKun Meng wrote:
> Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
> it is a PCI device with two private DMA controllers, one for playback,
> the other for capture.
> 
> The driver supports the use of DTS or ACPI to describe device resources.
> 
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next

Thanks!

[1/3] ASoC: Add support for Loongson I2S controller
      commit: d84881e06836dc1655777a592b4279be76ad7324

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark


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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-15 13:58 ` Mark Brown
@ 2023-06-16  1:12   ` Yingkun Meng
  0 siblings, 0 replies; 10+ messages in thread
From: Yingkun Meng @ 2023-06-16  1:12 UTC (permalink / raw)
  To: Mark Brown, lgirdwood
  Cc: krzysztof.kozlowski, linux-kernel, alsa-devel, loongarch,
	loongson-kernel


On 2023/6/15 21:58, Mark Brown wrote:
> On Thu, 15 Jun 2023 20:27:18 +0800, YingKun Meng wrote:
>> Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
>> it is a PCI device with two private DMA controllers, one for playback,
>> the other for capture.
>>
>> The driver supports the use of DTS or ACPI to describe device resources.
>>
>>
>> [...]
> Applied to
>
>     https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
>
> Thanks!
>
> [1/3] ASoC: Add support for Loongson I2S controller
>        commit: d84881e06836dc1655777a592b4279be76ad7324
>
> All being well this means that it will be integrated into the linux-next
> tree (usually sometime in the next 24 hours) and sent to Linus during
> the next merge window (or sooner if it is a bug fix), however if
> problems are discovered then the patch may be dropped or reverted.
>
> You may get further e-mails resulting from automated or manual testing
> and review of the tree, please engage with people reporting problems and
> send followup patches addressing any issues that are reported if needed.
>
> If any updates are required or you are submitting further changes they
> should be sent as incremental updates against current git, existing
> patches will not be replaced.
>
> Please add any relevant lists and maintainers to the CCs when replying
> to this mail.
>
> Thanks,
> Mark

Thanks for your review.

Thanks,

Yingkun


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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-15 12:27 [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller YingKun Meng
  2023-06-15 13:58 ` Mark Brown
@ 2023-06-18 20:11 ` Harshit Mogalapalli
  2023-06-18 20:14   ` Harshit Mogalapalli
  2023-06-19 11:20   ` Mark Brown
  1 sibling, 2 replies; 10+ messages in thread
From: Harshit Mogalapalli @ 2023-06-18 20:11 UTC (permalink / raw)
  To: YingKun Meng, broonie, lgirdwood
  Cc: krzysztof.kozlowski, linux-kernel, alsa-devel, loongarch,
	loongson-kernel, Dan Carpenter, Dan Carpenter, kernel-janitors,
	Harshit Mogalapalli

Hi YingKun,

On 15/06/23 5:57 pm, YingKun Meng wrote:
> From: Yingkun Meng <mengyingkun@loongson.cn>
> 
> Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
> it is a PCI device with two private DMA controllers, one for playback,
> the other for capture.
> 
> The driver supports the use of DTS or ACPI to describe device resources.
> 
> Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
> ---
> changes v3 -> v4
>    Add __packed attribute for loongson_dma_desc struct.
> 
> sound/soc/Kconfig                     |   1 +
>   sound/soc/Makefile                    |   1 +
>   sound/soc/loongson/Kconfig            |  16 ++
>   sound/soc/loongson/Makefile           |   4 +
>   sound/soc/loongson/loongson_dma.c     | 350 ++++++++++++++++++++++++++
>   sound/soc/loongson/loongson_dma.h     |  16 ++
>   sound/soc/loongson/loongson_i2s.c     | 269 ++++++++++++++++++++
>   sound/soc/loongson/loongson_i2s.h     |  71 ++++++
>   sound/soc/loongson/loongson_i2s_pci.c | 171 +++++++++++++
>   9 files changed, 899 insertions(+)
>   create mode 100644 sound/soc/loongson/Kconfig
>   create mode 100644 sound/soc/loongson/Makefile
>   create mode 100644 sound/soc/loongson/loongson_dma.c
>   create mode 100644 sound/soc/loongson/loongson_dma.h
>   create mode 100644 sound/soc/loongson/loongson_i2s.c
>   create mode 100644 sound/soc/loongson/loongson_i2s.h
>   create mode 100644 sound/soc/loongson/loongson_i2s_pci.c
> 
> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
> index 4b6e5a802880..bfa9622e1ab1 100644
> --- a/sound/soc/Kconfig
> +++ b/sound/soc/Kconfig
> @@ -79,6 +79,7 @@ source "sound/soc/google/Kconfig"
>   source "sound/soc/hisilicon/Kconfig"
>   source "sound/soc/jz4740/Kconfig"
>   source "sound/soc/kirkwood/Kconfig"
> +source "sound/soc/loongson/Kconfig"
>   source "sound/soc/img/Kconfig"
>   source "sound/soc/intel/Kconfig"
>   source "sound/soc/mediatek/Kconfig"
> diff --git a/sound/soc/Makefile b/sound/soc/Makefile
> index 9d9b228e4508..8376fdb217ed 100644
> --- a/sound/soc/Makefile
> +++ b/sound/soc/Makefile
> @@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC)	+= fsl/
>   obj-$(CONFIG_SND_SOC)	+= google/
>   obj-$(CONFIG_SND_SOC)	+= hisilicon/
>   obj-$(CONFIG_SND_SOC)	+= jz4740/
> +obj-$(CONFIG_SND_SOC)	+= loongson/
>   obj-$(CONFIG_SND_SOC)	+= img/
>   obj-$(CONFIG_SND_SOC)	+= intel/
>   obj-$(CONFIG_SND_SOC)	+= mediatek/
> diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig
> new file mode 100644
> index 000000000000..4478ac91e402
> --- /dev/null
> +++ b/sound/soc/loongson/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0
> +menu "SoC Audio for Loongson CPUs"
> +	depends on LOONGARCH || COMPILE_TEST
> +
> +config SND_SOC_LOONGSON_I2S_PCI
> +	tristate "Loongson I2S-PCI Device Driver"
> +	select REGMAP_MMIO
> +	depends on PCI
> +	help
> +	  Say Y or M if you want to add support for I2S driver for
> +	  Loongson I2S controller.
> +
> +	  The controller is found in loongson bridge chips or SoCs,
> +	  and work as a PCI device.
> +
> +endmenu
> diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile
> new file mode 100644
> index 000000000000..099af7989103
> --- /dev/null
> +++ b/sound/soc/loongson/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#Platform Support
> +snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o loongson_dma.o
> +obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
> diff --git a/sound/soc/loongson/loongson_dma.c b/sound/soc/loongson/loongson_dma.c
> new file mode 100644
> index 000000000000..65b6719e61c5
> --- /dev/null
> +++ b/sound/soc/loongson/loongson_dma.c
> @@ -0,0 +1,350 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Loongson ALSA SoC Platform (DMA) driver
> +//
> +// Copyright (C) 2023 Loongson Technology Corporation Limited
> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
> +//
> +
> +#include <linux/module.h>
> +#include <linux/io-64-nonatomic-lo-hi.h>
> +#include <linux/delay.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/dma-mapping.h>
> +#include <sound/soc.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include "loongson_i2s.h"
> +
> +/* DMA dma_order Register */
> +#define DMA_ORDER_STOP          (1 << 4) /* DMA stop */
> +#define DMA_ORDER_START         (1 << 3) /* DMA start */
> +#define DMA_ORDER_ASK_VALID     (1 << 2) /* DMA ask valid flag */
> +#define DMA_ORDER_AXI_UNCO      (1 << 1) /* Uncache access */
> +#define DMA_ORDER_ADDR_64       (1 << 0) /* 64bits address support */
> +
> +#define DMA_ORDER_ASK_MASK      (~0x1fUL) /* Ask addr mask */
> +#define DMA_ORDER_CTRL_MASK     (0x0fUL)  /* Control mask  */
> +
> +/*
> + * DMA registers descriptor.
> + */
> +struct loongson_dma_desc {
> +	u32 order;		/* Next descriptor address register */
> +	u32 saddr;		/* Source address register */
> +	u32 daddr;		/* Device address register */
> +	u32 length;		/* Total length register */
> +	u32 step_length;	/* Memory stride register */
> +	u32 step_times;		/* Repeat time register */
> +	u32 cmd;		/* Command register */
> +	u32 stats;		/* Status register */
> +	u32 order_hi;		/* Next descriptor high address register */
> +	u32 saddr_hi;		/* High source address register */
> +	u32 res[6];		/* Reserved */
> +} __packed;
> +
> +struct loongson_runtime_data {
> +	struct loongson_dma_data *dma_data;
> +
> +	struct loongson_dma_desc *dma_desc_arr;
> +	dma_addr_t dma_desc_arr_phy;
> +	int dma_desc_arr_size;
> +
> +	struct loongson_dma_desc *dma_pos_desc;
> +	dma_addr_t dma_pos_desc_phy;
> +};
> +
> +static const struct snd_pcm_hardware ls_pcm_hardware = {
> +	.info = SNDRV_PCM_INFO_MMAP |
> +		SNDRV_PCM_INFO_INTERLEAVED |
> +		SNDRV_PCM_INFO_MMAP_VALID |
> +		SNDRV_PCM_INFO_RESUME |
> +		SNDRV_PCM_INFO_PAUSE,
> +	.formats = (SNDRV_PCM_FMTBIT_S8 |
> +		SNDRV_PCM_FMTBIT_S16_LE |
> +		SNDRV_PCM_FMTBIT_S20_3LE |
> +		SNDRV_PCM_FMTBIT_S24_LE),
> +	.period_bytes_min = 128,
> +	.period_bytes_max = 128 * 1024,
> +	.periods_min = 1,
> +	.periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc),
> +	.buffer_bytes_max = 1024 * 1024,
> +};
> +
> +static struct
> +loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd)
> +{
> +	void __iomem *order_reg = prtd->dma_data->order_addr;
> +	u64 val;
> +
> +	val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
> +	val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
> +	val |= DMA_ORDER_ASK_VALID;
> +	writeq(val, order_reg);
> +
> +	while (readl(order_reg) & DMA_ORDER_ASK_VALID)
> +		udelay(2);
> +
> +	return prtd->dma_pos_desc;
> +}
> +
> +static int loongson_pcm_trigger(struct snd_soc_component *component,
> +				struct snd_pcm_substream *substream, int cmd)
> +{
> +	struct loongson_runtime_data *prtd = substream->runtime->private_data;
> +	struct device *dev = substream->pcm->card->dev;
> +	void __iomem *order_reg = prtd->dma_data->order_addr;
> +	u64 val;
> +	int ret = 0;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
> +		if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
> +			val |= DMA_ORDER_ADDR_64;
> +		else
> +			val &= ~DMA_ORDER_ADDR_64;
> +		val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
> +		val |= DMA_ORDER_START;
> +		writeq(val, order_reg);
> +
> +		while ((readl(order_reg) & DMA_ORDER_START))
> +			udelay(2);
> +		break;
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_SUSPEND:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		dma_desc_save(prtd);
> +
> +		/* dma stop */
> +		val = readq(order_reg) | DMA_ORDER_STOP;
> +		writeq(val, order_reg);
> +		udelay(1000);
> +
> +		break;
> +	default:
> +		dev_err(dev, "Invalid pcm trigger operation\n");
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int loongson_pcm_hw_params(struct snd_soc_component *component,
> +				  struct snd_pcm_substream *substream,
> +				  struct snd_pcm_hw_params *params)
> +{
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	struct device *dev = substream->pcm->card->dev;
> +	struct loongson_runtime_data *prtd = runtime->private_data;
> +	size_t buf_len = params_buffer_bytes(params);
> +	size_t period_len = params_period_bytes(params);
> +	dma_addr_t order_addr, mem_addr;
> +	struct loongson_dma_desc *desc;
> +	u32 num_periods;
> +	int i;
> +
> +	if (buf_len % period_len) {
> +		dev_err(dev, "buf len not multiply of period len\n");
> +		return -EINVAL;
> +	}
> +
> +	num_periods = buf_len / period_len;
> +	if (!num_periods || num_periods > prtd->dma_desc_arr_size) {
> +		dev_err(dev, "dma data too small or too big\n");
> +		return -EINVAL;
> +	}
> +
> +	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
> +	runtime->dma_bytes = buf_len;
> +
> +	/* initialize dma descriptor array */
> +	mem_addr = runtime->dma_addr;
> +	order_addr = prtd->dma_desc_arr_phy;
> +	for (i = 0; i < num_periods; i++) {
> +		desc = &prtd->dma_desc_arr[i];
> +
> +		/* next descriptor physical address */
> +		order_addr += sizeof(*desc);
> +		desc->order = lower_32_bits(order_addr | BIT(0));
> +		desc->order_hi = upper_32_bits(order_addr);
> +
> +		desc->saddr = lower_32_bits(mem_addr);
> +		desc->saddr_hi = upper_32_bits(mem_addr);
> +		desc->daddr = prtd->dma_data->dev_addr;
> +
> +		desc->cmd = BIT(0);
> +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +			desc->cmd |= BIT(12);
> +
> +		desc->length = period_len >> 2;
> +		desc->step_length = 0;
> +		desc->step_times = 1;
> +
> +		mem_addr += period_len;
> +	}
> +	desc = &prtd->dma_desc_arr[num_periods - 1];
> +	desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0));
> +	desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy);
> +
> +	/* init position descriptor */
> +	*prtd->dma_pos_desc = *prtd->dma_desc_arr;
> +
> +	return 0;
> +}
> +
> +static snd_pcm_uframes_t
> +loongson_pcm_pointer(struct snd_soc_component *component,
> +		     struct snd_pcm_substream *substream)
> +{
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	struct loongson_runtime_data *prtd = runtime->private_data;
> +	struct loongson_dma_desc *desc;
> +	snd_pcm_uframes_t x;
> +	u64 addr;
> +
> +	desc = dma_desc_save(prtd);
> +	addr = ((u64)desc->saddr_hi << 32) | desc->saddr;
> +
> +	x = bytes_to_frames(runtime, addr - runtime->dma_addr);
> +	if (x == runtime->buffer_size)
> +		x = 0;
> +	return x;
> +}
> +
> +static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid)
> +{
> +	struct snd_pcm_substream *substream = devid;
> +
> +	snd_pcm_period_elapsed(substream);
> +	return IRQ_HANDLED;
> +}
> +
> +static int loongson_pcm_open(struct snd_soc_component *component,
> +			     struct snd_pcm_substream *substream)
> +{
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct snd_card *card = substream->pcm->card;
> +	struct loongson_runtime_data *prtd;
> +	struct loongson_dma_data *dma_data;
> +	int ret;
> +
> +	/*
> +	 * For mysterious reasons (and despite what the manual says)
> +	 * playback samples are lost if the DMA count is not a multiple
> +	 * of the DMA burst size.  Let's add a rule to enforce that.
> +	 */
> +	snd_pcm_hw_constraint_step(runtime, 0,
> +				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
> +	snd_pcm_hw_constraint_step(runtime, 0,
> +				   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
> +	snd_pcm_hw_constraint_integer(substream->runtime,
> +				      SNDRV_PCM_HW_PARAM_PERIODS);
> +	snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware);
> +
> +	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
> +	if (!prtd)
> +		return -ENOMEM;
> +
> +	prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE,
> +						&prtd->dma_desc_arr_phy,
> +						GFP_KERNEL);
> +	if (!prtd->dma_desc_arr) {
> +		ret = -ENOMEM;
> +		goto desc_err;
> +	}
> +	prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr);
> +
> +	prtd->dma_pos_desc = dma_alloc_coherent(card->dev,
> +						sizeof(*prtd->dma_pos_desc),
> +						&prtd->dma_pos_desc_phy,
> +						GFP_KERNEL);
> +	if (!prtd->dma_pos_desc) {
> +		ret = -ENOMEM;
> +		goto pos_err;
> +	}
> +
> +	dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
> +	prtd->dma_data = dma_data;
> +
> +	substream->runtime->private_data = prtd;
> +
> +	return 0;
> +pos_err:
> +	dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
> +			  prtd->dma_desc_arr_phy);
> +desc_err:
> +	kfree(prtd);
> +
> +	return ret;
> +}
> +
> +static int loongson_pcm_close(struct snd_soc_component *component,
> +			      struct snd_pcm_substream *substream)
> +{
> +	struct snd_card *card = substream->pcm->card;
> +	struct loongson_runtime_data *prtd = substream->runtime->private_data;
> +
> +	dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
> +			  prtd->dma_desc_arr_phy);
> +
> +	dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc),
> +			  prtd->dma_pos_desc, prtd->dma_pos_desc_phy);
> +
> +	kfree(prtd);
> +	return 0;
> +}
> +
> +static int loongson_pcm_mmap(struct snd_soc_component *component,
> +			     struct snd_pcm_substream *substream,
> +			     struct vm_area_struct *vma)
> +{
> +	return remap_pfn_range(vma, vma->vm_start,
> +			substream->dma_buffer.addr >> PAGE_SHIFT,
> +			vma->vm_end - vma->vm_start, vma->vm_page_prot);
> +}
> +
> +static int loongson_pcm_new(struct snd_soc_component *component,
> +			    struct snd_soc_pcm_runtime *rtd)
> +{
> +	struct snd_card *card = rtd->card->snd_card;
> +	struct snd_pcm_substream *substream;
> +	struct loongson_dma_data *dma_data;
> +	unsigned int i;
> +	int ret;
> +
> +	for_each_pcm_streams(i) {
> +		substream = rtd->pcm->streams[i].substream;
> +		if (!substream)
> +			continue;
> +
> +		dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0),
> +						    substream);
> +		ret = devm_request_irq(card->dev, dma_data->irq,
> +				       loongson_pcm_dma_irq,
> +				       IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME,
> +				       substream);
> +		if (ret < 0) {
> +			dev_err(card->dev, "request irq for DMA failed\n");
> +			return ret;
> +		}
> +	}
> +
> +	return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
> +					    card->dev,
> +					    ls_pcm_hardware.buffer_bytes_max);
> +}
> +
> +const struct snd_soc_component_driver loongson_i2s_component = {
> +	.name		= LS_I2S_DRVNAME,
> +	.open		= loongson_pcm_open,
> +	.close		= loongson_pcm_close,
> +	.hw_params	= loongson_pcm_hw_params,
> +	.trigger	= loongson_pcm_trigger,
> +	.pointer	= loongson_pcm_pointer,
> +	.mmap		= loongson_pcm_mmap,
> +	.pcm_construct	= loongson_pcm_new,
> +};
> diff --git a/sound/soc/loongson/loongson_dma.h b/sound/soc/loongson/loongson_dma.h
> new file mode 100644
> index 000000000000..073ee8c0c046
> --- /dev/null
> +++ b/sound/soc/loongson/loongson_dma.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * ALSA ASoC interface for the Loongson platform
> + *
> + * Copyright (C) 2023 Loongson Technology Corporation Limited
> + * Author: Yingkun Meng <mengyingkun@loongson.cn>
> + */
> +
> +#ifndef _LOONGSON_DMA_H
> +#define _LOONGSON_DMA_H
> +
> +#include <sound/soc.h>
> +
> +extern const struct snd_soc_component_driver loongson_i2s_component;
> +
> +#endif
> diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c
> new file mode 100644
> index 000000000000..35d34568be79
> --- /dev/null
> +++ b/sound/soc/loongson/loongson_i2s.c
> @@ -0,0 +1,269 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Common functions for loongson I2S controller driver
> +//
> +// Copyright (C) 2023 Loongson Technology Corporation Limited.
> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
> +//
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/dma-mapping.h>
> +#include <sound/soc.h>
> +#include <linux/regmap.h>
> +#include <sound/pcm_params.h>
> +#include "loongson_i2s.h"
> +
> +#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
> +			SNDRV_PCM_FMTBIT_S16_LE | \
> +			SNDRV_PCM_FMTBIT_S20_3LE | \
> +			SNDRV_PCM_FMTBIT_S24_LE)
> +
> +static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
> +				struct snd_soc_dai *dai)
> +{
> +	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
> +	int ret = 0;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
> +					   I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN,
> +					   I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN);
> +		else
> +			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
> +					   I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN,
> +					   I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN);
> +		break;
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_SUSPEND:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
> +					I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0);
> +		else
> +			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
> +					I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int loongson_i2s_hw_params(struct snd_pcm_substream *substream,
> +				  struct snd_pcm_hw_params *params,
> +				  struct snd_soc_dai *dai)
> +{
> +	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
> +	u32 clk_rate = i2s->clk_rate;
> +	u32 sysclk = i2s->sysclk;
> +	u32 bits = params_width(params);
> +	u32 chans = params_channels(params);
> +	u32 fs = params_rate(params);
> +	u32 bclk_ratio, mclk_ratio;
> +	u32 mclk_ratio_frac;
> +	u32 val = 0;
> +
> +	switch (i2s->rev_id) {
> +	case 0:
> +		bclk_ratio = DIV_ROUND_CLOSEST(clk_rate,
> +					       (bits * chans * fs * 2)) - 1;
> +		mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1;
> +
> +		/* According to 2k1000LA user manual, set bits == depth */
> +		val |= (bits << 24);
> +		val |= (bits << 16);
> +		val |= (bclk_ratio << 8);
> +		val |= mclk_ratio;
> +		regmap_write(i2s->regmap, LS_I2S_CFG, val);
> +
> +		break;
> +	case 1:
> +		bclk_ratio = DIV_ROUND_CLOSEST(sysclk,
> +					       (bits * chans * fs * 2)) - 1;
> +		mclk_ratio = clk_rate / sysclk;
> +		mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16),
> +						    sysclk) - (mclk_ratio << 16);
> +
> +		regmap_read(i2s->regmap, LS_I2S_CFG, &val);
> +		val |= (bits << 24);
> +		val |= (bclk_ratio << 8);
> +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +			val |= (bits << 16);
> +		else
> +			val |= bits;
> +		regmap_write(i2s->regmap, LS_I2S_CFG, val);
> +
> +		val = (mclk_ratio_frac << 16) | mclk_ratio;
> +		regmap_write(i2s->regmap, LS_I2S_CFG1, val);
> +
> +		break;
> +	default:
> +		dev_err(i2s->dev, "I2S revision invalid\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
> +				       unsigned int freq, int dir)
> +{
> +	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
> +
> +	i2s->sysclk = freq;
> +
> +	return 0;
> +}
> +
> +static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
> +{
> +	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
> +	u32 val;
> +	int ret;
> +
> +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> +	case SND_SOC_DAIFMT_I2S:
> +		break;
> +	case SND_SOC_DAIFMT_RIGHT_J:
> +		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB,
> +				   I2S_CTRL_MSB);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +
> +	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
> +	case SND_SOC_DAIFMT_BC_FC:
> +		break;
> +	case SND_SOC_DAIFMT_BP_FC:
> +		/* Enable master mode */
> +		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
> +				   I2S_CTRL_MASTER);
> +		if (i2s->rev_id == 1) {
> +			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
> +						LS_I2S_CTRL, val,
> +						val & I2S_CTRL_CLK_READY,
> +						10, 500000);
> +			if (ret < 0)
> +				dev_warn(dai->dev, "wait BCLK ready timeout\n");
> +		}
> +		break;
> +	case SND_SOC_DAIFMT_BC_FP:
> +		/* Enable MCLK */
> +		if (i2s->rev_id == 1) {
> +			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
> +					   I2S_CTRL_MCLK_EN,
> +					   I2S_CTRL_MCLK_EN);
> +			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
> +						LS_I2S_CTRL, val,
> +						val & I2S_CTRL_MCLK_READY,
> +						10, 500000);
> +			if (ret < 0)
> +				dev_warn(dai->dev, "wait MCLK ready timeout\n");
> +		}
> +		break;
> +	case SND_SOC_DAIFMT_BP_FP:
> +		/* Enable MCLK */
> +		if (i2s->rev_id == 1) {
> +			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
> +					   I2S_CTRL_MCLK_EN,
> +					   I2S_CTRL_MCLK_EN);
> +			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
> +						LS_I2S_CTRL, val,
> +						val & I2S_CTRL_MCLK_READY,
> +						10, 500000);
> +			if (ret < 0)
> +				dev_warn(dai->dev, "wait MCLK ready timeout\n");
> +		}
> +
> +		/* Enable master mode */
> +		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
> +				   I2S_CTRL_MASTER);
> +		if (i2s->rev_id == 1) {
> +			ret = regmap_read_poll_timeout_atomic(i2s->regmap,
> +						LS_I2S_CTRL, val,
> +						val & I2S_CTRL_CLK_READY,
> +						10, 500000);
> +			if (ret < 0)
> +				dev_warn(dai->dev, "wait BCLK ready timeout\n");
> +		}
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
> +	.trigger	= loongson_i2s_trigger,
> +	.hw_params	= loongson_i2s_hw_params,
> +	.set_sysclk	= loongson_i2s_set_dai_sysclk,
> +	.set_fmt	= loongson_i2s_set_fmt,
> +};
> +
> +static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
> +{
> +	struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
> +
> +	snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
> +				  &i2s->capture_dma_data);
> +	snd_soc_dai_set_drvdata(cpu_dai, i2s);
> +
> +	return 0;
> +}
> +
> +struct snd_soc_dai_driver loongson_i2s_dai = {
> +	.name = "loongson-i2s",
> +	.probe = loongson_i2s_dai_probe,
> +	.playback = {
> +		.stream_name = "CPU-Playback",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_8000_96000,
> +		.formats = LOONGSON_I2S_FORMATS,
> +	},
> +	.capture = {
> +		.stream_name = "CPU-Capture",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_8000_96000,
> +		.formats = LOONGSON_I2S_FORMATS,
> +	},
> +	.ops = &loongson_i2s_dai_ops,
> +	.symmetric_rate = 1,
> +};
> +
> +static int i2s_suspend(struct device *dev)
> +{
> +	struct loongson_i2s *i2s = dev_get_drvdata(dev);
> +
> +	regcache_cache_only(i2s->regmap, true);
> +
> +	return 0;
> +}
> +
> +static int i2s_resume(struct device *dev)
> +{
> +	struct loongson_i2s *i2s = dev_get_drvdata(dev);
> +	int ret;
> +
> +	regcache_cache_only(i2s->regmap, false);
> +	regcache_mark_dirty(i2s->regmap);
> +	ret = regcache_sync(i2s->regmap);
> +
> +	return ret;
> +}
> +
> +const struct dev_pm_ops loongson_i2s_pm = {
> +	SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
> +};
> diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h
> new file mode 100644
> index 000000000000..52788f6a94ad
> --- /dev/null
> +++ b/sound/soc/loongson/loongson_i2s.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * ALSA I2S interface for the Loongson platform
> + *
> + * Copyright (C) 2023 Loongson Technology Corporation Limited
> + * Author: Yingkun Meng <mengyingkun@loongson.cn>
> + */
> +
> +#ifndef _LOONGSON_I2S_H
> +#define _LOONGSON_I2S_H
> +
> +#include <linux/regmap.h>
> +#include <sound/dmaengine_pcm.h>
> +
> +/* I2S Common Registers */
> +#define LS_I2S_VER	0x00 /* I2S Version */
> +#define LS_I2S_CFG	0x04 /* I2S Config */
> +#define LS_I2S_CTRL	0x08 /* I2S Control */
> +#define LS_I2S_RX_DATA	0x0C /* I2S DMA RX Address */
> +#define LS_I2S_TX_DATA	0x10 /* I2S DMA TX Address */
> +
> +/* 2K2000 I2S Specify Registers */
> +#define LS_I2S_CFG1	0x14 /* I2S Config1 */
> +
> +/* 7A2000 I2S Specify Registers */
> +#define LS_I2S_TX_ORDER	0x100 /* TX DMA Order */
> +#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */
> +
> +/* Loongson I2S Control Register */
> +#define I2S_CTRL_MCLK_READY	(1 << 16) /* MCLK ready */
> +#define I2S_CTRL_MASTER		(1 << 15) /* Master mode */
> +#define I2S_CTRL_MSB		(1 << 14) /* MSB bit order */
> +#define I2S_CTRL_RX_EN		(1 << 13) /* RX enable */
> +#define I2S_CTRL_TX_EN		(1 << 12) /* TX enable */
> +#define I2S_CTRL_RX_DMA_EN	(1 << 11) /* DMA RX enable */
> +#define I2S_CTRL_CLK_READY	(1 << 8)  /* BCLK ready */
> +#define I2S_CTRL_TX_DMA_EN	(1 << 7)  /* DMA TX enable */
> +#define I2S_CTRL_RESET		(1 << 4)  /* Controller soft reset */
> +#define I2S_CTRL_MCLK_EN	(1 << 3)  /* Enable MCLK */
> +#define I2S_CTRL_RX_INT_EN	(1 << 1)  /* RX interrupt enable */
> +#define I2S_CTRL_TX_INT_EN	(1 << 0)  /* TX interrupt enable */
> +
> +#define LS_I2S_DRVNAME		"loongson-i2s"
> +
> +struct loongson_dma_data {
> +	dma_addr_t dev_addr;		/* device physical address for DMA */
> +	void __iomem *order_addr;	/* DMA order register */
> +	u32 irq;			/* DMA irq */
> +};
> +
> +struct loongson_i2s {
> +	struct device *dev;
> +	union {
> +		struct snd_dmaengine_dai_dma_data playback_dma_data;
> +		struct loongson_dma_data tx_dma_data;
> +	};
> +	union {
> +		struct snd_dmaengine_dai_dma_data capture_dma_data;
> +		struct loongson_dma_data rx_dma_data;
> +	};
> +	struct regmap *regmap;
> +	void __iomem *reg_base;
> +	u32 rev_id;
> +	u32 clk_rate;
> +	u32 sysclk;
> +};
> +
> +extern const struct dev_pm_ops loongson_i2s_pm;
> +extern struct snd_soc_dai_driver loongson_i2s_dai;
> +
> +#endif
> diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c
> new file mode 100644
> index 000000000000..6dcfb17d3276
> --- /dev/null
> +++ b/sound/soc/loongson/loongson_i2s_pci.c
> @@ -0,0 +1,171 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// loongson_i2s_pci.c -- Loongson I2S controller driver
> +//
> +// Copyright (C) 2023 Loongson Technology Corporation Limited
> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
> +//
> +
> +#include <linux/module.h>
> +#include <linux/delay.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/acpi.h>
> +#include <linux/pci.h>
> +#include <sound/soc.h>
> +#include "loongson_i2s.h"
> +#include "loongson_dma.h"
> +
> +static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case LS_I2S_CFG:
> +	case LS_I2S_CTRL:
> +	case LS_I2S_RX_DATA:
> +	case LS_I2S_TX_DATA:
> +	case LS_I2S_CFG1:
> +		return true;
> +	default:
> +		return false;
> +	};
> +}
> +
> +static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case LS_I2S_VER:
> +	case LS_I2S_CFG:
> +	case LS_I2S_CTRL:
> +	case LS_I2S_RX_DATA:
> +	case LS_I2S_TX_DATA:
> +	case LS_I2S_CFG1:
> +		return true;
> +	default:
> +		return false;
> +	};
> +}
> +
> +static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case LS_I2S_CFG:
> +	case LS_I2S_CTRL:
> +	case LS_I2S_RX_DATA:
> +	case LS_I2S_TX_DATA:
> +	case LS_I2S_CFG1:
> +		return true;
> +	default:
> +		return false;
> +	};
> +}
> +
> +static const struct regmap_config loongson_i2s_regmap_config = {
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.val_bits = 32,
> +	.max_register = LS_I2S_CFG1,
> +	.writeable_reg = loongson_i2s_wr_reg,
> +	.readable_reg = loongson_i2s_rd_reg,
> +	.volatile_reg = loongson_i2s_volatile_reg,
> +	.cache_type = REGCACHE_FLAT,
> +};
> +
> +static int loongson_i2s_pci_probe(struct pci_dev *pdev,
> +				  const struct pci_device_id *pid)
> +{
> +	const struct fwnode_handle *fwnode = pdev->dev.fwnode;
> +	struct loongson_dma_data *tx_data, *rx_data;
> +	struct loongson_i2s *i2s;
> +	int ret;
> +
> +	if (pcim_enable_device(pdev)) {
> +		dev_err(&pdev->dev, "pci_enable_device failed\n");
> +		return -ENODEV;
> +	}
> +
> +	i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
> +	if (!i2s)
> +		return -ENOMEM;
> +
> +	i2s->rev_id = pdev->revision;
> +	i2s->dev = &pdev->dev;
> +	pci_set_drvdata(pdev, i2s);
> +
> +	ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev));
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "iomap_regions failed\n");
> +		return ret;
> +	}
> +	i2s->reg_base = pcim_iomap_table(pdev)[0];
> +	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
> +					    &loongson_i2s_regmap_config);
> +	if (IS_ERR(i2s->regmap)) {
> +		dev_err(&pdev->dev, "regmap_init_mmio failed\n");
> +		return PTR_ERR(i2s->regmap);
> +	}
> +
> +	tx_data = &i2s->tx_dma_data;
> +	rx_data = &i2s->rx_dma_data;
> +
> +	tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA;
> +	tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER;
> +
> +	rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA;
> +	rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
> +
> +	tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");

Smatch detects that tx_data->irq and rx_data->irq are of type 
u32(unsigned) so they can never be negative.

> +	if (tx_data->irq < 0) {
		    ^^^^^^^^ This can never be true.

Should irq be of type 'int' instead?

> +		dev_err(&pdev->dev, "dma tx irq invalid\n");
> +		return tx_data->irq;
> +	}
> +
> +	rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
> +	if (rx_data->irq < 0) {
			^^^ Same problem here.

Should irq
> +		dev_err(&pdev->dev, "dma rx irq invalid\n");
> +		return rx_data->irq;
> +	}
> +
> +	device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate);
> +	if (!i2s->clk_rate) {
> +		dev_err(&pdev->dev, "clock-frequency property invalid\n");
> +		return -EINVAL;
> +	}
> +
> +	dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
> +
> +	if (i2s->rev_id == 1) {
> +		regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET);
> +		udelay(200);
> +	}
> +
> +	ret = devm_snd_soc_register_component(&pdev->dev,
> +					      &loongson_i2s_component,
> +					      &loongson_i2s_dai, 1);
> +	if (ret) {
> +		dev_err(&pdev->dev, "register DAI failed %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct pci_device_id loongson_i2s_ids[] = {
> +	{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
> +
> +static struct pci_driver loongson_i2s_driver = {
> +	.name = "loongson-i2s-pci",
> +	.id_table = loongson_i2s_ids,
> +	.probe = loongson_i2s_pci_probe,
> +	.driver = {
> +		.owner = THIS_MODULE,
> +		.pm = pm_sleep_ptr(&loongson_i2s_pm),
> +	},
> +};
> +module_pci_driver(loongson_i2s_driver);
> +
> +MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");

Thanks,
Harshit

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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-18 20:11 ` Harshit Mogalapalli
@ 2023-06-18 20:14   ` Harshit Mogalapalli
  2023-06-19  1:45     ` Yingkun Meng
  2023-06-19 11:20   ` Mark Brown
  1 sibling, 1 reply; 10+ messages in thread
From: Harshit Mogalapalli @ 2023-06-18 20:14 UTC (permalink / raw)
  To: YingKun Meng, broonie, lgirdwood
  Cc: krzysztof.kozlowski, linux-kernel, alsa-devel, loongarch,
	loongson-kernel, Dan Carpenter, Dan Carpenter, kernel-janitors,
	Harshit Mogalapalli



On 19/06/23 1:41 am, Harshit Mogalapalli wrote:
> Hi YingKun,
> 
> On 15/06/23 5:57 pm, YingKun Meng wrote:
>> From: Yingkun Meng <mengyingkun@loongson.cn>
>>
>> Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
>> it is a PCI device with two private DMA controllers, one for playback,
>> the other for capture.
>>
>> The driver supports the use of DTS or ACPI to describe device resources.
>>
>> Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
>> ---
>> changes v3 -> v4
>>    Add __packed attribute for loongson_dma_desc struct.
>>
>> sound/soc/Kconfig                     |   1 +
>>   sound/soc/Makefile                    |   1 +
>>   sound/soc/loongson/Kconfig            |  16 ++
>>   sound/soc/loongson/Makefile           |   4 +
>>   sound/soc/loongson/loongson_dma.c     | 350 ++++++++++++++++++++++++++
>>   sound/soc/loongson/loongson_dma.h     |  16 ++
>>   sound/soc/loongson/loongson_i2s.c     | 269 ++++++++++++++++++++
>>   sound/soc/loongson/loongson_i2s.h     |  71 ++++++
>>   sound/soc/loongson/loongson_i2s_pci.c | 171 +++++++++++++
>>   9 files changed, 899 insertions(+)
>>   create mode 100644 sound/soc/loongson/Kconfig
>>   create mode 100644 sound/soc/loongson/Makefile
>>   create mode 100644 sound/soc/loongson/loongson_dma.c
>>   create mode 100644 sound/soc/loongson/loongson_dma.h
>>   create mode 100644 sound/soc/loongson/loongson_i2s.c
>>   create mode 100644 sound/soc/loongson/loongson_i2s.h
>>   create mode 100644 sound/soc/loongson/loongson_i2s_pci.c
>>
>> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
>> index 4b6e5a802880..bfa9622e1ab1 100644
>> --- a/sound/soc/Kconfig
>> +++ b/sound/soc/Kconfig
>> @@ -79,6 +79,7 @@ source "sound/soc/google/Kconfig"
>>   source "sound/soc/hisilicon/Kconfig"
>>   source "sound/soc/jz4740/Kconfig"
>>   source "sound/soc/kirkwood/Kconfig"
>> +source "sound/soc/loongson/Kconfig"
>>   source "sound/soc/img/Kconfig"
>>   source "sound/soc/intel/Kconfig"
>>   source "sound/soc/mediatek/Kconfig"
>> diff --git a/sound/soc/Makefile b/sound/soc/Makefile
>> index 9d9b228e4508..8376fdb217ed 100644
>> --- a/sound/soc/Makefile
>> +++ b/sound/soc/Makefile
>> @@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC)    += fsl/
>>   obj-$(CONFIG_SND_SOC)    += google/
>>   obj-$(CONFIG_SND_SOC)    += hisilicon/
>>   obj-$(CONFIG_SND_SOC)    += jz4740/
>> +obj-$(CONFIG_SND_SOC)    += loongson/
>>   obj-$(CONFIG_SND_SOC)    += img/
>>   obj-$(CONFIG_SND_SOC)    += intel/
>>   obj-$(CONFIG_SND_SOC)    += mediatek/
>> diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig
>> new file mode 100644
>> index 000000000000..4478ac91e402
>> --- /dev/null
>> +++ b/sound/soc/loongson/Kconfig
>> @@ -0,0 +1,16 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +menu "SoC Audio for Loongson CPUs"
>> +    depends on LOONGARCH || COMPILE_TEST
>> +
>> +config SND_SOC_LOONGSON_I2S_PCI
>> +    tristate "Loongson I2S-PCI Device Driver"
>> +    select REGMAP_MMIO
>> +    depends on PCI
>> +    help
>> +      Say Y or M if you want to add support for I2S driver for
>> +      Loongson I2S controller.
>> +
>> +      The controller is found in loongson bridge chips or SoCs,
>> +      and work as a PCI device.
>> +
>> +endmenu
>> diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile
>> new file mode 100644
>> index 000000000000..099af7989103
>> --- /dev/null
>> +++ b/sound/soc/loongson/Makefile
>> @@ -0,0 +1,4 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +#Platform Support
>> +snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o 
>> loongson_dma.o
>> +obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
>> diff --git a/sound/soc/loongson/loongson_dma.c 
>> b/sound/soc/loongson/loongson_dma.c
>> new file mode 100644
>> index 000000000000..65b6719e61c5
>> --- /dev/null
>> +++ b/sound/soc/loongson/loongson_dma.c
>> @@ -0,0 +1,350 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +//
>> +// Loongson ALSA SoC Platform (DMA) driver
>> +//
>> +// Copyright (C) 2023 Loongson Technology Corporation Limited
>> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
>> +//
>> +
>> +#include <linux/module.h>
>> +#include <linux/io-64-nonatomic-lo-hi.h>
>> +#include <linux/delay.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/dma-mapping.h>
>> +#include <sound/soc.h>
>> +#include <sound/pcm.h>
>> +#include <sound/pcm_params.h>
>> +#include "loongson_i2s.h"
>> +
>> +/* DMA dma_order Register */
>> +#define DMA_ORDER_STOP          (1 << 4) /* DMA stop */
>> +#define DMA_ORDER_START         (1 << 3) /* DMA start */
>> +#define DMA_ORDER_ASK_VALID     (1 << 2) /* DMA ask valid flag */
>> +#define DMA_ORDER_AXI_UNCO      (1 << 1) /* Uncache access */
>> +#define DMA_ORDER_ADDR_64       (1 << 0) /* 64bits address support */
>> +
>> +#define DMA_ORDER_ASK_MASK      (~0x1fUL) /* Ask addr mask */
>> +#define DMA_ORDER_CTRL_MASK     (0x0fUL)  /* Control mask  */
>> +
>> +/*
>> + * DMA registers descriptor.
>> + */
>> +struct loongson_dma_desc {
>> +    u32 order;        /* Next descriptor address register */
>> +    u32 saddr;        /* Source address register */
>> +    u32 daddr;        /* Device address register */
>> +    u32 length;        /* Total length register */
>> +    u32 step_length;    /* Memory stride register */
>> +    u32 step_times;        /* Repeat time register */
>> +    u32 cmd;        /* Command register */
>> +    u32 stats;        /* Status register */
>> +    u32 order_hi;        /* Next descriptor high address register */
>> +    u32 saddr_hi;        /* High source address register */
>> +    u32 res[6];        /* Reserved */
>> +} __packed;
>> +
>> +struct loongson_runtime_data {
>> +    struct loongson_dma_data *dma_data;
>> +
>> +    struct loongson_dma_desc *dma_desc_arr;
>> +    dma_addr_t dma_desc_arr_phy;
>> +    int dma_desc_arr_size;
>> +
>> +    struct loongson_dma_desc *dma_pos_desc;
>> +    dma_addr_t dma_pos_desc_phy;
>> +};
>> +
>> +static const struct snd_pcm_hardware ls_pcm_hardware = {
>> +    .info = SNDRV_PCM_INFO_MMAP |
>> +        SNDRV_PCM_INFO_INTERLEAVED |
>> +        SNDRV_PCM_INFO_MMAP_VALID |
>> +        SNDRV_PCM_INFO_RESUME |
>> +        SNDRV_PCM_INFO_PAUSE,
>> +    .formats = (SNDRV_PCM_FMTBIT_S8 |
>> +        SNDRV_PCM_FMTBIT_S16_LE |
>> +        SNDRV_PCM_FMTBIT_S20_3LE |
>> +        SNDRV_PCM_FMTBIT_S24_LE),
>> +    .period_bytes_min = 128,
>> +    .period_bytes_max = 128 * 1024,
>> +    .periods_min = 1,
>> +    .periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc),
>> +    .buffer_bytes_max = 1024 * 1024,
>> +};
>> +
>> +static struct
>> +loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd)
>> +{
>> +    void __iomem *order_reg = prtd->dma_data->order_addr;
>> +    u64 val;
>> +
>> +    val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
>> +    val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
>> +    val |= DMA_ORDER_ASK_VALID;
>> +    writeq(val, order_reg);
>> +
>> +    while (readl(order_reg) & DMA_ORDER_ASK_VALID)
>> +        udelay(2);
>> +
>> +    return prtd->dma_pos_desc;
>> +}
>> +
>> +static int loongson_pcm_trigger(struct snd_soc_component *component,
>> +                struct snd_pcm_substream *substream, int cmd)
>> +{
>> +    struct loongson_runtime_data *prtd = 
>> substream->runtime->private_data;
>> +    struct device *dev = substream->pcm->card->dev;
>> +    void __iomem *order_reg = prtd->dma_data->order_addr;
>> +    u64 val;
>> +    int ret = 0;
>> +
>> +    switch (cmd) {
>> +    case SNDRV_PCM_TRIGGER_START:
>> +    case SNDRV_PCM_TRIGGER_RESUME:
>> +    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>> +        val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
>> +        if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
>> +            val |= DMA_ORDER_ADDR_64;
>> +        else
>> +            val &= ~DMA_ORDER_ADDR_64;
>> +        val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
>> +        val |= DMA_ORDER_START;
>> +        writeq(val, order_reg);
>> +
>> +        while ((readl(order_reg) & DMA_ORDER_START))
>> +            udelay(2);
>> +        break;
>> +    case SNDRV_PCM_TRIGGER_STOP:
>> +    case SNDRV_PCM_TRIGGER_SUSPEND:
>> +    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>> +        dma_desc_save(prtd);
>> +
>> +        /* dma stop */
>> +        val = readq(order_reg) | DMA_ORDER_STOP;
>> +        writeq(val, order_reg);
>> +        udelay(1000);
>> +
>> +        break;
>> +    default:
>> +        dev_err(dev, "Invalid pcm trigger operation\n");
>> +        return -EINVAL;
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static int loongson_pcm_hw_params(struct snd_soc_component *component,
>> +                  struct snd_pcm_substream *substream,
>> +                  struct snd_pcm_hw_params *params)
>> +{
>> +    struct snd_pcm_runtime *runtime = substream->runtime;
>> +    struct device *dev = substream->pcm->card->dev;
>> +    struct loongson_runtime_data *prtd = runtime->private_data;
>> +    size_t buf_len = params_buffer_bytes(params);
>> +    size_t period_len = params_period_bytes(params);
>> +    dma_addr_t order_addr, mem_addr;
>> +    struct loongson_dma_desc *desc;
>> +    u32 num_periods;
>> +    int i;
>> +
>> +    if (buf_len % period_len) {
>> +        dev_err(dev, "buf len not multiply of period len\n");
>> +        return -EINVAL;
>> +    }
>> +
>> +    num_periods = buf_len / period_len;
>> +    if (!num_periods || num_periods > prtd->dma_desc_arr_size) {
>> +        dev_err(dev, "dma data too small or too big\n");
>> +        return -EINVAL;
>> +    }
>> +
>> +    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
>> +    runtime->dma_bytes = buf_len;
>> +
>> +    /* initialize dma descriptor array */
>> +    mem_addr = runtime->dma_addr;
>> +    order_addr = prtd->dma_desc_arr_phy;
>> +    for (i = 0; i < num_periods; i++) {
>> +        desc = &prtd->dma_desc_arr[i];
>> +
>> +        /* next descriptor physical address */
>> +        order_addr += sizeof(*desc);
>> +        desc->order = lower_32_bits(order_addr | BIT(0));
>> +        desc->order_hi = upper_32_bits(order_addr);
>> +
>> +        desc->saddr = lower_32_bits(mem_addr);
>> +        desc->saddr_hi = upper_32_bits(mem_addr);
>> +        desc->daddr = prtd->dma_data->dev_addr;
>> +
>> +        desc->cmd = BIT(0);
>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>> +            desc->cmd |= BIT(12);
>> +
>> +        desc->length = period_len >> 2;
>> +        desc->step_length = 0;
>> +        desc->step_times = 1;
>> +
>> +        mem_addr += period_len;
>> +    }
>> +    desc = &prtd->dma_desc_arr[num_periods - 1];
>> +    desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0));
>> +    desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy);
>> +
>> +    /* init position descriptor */
>> +    *prtd->dma_pos_desc = *prtd->dma_desc_arr;
>> +
>> +    return 0;
>> +}
>> +
>> +static snd_pcm_uframes_t
>> +loongson_pcm_pointer(struct snd_soc_component *component,
>> +             struct snd_pcm_substream *substream)
>> +{
>> +    struct snd_pcm_runtime *runtime = substream->runtime;
>> +    struct loongson_runtime_data *prtd = runtime->private_data;
>> +    struct loongson_dma_desc *desc;
>> +    snd_pcm_uframes_t x;
>> +    u64 addr;
>> +
>> +    desc = dma_desc_save(prtd);
>> +    addr = ((u64)desc->saddr_hi << 32) | desc->saddr;
>> +
>> +    x = bytes_to_frames(runtime, addr - runtime->dma_addr);
>> +    if (x == runtime->buffer_size)
>> +        x = 0;
>> +    return x;
>> +}
>> +
>> +static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid)
>> +{
>> +    struct snd_pcm_substream *substream = devid;
>> +
>> +    snd_pcm_period_elapsed(substream);
>> +    return IRQ_HANDLED;
>> +}
>> +
>> +static int loongson_pcm_open(struct snd_soc_component *component,
>> +                 struct snd_pcm_substream *substream)
>> +{
>> +    struct snd_pcm_runtime *runtime = substream->runtime;
>> +    struct snd_soc_pcm_runtime *rtd = substream->private_data;
>> +    struct snd_card *card = substream->pcm->card;
>> +    struct loongson_runtime_data *prtd;
>> +    struct loongson_dma_data *dma_data;
>> +    int ret;
>> +
>> +    /*
>> +     * For mysterious reasons (and despite what the manual says)
>> +     * playback samples are lost if the DMA count is not a multiple
>> +     * of the DMA burst size.  Let's add a rule to enforce that.
>> +     */
>> +    snd_pcm_hw_constraint_step(runtime, 0,
>> +                   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
>> +    snd_pcm_hw_constraint_step(runtime, 0,
>> +                   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
>> +    snd_pcm_hw_constraint_integer(substream->runtime,
>> +                      SNDRV_PCM_HW_PARAM_PERIODS);
>> +    snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware);
>> +
>> +    prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
>> +    if (!prtd)
>> +        return -ENOMEM;
>> +
>> +    prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE,
>> +                        &prtd->dma_desc_arr_phy,
>> +                        GFP_KERNEL);
>> +    if (!prtd->dma_desc_arr) {
>> +        ret = -ENOMEM;
>> +        goto desc_err;
>> +    }
>> +    prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr);
>> +
>> +    prtd->dma_pos_desc = dma_alloc_coherent(card->dev,
>> +                        sizeof(*prtd->dma_pos_desc),
>> +                        &prtd->dma_pos_desc_phy,
>> +                        GFP_KERNEL);
>> +    if (!prtd->dma_pos_desc) {
>> +        ret = -ENOMEM;
>> +        goto pos_err;
>> +    }
>> +
>> +    dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), 
>> substream);
>> +    prtd->dma_data = dma_data;
>> +
>> +    substream->runtime->private_data = prtd;
>> +
>> +    return 0;
>> +pos_err:
>> +    dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
>> +              prtd->dma_desc_arr_phy);
>> +desc_err:
>> +    kfree(prtd);
>> +
>> +    return ret;
>> +}
>> +
>> +static int loongson_pcm_close(struct snd_soc_component *component,
>> +                  struct snd_pcm_substream *substream)
>> +{
>> +    struct snd_card *card = substream->pcm->card;
>> +    struct loongson_runtime_data *prtd = 
>> substream->runtime->private_data;
>> +
>> +    dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
>> +              prtd->dma_desc_arr_phy);
>> +
>> +    dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc),
>> +              prtd->dma_pos_desc, prtd->dma_pos_desc_phy);
>> +
>> +    kfree(prtd);
>> +    return 0;
>> +}
>> +
>> +static int loongson_pcm_mmap(struct snd_soc_component *component,
>> +                 struct snd_pcm_substream *substream,
>> +                 struct vm_area_struct *vma)
>> +{
>> +    return remap_pfn_range(vma, vma->vm_start,
>> +            substream->dma_buffer.addr >> PAGE_SHIFT,
>> +            vma->vm_end - vma->vm_start, vma->vm_page_prot);
>> +}
>> +
>> +static int loongson_pcm_new(struct snd_soc_component *component,
>> +                struct snd_soc_pcm_runtime *rtd)
>> +{
>> +    struct snd_card *card = rtd->card->snd_card;
>> +    struct snd_pcm_substream *substream;
>> +    struct loongson_dma_data *dma_data;
>> +    unsigned int i;
>> +    int ret;
>> +
>> +    for_each_pcm_streams(i) {
>> +        substream = rtd->pcm->streams[i].substream;
>> +        if (!substream)
>> +            continue;
>> +
>> +        dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0),
>> +                            substream);
>> +        ret = devm_request_irq(card->dev, dma_data->irq,
>> +                       loongson_pcm_dma_irq,
>> +                       IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME,
>> +                       substream);
>> +        if (ret < 0) {
>> +            dev_err(card->dev, "request irq for DMA failed\n");
>> +            return ret;
>> +        }
>> +    }
>> +
>> +    return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
>> +                        card->dev,
>> +                        ls_pcm_hardware.buffer_bytes_max);
>> +}
>> +
>> +const struct snd_soc_component_driver loongson_i2s_component = {
>> +    .name        = LS_I2S_DRVNAME,
>> +    .open        = loongson_pcm_open,
>> +    .close        = loongson_pcm_close,
>> +    .hw_params    = loongson_pcm_hw_params,
>> +    .trigger    = loongson_pcm_trigger,
>> +    .pointer    = loongson_pcm_pointer,
>> +    .mmap        = loongson_pcm_mmap,
>> +    .pcm_construct    = loongson_pcm_new,
>> +};
>> diff --git a/sound/soc/loongson/loongson_dma.h 
>> b/sound/soc/loongson/loongson_dma.h
>> new file mode 100644
>> index 000000000000..073ee8c0c046
>> --- /dev/null
>> +++ b/sound/soc/loongson/loongson_dma.h
>> @@ -0,0 +1,16 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * ALSA ASoC interface for the Loongson platform
>> + *
>> + * Copyright (C) 2023 Loongson Technology Corporation Limited
>> + * Author: Yingkun Meng <mengyingkun@loongson.cn>
>> + */
>> +
>> +#ifndef _LOONGSON_DMA_H
>> +#define _LOONGSON_DMA_H
>> +
>> +#include <sound/soc.h>
>> +
>> +extern const struct snd_soc_component_driver loongson_i2s_component;
>> +
>> +#endif
>> diff --git a/sound/soc/loongson/loongson_i2s.c 
>> b/sound/soc/loongson/loongson_i2s.c
>> new file mode 100644
>> index 000000000000..35d34568be79
>> --- /dev/null
>> +++ b/sound/soc/loongson/loongson_i2s.c
>> @@ -0,0 +1,269 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +//
>> +// Common functions for loongson I2S controller driver
>> +//
>> +// Copyright (C) 2023 Loongson Technology Corporation Limited.
>> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
>> +//
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/delay.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/dma-mapping.h>
>> +#include <sound/soc.h>
>> +#include <linux/regmap.h>
>> +#include <sound/pcm_params.h>
>> +#include "loongson_i2s.h"
>> +
>> +#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
>> +            SNDRV_PCM_FMTBIT_S16_LE | \
>> +            SNDRV_PCM_FMTBIT_S20_3LE | \
>> +            SNDRV_PCM_FMTBIT_S24_LE)
>> +
>> +static int loongson_i2s_trigger(struct snd_pcm_substream *substream, 
>> int cmd,
>> +                struct snd_soc_dai *dai)
>> +{
>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>> +    int ret = 0;
>> +
>> +    switch (cmd) {
>> +    case SNDRV_PCM_TRIGGER_START:
>> +    case SNDRV_PCM_TRIGGER_RESUME:
>> +    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>> +                       I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN,
>> +                       I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN);
>> +        else
>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>> +                       I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN,
>> +                       I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN);
>> +        break;
>> +    case SNDRV_PCM_TRIGGER_STOP:
>> +    case SNDRV_PCM_TRIGGER_SUSPEND:
>> +    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>> +                    I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0);
>> +        else
>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>> +                    I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0);
>> +        break;
>> +    default:
>> +        ret = -EINVAL;
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static int loongson_i2s_hw_params(struct snd_pcm_substream *substream,
>> +                  struct snd_pcm_hw_params *params,
>> +                  struct snd_soc_dai *dai)
>> +{
>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>> +    u32 clk_rate = i2s->clk_rate;
>> +    u32 sysclk = i2s->sysclk;
>> +    u32 bits = params_width(params);
>> +    u32 chans = params_channels(params);
>> +    u32 fs = params_rate(params);
>> +    u32 bclk_ratio, mclk_ratio;
>> +    u32 mclk_ratio_frac;
>> +    u32 val = 0;
>> +
>> +    switch (i2s->rev_id) {
>> +    case 0:
>> +        bclk_ratio = DIV_ROUND_CLOSEST(clk_rate,
>> +                           (bits * chans * fs * 2)) - 1;
>> +        mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1;
>> +
>> +        /* According to 2k1000LA user manual, set bits == depth */
>> +        val |= (bits << 24);
>> +        val |= (bits << 16);
>> +        val |= (bclk_ratio << 8);
>> +        val |= mclk_ratio;
>> +        regmap_write(i2s->regmap, LS_I2S_CFG, val);
>> +
>> +        break;
>> +    case 1:
>> +        bclk_ratio = DIV_ROUND_CLOSEST(sysclk,
>> +                           (bits * chans * fs * 2)) - 1;
>> +        mclk_ratio = clk_rate / sysclk;
>> +        mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16),
>> +                            sysclk) - (mclk_ratio << 16);
>> +
>> +        regmap_read(i2s->regmap, LS_I2S_CFG, &val);
>> +        val |= (bits << 24);
>> +        val |= (bclk_ratio << 8);
>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>> +            val |= (bits << 16);
>> +        else
>> +            val |= bits;
>> +        regmap_write(i2s->regmap, LS_I2S_CFG, val);
>> +
>> +        val = (mclk_ratio_frac << 16) | mclk_ratio;
>> +        regmap_write(i2s->regmap, LS_I2S_CFG1, val);
>> +
>> +        break;
>> +    default:
>> +        dev_err(i2s->dev, "I2S revision invalid\n");
>> +        return -EINVAL;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int 
>> clk_id,
>> +                       unsigned int freq, int dir)
>> +{
>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>> +
>> +    i2s->sysclk = freq;
>> +
>> +    return 0;
>> +}
>> +
>> +static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int 
>> fmt)
>> +{
>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>> +    u32 val;
>> +    int ret;
>> +
>> +    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
>> +    case SND_SOC_DAIFMT_I2S:
>> +        break;
>> +    case SND_SOC_DAIFMT_RIGHT_J:
>> +        regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB,
>> +                   I2S_CTRL_MSB);
>> +        break;
>> +    default:
>> +        return -EINVAL;
>> +    }
>> +
>> +
>> +    switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
>> +    case SND_SOC_DAIFMT_BC_FC:
>> +        break;
>> +    case SND_SOC_DAIFMT_BP_FC:
>> +        /* Enable master mode */
>> +        regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
>> +                   I2S_CTRL_MASTER);
>> +        if (i2s->rev_id == 1) {
>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>> +                        LS_I2S_CTRL, val,
>> +                        val & I2S_CTRL_CLK_READY,
>> +                        10, 500000);
>> +            if (ret < 0)
>> +                dev_warn(dai->dev, "wait BCLK ready timeout\n");
>> +        }
>> +        break;
>> +    case SND_SOC_DAIFMT_BC_FP:
>> +        /* Enable MCLK */
>> +        if (i2s->rev_id == 1) {
>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>> +                       I2S_CTRL_MCLK_EN,
>> +                       I2S_CTRL_MCLK_EN);
>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>> +                        LS_I2S_CTRL, val,
>> +                        val & I2S_CTRL_MCLK_READY,
>> +                        10, 500000);
>> +            if (ret < 0)
>> +                dev_warn(dai->dev, "wait MCLK ready timeout\n");
>> +        }
>> +        break;
>> +    case SND_SOC_DAIFMT_BP_FP:
>> +        /* Enable MCLK */
>> +        if (i2s->rev_id == 1) {
>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>> +                       I2S_CTRL_MCLK_EN,
>> +                       I2S_CTRL_MCLK_EN);
>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>> +                        LS_I2S_CTRL, val,
>> +                        val & I2S_CTRL_MCLK_READY,
>> +                        10, 500000);
>> +            if (ret < 0)
>> +                dev_warn(dai->dev, "wait MCLK ready timeout\n");
>> +        }
>> +
>> +        /* Enable master mode */
>> +        regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
>> +                   I2S_CTRL_MASTER);
>> +        if (i2s->rev_id == 1) {
>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>> +                        LS_I2S_CTRL, val,
>> +                        val & I2S_CTRL_CLK_READY,
>> +                        10, 500000);
>> +            if (ret < 0)
>> +                dev_warn(dai->dev, "wait BCLK ready timeout\n");
>> +        }
>> +        break;
>> +    default:
>> +        return -EINVAL;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
>> +    .trigger    = loongson_i2s_trigger,
>> +    .hw_params    = loongson_i2s_hw_params,
>> +    .set_sysclk    = loongson_i2s_set_dai_sysclk,
>> +    .set_fmt    = loongson_i2s_set_fmt,
>> +};
>> +
>> +static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
>> +{
>> +    struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
>> +
>> +    snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
>> +                  &i2s->capture_dma_data);
>> +    snd_soc_dai_set_drvdata(cpu_dai, i2s);
>> +
>> +    return 0;
>> +}
>> +
>> +struct snd_soc_dai_driver loongson_i2s_dai = {
>> +    .name = "loongson-i2s",
>> +    .probe = loongson_i2s_dai_probe,
>> +    .playback = {
>> +        .stream_name = "CPU-Playback",
>> +        .channels_min = 1,
>> +        .channels_max = 2,
>> +        .rates = SNDRV_PCM_RATE_8000_96000,
>> +        .formats = LOONGSON_I2S_FORMATS,
>> +    },
>> +    .capture = {
>> +        .stream_name = "CPU-Capture",
>> +        .channels_min = 1,
>> +        .channels_max = 2,
>> +        .rates = SNDRV_PCM_RATE_8000_96000,
>> +        .formats = LOONGSON_I2S_FORMATS,
>> +    },
>> +    .ops = &loongson_i2s_dai_ops,
>> +    .symmetric_rate = 1,
>> +};
>> +
>> +static int i2s_suspend(struct device *dev)
>> +{
>> +    struct loongson_i2s *i2s = dev_get_drvdata(dev);
>> +
>> +    regcache_cache_only(i2s->regmap, true);
>> +
>> +    return 0;
>> +}
>> +
>> +static int i2s_resume(struct device *dev)
>> +{
>> +    struct loongson_i2s *i2s = dev_get_drvdata(dev);
>> +    int ret;
>> +
>> +    regcache_cache_only(i2s->regmap, false);
>> +    regcache_mark_dirty(i2s->regmap);
>> +    ret = regcache_sync(i2s->regmap);
>> +
>> +    return ret;
>> +}
>> +
>> +const struct dev_pm_ops loongson_i2s_pm = {
>> +    SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
>> +};
>> diff --git a/sound/soc/loongson/loongson_i2s.h 
>> b/sound/soc/loongson/loongson_i2s.h
>> new file mode 100644
>> index 000000000000..52788f6a94ad
>> --- /dev/null
>> +++ b/sound/soc/loongson/loongson_i2s.h
>> @@ -0,0 +1,71 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * ALSA I2S interface for the Loongson platform
>> + *
>> + * Copyright (C) 2023 Loongson Technology Corporation Limited
>> + * Author: Yingkun Meng <mengyingkun@loongson.cn>
>> + */
>> +
>> +#ifndef _LOONGSON_I2S_H
>> +#define _LOONGSON_I2S_H
>> +
>> +#include <linux/regmap.h>
>> +#include <sound/dmaengine_pcm.h>
>> +
>> +/* I2S Common Registers */
>> +#define LS_I2S_VER    0x00 /* I2S Version */
>> +#define LS_I2S_CFG    0x04 /* I2S Config */
>> +#define LS_I2S_CTRL    0x08 /* I2S Control */
>> +#define LS_I2S_RX_DATA    0x0C /* I2S DMA RX Address */
>> +#define LS_I2S_TX_DATA    0x10 /* I2S DMA TX Address */
>> +
>> +/* 2K2000 I2S Specify Registers */
>> +#define LS_I2S_CFG1    0x14 /* I2S Config1 */
>> +
>> +/* 7A2000 I2S Specify Registers */
>> +#define LS_I2S_TX_ORDER    0x100 /* TX DMA Order */
>> +#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */
>> +
>> +/* Loongson I2S Control Register */
>> +#define I2S_CTRL_MCLK_READY    (1 << 16) /* MCLK ready */
>> +#define I2S_CTRL_MASTER        (1 << 15) /* Master mode */
>> +#define I2S_CTRL_MSB        (1 << 14) /* MSB bit order */
>> +#define I2S_CTRL_RX_EN        (1 << 13) /* RX enable */
>> +#define I2S_CTRL_TX_EN        (1 << 12) /* TX enable */
>> +#define I2S_CTRL_RX_DMA_EN    (1 << 11) /* DMA RX enable */
>> +#define I2S_CTRL_CLK_READY    (1 << 8)  /* BCLK ready */
>> +#define I2S_CTRL_TX_DMA_EN    (1 << 7)  /* DMA TX enable */
>> +#define I2S_CTRL_RESET        (1 << 4)  /* Controller soft reset */
>> +#define I2S_CTRL_MCLK_EN    (1 << 3)  /* Enable MCLK */
>> +#define I2S_CTRL_RX_INT_EN    (1 << 1)  /* RX interrupt enable */
>> +#define I2S_CTRL_TX_INT_EN    (1 << 0)  /* TX interrupt enable */
>> +
>> +#define LS_I2S_DRVNAME        "loongson-i2s"
>> +
>> +struct loongson_dma_data {
>> +    dma_addr_t dev_addr;        /* device physical address for DMA */
>> +    void __iomem *order_addr;    /* DMA order register */
>> +    u32 irq;            /* DMA irq */
>> +};
>> +
>> +struct loongson_i2s {
>> +    struct device *dev;
>> +    union {
>> +        struct snd_dmaengine_dai_dma_data playback_dma_data;
>> +        struct loongson_dma_data tx_dma_data;
>> +    };
>> +    union {
>> +        struct snd_dmaengine_dai_dma_data capture_dma_data;
>> +        struct loongson_dma_data rx_dma_data;
>> +    };
>> +    struct regmap *regmap;
>> +    void __iomem *reg_base;
>> +    u32 rev_id;
>> +    u32 clk_rate;
>> +    u32 sysclk;
>> +};
>> +
>> +extern const struct dev_pm_ops loongson_i2s_pm;
>> +extern struct snd_soc_dai_driver loongson_i2s_dai;
>> +
>> +#endif
>> diff --git a/sound/soc/loongson/loongson_i2s_pci.c 
>> b/sound/soc/loongson/loongson_i2s_pci.c
>> new file mode 100644
>> index 000000000000..6dcfb17d3276
>> --- /dev/null
>> +++ b/sound/soc/loongson/loongson_i2s_pci.c
>> @@ -0,0 +1,171 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +//
>> +// loongson_i2s_pci.c -- Loongson I2S controller driver
>> +//
>> +// Copyright (C) 2023 Loongson Technology Corporation Limited
>> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
>> +//
>> +
>> +#include <linux/module.h>
>> +#include <linux/delay.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/acpi.h>
>> +#include <linux/pci.h>
>> +#include <sound/soc.h>
>> +#include "loongson_i2s.h"
>> +#include "loongson_dma.h"
>> +
>> +static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg)
>> +{
>> +    switch (reg) {
>> +    case LS_I2S_CFG:
>> +    case LS_I2S_CTRL:
>> +    case LS_I2S_RX_DATA:
>> +    case LS_I2S_TX_DATA:
>> +    case LS_I2S_CFG1:
>> +        return true;
>> +    default:
>> +        return false;
>> +    };
>> +}
>> +
>> +static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg)
>> +{
>> +    switch (reg) {
>> +    case LS_I2S_VER:
>> +    case LS_I2S_CFG:
>> +    case LS_I2S_CTRL:
>> +    case LS_I2S_RX_DATA:
>> +    case LS_I2S_TX_DATA:
>> +    case LS_I2S_CFG1:
>> +        return true;
>> +    default:
>> +        return false;
>> +    };
>> +}
>> +
>> +static bool loongson_i2s_volatile_reg(struct device *dev, unsigned 
>> int reg)
>> +{
>> +    switch (reg) {
>> +    case LS_I2S_CFG:
>> +    case LS_I2S_CTRL:
>> +    case LS_I2S_RX_DATA:
>> +    case LS_I2S_TX_DATA:
>> +    case LS_I2S_CFG1:
>> +        return true;
>> +    default:
>> +        return false;
>> +    };
>> +}
>> +
>> +static const struct regmap_config loongson_i2s_regmap_config = {
>> +    .reg_bits = 32,
>> +    .reg_stride = 4,
>> +    .val_bits = 32,
>> +    .max_register = LS_I2S_CFG1,
>> +    .writeable_reg = loongson_i2s_wr_reg,
>> +    .readable_reg = loongson_i2s_rd_reg,
>> +    .volatile_reg = loongson_i2s_volatile_reg,
>> +    .cache_type = REGCACHE_FLAT,
>> +};
>> +
>> +static int loongson_i2s_pci_probe(struct pci_dev *pdev,
>> +                  const struct pci_device_id *pid)
>> +{
>> +    const struct fwnode_handle *fwnode = pdev->dev.fwnode;
>> +    struct loongson_dma_data *tx_data, *rx_data;
>> +    struct loongson_i2s *i2s;
>> +    int ret;
>> +
>> +    if (pcim_enable_device(pdev)) {
>> +        dev_err(&pdev->dev, "pci_enable_device failed\n");
>> +        return -ENODEV;
>> +    }
>> +
>> +    i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
>> +    if (!i2s)
>> +        return -ENOMEM;
>> +
>> +    i2s->rev_id = pdev->revision;
>> +    i2s->dev = &pdev->dev;
>> +    pci_set_drvdata(pdev, i2s);
>> +
>> +    ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev));
>> +    if (ret < 0) {
>> +        dev_err(&pdev->dev, "iomap_regions failed\n");
>> +        return ret;
>> +    }
>> +    i2s->reg_base = pcim_iomap_table(pdev)[0];
>> +    i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
>> +                        &loongson_i2s_regmap_config);
>> +    if (IS_ERR(i2s->regmap)) {
>> +        dev_err(&pdev->dev, "regmap_init_mmio failed\n");
>> +        return PTR_ERR(i2s->regmap);
>> +    }
>> +
>> +    tx_data = &i2s->tx_dma_data;
>> +    rx_data = &i2s->rx_dma_data;
>> +
>> +    tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA;
>> +    tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER;
>> +
>> +    rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA;
>> +    rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
>> +
>> +    tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
> 
> Smatch detects that tx_data->irq and rx_data->irq are of type 
> u32(unsigned) so they can never be negative.
> 
>> +    if (tx_data->irq < 0) {
>              ^^^^^^^^ This can never be true.
> 
> Should irq be of type 'int' instead?
> 
>> +        dev_err(&pdev->dev, "dma tx irq invalid\n");
>> +        return tx_data->irq;
>> +    }
>> +
>> +    rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
>> +    if (rx_data->irq < 0) {
>              ^^^ Same problem here.
> 
> Should irq

Should 'irq' be of type int instead?

As fwnode_irq_get_byname() returns a integer.

Thanks,
Harshit

>> +        dev_err(&pdev->dev, "dma rx irq invalid\n");
>> +        return rx_data->irq;
>> +    }
>> +
>> +    device_property_read_u32(&pdev->dev, "clock-frequency", 
>> &i2s->clk_rate);
>> +    if (!i2s->clk_rate) {
>> +        dev_err(&pdev->dev, "clock-frequency property invalid\n");
>> +        return -EINVAL;
>> +    }
>> +
>> +    dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
>> +
>> +    if (i2s->rev_id == 1) {
>> +        regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET);
>> +        udelay(200);
>> +    }
>> +
>> +    ret = devm_snd_soc_register_component(&pdev->dev,
>> +                          &loongson_i2s_component,
>> +                          &loongson_i2s_dai, 1);
>> +    if (ret) {
>> +        dev_err(&pdev->dev, "register DAI failed %d\n", ret);
>> +        return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static const struct pci_device_id loongson_i2s_ids[] = {
>> +    { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) },
>> +    { },
>> +};
>> +MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
>> +
>> +static struct pci_driver loongson_i2s_driver = {
>> +    .name = "loongson-i2s-pci",
>> +    .id_table = loongson_i2s_ids,
>> +    .probe = loongson_i2s_pci_probe,
>> +    .driver = {
>> +        .owner = THIS_MODULE,
>> +        .pm = pm_sleep_ptr(&loongson_i2s_pm),
>> +    },
>> +};
>> +module_pci_driver(loongson_i2s_driver);
>> +
>> +MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver");
>> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
>> +MODULE_LICENSE("GPL");
> 
> Thanks,
> Harshit

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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-18 20:14   ` Harshit Mogalapalli
@ 2023-06-19  1:45     ` Yingkun Meng
  2023-06-19  7:00       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 10+ messages in thread
From: Yingkun Meng @ 2023-06-19  1:45 UTC (permalink / raw)
  To: Harshit Mogalapalli, broonie, lgirdwood
  Cc: krzysztof.kozlowski, linux-kernel, alsa-devel, loongarch,
	loongson-kernel, Dan Carpenter, Dan Carpenter, kernel-janitors

Hi Harshit,

Thanks you for pointing this bug.


On 2023/6/19 04:14, Harshit Mogalapalli wrote:
>
>
> On 19/06/23 1:41 am, Harshit Mogalapalli wrote:
>> Hi YingKun,
>>
>> On 15/06/23 5:57 pm, YingKun Meng wrote:
>>> From: Yingkun Meng <mengyingkun@loongson.cn>
>>>
>>> Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
>>> it is a PCI device with two private DMA controllers, one for playback,
>>> the other for capture.
>>>
>>> The driver supports the use of DTS or ACPI to describe device 
>>> resources.
>>>
>>> Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
>>> ---
>>> changes v3 -> v4
>>>    Add __packed attribute for loongson_dma_desc struct.
>>>
>>> sound/soc/Kconfig                     |   1 +
>>>   sound/soc/Makefile                    |   1 +
>>>   sound/soc/loongson/Kconfig            |  16 ++
>>>   sound/soc/loongson/Makefile           |   4 +
>>>   sound/soc/loongson/loongson_dma.c     | 350 
>>> ++++++++++++++++++++++++++
>>>   sound/soc/loongson/loongson_dma.h     |  16 ++
>>>   sound/soc/loongson/loongson_i2s.c     | 269 ++++++++++++++++++++
>>>   sound/soc/loongson/loongson_i2s.h     |  71 ++++++
>>>   sound/soc/loongson/loongson_i2s_pci.c | 171 +++++++++++++
>>>   9 files changed, 899 insertions(+)
>>>   create mode 100644 sound/soc/loongson/Kconfig
>>>   create mode 100644 sound/soc/loongson/Makefile
>>>   create mode 100644 sound/soc/loongson/loongson_dma.c
>>>   create mode 100644 sound/soc/loongson/loongson_dma.h
>>>   create mode 100644 sound/soc/loongson/loongson_i2s.c
>>>   create mode 100644 sound/soc/loongson/loongson_i2s.h
>>>   create mode 100644 sound/soc/loongson/loongson_i2s_pci.c
>>>
>>> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
>>> index 4b6e5a802880..bfa9622e1ab1 100644
>>> --- a/sound/soc/Kconfig
>>> +++ b/sound/soc/Kconfig
>>> @@ -79,6 +79,7 @@ source "sound/soc/google/Kconfig"
>>>   source "sound/soc/hisilicon/Kconfig"
>>>   source "sound/soc/jz4740/Kconfig"
>>>   source "sound/soc/kirkwood/Kconfig"
>>> +source "sound/soc/loongson/Kconfig"
>>>   source "sound/soc/img/Kconfig"
>>>   source "sound/soc/intel/Kconfig"
>>>   source "sound/soc/mediatek/Kconfig"
>>> diff --git a/sound/soc/Makefile b/sound/soc/Makefile
>>> index 9d9b228e4508..8376fdb217ed 100644
>>> --- a/sound/soc/Makefile
>>> +++ b/sound/soc/Makefile
>>> @@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC)    += fsl/
>>>   obj-$(CONFIG_SND_SOC)    += google/
>>>   obj-$(CONFIG_SND_SOC)    += hisilicon/
>>>   obj-$(CONFIG_SND_SOC)    += jz4740/
>>> +obj-$(CONFIG_SND_SOC)    += loongson/
>>>   obj-$(CONFIG_SND_SOC)    += img/
>>>   obj-$(CONFIG_SND_SOC)    += intel/
>>>   obj-$(CONFIG_SND_SOC)    += mediatek/
>>> diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig
>>> new file mode 100644
>>> index 000000000000..4478ac91e402
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/Kconfig
>>> @@ -0,0 +1,16 @@
>>> +# SPDX-License-Identifier: GPL-2.0
>>> +menu "SoC Audio for Loongson CPUs"
>>> +    depends on LOONGARCH || COMPILE_TEST
>>> +
>>> +config SND_SOC_LOONGSON_I2S_PCI
>>> +    tristate "Loongson I2S-PCI Device Driver"
>>> +    select REGMAP_MMIO
>>> +    depends on PCI
>>> +    help
>>> +      Say Y or M if you want to add support for I2S driver for
>>> +      Loongson I2S controller.
>>> +
>>> +      The controller is found in loongson bridge chips or SoCs,
>>> +      and work as a PCI device.
>>> +
>>> +endmenu
>>> diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile
>>> new file mode 100644
>>> index 000000000000..099af7989103
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/Makefile
>>> @@ -0,0 +1,4 @@
>>> +# SPDX-License-Identifier: GPL-2.0
>>> +#Platform Support
>>> +snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o 
>>> loongson_dma.o
>>> +obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
>>> diff --git a/sound/soc/loongson/loongson_dma.c 
>>> b/sound/soc/loongson/loongson_dma.c
>>> new file mode 100644
>>> index 000000000000..65b6719e61c5
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/loongson_dma.c
>>> @@ -0,0 +1,350 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +//
>>> +// Loongson ALSA SoC Platform (DMA) driver
>>> +//
>>> +// Copyright (C) 2023 Loongson Technology Corporation Limited
>>> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
>>> +//
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/io-64-nonatomic-lo-hi.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <sound/soc.h>
>>> +#include <sound/pcm.h>
>>> +#include <sound/pcm_params.h>
>>> +#include "loongson_i2s.h"
>>> +
>>> +/* DMA dma_order Register */
>>> +#define DMA_ORDER_STOP          (1 << 4) /* DMA stop */
>>> +#define DMA_ORDER_START         (1 << 3) /* DMA start */
>>> +#define DMA_ORDER_ASK_VALID     (1 << 2) /* DMA ask valid flag */
>>> +#define DMA_ORDER_AXI_UNCO      (1 << 1) /* Uncache access */
>>> +#define DMA_ORDER_ADDR_64       (1 << 0) /* 64bits address support */
>>> +
>>> +#define DMA_ORDER_ASK_MASK      (~0x1fUL) /* Ask addr mask */
>>> +#define DMA_ORDER_CTRL_MASK     (0x0fUL)  /* Control mask  */
>>> +
>>> +/*
>>> + * DMA registers descriptor.
>>> + */
>>> +struct loongson_dma_desc {
>>> +    u32 order;        /* Next descriptor address register */
>>> +    u32 saddr;        /* Source address register */
>>> +    u32 daddr;        /* Device address register */
>>> +    u32 length;        /* Total length register */
>>> +    u32 step_length;    /* Memory stride register */
>>> +    u32 step_times;        /* Repeat time register */
>>> +    u32 cmd;        /* Command register */
>>> +    u32 stats;        /* Status register */
>>> +    u32 order_hi;        /* Next descriptor high address register */
>>> +    u32 saddr_hi;        /* High source address register */
>>> +    u32 res[6];        /* Reserved */
>>> +} __packed;
>>> +
>>> +struct loongson_runtime_data {
>>> +    struct loongson_dma_data *dma_data;
>>> +
>>> +    struct loongson_dma_desc *dma_desc_arr;
>>> +    dma_addr_t dma_desc_arr_phy;
>>> +    int dma_desc_arr_size;
>>> +
>>> +    struct loongson_dma_desc *dma_pos_desc;
>>> +    dma_addr_t dma_pos_desc_phy;
>>> +};
>>> +
>>> +static const struct snd_pcm_hardware ls_pcm_hardware = {
>>> +    .info = SNDRV_PCM_INFO_MMAP |
>>> +        SNDRV_PCM_INFO_INTERLEAVED |
>>> +        SNDRV_PCM_INFO_MMAP_VALID |
>>> +        SNDRV_PCM_INFO_RESUME |
>>> +        SNDRV_PCM_INFO_PAUSE,
>>> +    .formats = (SNDRV_PCM_FMTBIT_S8 |
>>> +        SNDRV_PCM_FMTBIT_S16_LE |
>>> +        SNDRV_PCM_FMTBIT_S20_3LE |
>>> +        SNDRV_PCM_FMTBIT_S24_LE),
>>> +    .period_bytes_min = 128,
>>> +    .period_bytes_max = 128 * 1024,
>>> +    .periods_min = 1,
>>> +    .periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc),
>>> +    .buffer_bytes_max = 1024 * 1024,
>>> +};
>>> +
>>> +static struct
>>> +loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd)
>>> +{
>>> +    void __iomem *order_reg = prtd->dma_data->order_addr;
>>> +    u64 val;
>>> +
>>> +    val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
>>> +    val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
>>> +    val |= DMA_ORDER_ASK_VALID;
>>> +    writeq(val, order_reg);
>>> +
>>> +    while (readl(order_reg) & DMA_ORDER_ASK_VALID)
>>> +        udelay(2);
>>> +
>>> +    return prtd->dma_pos_desc;
>>> +}
>>> +
>>> +static int loongson_pcm_trigger(struct snd_soc_component *component,
>>> +                struct snd_pcm_substream *substream, int cmd)
>>> +{
>>> +    struct loongson_runtime_data *prtd = 
>>> substream->runtime->private_data;
>>> +    struct device *dev = substream->pcm->card->dev;
>>> +    void __iomem *order_reg = prtd->dma_data->order_addr;
>>> +    u64 val;
>>> +    int ret = 0;
>>> +
>>> +    switch (cmd) {
>>> +    case SNDRV_PCM_TRIGGER_START:
>>> +    case SNDRV_PCM_TRIGGER_RESUME:
>>> +    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>>> +        val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
>>> +        if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
>>> +            val |= DMA_ORDER_ADDR_64;
>>> +        else
>>> +            val &= ~DMA_ORDER_ADDR_64;
>>> +        val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
>>> +        val |= DMA_ORDER_START;
>>> +        writeq(val, order_reg);
>>> +
>>> +        while ((readl(order_reg) & DMA_ORDER_START))
>>> +            udelay(2);
>>> +        break;
>>> +    case SNDRV_PCM_TRIGGER_STOP:
>>> +    case SNDRV_PCM_TRIGGER_SUSPEND:
>>> +    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>>> +        dma_desc_save(prtd);
>>> +
>>> +        /* dma stop */
>>> +        val = readq(order_reg) | DMA_ORDER_STOP;
>>> +        writeq(val, order_reg);
>>> +        udelay(1000);
>>> +
>>> +        break;
>>> +    default:
>>> +        dev_err(dev, "Invalid pcm trigger operation\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int loongson_pcm_hw_params(struct snd_soc_component *component,
>>> +                  struct snd_pcm_substream *substream,
>>> +                  struct snd_pcm_hw_params *params)
>>> +{
>>> +    struct snd_pcm_runtime *runtime = substream->runtime;
>>> +    struct device *dev = substream->pcm->card->dev;
>>> +    struct loongson_runtime_data *prtd = runtime->private_data;
>>> +    size_t buf_len = params_buffer_bytes(params);
>>> +    size_t period_len = params_period_bytes(params);
>>> +    dma_addr_t order_addr, mem_addr;
>>> +    struct loongson_dma_desc *desc;
>>> +    u32 num_periods;
>>> +    int i;
>>> +
>>> +    if (buf_len % period_len) {
>>> +        dev_err(dev, "buf len not multiply of period len\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    num_periods = buf_len / period_len;
>>> +    if (!num_periods || num_periods > prtd->dma_desc_arr_size) {
>>> +        dev_err(dev, "dma data too small or too big\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
>>> +    runtime->dma_bytes = buf_len;
>>> +
>>> +    /* initialize dma descriptor array */
>>> +    mem_addr = runtime->dma_addr;
>>> +    order_addr = prtd->dma_desc_arr_phy;
>>> +    for (i = 0; i < num_periods; i++) {
>>> +        desc = &prtd->dma_desc_arr[i];
>>> +
>>> +        /* next descriptor physical address */
>>> +        order_addr += sizeof(*desc);
>>> +        desc->order = lower_32_bits(order_addr | BIT(0));
>>> +        desc->order_hi = upper_32_bits(order_addr);
>>> +
>>> +        desc->saddr = lower_32_bits(mem_addr);
>>> +        desc->saddr_hi = upper_32_bits(mem_addr);
>>> +        desc->daddr = prtd->dma_data->dev_addr;
>>> +
>>> +        desc->cmd = BIT(0);
>>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>> +            desc->cmd |= BIT(12);
>>> +
>>> +        desc->length = period_len >> 2;
>>> +        desc->step_length = 0;
>>> +        desc->step_times = 1;
>>> +
>>> +        mem_addr += period_len;
>>> +    }
>>> +    desc = &prtd->dma_desc_arr[num_periods - 1];
>>> +    desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0));
>>> +    desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy);
>>> +
>>> +    /* init position descriptor */
>>> +    *prtd->dma_pos_desc = *prtd->dma_desc_arr;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static snd_pcm_uframes_t
>>> +loongson_pcm_pointer(struct snd_soc_component *component,
>>> +             struct snd_pcm_substream *substream)
>>> +{
>>> +    struct snd_pcm_runtime *runtime = substream->runtime;
>>> +    struct loongson_runtime_data *prtd = runtime->private_data;
>>> +    struct loongson_dma_desc *desc;
>>> +    snd_pcm_uframes_t x;
>>> +    u64 addr;
>>> +
>>> +    desc = dma_desc_save(prtd);
>>> +    addr = ((u64)desc->saddr_hi << 32) | desc->saddr;
>>> +
>>> +    x = bytes_to_frames(runtime, addr - runtime->dma_addr);
>>> +    if (x == runtime->buffer_size)
>>> +        x = 0;
>>> +    return x;
>>> +}
>>> +
>>> +static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid)
>>> +{
>>> +    struct snd_pcm_substream *substream = devid;
>>> +
>>> +    snd_pcm_period_elapsed(substream);
>>> +    return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int loongson_pcm_open(struct snd_soc_component *component,
>>> +                 struct snd_pcm_substream *substream)
>>> +{
>>> +    struct snd_pcm_runtime *runtime = substream->runtime;
>>> +    struct snd_soc_pcm_runtime *rtd = substream->private_data;
>>> +    struct snd_card *card = substream->pcm->card;
>>> +    struct loongson_runtime_data *prtd;
>>> +    struct loongson_dma_data *dma_data;
>>> +    int ret;
>>> +
>>> +    /*
>>> +     * For mysterious reasons (and despite what the manual says)
>>> +     * playback samples are lost if the DMA count is not a multiple
>>> +     * of the DMA burst size.  Let's add a rule to enforce that.
>>> +     */
>>> +    snd_pcm_hw_constraint_step(runtime, 0,
>>> +                   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
>>> +    snd_pcm_hw_constraint_step(runtime, 0,
>>> +                   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
>>> +    snd_pcm_hw_constraint_integer(substream->runtime,
>>> +                      SNDRV_PCM_HW_PARAM_PERIODS);
>>> +    snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware);
>>> +
>>> +    prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
>>> +    if (!prtd)
>>> +        return -ENOMEM;
>>> +
>>> +    prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE,
>>> +                        &prtd->dma_desc_arr_phy,
>>> +                        GFP_KERNEL);
>>> +    if (!prtd->dma_desc_arr) {
>>> +        ret = -ENOMEM;
>>> +        goto desc_err;
>>> +    }
>>> +    prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr);
>>> +
>>> +    prtd->dma_pos_desc = dma_alloc_coherent(card->dev,
>>> +                        sizeof(*prtd->dma_pos_desc),
>>> +                        &prtd->dma_pos_desc_phy,
>>> +                        GFP_KERNEL);
>>> +    if (!prtd->dma_pos_desc) {
>>> +        ret = -ENOMEM;
>>> +        goto pos_err;
>>> +    }
>>> +
>>> +    dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), 
>>> substream);
>>> +    prtd->dma_data = dma_data;
>>> +
>>> +    substream->runtime->private_data = prtd;
>>> +
>>> +    return 0;
>>> +pos_err:
>>> +    dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
>>> +              prtd->dma_desc_arr_phy);
>>> +desc_err:
>>> +    kfree(prtd);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int loongson_pcm_close(struct snd_soc_component *component,
>>> +                  struct snd_pcm_substream *substream)
>>> +{
>>> +    struct snd_card *card = substream->pcm->card;
>>> +    struct loongson_runtime_data *prtd = 
>>> substream->runtime->private_data;
>>> +
>>> +    dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
>>> +              prtd->dma_desc_arr_phy);
>>> +
>>> +    dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc),
>>> +              prtd->dma_pos_desc, prtd->dma_pos_desc_phy);
>>> +
>>> +    kfree(prtd);
>>> +    return 0;
>>> +}
>>> +
>>> +static int loongson_pcm_mmap(struct snd_soc_component *component,
>>> +                 struct snd_pcm_substream *substream,
>>> +                 struct vm_area_struct *vma)
>>> +{
>>> +    return remap_pfn_range(vma, vma->vm_start,
>>> +            substream->dma_buffer.addr >> PAGE_SHIFT,
>>> +            vma->vm_end - vma->vm_start, vma->vm_page_prot);
>>> +}
>>> +
>>> +static int loongson_pcm_new(struct snd_soc_component *component,
>>> +                struct snd_soc_pcm_runtime *rtd)
>>> +{
>>> +    struct snd_card *card = rtd->card->snd_card;
>>> +    struct snd_pcm_substream *substream;
>>> +    struct loongson_dma_data *dma_data;
>>> +    unsigned int i;
>>> +    int ret;
>>> +
>>> +    for_each_pcm_streams(i) {
>>> +        substream = rtd->pcm->streams[i].substream;
>>> +        if (!substream)
>>> +            continue;
>>> +
>>> +        dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0),
>>> +                            substream);
>>> +        ret = devm_request_irq(card->dev, dma_data->irq,
>>> +                       loongson_pcm_dma_irq,
>>> +                       IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME,
>>> +                       substream);
>>> +        if (ret < 0) {
>>> +            dev_err(card->dev, "request irq for DMA failed\n");
>>> +            return ret;
>>> +        }
>>> +    }
>>> +
>>> +    return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
>>> +                        card->dev,
>>> +                        ls_pcm_hardware.buffer_bytes_max);
>>> +}
>>> +
>>> +const struct snd_soc_component_driver loongson_i2s_component = {
>>> +    .name        = LS_I2S_DRVNAME,
>>> +    .open        = loongson_pcm_open,
>>> +    .close        = loongson_pcm_close,
>>> +    .hw_params    = loongson_pcm_hw_params,
>>> +    .trigger    = loongson_pcm_trigger,
>>> +    .pointer    = loongson_pcm_pointer,
>>> +    .mmap        = loongson_pcm_mmap,
>>> +    .pcm_construct    = loongson_pcm_new,
>>> +};
>>> diff --git a/sound/soc/loongson/loongson_dma.h 
>>> b/sound/soc/loongson/loongson_dma.h
>>> new file mode 100644
>>> index 000000000000..073ee8c0c046
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/loongson_dma.h
>>> @@ -0,0 +1,16 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * ALSA ASoC interface for the Loongson platform
>>> + *
>>> + * Copyright (C) 2023 Loongson Technology Corporation Limited
>>> + * Author: Yingkun Meng <mengyingkun@loongson.cn>
>>> + */
>>> +
>>> +#ifndef _LOONGSON_DMA_H
>>> +#define _LOONGSON_DMA_H
>>> +
>>> +#include <sound/soc.h>
>>> +
>>> +extern const struct snd_soc_component_driver loongson_i2s_component;
>>> +
>>> +#endif
>>> diff --git a/sound/soc/loongson/loongson_i2s.c 
>>> b/sound/soc/loongson/loongson_i2s.c
>>> new file mode 100644
>>> index 000000000000..35d34568be79
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/loongson_i2s.c
>>> @@ -0,0 +1,269 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +//
>>> +// Common functions for loongson I2S controller driver
>>> +//
>>> +// Copyright (C) 2023 Loongson Technology Corporation Limited.
>>> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
>>> +//
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <sound/soc.h>
>>> +#include <linux/regmap.h>
>>> +#include <sound/pcm_params.h>
>>> +#include "loongson_i2s.h"
>>> +
>>> +#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
>>> +            SNDRV_PCM_FMTBIT_S16_LE | \
>>> +            SNDRV_PCM_FMTBIT_S20_3LE | \
>>> +            SNDRV_PCM_FMTBIT_S24_LE)
>>> +
>>> +static int loongson_i2s_trigger(struct snd_pcm_substream 
>>> *substream, int cmd,
>>> +                struct snd_soc_dai *dai)
>>> +{
>>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>>> +    int ret = 0;
>>> +
>>> +    switch (cmd) {
>>> +    case SNDRV_PCM_TRIGGER_START:
>>> +    case SNDRV_PCM_TRIGGER_RESUME:
>>> +    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
>>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>>> +                       I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN,
>>> +                       I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN);
>>> +        else
>>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>>> +                       I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN,
>>> +                       I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN);
>>> +        break;
>>> +    case SNDRV_PCM_TRIGGER_STOP:
>>> +    case SNDRV_PCM_TRIGGER_SUSPEND:
>>> +    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>>> +                    I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0);
>>> +        else
>>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>>> +                    I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0);
>>> +        break;
>>> +    default:
>>> +        ret = -EINVAL;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int loongson_i2s_hw_params(struct snd_pcm_substream *substream,
>>> +                  struct snd_pcm_hw_params *params,
>>> +                  struct snd_soc_dai *dai)
>>> +{
>>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>>> +    u32 clk_rate = i2s->clk_rate;
>>> +    u32 sysclk = i2s->sysclk;
>>> +    u32 bits = params_width(params);
>>> +    u32 chans = params_channels(params);
>>> +    u32 fs = params_rate(params);
>>> +    u32 bclk_ratio, mclk_ratio;
>>> +    u32 mclk_ratio_frac;
>>> +    u32 val = 0;
>>> +
>>> +    switch (i2s->rev_id) {
>>> +    case 0:
>>> +        bclk_ratio = DIV_ROUND_CLOSEST(clk_rate,
>>> +                           (bits * chans * fs * 2)) - 1;
>>> +        mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1;
>>> +
>>> +        /* According to 2k1000LA user manual, set bits == depth */
>>> +        val |= (bits << 24);
>>> +        val |= (bits << 16);
>>> +        val |= (bclk_ratio << 8);
>>> +        val |= mclk_ratio;
>>> +        regmap_write(i2s->regmap, LS_I2S_CFG, val);
>>> +
>>> +        break;
>>> +    case 1:
>>> +        bclk_ratio = DIV_ROUND_CLOSEST(sysclk,
>>> +                           (bits * chans * fs * 2)) - 1;
>>> +        mclk_ratio = clk_rate / sysclk;
>>> +        mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16),
>>> +                            sysclk) - (mclk_ratio << 16);
>>> +
>>> +        regmap_read(i2s->regmap, LS_I2S_CFG, &val);
>>> +        val |= (bits << 24);
>>> +        val |= (bclk_ratio << 8);
>>> +        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>>> +            val |= (bits << 16);
>>> +        else
>>> +            val |= bits;
>>> +        regmap_write(i2s->regmap, LS_I2S_CFG, val);
>>> +
>>> +        val = (mclk_ratio_frac << 16) | mclk_ratio;
>>> +        regmap_write(i2s->regmap, LS_I2S_CFG1, val);
>>> +
>>> +        break;
>>> +    default:
>>> +        dev_err(i2s->dev, "I2S revision invalid\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int 
>>> clk_id,
>>> +                       unsigned int freq, int dir)
>>> +{
>>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>>> +
>>> +    i2s->sysclk = freq;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned 
>>> int fmt)
>>> +{
>>> +    struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
>>> +    u32 val;
>>> +    int ret;
>>> +
>>> +    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
>>> +    case SND_SOC_DAIFMT_I2S:
>>> +        break;
>>> +    case SND_SOC_DAIFMT_RIGHT_J:
>>> +        regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB,
>>> +                   I2S_CTRL_MSB);
>>> +        break;
>>> +    default:
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +
>>> +    switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
>>> +    case SND_SOC_DAIFMT_BC_FC:
>>> +        break;
>>> +    case SND_SOC_DAIFMT_BP_FC:
>>> +        /* Enable master mode */
>>> +        regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
>>> +                   I2S_CTRL_MASTER);
>>> +        if (i2s->rev_id == 1) {
>>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>>> +                        LS_I2S_CTRL, val,
>>> +                        val & I2S_CTRL_CLK_READY,
>>> +                        10, 500000);
>>> +            if (ret < 0)
>>> +                dev_warn(dai->dev, "wait BCLK ready timeout\n");
>>> +        }
>>> +        break;
>>> +    case SND_SOC_DAIFMT_BC_FP:
>>> +        /* Enable MCLK */
>>> +        if (i2s->rev_id == 1) {
>>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>>> +                       I2S_CTRL_MCLK_EN,
>>> +                       I2S_CTRL_MCLK_EN);
>>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>>> +                        LS_I2S_CTRL, val,
>>> +                        val & I2S_CTRL_MCLK_READY,
>>> +                        10, 500000);
>>> +            if (ret < 0)
>>> +                dev_warn(dai->dev, "wait MCLK ready timeout\n");
>>> +        }
>>> +        break;
>>> +    case SND_SOC_DAIFMT_BP_FP:
>>> +        /* Enable MCLK */
>>> +        if (i2s->rev_id == 1) {
>>> +            regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
>>> +                       I2S_CTRL_MCLK_EN,
>>> +                       I2S_CTRL_MCLK_EN);
>>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>>> +                        LS_I2S_CTRL, val,
>>> +                        val & I2S_CTRL_MCLK_READY,
>>> +                        10, 500000);
>>> +            if (ret < 0)
>>> +                dev_warn(dai->dev, "wait MCLK ready timeout\n");
>>> +        }
>>> +
>>> +        /* Enable master mode */
>>> +        regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
>>> +                   I2S_CTRL_MASTER);
>>> +        if (i2s->rev_id == 1) {
>>> +            ret = regmap_read_poll_timeout_atomic(i2s->regmap,
>>> +                        LS_I2S_CTRL, val,
>>> +                        val & I2S_CTRL_CLK_READY,
>>> +                        10, 500000);
>>> +            if (ret < 0)
>>> +                dev_warn(dai->dev, "wait BCLK ready timeout\n");
>>> +        }
>>> +        break;
>>> +    default:
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
>>> +    .trigger    = loongson_i2s_trigger,
>>> +    .hw_params    = loongson_i2s_hw_params,
>>> +    .set_sysclk    = loongson_i2s_set_dai_sysclk,
>>> +    .set_fmt    = loongson_i2s_set_fmt,
>>> +};
>>> +
>>> +static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
>>> +{
>>> +    struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
>>> +
>>> +    snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
>>> +                  &i2s->capture_dma_data);
>>> +    snd_soc_dai_set_drvdata(cpu_dai, i2s);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +struct snd_soc_dai_driver loongson_i2s_dai = {
>>> +    .name = "loongson-i2s",
>>> +    .probe = loongson_i2s_dai_probe,
>>> +    .playback = {
>>> +        .stream_name = "CPU-Playback",
>>> +        .channels_min = 1,
>>> +        .channels_max = 2,
>>> +        .rates = SNDRV_PCM_RATE_8000_96000,
>>> +        .formats = LOONGSON_I2S_FORMATS,
>>> +    },
>>> +    .capture = {
>>> +        .stream_name = "CPU-Capture",
>>> +        .channels_min = 1,
>>> +        .channels_max = 2,
>>> +        .rates = SNDRV_PCM_RATE_8000_96000,
>>> +        .formats = LOONGSON_I2S_FORMATS,
>>> +    },
>>> +    .ops = &loongson_i2s_dai_ops,
>>> +    .symmetric_rate = 1,
>>> +};
>>> +
>>> +static int i2s_suspend(struct device *dev)
>>> +{
>>> +    struct loongson_i2s *i2s = dev_get_drvdata(dev);
>>> +
>>> +    regcache_cache_only(i2s->regmap, true);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int i2s_resume(struct device *dev)
>>> +{
>>> +    struct loongson_i2s *i2s = dev_get_drvdata(dev);
>>> +    int ret;
>>> +
>>> +    regcache_cache_only(i2s->regmap, false);
>>> +    regcache_mark_dirty(i2s->regmap);
>>> +    ret = regcache_sync(i2s->regmap);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +const struct dev_pm_ops loongson_i2s_pm = {
>>> +    SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
>>> +};
>>> diff --git a/sound/soc/loongson/loongson_i2s.h 
>>> b/sound/soc/loongson/loongson_i2s.h
>>> new file mode 100644
>>> index 000000000000..52788f6a94ad
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/loongson_i2s.h
>>> @@ -0,0 +1,71 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * ALSA I2S interface for the Loongson platform
>>> + *
>>> + * Copyright (C) 2023 Loongson Technology Corporation Limited
>>> + * Author: Yingkun Meng <mengyingkun@loongson.cn>
>>> + */
>>> +
>>> +#ifndef _LOONGSON_I2S_H
>>> +#define _LOONGSON_I2S_H
>>> +
>>> +#include <linux/regmap.h>
>>> +#include <sound/dmaengine_pcm.h>
>>> +
>>> +/* I2S Common Registers */
>>> +#define LS_I2S_VER    0x00 /* I2S Version */
>>> +#define LS_I2S_CFG    0x04 /* I2S Config */
>>> +#define LS_I2S_CTRL    0x08 /* I2S Control */
>>> +#define LS_I2S_RX_DATA    0x0C /* I2S DMA RX Address */
>>> +#define LS_I2S_TX_DATA    0x10 /* I2S DMA TX Address */
>>> +
>>> +/* 2K2000 I2S Specify Registers */
>>> +#define LS_I2S_CFG1    0x14 /* I2S Config1 */
>>> +
>>> +/* 7A2000 I2S Specify Registers */
>>> +#define LS_I2S_TX_ORDER    0x100 /* TX DMA Order */
>>> +#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */
>>> +
>>> +/* Loongson I2S Control Register */
>>> +#define I2S_CTRL_MCLK_READY    (1 << 16) /* MCLK ready */
>>> +#define I2S_CTRL_MASTER        (1 << 15) /* Master mode */
>>> +#define I2S_CTRL_MSB        (1 << 14) /* MSB bit order */
>>> +#define I2S_CTRL_RX_EN        (1 << 13) /* RX enable */
>>> +#define I2S_CTRL_TX_EN        (1 << 12) /* TX enable */
>>> +#define I2S_CTRL_RX_DMA_EN    (1 << 11) /* DMA RX enable */
>>> +#define I2S_CTRL_CLK_READY    (1 << 8)  /* BCLK ready */
>>> +#define I2S_CTRL_TX_DMA_EN    (1 << 7)  /* DMA TX enable */
>>> +#define I2S_CTRL_RESET        (1 << 4)  /* Controller soft reset */
>>> +#define I2S_CTRL_MCLK_EN    (1 << 3)  /* Enable MCLK */
>>> +#define I2S_CTRL_RX_INT_EN    (1 << 1)  /* RX interrupt enable */
>>> +#define I2S_CTRL_TX_INT_EN    (1 << 0)  /* TX interrupt enable */
>>> +
>>> +#define LS_I2S_DRVNAME        "loongson-i2s"
>>> +
>>> +struct loongson_dma_data {
>>> +    dma_addr_t dev_addr;        /* device physical address for DMA */
>>> +    void __iomem *order_addr;    /* DMA order register */
>>> +    u32 irq;            /* DMA irq */
>>> +};
>>> +
>>> +struct loongson_i2s {
>>> +    struct device *dev;
>>> +    union {
>>> +        struct snd_dmaengine_dai_dma_data playback_dma_data;
>>> +        struct loongson_dma_data tx_dma_data;
>>> +    };
>>> +    union {
>>> +        struct snd_dmaengine_dai_dma_data capture_dma_data;
>>> +        struct loongson_dma_data rx_dma_data;
>>> +    };
>>> +    struct regmap *regmap;
>>> +    void __iomem *reg_base;
>>> +    u32 rev_id;
>>> +    u32 clk_rate;
>>> +    u32 sysclk;
>>> +};
>>> +
>>> +extern const struct dev_pm_ops loongson_i2s_pm;
>>> +extern struct snd_soc_dai_driver loongson_i2s_dai;
>>> +
>>> +#endif
>>> diff --git a/sound/soc/loongson/loongson_i2s_pci.c 
>>> b/sound/soc/loongson/loongson_i2s_pci.c
>>> new file mode 100644
>>> index 000000000000..6dcfb17d3276
>>> --- /dev/null
>>> +++ b/sound/soc/loongson/loongson_i2s_pci.c
>>> @@ -0,0 +1,171 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +//
>>> +// loongson_i2s_pci.c -- Loongson I2S controller driver
>>> +//
>>> +// Copyright (C) 2023 Loongson Technology Corporation Limited
>>> +// Author: Yingkun Meng <mengyingkun@loongson.cn>
>>> +//
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/acpi.h>
>>> +#include <linux/pci.h>
>>> +#include <sound/soc.h>
>>> +#include "loongson_i2s.h"
>>> +#include "loongson_dma.h"
>>> +
>>> +static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg)
>>> +{
>>> +    switch (reg) {
>>> +    case LS_I2S_CFG:
>>> +    case LS_I2S_CTRL:
>>> +    case LS_I2S_RX_DATA:
>>> +    case LS_I2S_TX_DATA:
>>> +    case LS_I2S_CFG1:
>>> +        return true;
>>> +    default:
>>> +        return false;
>>> +    };
>>> +}
>>> +
>>> +static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg)
>>> +{
>>> +    switch (reg) {
>>> +    case LS_I2S_VER:
>>> +    case LS_I2S_CFG:
>>> +    case LS_I2S_CTRL:
>>> +    case LS_I2S_RX_DATA:
>>> +    case LS_I2S_TX_DATA:
>>> +    case LS_I2S_CFG1:
>>> +        return true;
>>> +    default:
>>> +        return false;
>>> +    };
>>> +}
>>> +
>>> +static bool loongson_i2s_volatile_reg(struct device *dev, unsigned 
>>> int reg)
>>> +{
>>> +    switch (reg) {
>>> +    case LS_I2S_CFG:
>>> +    case LS_I2S_CTRL:
>>> +    case LS_I2S_RX_DATA:
>>> +    case LS_I2S_TX_DATA:
>>> +    case LS_I2S_CFG1:
>>> +        return true;
>>> +    default:
>>> +        return false;
>>> +    };
>>> +}
>>> +
>>> +static const struct regmap_config loongson_i2s_regmap_config = {
>>> +    .reg_bits = 32,
>>> +    .reg_stride = 4,
>>> +    .val_bits = 32,
>>> +    .max_register = LS_I2S_CFG1,
>>> +    .writeable_reg = loongson_i2s_wr_reg,
>>> +    .readable_reg = loongson_i2s_rd_reg,
>>> +    .volatile_reg = loongson_i2s_volatile_reg,
>>> +    .cache_type = REGCACHE_FLAT,
>>> +};
>>> +
>>> +static int loongson_i2s_pci_probe(struct pci_dev *pdev,
>>> +                  const struct pci_device_id *pid)
>>> +{
>>> +    const struct fwnode_handle *fwnode = pdev->dev.fwnode;
>>> +    struct loongson_dma_data *tx_data, *rx_data;
>>> +    struct loongson_i2s *i2s;
>>> +    int ret;
>>> +
>>> +    if (pcim_enable_device(pdev)) {
>>> +        dev_err(&pdev->dev, "pci_enable_device failed\n");
>>> +        return -ENODEV;
>>> +    }
>>> +
>>> +    i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
>>> +    if (!i2s)
>>> +        return -ENOMEM;
>>> +
>>> +    i2s->rev_id = pdev->revision;
>>> +    i2s->dev = &pdev->dev;
>>> +    pci_set_drvdata(pdev, i2s);
>>> +
>>> +    ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev));
>>> +    if (ret < 0) {
>>> +        dev_err(&pdev->dev, "iomap_regions failed\n");
>>> +        return ret;
>>> +    }
>>> +    i2s->reg_base = pcim_iomap_table(pdev)[0];
>>> +    i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
>>> +                        &loongson_i2s_regmap_config);
>>> +    if (IS_ERR(i2s->regmap)) {
>>> +        dev_err(&pdev->dev, "regmap_init_mmio failed\n");
>>> +        return PTR_ERR(i2s->regmap);
>>> +    }
>>> +
>>> +    tx_data = &i2s->tx_dma_data;
>>> +    rx_data = &i2s->rx_dma_data;
>>> +
>>> +    tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA;
>>> +    tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER;
>>> +
>>> +    rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA;
>>> +    rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
>>> +
>>> +    tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
>>
>> Smatch detects that tx_data->irq and rx_data->irq are of type 
>> u32(unsigned) so they can never be negative.
>>
>>> +    if (tx_data->irq < 0) {
>>              ^^^^^^^^ This can never be true.
>>
>> Should irq be of type 'int' instead?
>>
>>> +        dev_err(&pdev->dev, "dma tx irq invalid\n");
>>> +        return tx_data->irq;
>>> +    }
>>> +
>>> +    rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
>>> +    if (rx_data->irq < 0) {
>>              ^^^ Same problem here.
>>
>> Should irq
>
> Should 'irq' be of type int instead?
>
> As fwnode_irq_get_byname() returns a integer.
>
Yes, you are right. I will add a patch to fix the type of 'irq' to int.

Thanks,
Yingkun


> Thanks,
> Harshit
>
>>> +        dev_err(&pdev->dev, "dma rx irq invalid\n");
>>> +        return rx_data->irq;
>>> +    }
>>> +
>>> +    device_property_read_u32(&pdev->dev, "clock-frequency", 
>>> &i2s->clk_rate);
>>> +    if (!i2s->clk_rate) {
>>> +        dev_err(&pdev->dev, "clock-frequency property invalid\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
>>> +
>>> +    if (i2s->rev_id == 1) {
>>> +        regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET);
>>> +        udelay(200);
>>> +    }
>>> +
>>> +    ret = devm_snd_soc_register_component(&pdev->dev,
>>> +                          &loongson_i2s_component,
>>> +                          &loongson_i2s_dai, 1);
>>> +    if (ret) {
>>> +        dev_err(&pdev->dev, "register DAI failed %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static const struct pci_device_id loongson_i2s_ids[] = {
>>> +    { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) },
>>> +    { },
>>> +};
>>> +MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
>>> +
>>> +static struct pci_driver loongson_i2s_driver = {
>>> +    .name = "loongson-i2s-pci",
>>> +    .id_table = loongson_i2s_ids,
>>> +    .probe = loongson_i2s_pci_probe,
>>> +    .driver = {
>>> +        .owner = THIS_MODULE,
>>> +        .pm = pm_sleep_ptr(&loongson_i2s_pm),
>>> +    },
>>> +};
>>> +module_pci_driver(loongson_i2s_driver);
>>> +
>>> +MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver");
>>> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
>>> +MODULE_LICENSE("GPL");
>>
>> Thanks,
>> Harshit


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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-19  1:45     ` Yingkun Meng
@ 2023-06-19  7:00       ` Krzysztof Kozlowski
  2023-06-19  7:49         ` Yingkun Meng
  0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Kozlowski @ 2023-06-19  7:00 UTC (permalink / raw)
  To: Yingkun Meng, Harshit Mogalapalli, broonie, lgirdwood
  Cc: linux-kernel, alsa-devel, loongarch, loongson-kernel,
	Dan Carpenter, Dan Carpenter, kernel-janitors

On 19/06/2023 03:45, Yingkun Meng wrote:
>>>> +    tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
>>>
>>> Smatch detects that tx_data->irq and rx_data->irq are of type 
>>> u32(unsigned) so they can never be negative.
>>>
>>>> +    if (tx_data->irq < 0) {
>>>              ^^^^^^^^ This can never be true.
>>>
>>> Should irq be of type 'int' instead?
>>>
>>>> +        dev_err(&pdev->dev, "dma tx irq invalid\n");
>>>> +        return tx_data->irq;
>>>> +    }
>>>> +
>>>> +    rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
>>>> +    if (rx_data->irq < 0) {
>>>              ^^^ Same problem here.
>>>
>>> Should irq
>>
>> Should 'irq' be of type int instead?
>>
>> As fwnode_irq_get_byname() returns a integer.
>>
> Yes, you are right. I will add a patch to fix the type of 'irq' to int.

Run smatch and sparse on your code before posting. It would find such
trivial mistakes.

Best regards,
Krzysztof


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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-19  7:00       ` Krzysztof Kozlowski
@ 2023-06-19  7:49         ` Yingkun Meng
  0 siblings, 0 replies; 10+ messages in thread
From: Yingkun Meng @ 2023-06-19  7:49 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Harshit Mogalapalli, broonie, lgirdwood
  Cc: linux-kernel, alsa-devel, loongarch, loongson-kernel,
	Dan Carpenter, Dan Carpenter, kernel-janitors


On 2023/6/19 15:00, Krzysztof Kozlowski wrote:
> On 19/06/2023 03:45, Yingkun Meng wrote:
>>>>> +    tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
>>>> Smatch detects that tx_data->irq and rx_data->irq are of type
>>>> u32(unsigned) so they can never be negative.
>>>>
>>>>> +    if (tx_data->irq < 0) {
>>>>               ^^^^^^^^ This can never be true.
>>>>
>>>> Should irq be of type 'int' instead?
>>>>
>>>>> +        dev_err(&pdev->dev, "dma tx irq invalid\n");
>>>>> +        return tx_data->irq;
>>>>> +    }
>>>>> +
>>>>> +    rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
>>>>> +    if (rx_data->irq < 0) {
>>>>               ^^^ Same problem here.
>>>>
>>>> Should irq
>>> Should 'irq' be of type int instead?
>>>
>>> As fwnode_irq_get_byname() returns a integer.
>>>
>> Yes, you are right. I will add a patch to fix the type of 'irq' to int.
> Run smatch and sparse on your code before posting. It would find such
> trivial mistakes.

Thanks, got it.

Thanks,
Yingkun

>
> Best regards,
> Krzysztof


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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-18 20:11 ` Harshit Mogalapalli
  2023-06-18 20:14   ` Harshit Mogalapalli
@ 2023-06-19 11:20   ` Mark Brown
  2023-06-19 11:27     ` Harshit Mogalapalli
  1 sibling, 1 reply; 10+ messages in thread
From: Mark Brown @ 2023-06-19 11:20 UTC (permalink / raw)
  To: Harshit Mogalapalli
  Cc: YingKun Meng, lgirdwood, krzysztof.kozlowski, linux-kernel,
	alsa-devel, loongarch, loongson-kernel, Dan Carpenter,
	Dan Carpenter, kernel-janitors

[-- Attachment #1: Type: text/plain, Size: 676 bytes --]

On Mon, Jun 19, 2023 at 01:41:48AM +0530, Harshit Mogalapalli wrote:
> Hi YingKun,
> 
> On 15/06/23 5:57 pm, YingKun Meng wrote:
> > From: Yingkun Meng <mengyingkun@loongson.cn>
> > 
> > Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
> > it is a PCI device with two private DMA controllers, one for playback,
> > the other for capture.
> > 
> > The driver supports the use of DTS or ACPI to describe device resources.

Please delete unneeded context from mails when replying.  Doing this
makes it much easier to find your reply in the message, helping ensure
it won't be missed by people scrolling through the irrelevant quoted
material.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller
  2023-06-19 11:20   ` Mark Brown
@ 2023-06-19 11:27     ` Harshit Mogalapalli
  0 siblings, 0 replies; 10+ messages in thread
From: Harshit Mogalapalli @ 2023-06-19 11:27 UTC (permalink / raw)
  To: Mark Brown
  Cc: YingKun Meng, lgirdwood, krzysztof.kozlowski, linux-kernel,
	alsa-devel, loongarch, loongson-kernel, Dan Carpenter,
	Dan Carpenter, kernel-janitors

Hi Mark,

On 19/06/23 4:50 pm, Mark Brown wrote:
> Please delete unneeded context from mails when replying.  Doing this
> makes it much easier to find your reply in the message, helping ensure
> it won't be missed by people scrolling through the irrelevant quoted
> material.

Thanks for the suggestion. Will take care of this from next time.

Regards,
Harshit

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

end of thread, other threads:[~2023-06-19 11:27 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-15 12:27 [PATCH v4 1/3] ASoC: Add support for Loongson I2S controller YingKun Meng
2023-06-15 13:58 ` Mark Brown
2023-06-16  1:12   ` Yingkun Meng
2023-06-18 20:11 ` Harshit Mogalapalli
2023-06-18 20:14   ` Harshit Mogalapalli
2023-06-19  1:45     ` Yingkun Meng
2023-06-19  7:00       ` Krzysztof Kozlowski
2023-06-19  7:49         ` Yingkun Meng
2023-06-19 11:20   ` Mark Brown
2023-06-19 11:27     ` Harshit Mogalapalli

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).