diff mbox series

[1/1] ramips: mt7621: add ralink pcm controller driver

Message ID 1593784297-31453-2-git-send-email-puyou.lu@gmail.com
State Changes Requested
Headers show
Series ralink pcm controller driver | expand

Commit Message

Puyou Lu July 3, 2020, 1:51 p.m. UTC
From: Puyou Lu <puyou.lu@gmail.com>

Only tested on mt7621 currently.

Signed-off-by: Puyou Lu <puyou.lu@gmail.com>
---
 target/linux/ramips/dts/AP-MT7621A-V60.dts         |  13 +
 target/linux/ramips/dts/mt7621.dtsi                |  18 +
 target/linux/ramips/modules.mk                     |   5 +-
 .../0049-asoc-add-mt7620-pcm-support.patch         | 920 +++++++++++++++++++++
 4 files changed, 955 insertions(+), 1 deletion(-)
 create mode 100644 target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch

Comments

Daniel Golle July 3, 2020, 2:09 p.m. UTC | #1
Hi Puyou,


On Fri, Jul 03, 2020 at 09:51:37PM +0800, puyou.lu@gmail.com wrote:
> From: Puyou Lu <puyou.lu@gmail.com>
> 
> Only tested on mt7621 currently.
> 
> Signed-off-by: Puyou Lu <puyou.lu@gmail.com>
> ---
>  target/linux/ramips/dts/AP-MT7621A-V60.dts         |  13 +

This file was renamed into mt7621_mediatek_ap-mt7621a-v60.dts.
Please rebase your patch on top of the master branch of
https://git.openwrt.org/openwrt/openwrt.git

Cheers

Daniel


>  target/linux/ramips/dts/mt7621.dtsi                |  18 +
>  target/linux/ramips/modules.mk                     |   5 +-
>  .../0049-asoc-add-mt7620-pcm-support.patch         | 920 +++++++++++++++++++++
>  4 files changed, 955 insertions(+), 1 deletion(-)
>  create mode 100644 target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch
> 
> diff --git a/target/linux/ramips/dts/AP-MT7621A-V60.dts b/target/linux/ramips/dts/AP-MT7621A-V60.dts
> index b613c9c..c4db011 100644
> --- a/target/linux/ramips/dts/AP-MT7621A-V60.dts
> +++ b/target/linux/ramips/dts/AP-MT7621A-V60.dts
> @@ -54,6 +54,12 @@
>  			ralink,function = "i2s";
>  		};
>  	};
> +	pcm_pins: pcm {
> +		pcm {
> +			ralink,group = "uart2";
> +			ralink,function = "pcm";
> +		};
> +	};
>  };
>  
>  &i2c {
> @@ -78,6 +84,13 @@
>  	pinctrl-0 = <&i2s_pins>;
>  };
>  
> +&pcm {
> +	#sound-dai-cells = <0>;
> +	status = "okay";
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&pcm_pins>;
> +};
> +
>  &spi0 {
>  	status = "okay";
>  
> diff --git a/target/linux/ramips/dts/mt7621.dtsi b/target/linux/ramips/dts/mt7621.dtsi
> index 4f69e09..759a928 100644
> --- a/target/linux/ramips/dts/mt7621.dtsi
> +++ b/target/linux/ramips/dts/mt7621.dtsi
> @@ -141,6 +141,24 @@
>  			status = "disabled";
>  		};
>  
> +		pcm: pcm@2000 {
> +			compatible = "mediatek,mt7621-pcm";
> +			reg = <0x2000 0x800>;
> +
> +			clocks = <&sysclock>;
> +
> +			resets = <&rstctrl 11>;
> +			reset-names = "pcm";
> +
> +			interrupt-parent = <&gic>;
> +			interrupts = <GIC_SHARED 10 IRQ_TYPE_LEVEL_HIGH>;
> +
> +			txdma-req = <6>;
> +			rxdma-req = <5>;
> +
> +			status = "disabled";
> +		};
> +
>  		systick: systick@500 {
>  			compatible = "ralink,mt7621-systick", "ralink,cevt-systick";
>  			reg = <0x500 0x10>;
> diff --git a/target/linux/ramips/modules.mk b/target/linux/ramips/modules.mk
> index b604110..5976a68 100644
> --- a/target/linux/ramips/modules.mk
> +++ b/target/linux/ramips/modules.mk
> @@ -119,20 +119,23 @@ define KernelPackage/sound-mt7620
>    DEPENDS:=@TARGET_ramips +kmod-sound-soc-core +kmod-regmap-i2c +kmod-dma-ralink @!TARGET_ramips_rt288x
>    KCONFIG:= \
>  	CONFIG_SND_RALINK_SOC_I2S \
> +	CONFIG_SND_RALINK_SOC_PCM \
>  	CONFIG_SND_SIMPLE_CARD \
>  	CONFIG_SND_SIMPLE_CARD_UTILS \
>  	CONFIG_SND_SOC_WM8960
>    FILES:= \
>  	$(LINUX_DIR)/sound/soc/ralink/snd-soc-ralink-i2s.ko \
> +	$(LINUX_DIR)/sound/soc/ralink/snd-soc-ralink-pcm.ko \
>  	$(LINUX_DIR)/sound/soc/generic/snd-soc-simple-card.ko \
>  	$(LINUX_DIR)/sound/soc/generic/snd-soc-simple-card-utils.ko \
>  	$(LINUX_DIR)/sound/soc/codecs/snd-soc-wm8960.ko
>    AUTOLOAD:=$(call AutoLoad,90,snd-soc-wm8960 snd-soc-ralink-i2s snd-soc-simple-card)
> +  AUTOLOAD:=$(call AutoLoad,90,snd-soc-wm8960 snd-soc-ralink-pcm snd-soc-simple-card)
>    $(call AddDepends/sound)
>  endef
>  
>  define KernelPackage/sound-mt7620/description
> - Alsa modules for ralink i2s controller.
> + Alsa modules for ralink i2s/pcm controller.
>  endef
>  
>  $(eval $(call KernelPackage,sound-mt7620))
> diff --git a/target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch b/target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch
> new file mode 100644
> index 0000000..57c65d1
> --- /dev/null
> +++ b/target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch
> @@ -0,0 +1,920 @@
> +--- a/sound/soc/ralink/Kconfig
> ++++ b/sound/soc/ralink/Kconfig
> +@@ -6,3 +6,12 @@ config SND_RALINK_SOC_I2S
> + 	help
> + 	  Say Y if you want to use I2S protocol and I2S codec on Ralink/MediaTek
> + 	  based boards.
> ++
> ++config SND_RALINK_SOC_PCM
> ++	depends on RALINK && SND_SOC && !SOC_RT288X
> ++	select SND_SOC_GENERIC_DMAENGINE_PCM
> ++	select REGMAP_MMIO
> ++	tristate "SoC Audio (PCM protocol) for Ralink SoC"
> ++	help
> ++	  Say Y if you want to use PCM protocol and PCM codec on Ralink/MediaTek
> ++	  based boards.
> +--- a/sound/soc/ralink/Makefile
> ++++ b/sound/soc/ralink/Makefile
> +@@ -2,5 +2,7 @@
> + # Ralink/MediaTek Platform Support
> + #
> + snd-soc-ralink-i2s-objs := ralink-i2s.o
> ++snd-soc-ralink-pcm-objs := ralink-pcm.o
> + 
> + obj-$(CONFIG_SND_RALINK_SOC_I2S) += snd-soc-ralink-i2s.o
> ++obj-$(CONFIG_SND_RALINK_SOC_PCM) += snd-soc-ralink-pcm.o
> +--- /dev/null
> ++++ b/sound/soc/ralink/ralink-pcm.c
> +@@ -0,0 +1,892 @@
> ++/*
> ++ *  This program is free software; you can redistribute it and/or modify it
> ++ *  under  the terms of the GNU General  Public License as published by the
> ++ *  Free Software Foundation;  either version 2 of the License, or (at your
> ++ *  option) any later version.
> ++ *
> ++ *  You should have received a copy of the GNU General Public License along
> ++ *  with this program; if not, write to the Free Software Foundation, Inc.,
> ++ *  675 Mass Ave, Cambridge, MA 02139, USA.
> ++ *
> ++ */
> ++
> ++#include <linux/module.h>
> ++#include <linux/platform_device.h>
> ++#include <linux/clk.h>
> ++#include <linux/regmap.h>
> ++#include <linux/reset.h>
> ++#include <linux/debugfs.h>
> ++#include <linux/of_device.h>
> ++#include <sound/pcm_params.h>
> ++#include <sound/dmaengine_pcm.h>
> ++
> ++#include <asm/mach-ralink/ralink_regs.h>
> ++
> ++#define DRV_NAME "ralink-pcm"
> ++
> ++#define PCM_REG_GLB_CFG			0x000
> ++#define PCM_REG_PCM_CFG			0x004
> ++#define PCM_REG_INT_STATUS		0x008
> ++#define PCM_REG_INT_EN			0x00C
> ++#define PCM_REG_CHA0_FF_STATUS		0x010
> ++#define PCM_REG_CHB0_FF_STATUS		0x014
> ++#define PCM_REG_CHA0_CFG		0x020
> ++#define PCM_REG_CHB0_CFG		0x024
> ++#define PCM_REG_FSYNC_CFG		0x030
> ++#define PCM_REG_CHA0_CFG2		0x034
> ++#define PCM_REG_CHB0_CFG2		0x038
> ++#define PCM_REG_IP_INFO			0x040
> ++#define PCM_REG_RSV_REG16		0x044
> ++#define PCM_REG_DIVCOMP_CFG		0x050
> ++#define PCM_REG_DIVINT_CFG		0x054
> ++#define PCM_REG_DIGDELAY_CFG		0x060
> ++#define PCM_REG_CH0_FIFO		0x080
> ++#define PCM_REG_CH1_FIFO		0x084
> ++#define PCM_REG_CH2_FIFO		0x088
> ++#define PCM_REG_CH3_FIFO		0x08C
> ++#define PCM_REG_CHA1_FF_STATUS		0x110
> ++#define PCM_REG_CHB1_FF_STATUS		0x114
> ++#define PCM_REG_CHA1_CFG		0x120
> ++#define PCM_REG_CHB1_CFG		0x124
> ++#define PCM_REG_CHA1_CFG2		0x134
> ++#define PCM_REG_CHB1_CFG2		0x138
> ++
> ++/* PCM_REG_GLB_CFG */
> ++#define PCM_REG_GLB_CFG_EN		BIT(31)
> ++#define PCM_REG_GLB_CFG_DMA_EN		BIT(30)
> ++#define PCM_REG_GLB_CFG_RX_THRES	20
> ++#define PCM_REG_GLB_CFG_TX_THRES	16
> ++#define PCM_REG_GLB_CFG_THRES_MASK	(7 << PCM_REG_GLB_CFG_RX_THRES) | \
> ++					(7 << PCM_REG_GLB_CFG_TX_THRES)
> ++#define PCM_REG_GLB_CFG_DFT_THRES	(4 << PCM_REG_GLB_CFG_RX_THRES) | \
> ++					(4 << PCM_REG_GLB_CFG_TX_THRES)
> ++#define PCM_REG_GLB_CFG_CH		0
> ++#define PCM_REG_GLB_CFG_CH_MASK		(0xF << PCM_REG_GLB_CFG_CH)
> ++#define PCM_REG_GLB_CFG_CH0		(BIT(0) << PCM_REG_GLB_CFG_CH)
> ++#define PCM_REG_GLB_CFG_CH1		(BIT(1) << PCM_REG_GLB_CFG_CH)
> ++#define PCM_REG_GLB_CFG_CH2		(BIT(2) << PCM_REG_GLB_CFG_CH)
> ++#define PCM_REG_GLB_CFG_CH3		(BIT(3) << PCM_REG_GLB_CFG_CH)
> ++
> ++/* PCM_REG_PCM_CFG */
> ++#define PCM_REG_PCM_CFG_CLKOUT_EN	BIT(30)
> ++#define PCM_REG_PCM_CFG_EXT_FSYNC	BIT(27)
> ++#define PCM_REG_PCM_CFG_FSYNC_POL	BIT(25)
> ++#define PCM_REG_PCM_CFG_DTX_TRI		BIT(24)
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_MASK	0x7
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_4	0x0 // FS = BCLK / 8 / 4
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_8	0x1 // FS = BCLK / 8 / 8
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_16	0x2 // FS = BCLK / 8 / 16
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_32	0x3 // FS = BCLK / 8 / 32
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_64	0x4 // FS = BCLK / 8 / 64
> ++#define PCM_REG_PCM_CFG_SLOT_MODE_128	0x5 // FS = BCLK / 8 / 128
> ++
> ++/* PCM_REG_INT_STATUS */
> ++#define PCM_REG_INT_TX_FAULT		BIT(7)
> ++#define PCM_REG_INT_TX_OVRUN		BIT(6)
> ++#define PCM_REG_INT_TX_UNRUN		BIT(5)
> ++#define PCM_REG_INT_TX_THRES		BIT(4)
> ++#define PCM_REG_INT_RX_FAULT		BIT(3)
> ++#define PCM_REG_INT_RX_OVRUN		BIT(2)
> ++#define PCM_REG_INT_RX_UNRUN		BIT(1)
> ++#define PCM_REG_INT_RX_THRES		BIT(0)
> ++#define PCM_REG_INT_TX_MASK		0xF0
> ++#define PCM_REG_INT_RX_MASK		0x0F
> ++
> ++/* PCM_REG_CHA0_CFG */
> ++#define PCM_REG_CHA0_CFG_TS_START	0
> ++#define PCM_REG_CHA0_CFG_TS_START_MASK	(0x3FF << PCM_REG_CHA0_CFG_TS_START)
> ++
> ++/* PCM_REG_FSYNC_CFG */
> ++#define PCM_REG_FSYNC_CFG_EN		BIT(31)
> ++#define PCM_REG_FSYNC_CFG_CAP_DT	BIT(30)
> ++#define PCM_REG_FSYNC_CFG_DRV_DT	BIT(29)
> ++#define PCM_REG_FSYNC_CFG_CAP_FSYNC	BIT(28)
> ++#define PCM_REG_FSYNC_CFG_DRV_FSYNC	BIT(27)
> ++#define PCM_REG_FSYNC_CFG_FSYNC_INTV	0
> ++#define PCM_REG_FSYNC_CFG_FSYNC_INTV_MASK \
> ++					(0x3F << PCM_REG_FSYNC_CFG_FSYNC_INTV)
> ++
> ++/* PCM_REG_DIVCOMP_CFG */
> ++#define PCM_REG_DIVCOMP_CFG_CLKEN	BIT(31)
> ++#define PCM_REG_DIVCOMP_CFG_MASK	0xFF
> ++
> ++/* PCM_REG_DIVINT_CFG */
> ++#define PCM_REG_DIVINT_CFG_MASK		0x3FF
> ++
> ++/* FIFO */
> ++#define RALINK_PCM_FIFO_SIZE		32
> ++
> ++#define RALINK_PCM_INT_EN		1
> ++
> ++struct ralink_pcm_stats {
> ++	u32 dmafault;
> ++	u32 overrun;
> ++	u32 underrun;
> ++	u32 belowthres;
> ++};
> ++
> ++struct ralink_pcm {
> ++	struct device *dev;
> ++	void __iomem *regs;
> ++	struct clk *clk;
> ++	struct regmap *regmap;
> ++	u32 flags;
> ++	unsigned int fmt;
> ++	u16 txdma_req;
> ++	u16 rxdma_req;
> ++
> ++	struct snd_dmaengine_dai_dma_data playback_dma_data;
> ++	struct snd_dmaengine_dai_dma_data capture_dma_data;
> ++
> ++	struct dentry *dbg_dir;
> ++	struct dentry *dbg_stats;
> ++	struct ralink_pcm_stats txstats;
> ++	struct ralink_pcm_stats rxstats;
> ++};
> ++
> ++#define PRINT_REG(reg) \
> ++		regmap_read(pcm->regmap, PCM_REG_##reg, &buf); \
> ++		printk(KERN_DEBUG "%3x: %08x " #reg "\n", PCM_REG_##reg, buf);
> ++
> ++static void ralink_pcm_dump_regs(struct ralink_pcm *pcm)
> ++{
> ++	u32 buf;
> ++
> ++	printk(KERN_DEBUG "mt7621 pcm regs:\n");
> ++	PRINT_REG(GLB_CFG);
> ++	PRINT_REG(PCM_CFG);
> ++	PRINT_REG(INT_STATUS);
> ++	PRINT_REG(INT_EN);
> ++	PRINT_REG(CHA0_FF_STATUS);
> ++	PRINT_REG(CHB0_FF_STATUS);
> ++	PRINT_REG(CHA0_CFG);
> ++	PRINT_REG(CHB0_CFG);
> ++	PRINT_REG(FSYNC_CFG);
> ++	PRINT_REG(CHA0_CFG2);
> ++	PRINT_REG(CHB0_CFG2);
> ++	PRINT_REG(IP_INFO);
> ++	PRINT_REG(RSV_REG16);
> ++	PRINT_REG(DIVCOMP_CFG);
> ++	PRINT_REG(DIVINT_CFG);
> ++	PRINT_REG(DIGDELAY_CFG);
> ++	PRINT_REG(CH0_FIFO);
> ++	PRINT_REG(CH1_FIFO);
> ++	PRINT_REG(CH2_FIFO);
> ++	PRINT_REG(CH3_FIFO);
> ++	PRINT_REG(CHA1_FF_STATUS);
> ++	PRINT_REG(CHB1_FF_STATUS);
> ++	PRINT_REG(CHA1_CFG);
> ++	PRINT_REG(CHB1_CFG);
> ++	PRINT_REG(CHA1_CFG2);
> ++	PRINT_REG(CHB1_CFG2);
> ++}
> ++
> ++static int ralink_pcm_set_sysclk(struct snd_soc_dai *dai,
> ++		int clk_id, unsigned int freq, int dir)
> ++{
> ++	return 0;
> ++}
> ++
> ++static int ralink_pcm_set_bclk(struct snd_soc_dai *dai, int width, int rate)
> ++{
> ++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
> ++	unsigned long clk;
> ++	unsigned long freqin = 294000000;
> ++	int divint, divcomp;
> ++
> ++	/* now use fixed PCM_REG_PCM_CFG_SLOT_MODE_8 */
> ++	if (width > 64)
> ++		return -EINVAL;
> ++	width = 64;
> ++
> ++	/* disable clock at slave mode */
> ++	if ((pcm->fmt & SND_SOC_DAIFMT_MASTER_MASK) ==
> ++			SND_SOC_DAIFMT_CBM_CFM) {
> ++		regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
> ++				PCM_REG_DIVCOMP_CFG_CLKEN, 0);
> ++		return 0;
> ++	}
> ++
> ++	/* FREQOUT = FREQIN * (1/2) * (1/(DIVINT + DIVCOMP/256)) */
> ++	clk = freqin / (2 * 1 * width);
> ++	divint = clk / rate;
> ++	divcomp = ((clk % rate) * 256) / rate;
> ++
> ++	if ((divint > PCM_REG_DIVINT_CFG_MASK) ||
> ++			(divcomp > PCM_REG_DIVCOMP_CFG_MASK))
> ++		return -EINVAL;
> ++
> ++	regmap_update_bits(pcm->regmap, PCM_REG_DIVINT_CFG,
> ++			PCM_REG_DIVINT_CFG_MASK,
> ++			divint);
> ++	regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
> ++			PCM_REG_DIVCOMP_CFG_MASK,
> ++			divcomp);
> ++
> ++	/* enable clock */
> ++	regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
> ++			PCM_REG_DIVCOMP_CFG_CLKEN,
> ++			PCM_REG_DIVCOMP_CFG_CLKEN);
> ++
> ++	dev_dbg(pcm->dev, "freqin: %lu, rate: %u, width: %u, int: %d, comp: %d\n",
> ++			freqin, rate, width, divint, divcomp);
> ++
> ++	return 0;
> ++}
> ++
> ++static int ralink_pcm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
> ++{
> ++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
> ++
> ++	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
> ++	case SND_SOC_DAIFMT_CBM_CFM:
> ++		regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
> ++				PCM_REG_PCM_CFG_CLKOUT_EN |
> ++				PCM_REG_PCM_CFG_EXT_FSYNC,
> ++				0 |
> ++				PCM_REG_PCM_CFG_EXT_FSYNC);
> ++		break;
> ++	case SND_SOC_DAIFMT_CBS_CFS:
> ++		regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
> ++				PCM_REG_PCM_CFG_CLKOUT_EN |
> ++				PCM_REG_PCM_CFG_EXT_FSYNC,
> ++				PCM_REG_PCM_CFG_CLKOUT_EN |
> ++				0);
> ++		break;
> ++	default:
> ++		return -EINVAL;
> ++	}
> ++
> ++	/* interface format */
> ++	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> ++	case SND_SOC_DAIFMT_DSP_A:
> ++		break;
> ++	default:
> ++		return -EINVAL;
> ++	}
> ++
> ++	/* clock inversion */
> ++	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
> ++	case SND_SOC_DAIFMT_IB_NF:
> ++		regmap_update_bits(pcm->regmap, PCM_REG_FSYNC_CFG,
> ++				PCM_REG_FSYNC_CFG_CAP_DT |
> ++				PCM_REG_FSYNC_CFG_DRV_DT |
> ++				PCM_REG_FSYNC_CFG_CAP_FSYNC |
> ++				PCM_REG_FSYNC_CFG_DRV_FSYNC,
> ++				0 |
> ++				PCM_REG_FSYNC_CFG_DRV_DT |
> ++				0 |
> ++				PCM_REG_FSYNC_CFG_DRV_FSYNC);
> ++		break;
> ++	default:
> ++		return -EINVAL;
> ++	}
> ++
> ++	pcm->fmt = fmt;
> ++
> ++	return 0;
> ++}
> ++
> ++static int ralink_pcm_startup(struct snd_pcm_substream *substream,
> ++		struct snd_soc_dai *dai)
> ++{
> ++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
> ++
> ++	if (dai->active)
> ++		return 0;
> ++
> ++	/* setup status interrupt */
> ++#if (RALINK_PCM_INT_EN)
> ++	regmap_write(pcm->regmap, PCM_REG_INT_EN, 0xff);
> ++#else
> ++	regmap_write(pcm->regmap, PCM_REG_INT_EN, 0x0);
> ++#endif
> ++
> ++	/* enable */
> ++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
> ++			PCM_REG_GLB_CFG_EN |
> ++			PCM_REG_GLB_CFG_CH_MASK,
> ++			PCM_REG_GLB_CFG_EN |
> ++			PCM_REG_GLB_CFG_CH0 | PCM_REG_GLB_CFG_CH1);
> ++
> ++	return 0;
> ++}
> ++
> ++static void ralink_pcm_shutdown(struct snd_pcm_substream *substream,
> ++		struct snd_soc_dai *dai)
> ++{
> ++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
> ++
> ++	/* If both streams are stopped, disable module and clock */
> ++	if (dai->active)
> ++		return;
> ++
> ++	/*
> ++	 * datasheet mention when disable all control regs are cleared
> ++	 * to initial values. need reinit at startup.
> ++	 */
> ++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
> ++			PCM_REG_GLB_CFG_EN |
> ++			PCM_REG_GLB_CFG_CH_MASK,
> ++			0 |
> ++			0);
> ++}
> ++
> ++static int ralink_pcm_hw_params(struct snd_pcm_substream *substream,
> ++		struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
> ++{
> ++	int width;
> ++	int ret;
> ++
> ++	width = params_width(params);
> ++	switch (width) {
> ++	case 16:
> ++		break;
> ++	default:
> ++		return -EINVAL;
> ++	}
> ++
> ++	switch (params_channels(params)) {
> ++	case 1:
> ++		break;
> ++	default:
> ++		return -EINVAL;
> ++	}
> ++
> ++	/* setup bclk rate */
> ++	ret = ralink_pcm_set_bclk(dai, width, params_rate(params));
> ++
> ++	return ret;
> ++}
> ++
> ++static int ralink_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
> ++		struct snd_soc_dai *dai)
> ++{
> ++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
> ++
> ++	switch (cmd) {
> ++	case SNDRV_PCM_TRIGGER_START:
> ++	case SNDRV_PCM_TRIGGER_RESUME:
> ++	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> ++		regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
> ++				PCM_REG_GLB_CFG_DMA_EN,
> ++				PCM_REG_GLB_CFG_DMA_EN);
> ++		break;
> ++	case SNDRV_PCM_TRIGGER_STOP:
> ++	case SNDRV_PCM_TRIGGER_SUSPEND:
> ++	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> ++		regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
> ++				PCM_REG_GLB_CFG_DMA_EN,
> ++				0);
> ++		break;
> ++	default:
> ++		return -EINVAL;
> ++	}
> ++
> ++	return 0;
> ++}
> ++
> ++static void ralink_pcm_init_dma_data(struct ralink_pcm *pcm,
> ++		struct resource *res)
> ++{
> ++	struct snd_dmaengine_dai_dma_data *dma_data;
> ++
> ++	/* channel 0 Playback */
> ++	dma_data = &pcm->playback_dma_data;
> ++	dma_data->addr = res->start + PCM_REG_CH0_FIFO;
> ++	dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> ++	dma_data->maxburst = 1;
> ++	dma_data->slave_id = pcm->txdma_req;
> ++
> ++	/* channel 1 Capture */
> ++	dma_data = &pcm->capture_dma_data;
> ++	dma_data->addr = res->start + PCM_REG_CH1_FIFO;
> ++	dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> ++	dma_data->maxburst = 1;
> ++	dma_data->slave_id = pcm->rxdma_req;
> ++}
> ++
> ++static int ralink_pcm_dai_probe(struct snd_soc_dai *dai)
> ++{
> ++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
> ++
> ++	snd_soc_dai_init_dma_data(dai, &pcm->playback_dma_data,
> ++			&pcm->capture_dma_data);
> ++
> ++	regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
> ++			PCM_REG_PCM_CFG_FSYNC_POL |
> ++			PCM_REG_PCM_CFG_DTX_TRI |
> ++			PCM_REG_PCM_CFG_SLOT_MODE_MASK,
> ++			PCM_REG_PCM_CFG_FSYNC_POL |
> ++			0 |
> ++			PCM_REG_PCM_CFG_SLOT_MODE_8);
> ++
> ++	regmap_update_bits(pcm->regmap, PCM_REG_CHA0_CFG,
> ++			PCM_REG_CHA0_CFG_TS_START_MASK,
> ++			1);
> ++
> ++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
> ++			PCM_REG_GLB_CFG_THRES_MASK,
> ++			PCM_REG_GLB_CFG_DFT_THRES);
> ++
> ++	regmap_update_bits(pcm->regmap, PCM_REG_FSYNC_CFG,
> ++			PCM_REG_FSYNC_CFG_EN |
> ++			PCM_REG_FSYNC_CFG_FSYNC_INTV_MASK,
> ++			PCM_REG_FSYNC_CFG_EN |
> ++			1);
> ++
> ++	return 0;
> ++}
> ++
> ++static int ralink_pcm_dai_remove(struct snd_soc_dai *dai)
> ++{
> ++	return 0;
> ++}
> ++
> ++static const struct snd_soc_dai_ops ralink_pcm_dai_ops = {
> ++	.set_sysclk = ralink_pcm_set_sysclk,
> ++	.set_fmt = ralink_pcm_set_fmt,
> ++	.startup = ralink_pcm_startup,
> ++	.shutdown = ralink_pcm_shutdown,
> ++	.hw_params = ralink_pcm_hw_params,
> ++	.trigger = ralink_pcm_trigger,
> ++};
> ++
> ++static struct snd_soc_dai_driver ralink_pcm_dai = {
> ++	.name = DRV_NAME,
> ++	.probe = ralink_pcm_dai_probe,
> ++	.remove = ralink_pcm_dai_remove,
> ++	.ops = &ralink_pcm_dai_ops,
> ++	.capture = {
> ++		.stream_name = "PCM Capture",
> ++		.channels_min = 1,
> ++		.channels_max = 1,
> ++		.rate_min = 5512,
> ++		.rate_max = 192000,
> ++		.rates = SNDRV_PCM_RATE_CONTINUOUS,
> ++		.formats = SNDRV_PCM_FMTBIT_S16_LE,
> ++	},
> ++	.playback = {
> ++		.stream_name = "PCM Playback",
> ++		.channels_min = 1,
> ++		.channels_max = 1,
> ++		.rate_min = 5512,
> ++		.rate_max = 192000,
> ++		.rates = SNDRV_PCM_RATE_CONTINUOUS,
> ++		.formats = SNDRV_PCM_FMTBIT_S16_LE,
> ++	},
> ++	.symmetric_rates = 1,
> ++};
> ++
> ++static struct snd_pcm_hardware ralink_pcm_hardware = {
> ++	.info = SNDRV_PCM_INFO_MMAP |
> ++		SNDRV_PCM_INFO_MMAP_VALID |
> ++		SNDRV_PCM_INFO_INTERLEAVED |
> ++		SNDRV_PCM_INFO_BLOCK_TRANSFER,
> ++	.formats = SNDRV_PCM_FMTBIT_S16_LE,
> ++	.channels_min		= 2,
> ++	.channels_max		= 2,
> ++	.period_bytes_min	= PAGE_SIZE,
> ++	.period_bytes_max	= PAGE_SIZE * 2,
> ++	.periods_min		= 2,
> ++	.periods_max		= 128,
> ++	.buffer_bytes_max	= 128 * 1024,
> ++	.fifo_size		= RALINK_PCM_FIFO_SIZE,
> ++};
> ++
> ++static const struct snd_dmaengine_pcm_config ralink_dmaengine_pcm_config = {
> ++	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
> ++	.pcm_hardware = &ralink_pcm_hardware,
> ++	.prealloc_buffer_size = 256 * PAGE_SIZE,
> ++};
> ++
> ++static const struct snd_soc_component_driver ralink_pcm_component = {
> ++	.name = DRV_NAME,
> ++};
> ++
> ++static bool ralink_pcm_writeable_reg(struct device *dev, unsigned int reg)
> ++{
> ++	return true;
> ++}
> ++
> ++static bool ralink_pcm_readable_reg(struct device *dev, unsigned int reg)
> ++{
> ++	return true;
> ++}
> ++
> ++static bool ralink_pcm_volatile_reg(struct device *dev, unsigned int reg)
> ++{
> ++	switch (reg) {
> ++	case PCM_REG_INT_STATUS:
> ++	case PCM_REG_CHA0_FF_STATUS:
> ++	case PCM_REG_CHB0_FF_STATUS:
> ++	case PCM_REG_CHA1_FF_STATUS:
> ++	case PCM_REG_CHB1_FF_STATUS:
> ++		return true;
> ++	}
> ++	return false;
> ++}
> ++
> ++static const struct regmap_config ralink_pcm_regmap_config = {
> ++	.reg_bits = 32,
> ++	.reg_stride = 4,
> ++	.val_bits = 32,
> ++	.writeable_reg = ralink_pcm_writeable_reg,
> ++	.readable_reg = ralink_pcm_readable_reg,
> ++	.volatile_reg = ralink_pcm_volatile_reg,
> ++	.max_register = PCM_REG_CHB1_CFG2,
> ++};
> ++
> ++#if (RALINK_PCM_INT_EN)
> ++static irqreturn_t ralink_pcm_irq(int irq, void *devid)
> ++{
> ++	struct ralink_pcm *pcm = devid;
> ++	u32 status;
> ++
> ++	regmap_read(pcm->regmap, PCM_REG_INT_STATUS, &status);
> ++	if (unlikely(!status))
> ++		return IRQ_NONE;
> ++
> ++	/* tx stats */
> ++	if (status & PCM_REG_INT_TX_MASK) {
> ++		if (status & PCM_REG_INT_TX_THRES)
> ++			pcm->txstats.belowthres++;
> ++		if (status & PCM_REG_INT_TX_UNRUN)
> ++			pcm->txstats.underrun++;
> ++		if (status & PCM_REG_INT_TX_OVRUN)
> ++			pcm->txstats.overrun++;
> ++		if (status & PCM_REG_INT_TX_FAULT)
> ++			pcm->txstats.dmafault++;
> ++	}
> ++
> ++	/* rx stats */
> ++	if (status & PCM_REG_INT_RX_MASK) {
> ++		if (status & PCM_REG_INT_RX_THRES)
> ++			pcm->rxstats.belowthres++;
> ++		if (status & PCM_REG_INT_RX_UNRUN)
> ++			pcm->rxstats.underrun++;
> ++		if (status & PCM_REG_INT_RX_OVRUN)
> ++			pcm->rxstats.overrun++;
> ++		if (status & PCM_REG_INT_RX_FAULT)
> ++			pcm->rxstats.dmafault++;
> ++	}
> ++
> ++	/* clean status bits */
> ++	regmap_write(pcm->regmap, PCM_REG_INT_STATUS, status);
> ++
> ++	return IRQ_HANDLED;
> ++}
> ++#endif
> ++
> ++#if IS_ENABLED(CONFIG_DEBUG_FS)
> ++static int ralink_pcm_stats_show(struct seq_file *s, void *unused)
> ++{
> ++	struct ralink_pcm *pcm = s->private;
> ++
> ++	seq_printf(s, "tx stats\n");
> ++	seq_printf(s, "\tbelow threshold\t%u\n", pcm->txstats.belowthres);
> ++	seq_printf(s, "\tunder run\t%u\n", pcm->txstats.underrun);
> ++	seq_printf(s, "\tover run\t%u\n", pcm->txstats.overrun);
> ++	seq_printf(s, "\tdma fault\t%u\n", pcm->txstats.dmafault);
> ++
> ++	seq_printf(s, "rx stats\n");
> ++	seq_printf(s, "\tbelow threshold\t%u\n", pcm->rxstats.belowthres);
> ++	seq_printf(s, "\tunder run\t%u\n", pcm->rxstats.underrun);
> ++	seq_printf(s, "\tover run\t%u\n", pcm->rxstats.overrun);
> ++	seq_printf(s, "\tdma fault\t%u\n", pcm->rxstats.dmafault);
> ++
> ++	ralink_pcm_dump_regs(pcm);
> ++
> ++	return 0;
> ++}
> ++
> ++static int ralink_pcm_stats_open(struct inode *inode, struct file *file)
> ++{
> ++	return single_open(file, ralink_pcm_stats_show, inode->i_private);
> ++}
> ++
> ++static const struct file_operations ralink_pcm_stats_ops = {
> ++	.open = ralink_pcm_stats_open,
> ++	.read = seq_read,
> ++	.llseek = seq_lseek,
> ++	.release = single_release,
> ++};
> ++
> ++static inline int ralink_pcm_debugfs_create(struct ralink_pcm *pcm)
> ++{
> ++	pcm->dbg_dir = debugfs_create_dir(dev_name(pcm->dev), NULL);
> ++	if (!pcm->dbg_dir)
> ++		return -ENOMEM;
> ++
> ++	pcm->dbg_stats = debugfs_create_file("stats", S_IRUGO,
> ++			pcm->dbg_dir, pcm, &ralink_pcm_stats_ops);
> ++	if (!pcm->dbg_stats) {
> ++		debugfs_remove(pcm->dbg_dir);
> ++		return -ENOMEM;
> ++	}
> ++
> ++	return 0;
> ++}
> ++
> ++static inline void ralink_pcm_debugfs_remove(struct ralink_pcm *pcm)
> ++{
> ++	debugfs_remove(pcm->dbg_stats);
> ++	debugfs_remove(pcm->dbg_dir);
> ++}
> ++#else
> ++static inline int ralink_pcm_debugfs_create(struct ralink_pcm *pcm)
> ++{
> ++	return 0;
> ++}
> ++
> ++static inline void ralink_pcm_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
> ++{
> ++}
> ++#endif
> ++
> ++#define RALINK_SYSCTL_BASE		0xBE000000
> ++#define RALINK_ANA_CTRL_BASE		0xBE000F00
> ++#define RALINK_REG_RD(addr)		(*(volatile u32 *)(addr))
> ++#define RALINK_REG_WR(addr, val)	(*(volatile u32 *)(addr) = (val))
> ++
> ++static void mt7621_refclk_setup(void)
> ++{
> ++	bool clk_20mhz = 0, clk_40mhz = 0;
> ++	u32 reg;
> ++
> ++	reg = RALINK_REG_RD(RALINK_SYSCTL_BASE + 0x10);
> ++	reg = (reg >> 6) & 0x7;
> ++	if (reg <= 2) {
> ++		/* 20MHz Xtal */
> ++		clk_20mhz = true;
> ++	} else if (reg >= 3 && reg <= 5) {
> ++		/* 40MHz Xtal */
> ++		clk_40mhz = true;
> ++	} else {
> ++		/* 25MHz Xtal */
> ++	}
> ++
> ++	/* reset required registers to default */
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008000);
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0014, 0x01401d61);
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0018, 0x38233d0e);
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, 0x80120004);
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0020, 0x1c7dbf48);
> ++
> ++	/* toggle RG_XPTL_CHG */
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008800);
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008c00);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x0014);
> ++	reg &= ~0x0000ffc0;
> ++	if (clk_20mhz || clk_40mhz) {
> ++		reg |= 0x1d << 8;
> ++	} else {
> ++		reg |= 0x17 << 8;
> ++	}
> ++	reg |= 0x1 << 6;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0014, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x0018);
> ++	reg &= ~0xf0773f00;
> ++	reg |= 0x3 << 28;
> ++	reg |= 0x2 << 20;
> ++	if (clk_20mhz || clk_40mhz) {
> ++		reg |= 0x3 << 16;
> ++	} else {
> ++		reg |= 0x2 << 16;
> ++	}
> ++	reg |= 0x3 << 12;
> ++	if (clk_20mhz || clk_40mhz) {
> ++		reg |= 0xd << 8;
> ++	} else {
> ++		reg |= 0x7 << 8;
> ++	}
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0018, reg);
> ++
> ++	if (clk_20mhz || clk_40mhz) {
> ++		reg = 0x1c7dbf48;
> ++	} else {
> ++		reg = 0x1697cc39;
> ++	}
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0020, reg);
> ++
> ++	/* Common setting - Set PLLGP_CTRL_4 */
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg &= ~(0x1 << 31);
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 0;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 3;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 8;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 6;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 5;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 7;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++
> ++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
> ++	reg |= 0x1 << 17;
> ++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
> ++}
> ++
> ++struct rt_pcm_data {
> ++	u32 flags;
> ++	void (*refclk_setup)(void);
> ++};
> ++
> ++static struct rt_pcm_data mt7621_pcm_data = {
> ++	.refclk_setup = mt7621_refclk_setup
> ++};
> ++
> ++static const struct of_device_id ralink_pcm_match_table[] = {
> ++	{ .compatible = "mediatek,mt7621-pcm",
> ++		.data = (void *)&mt7621_pcm_data },
> ++};
> ++MODULE_DEVICE_TABLE(of, ralink_pcm_match_table);
> ++
> ++static int ralink_pcm_probe(struct platform_device *pdev)
> ++{
> ++	const struct of_device_id *match;
> ++	struct device_node *np = pdev->dev.of_node;
> ++	struct ralink_pcm *pcm;
> ++	struct resource *res;
> ++	int irq, ret;
> ++	u32 dma_req;
> ++	struct rt_pcm_data *data;
> ++
> ++	pcm = devm_kzalloc(&pdev->dev, sizeof(*pcm), GFP_KERNEL);
> ++	if (!pcm)
> ++		return -ENOMEM;
> ++
> ++	platform_set_drvdata(pdev, pcm);
> ++	pcm->dev = &pdev->dev;
> ++
> ++	match = of_match_device(ralink_pcm_match_table, &pdev->dev);
> ++	if (!match)
> ++		return -EINVAL;
> ++	data = (struct rt_pcm_data *)match->data;
> ++	pcm->flags = data->flags;
> ++	if (data->refclk_setup)
> ++		data->refclk_setup();
> ++
> ++	if (of_property_read_u32(np, "txdma-req", &dma_req)) {
> ++		dev_err(&pdev->dev, "no txdma-req define\n");
> ++		return -EINVAL;
> ++	}
> ++	pcm->txdma_req = (u16)dma_req;
> ++	if (of_property_read_u32(np, "rxdma-req", &dma_req)) {
> ++		dev_err(&pdev->dev, "no rxdma-req define\n");
> ++		return -EINVAL;
> ++	}
> ++	pcm->rxdma_req = (u16)dma_req;
> ++
> ++	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> ++	pcm->regs = devm_ioremap_resource(&pdev->dev, res);
> ++	if (IS_ERR(pcm->regs))
> ++		return PTR_ERR(pcm->regs);
> ++
> ++	pcm->regmap = devm_regmap_init_mmio(&pdev->dev, pcm->regs,
> ++			&ralink_pcm_regmap_config);
> ++	if (IS_ERR(pcm->regmap)) {
> ++		dev_err(&pdev->dev, "regmap init failed\n");
> ++		return PTR_ERR(pcm->regmap);
> ++	}
> ++
> ++	irq = platform_get_irq(pdev, 0);
> ++	if (irq < 0) {
> ++		dev_err(&pdev->dev, "failed to get irq\n");
> ++		return -EINVAL;
> ++	}
> ++
> ++#if (RALINK_PCM_INT_EN)
> ++	ret = devm_request_irq(&pdev->dev, irq, ralink_pcm_irq,
> ++			0, dev_name(&pdev->dev), pcm);
> ++	if (ret) {
> ++		dev_err(&pdev->dev, "failed to request irq\n");
> ++		return ret;
> ++	}
> ++#endif
> ++
> ++	pcm->clk = devm_clk_get(&pdev->dev, NULL);
> ++	if (IS_ERR(pcm->clk)) {
> ++		dev_err(&pdev->dev, "no clock defined\n");
> ++		return PTR_ERR(pcm->clk);
> ++	}
> ++
> ++	ret = clk_prepare_enable(pcm->clk);
> ++	if (ret)
> ++		return ret;
> ++
> ++	ralink_pcm_init_dma_data(pcm, res);
> ++
> ++	device_reset(&pdev->dev);
> ++
> ++	ret = ralink_pcm_debugfs_create(pcm);
> ++	if (ret) {
> ++		dev_err(&pdev->dev, "create debugfs failed\n");
> ++		goto err_clk_disable;
> ++	}
> ++
> ++
> ++	ret = devm_snd_soc_register_component(&pdev->dev, &ralink_pcm_component,
> ++			&ralink_pcm_dai, 1);
> ++	if (ret)
> ++		goto err_debugfs;
> ++
> ++	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
> ++			&ralink_dmaengine_pcm_config,
> ++			SND_DMAENGINE_PCM_FLAG_COMPAT);
> ++	if (ret)
> ++		goto err_debugfs;
> ++
> ++	return 0;
> ++
> ++err_debugfs:
> ++	ralink_pcm_debugfs_remove(pcm);
> ++
> ++err_clk_disable:
> ++	clk_disable_unprepare(pcm->clk);
> ++
> ++	return ret;
> ++}
> ++
> ++static int ralink_pcm_remove(struct platform_device *pdev)
> ++{
> ++	struct ralink_pcm *pcm = platform_get_drvdata(pdev);
> ++
> ++	ralink_pcm_debugfs_remove(pcm);
> ++	clk_disable_unprepare(pcm->clk);
> ++
> ++	return 0;
> ++}
> ++
> ++static struct platform_driver ralink_pcm_driver = {
> ++	.probe = ralink_pcm_probe,
> ++	.remove = ralink_pcm_remove,
> ++	.driver = {
> ++		.name = DRV_NAME,
> ++		.of_match_table = ralink_pcm_match_table,
> ++	},
> ++};
> ++module_platform_driver(ralink_pcm_driver);
> ++
> ++MODULE_AUTHOR("Puyou Lu <puyou.lu@gmail.com>");
> ++MODULE_DESCRIPTION("Ralink/MediaTek PCM driver");
> ++MODULE_LICENSE("GPL");
> ++MODULE_ALIAS("platform:" DRV_NAME);
> -- 
> 2.7.4
> 
> 
> _______________________________________________
> openwrt-devel mailing list
> openwrt-devel@lists.openwrt.org
> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
Puyou Lu July 6, 2020, 8:05 a.m. UTC | #2
On Fri, Jul 3, 2020 at 10:09 PM Daniel Golle <daniel@makrotopia.org> wrote:
>
> Hi Puyou,
>
>
> On Fri, Jul 03, 2020 at 09:51:37PM +0800, puyou.lu@gmail.com wrote:
> > From: Puyou Lu <puyou.lu@gmail.com>
> >
> > Only tested on mt7621 currently.
> >
> > Signed-off-by: Puyou Lu <puyou.lu@gmail.com>
> > ---
> >  target/linux/ramips/dts/AP-MT7621A-V60.dts         |  13 +
>
> This file was renamed into mt7621_mediatek_ap-mt7621a-v60.dts.
> Please rebase your patch on top of the master branch of
> https://git.openwrt.org/openwrt/openwrt.git
>
> Cheers
>
> Daniel
>
>
Hi Daniel, thank you for the reply. Here I've rebased the patch on master
branch.

---
 target/linux/ramips/dts/mt7621.dtsi                |  18 +
 .../ramips/dts/mt7621_mediatek_ap-mt7621a-v60.dts  |  13 +
 target/linux/ramips/modules.mk                     |   6 +-
 .../0049-asoc-add-mt7620-pcm-support.patch         | 920 +++++++++++++++++++++
 4 files changed, 955 insertions(+), 2 deletions(-)
 create mode 100644 target/linux/ramips/patches-5.4/0049-asoc-add-mt7620-pcm-support.patch

diff --git a/target/linux/ramips/dts/mt7621.dtsi b/target/linux/ramips/dts/mt7621.dtsi
index 78979dc..9366393 100644
--- a/target/linux/ramips/dts/mt7621.dtsi
+++ b/target/linux/ramips/dts/mt7621.dtsi
@@ -119,6 +119,24 @@
 			status = "disabled";
 		};
 
+		pcm: pcm@2000 {
+			compatible = "mediatek,mt7621-pcm";
+			reg = <0x2000 0x800>;
+
+			clocks = <&sysclock>;
+
+			resets = <&rstctrl 11>;
+			reset-names = "pcm";
+
+			interrupt-parent = <&gic>;
+			interrupts = <GIC_SHARED 10 IRQ_TYPE_LEVEL_HIGH>;
+
+			txdma-req = <6>;
+			rxdma-req = <5>;
+
+			status = "disabled";
+		};
+
 		systick: systick@500 {
 			compatible = "ralink,mt7621-systick", "ralink,cevt-systick";
 			reg = <0x500 0x10>;
diff --git a/target/linux/ramips/dts/mt7621_mediatek_ap-mt7621a-v60.dts b/target/linux/ramips/dts/mt7621_mediatek_ap-mt7621a-v60.dts
index b202342..c1499ff 100644
--- a/target/linux/ramips/dts/mt7621_mediatek_ap-mt7621a-v60.dts
+++ b/target/linux/ramips/dts/mt7621_mediatek_ap-mt7621a-v60.dts
@@ -50,6 +50,12 @@
 			function = "i2s";
 		};
 	};
+	pcm_pins: pcm {
+		pcm {
+			groups = "uart2";
+			function = "pcm";
+		};
+	};
 };
 
 &i2c {
@@ -74,6 +80,13 @@
 	pinctrl-0 = <&i2s_pins>;
 };
 
+&pcm {
+	#sound-dai-cells = <0>;
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&pcm_pins>;
+};
+
 &spi0 {
 	status = "okay";
 
diff --git a/target/linux/ramips/modules.mk b/target/linux/ramips/modules.mk
index 6fd3b51..c31d7dc 100644
--- a/target/linux/ramips/modules.mk
+++ b/target/linux/ramips/modules.mk
@@ -121,20 +121,22 @@ define KernelPackage/sound-mt7620
   DEPENDS:=@TARGET_ramips +kmod-sound-soc-core +kmod-regmap-i2c +kmod-dma-ralink @!TARGET_ramips_rt288x
   KCONFIG:= \
 	CONFIG_SND_RALINK_SOC_I2S \
+	CONFIG_SND_RALINK_SOC_PCM \
 	CONFIG_SND_SIMPLE_CARD \
 	CONFIG_SND_SIMPLE_CARD_UTILS \
 	CONFIG_SND_SOC_WM8960
   FILES:= \
 	$(LINUX_DIR)/sound/soc/ralink/snd-soc-ralink-i2s.ko \
+	$(LINUX_DIR)/sound/soc/ralink/snd-soc-ralink-pcm.ko \
 	$(LINUX_DIR)/sound/soc/generic/snd-soc-simple-card.ko \
 	$(LINUX_DIR)/sound/soc/generic/snd-soc-simple-card-utils.ko \
 	$(LINUX_DIR)/sound/soc/codecs/snd-soc-wm8960.ko
-  AUTOLOAD:=$(call AutoLoad,90,snd-soc-wm8960 snd-soc-ralink-i2s snd-soc-simple-card)
+  AUTOLOAD:=$(call AutoLoad,90,snd-soc-wm8960 snd-soc-ralink-i2s snd-soc-ralink-pcm snd-soc-simple-card)
   $(call AddDepends/sound)
 endef
 
 define KernelPackage/sound-mt7620/description
- Alsa modules for ralink i2s controller.
+ Alsa modules for ralink i2s/pcm controller.
 endef
 
 $(eval $(call KernelPackage,sound-mt7620))
diff --git a/target/linux/ramips/patches-5.4/0049-asoc-add-mt7620-pcm-support.patch b/target/linux/ramips/patches-5.4/0049-asoc-add-mt7620-pcm-support.patch
new file mode 100644
index 0000000..57c65d1
--- /dev/null
+++ b/target/linux/ramips/patches-5.4/0049-asoc-add-mt7620-pcm-support.patch
@@ -0,0 +1,920 @@
+--- a/sound/soc/ralink/Kconfig
++++ b/sound/soc/ralink/Kconfig
+@@ -6,3 +6,12 @@ config SND_RALINK_SOC_I2S
+ 	help
+ 	  Say Y if you want to use I2S protocol and I2S codec on Ralink/MediaTek
+ 	  based boards.
++
++config SND_RALINK_SOC_PCM
++	depends on RALINK && SND_SOC && !SOC_RT288X
++	select SND_SOC_GENERIC_DMAENGINE_PCM
++	select REGMAP_MMIO
++	tristate "SoC Audio (PCM protocol) for Ralink SoC"
++	help
++	  Say Y if you want to use PCM protocol and PCM codec on Ralink/MediaTek
++	  based boards.
+--- a/sound/soc/ralink/Makefile
++++ b/sound/soc/ralink/Makefile
+@@ -2,5 +2,7 @@
+ # Ralink/MediaTek Platform Support
+ #
+ snd-soc-ralink-i2s-objs := ralink-i2s.o
++snd-soc-ralink-pcm-objs := ralink-pcm.o
+ 
+ obj-$(CONFIG_SND_RALINK_SOC_I2S) += snd-soc-ralink-i2s.o
++obj-$(CONFIG_SND_RALINK_SOC_PCM) += snd-soc-ralink-pcm.o
+--- /dev/null
++++ b/sound/soc/ralink/ralink-pcm.c
+@@ -0,0 +1,892 @@
++/*
++ *  This program is free software; you can redistribute it and/or modify it
++ *  under  the terms of the GNU General  Public License as published by the
++ *  Free Software Foundation;  either version 2 of the License, or (at your
++ *  option) any later version.
++ *
++ *  You should have received a copy of the GNU General Public License along
++ *  with this program; if not, write to the Free Software Foundation, Inc.,
++ *  675 Mass Ave, Cambridge, MA 02139, USA.
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/clk.h>
++#include <linux/regmap.h>
++#include <linux/reset.h>
++#include <linux/debugfs.h>
++#include <linux/of_device.h>
++#include <sound/pcm_params.h>
++#include <sound/dmaengine_pcm.h>
++
++#include <asm/mach-ralink/ralink_regs.h>
++
++#define DRV_NAME "ralink-pcm"
++
++#define PCM_REG_GLB_CFG			0x000
++#define PCM_REG_PCM_CFG			0x004
++#define PCM_REG_INT_STATUS		0x008
++#define PCM_REG_INT_EN			0x00C
++#define PCM_REG_CHA0_FF_STATUS		0x010
++#define PCM_REG_CHB0_FF_STATUS		0x014
++#define PCM_REG_CHA0_CFG		0x020
++#define PCM_REG_CHB0_CFG		0x024
++#define PCM_REG_FSYNC_CFG		0x030
++#define PCM_REG_CHA0_CFG2		0x034
++#define PCM_REG_CHB0_CFG2		0x038
++#define PCM_REG_IP_INFO			0x040
++#define PCM_REG_RSV_REG16		0x044
++#define PCM_REG_DIVCOMP_CFG		0x050
++#define PCM_REG_DIVINT_CFG		0x054
++#define PCM_REG_DIGDELAY_CFG		0x060
++#define PCM_REG_CH0_FIFO		0x080
++#define PCM_REG_CH1_FIFO		0x084
++#define PCM_REG_CH2_FIFO		0x088
++#define PCM_REG_CH3_FIFO		0x08C
++#define PCM_REG_CHA1_FF_STATUS		0x110
++#define PCM_REG_CHB1_FF_STATUS		0x114
++#define PCM_REG_CHA1_CFG		0x120
++#define PCM_REG_CHB1_CFG		0x124
++#define PCM_REG_CHA1_CFG2		0x134
++#define PCM_REG_CHB1_CFG2		0x138
++
++/* PCM_REG_GLB_CFG */
++#define PCM_REG_GLB_CFG_EN		BIT(31)
++#define PCM_REG_GLB_CFG_DMA_EN		BIT(30)
++#define PCM_REG_GLB_CFG_RX_THRES	20
++#define PCM_REG_GLB_CFG_TX_THRES	16
++#define PCM_REG_GLB_CFG_THRES_MASK	(7 << PCM_REG_GLB_CFG_RX_THRES) | \
++					(7 << PCM_REG_GLB_CFG_TX_THRES)
++#define PCM_REG_GLB_CFG_DFT_THRES	(4 << PCM_REG_GLB_CFG_RX_THRES) | \
++					(4 << PCM_REG_GLB_CFG_TX_THRES)
++#define PCM_REG_GLB_CFG_CH		0
++#define PCM_REG_GLB_CFG_CH_MASK		(0xF << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH0		(BIT(0) << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH1		(BIT(1) << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH2		(BIT(2) << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH3		(BIT(3) << PCM_REG_GLB_CFG_CH)
++
++/* PCM_REG_PCM_CFG */
++#define PCM_REG_PCM_CFG_CLKOUT_EN	BIT(30)
++#define PCM_REG_PCM_CFG_EXT_FSYNC	BIT(27)
++#define PCM_REG_PCM_CFG_FSYNC_POL	BIT(25)
++#define PCM_REG_PCM_CFG_DTX_TRI		BIT(24)
++#define PCM_REG_PCM_CFG_SLOT_MODE_MASK	0x7
++#define PCM_REG_PCM_CFG_SLOT_MODE_4	0x0 // FS = BCLK / 8 / 4
++#define PCM_REG_PCM_CFG_SLOT_MODE_8	0x1 // FS = BCLK / 8 / 8
++#define PCM_REG_PCM_CFG_SLOT_MODE_16	0x2 // FS = BCLK / 8 / 16
++#define PCM_REG_PCM_CFG_SLOT_MODE_32	0x3 // FS = BCLK / 8 / 32
++#define PCM_REG_PCM_CFG_SLOT_MODE_64	0x4 // FS = BCLK / 8 / 64
++#define PCM_REG_PCM_CFG_SLOT_MODE_128	0x5 // FS = BCLK / 8 / 128
++
++/* PCM_REG_INT_STATUS */
++#define PCM_REG_INT_TX_FAULT		BIT(7)
++#define PCM_REG_INT_TX_OVRUN		BIT(6)
++#define PCM_REG_INT_TX_UNRUN		BIT(5)
++#define PCM_REG_INT_TX_THRES		BIT(4)
++#define PCM_REG_INT_RX_FAULT		BIT(3)
++#define PCM_REG_INT_RX_OVRUN		BIT(2)
++#define PCM_REG_INT_RX_UNRUN		BIT(1)
++#define PCM_REG_INT_RX_THRES		BIT(0)
++#define PCM_REG_INT_TX_MASK		0xF0
++#define PCM_REG_INT_RX_MASK		0x0F
++
++/* PCM_REG_CHA0_CFG */
++#define PCM_REG_CHA0_CFG_TS_START	0
++#define PCM_REG_CHA0_CFG_TS_START_MASK	(0x3FF << PCM_REG_CHA0_CFG_TS_START)
++
++/* PCM_REG_FSYNC_CFG */
++#define PCM_REG_FSYNC_CFG_EN		BIT(31)
++#define PCM_REG_FSYNC_CFG_CAP_DT	BIT(30)
++#define PCM_REG_FSYNC_CFG_DRV_DT	BIT(29)
++#define PCM_REG_FSYNC_CFG_CAP_FSYNC	BIT(28)
++#define PCM_REG_FSYNC_CFG_DRV_FSYNC	BIT(27)
++#define PCM_REG_FSYNC_CFG_FSYNC_INTV	0
++#define PCM_REG_FSYNC_CFG_FSYNC_INTV_MASK \
++					(0x3F << PCM_REG_FSYNC_CFG_FSYNC_INTV)
++
++/* PCM_REG_DIVCOMP_CFG */
++#define PCM_REG_DIVCOMP_CFG_CLKEN	BIT(31)
++#define PCM_REG_DIVCOMP_CFG_MASK	0xFF
++
++/* PCM_REG_DIVINT_CFG */
++#define PCM_REG_DIVINT_CFG_MASK		0x3FF
++
++/* FIFO */
++#define RALINK_PCM_FIFO_SIZE		32
++
++#define RALINK_PCM_INT_EN		1
++
++struct ralink_pcm_stats {
++	u32 dmafault;
++	u32 overrun;
++	u32 underrun;
++	u32 belowthres;
++};
++
++struct ralink_pcm {
++	struct device *dev;
++	void __iomem *regs;
++	struct clk *clk;
++	struct regmap *regmap;
++	u32 flags;
++	unsigned int fmt;
++	u16 txdma_req;
++	u16 rxdma_req;
++
++	struct snd_dmaengine_dai_dma_data playback_dma_data;
++	struct snd_dmaengine_dai_dma_data capture_dma_data;
++
++	struct dentry *dbg_dir;
++	struct dentry *dbg_stats;
++	struct ralink_pcm_stats txstats;
++	struct ralink_pcm_stats rxstats;
++};
++
++#define PRINT_REG(reg) \
++		regmap_read(pcm->regmap, PCM_REG_##reg, &buf); \
++		printk(KERN_DEBUG "%3x: %08x " #reg "\n", PCM_REG_##reg, buf);
++
++static void ralink_pcm_dump_regs(struct ralink_pcm *pcm)
++{
++	u32 buf;
++
++	printk(KERN_DEBUG "mt7621 pcm regs:\n");
++	PRINT_REG(GLB_CFG);
++	PRINT_REG(PCM_CFG);
++	PRINT_REG(INT_STATUS);
++	PRINT_REG(INT_EN);
++	PRINT_REG(CHA0_FF_STATUS);
++	PRINT_REG(CHB0_FF_STATUS);
++	PRINT_REG(CHA0_CFG);
++	PRINT_REG(CHB0_CFG);
++	PRINT_REG(FSYNC_CFG);
++	PRINT_REG(CHA0_CFG2);
++	PRINT_REG(CHB0_CFG2);
++	PRINT_REG(IP_INFO);
++	PRINT_REG(RSV_REG16);
++	PRINT_REG(DIVCOMP_CFG);
++	PRINT_REG(DIVINT_CFG);
++	PRINT_REG(DIGDELAY_CFG);
++	PRINT_REG(CH0_FIFO);
++	PRINT_REG(CH1_FIFO);
++	PRINT_REG(CH2_FIFO);
++	PRINT_REG(CH3_FIFO);
++	PRINT_REG(CHA1_FF_STATUS);
++	PRINT_REG(CHB1_FF_STATUS);
++	PRINT_REG(CHA1_CFG);
++	PRINT_REG(CHB1_CFG);
++	PRINT_REG(CHA1_CFG2);
++	PRINT_REG(CHB1_CFG2);
++}
++
++static int ralink_pcm_set_sysclk(struct snd_soc_dai *dai,
++		int clk_id, unsigned int freq, int dir)
++{
++	return 0;
++}
++
++static int ralink_pcm_set_bclk(struct snd_soc_dai *dai, int width, int rate)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++	unsigned long clk;
++	unsigned long freqin = 294000000;
++	int divint, divcomp;
++
++	/* now use fixed PCM_REG_PCM_CFG_SLOT_MODE_8 */
++	if (width > 64)
++		return -EINVAL;
++	width = 64;
++
++	/* disable clock at slave mode */
++	if ((pcm->fmt & SND_SOC_DAIFMT_MASTER_MASK) ==
++			SND_SOC_DAIFMT_CBM_CFM) {
++		regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
++				PCM_REG_DIVCOMP_CFG_CLKEN, 0);
++		return 0;
++	}
++
++	/* FREQOUT = FREQIN * (1/2) * (1/(DIVINT + DIVCOMP/256)) */
++	clk = freqin / (2 * 1 * width);
++	divint = clk / rate;
++	divcomp = ((clk % rate) * 256) / rate;
++
++	if ((divint > PCM_REG_DIVINT_CFG_MASK) ||
++			(divcomp > PCM_REG_DIVCOMP_CFG_MASK))
++		return -EINVAL;
++
++	regmap_update_bits(pcm->regmap, PCM_REG_DIVINT_CFG,
++			PCM_REG_DIVINT_CFG_MASK,
++			divint);
++	regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
++			PCM_REG_DIVCOMP_CFG_MASK,
++			divcomp);
++
++	/* enable clock */
++	regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
++			PCM_REG_DIVCOMP_CFG_CLKEN,
++			PCM_REG_DIVCOMP_CFG_CLKEN);
++
++	dev_dbg(pcm->dev, "freqin: %lu, rate: %u, width: %u, int: %d, comp: %d\n",
++			freqin, rate, width, divint, divcomp);
++
++	return 0;
++}
++
++static int ralink_pcm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
++	case SND_SOC_DAIFMT_CBM_CFM:
++		regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
++				PCM_REG_PCM_CFG_CLKOUT_EN |
++				PCM_REG_PCM_CFG_EXT_FSYNC,
++				0 |
++				PCM_REG_PCM_CFG_EXT_FSYNC);
++		break;
++	case SND_SOC_DAIFMT_CBS_CFS:
++		regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
++				PCM_REG_PCM_CFG_CLKOUT_EN |
++				PCM_REG_PCM_CFG_EXT_FSYNC,
++				PCM_REG_PCM_CFG_CLKOUT_EN |
++				0);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	/* interface format */
++	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
++	case SND_SOC_DAIFMT_DSP_A:
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	/* clock inversion */
++	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
++	case SND_SOC_DAIFMT_IB_NF:
++		regmap_update_bits(pcm->regmap, PCM_REG_FSYNC_CFG,
++				PCM_REG_FSYNC_CFG_CAP_DT |
++				PCM_REG_FSYNC_CFG_DRV_DT |
++				PCM_REG_FSYNC_CFG_CAP_FSYNC |
++				PCM_REG_FSYNC_CFG_DRV_FSYNC,
++				0 |
++				PCM_REG_FSYNC_CFG_DRV_DT |
++				0 |
++				PCM_REG_FSYNC_CFG_DRV_FSYNC);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	pcm->fmt = fmt;
++
++	return 0;
++}
++
++static int ralink_pcm_startup(struct snd_pcm_substream *substream,
++		struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	if (dai->active)
++		return 0;
++
++	/* setup status interrupt */
++#if (RALINK_PCM_INT_EN)
++	regmap_write(pcm->regmap, PCM_REG_INT_EN, 0xff);
++#else
++	regmap_write(pcm->regmap, PCM_REG_INT_EN, 0x0);
++#endif
++
++	/* enable */
++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++			PCM_REG_GLB_CFG_EN |
++			PCM_REG_GLB_CFG_CH_MASK,
++			PCM_REG_GLB_CFG_EN |
++			PCM_REG_GLB_CFG_CH0 | PCM_REG_GLB_CFG_CH1);
++
++	return 0;
++}
++
++static void ralink_pcm_shutdown(struct snd_pcm_substream *substream,
++		struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	/* If both streams are stopped, disable module and clock */
++	if (dai->active)
++		return;
++
++	/*
++	 * datasheet mention when disable all control regs are cleared
++	 * to initial values. need reinit at startup.
++	 */
++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++			PCM_REG_GLB_CFG_EN |
++			PCM_REG_GLB_CFG_CH_MASK,
++			0 |
++			0);
++}
++
++static int ralink_pcm_hw_params(struct snd_pcm_substream *substream,
++		struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
++{
++	int width;
++	int ret;
++
++	width = params_width(params);
++	switch (width) {
++	case 16:
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	switch (params_channels(params)) {
++	case 1:
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	/* setup bclk rate */
++	ret = ralink_pcm_set_bclk(dai, width, params_rate(params));
++
++	return ret;
++}
++
++static int ralink_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
++		struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	switch (cmd) {
++	case SNDRV_PCM_TRIGGER_START:
++	case SNDRV_PCM_TRIGGER_RESUME:
++	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
++		regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++				PCM_REG_GLB_CFG_DMA_EN,
++				PCM_REG_GLB_CFG_DMA_EN);
++		break;
++	case SNDRV_PCM_TRIGGER_STOP:
++	case SNDRV_PCM_TRIGGER_SUSPEND:
++	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
++		regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++				PCM_REG_GLB_CFG_DMA_EN,
++				0);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static void ralink_pcm_init_dma_data(struct ralink_pcm *pcm,
++		struct resource *res)
++{
++	struct snd_dmaengine_dai_dma_data *dma_data;
++
++	/* channel 0 Playback */
++	dma_data = &pcm->playback_dma_data;
++	dma_data->addr = res->start + PCM_REG_CH0_FIFO;
++	dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
++	dma_data->maxburst = 1;
++	dma_data->slave_id = pcm->txdma_req;
++
++	/* channel 1 Capture */
++	dma_data = &pcm->capture_dma_data;
++	dma_data->addr = res->start + PCM_REG_CH1_FIFO;
++	dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
++	dma_data->maxburst = 1;
++	dma_data->slave_id = pcm->rxdma_req;
++}
++
++static int ralink_pcm_dai_probe(struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	snd_soc_dai_init_dma_data(dai, &pcm->playback_dma_data,
++			&pcm->capture_dma_data);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
++			PCM_REG_PCM_CFG_FSYNC_POL |
++			PCM_REG_PCM_CFG_DTX_TRI |
++			PCM_REG_PCM_CFG_SLOT_MODE_MASK,
++			PCM_REG_PCM_CFG_FSYNC_POL |
++			0 |
++			PCM_REG_PCM_CFG_SLOT_MODE_8);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_CHA0_CFG,
++			PCM_REG_CHA0_CFG_TS_START_MASK,
++			1);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++			PCM_REG_GLB_CFG_THRES_MASK,
++			PCM_REG_GLB_CFG_DFT_THRES);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_FSYNC_CFG,
++			PCM_REG_FSYNC_CFG_EN |
++			PCM_REG_FSYNC_CFG_FSYNC_INTV_MASK,
++			PCM_REG_FSYNC_CFG_EN |
++			1);
++
++	return 0;
++}
++
++static int ralink_pcm_dai_remove(struct snd_soc_dai *dai)
++{
++	return 0;
++}
++
++static const struct snd_soc_dai_ops ralink_pcm_dai_ops = {
++	.set_sysclk = ralink_pcm_set_sysclk,
++	.set_fmt = ralink_pcm_set_fmt,
++	.startup = ralink_pcm_startup,
++	.shutdown = ralink_pcm_shutdown,
++	.hw_params = ralink_pcm_hw_params,
++	.trigger = ralink_pcm_trigger,
++};
++
++static struct snd_soc_dai_driver ralink_pcm_dai = {
++	.name = DRV_NAME,
++	.probe = ralink_pcm_dai_probe,
++	.remove = ralink_pcm_dai_remove,
++	.ops = &ralink_pcm_dai_ops,
++	.capture = {
++		.stream_name = "PCM Capture",
++		.channels_min = 1,
++		.channels_max = 1,
++		.rate_min = 5512,
++		.rate_max = 192000,
++		.rates = SNDRV_PCM_RATE_CONTINUOUS,
++		.formats = SNDRV_PCM_FMTBIT_S16_LE,
++	},
++	.playback = {
++		.stream_name = "PCM Playback",
++		.channels_min = 1,
++		.channels_max = 1,
++		.rate_min = 5512,
++		.rate_max = 192000,
++		.rates = SNDRV_PCM_RATE_CONTINUOUS,
++		.formats = SNDRV_PCM_FMTBIT_S16_LE,
++	},
++	.symmetric_rates = 1,
++};
++
++static struct snd_pcm_hardware ralink_pcm_hardware = {
++	.info = SNDRV_PCM_INFO_MMAP |
++		SNDRV_PCM_INFO_MMAP_VALID |
++		SNDRV_PCM_INFO_INTERLEAVED |
++		SNDRV_PCM_INFO_BLOCK_TRANSFER,
++	.formats = SNDRV_PCM_FMTBIT_S16_LE,
++	.channels_min		= 2,
++	.channels_max		= 2,
++	.period_bytes_min	= PAGE_SIZE,
++	.period_bytes_max	= PAGE_SIZE * 2,
++	.periods_min		= 2,
++	.periods_max		= 128,
++	.buffer_bytes_max	= 128 * 1024,
++	.fifo_size		= RALINK_PCM_FIFO_SIZE,
++};
++
++static const struct snd_dmaengine_pcm_config ralink_dmaengine_pcm_config = {
++	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
++	.pcm_hardware = &ralink_pcm_hardware,
++	.prealloc_buffer_size = 256 * PAGE_SIZE,
++};
++
++static const struct snd_soc_component_driver ralink_pcm_component = {
++	.name = DRV_NAME,
++};
++
++static bool ralink_pcm_writeable_reg(struct device *dev, unsigned int reg)
++{
++	return true;
++}
++
++static bool ralink_pcm_readable_reg(struct device *dev, unsigned int reg)
++{
++	return true;
++}
++
++static bool ralink_pcm_volatile_reg(struct device *dev, unsigned int reg)
++{
++	switch (reg) {
++	case PCM_REG_INT_STATUS:
++	case PCM_REG_CHA0_FF_STATUS:
++	case PCM_REG_CHB0_FF_STATUS:
++	case PCM_REG_CHA1_FF_STATUS:
++	case PCM_REG_CHB1_FF_STATUS:
++		return true;
++	}
++	return false;
++}
++
++static const struct regmap_config ralink_pcm_regmap_config = {
++	.reg_bits = 32,
++	.reg_stride = 4,
++	.val_bits = 32,
++	.writeable_reg = ralink_pcm_writeable_reg,
++	.readable_reg = ralink_pcm_readable_reg,
++	.volatile_reg = ralink_pcm_volatile_reg,
++	.max_register = PCM_REG_CHB1_CFG2,
++};
++
++#if (RALINK_PCM_INT_EN)
++static irqreturn_t ralink_pcm_irq(int irq, void *devid)
++{
++	struct ralink_pcm *pcm = devid;
++	u32 status;
++
++	regmap_read(pcm->regmap, PCM_REG_INT_STATUS, &status);
++	if (unlikely(!status))
++		return IRQ_NONE;
++
++	/* tx stats */
++	if (status & PCM_REG_INT_TX_MASK) {
++		if (status & PCM_REG_INT_TX_THRES)
++			pcm->txstats.belowthres++;
++		if (status & PCM_REG_INT_TX_UNRUN)
++			pcm->txstats.underrun++;
++		if (status & PCM_REG_INT_TX_OVRUN)
++			pcm->txstats.overrun++;
++		if (status & PCM_REG_INT_TX_FAULT)
++			pcm->txstats.dmafault++;
++	}
++
++	/* rx stats */
++	if (status & PCM_REG_INT_RX_MASK) {
++		if (status & PCM_REG_INT_RX_THRES)
++			pcm->rxstats.belowthres++;
++		if (status & PCM_REG_INT_RX_UNRUN)
++			pcm->rxstats.underrun++;
++		if (status & PCM_REG_INT_RX_OVRUN)
++			pcm->rxstats.overrun++;
++		if (status & PCM_REG_INT_RX_FAULT)
++			pcm->rxstats.dmafault++;
++	}
++
++	/* clean status bits */
++	regmap_write(pcm->regmap, PCM_REG_INT_STATUS, status);
++
++	return IRQ_HANDLED;
++}
++#endif
++
++#if IS_ENABLED(CONFIG_DEBUG_FS)
++static int ralink_pcm_stats_show(struct seq_file *s, void *unused)
++{
++	struct ralink_pcm *pcm = s->private;
++
++	seq_printf(s, "tx stats\n");
++	seq_printf(s, "\tbelow threshold\t%u\n", pcm->txstats.belowthres);
++	seq_printf(s, "\tunder run\t%u\n", pcm->txstats.underrun);
++	seq_printf(s, "\tover run\t%u\n", pcm->txstats.overrun);
++	seq_printf(s, "\tdma fault\t%u\n", pcm->txstats.dmafault);
++
++	seq_printf(s, "rx stats\n");
++	seq_printf(s, "\tbelow threshold\t%u\n", pcm->rxstats.belowthres);
++	seq_printf(s, "\tunder run\t%u\n", pcm->rxstats.underrun);
++	seq_printf(s, "\tover run\t%u\n", pcm->rxstats.overrun);
++	seq_printf(s, "\tdma fault\t%u\n", pcm->rxstats.dmafault);
++
++	ralink_pcm_dump_regs(pcm);
++
++	return 0;
++}
++
++static int ralink_pcm_stats_open(struct inode *inode, struct file *file)
++{
++	return single_open(file, ralink_pcm_stats_show, inode->i_private);
++}
++
++static const struct file_operations ralink_pcm_stats_ops = {
++	.open = ralink_pcm_stats_open,
++	.read = seq_read,
++	.llseek = seq_lseek,
++	.release = single_release,
++};
++
++static inline int ralink_pcm_debugfs_create(struct ralink_pcm *pcm)
++{
++	pcm->dbg_dir = debugfs_create_dir(dev_name(pcm->dev), NULL);
++	if (!pcm->dbg_dir)
++		return -ENOMEM;
++
++	pcm->dbg_stats = debugfs_create_file("stats", S_IRUGO,
++			pcm->dbg_dir, pcm, &ralink_pcm_stats_ops);
++	if (!pcm->dbg_stats) {
++		debugfs_remove(pcm->dbg_dir);
++		return -ENOMEM;
++	}
++
++	return 0;
++}
++
++static inline void ralink_pcm_debugfs_remove(struct ralink_pcm *pcm)
++{
++	debugfs_remove(pcm->dbg_stats);
++	debugfs_remove(pcm->dbg_dir);
++}
++#else
++static inline int ralink_pcm_debugfs_create(struct ralink_pcm *pcm)
++{
++	return 0;
++}
++
++static inline void ralink_pcm_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
++{
++}
++#endif
++
++#define RALINK_SYSCTL_BASE		0xBE000000
++#define RALINK_ANA_CTRL_BASE		0xBE000F00
++#define RALINK_REG_RD(addr)		(*(volatile u32 *)(addr))
++#define RALINK_REG_WR(addr, val)	(*(volatile u32 *)(addr) = (val))
++
++static void mt7621_refclk_setup(void)
++{
++	bool clk_20mhz = 0, clk_40mhz = 0;
++	u32 reg;
++
++	reg = RALINK_REG_RD(RALINK_SYSCTL_BASE + 0x10);
++	reg = (reg >> 6) & 0x7;
++	if (reg <= 2) {
++		/* 20MHz Xtal */
++		clk_20mhz = true;
++	} else if (reg >= 3 && reg <= 5) {
++		/* 40MHz Xtal */
++		clk_40mhz = true;
++	} else {
++		/* 25MHz Xtal */
++	}
++
++	/* reset required registers to default */
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008000);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0014, 0x01401d61);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0018, 0x38233d0e);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, 0x80120004);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0020, 0x1c7dbf48);
++
++	/* toggle RG_XPTL_CHG */
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008800);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008c00);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x0014);
++	reg &= ~0x0000ffc0;
++	if (clk_20mhz || clk_40mhz) {
++		reg |= 0x1d << 8;
++	} else {
++		reg |= 0x17 << 8;
++	}
++	reg |= 0x1 << 6;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0014, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x0018);
++	reg &= ~0xf0773f00;
++	reg |= 0x3 << 28;
++	reg |= 0x2 << 20;
++	if (clk_20mhz || clk_40mhz) {
++		reg |= 0x3 << 16;
++	} else {
++		reg |= 0x2 << 16;
++	}
++	reg |= 0x3 << 12;
++	if (clk_20mhz || clk_40mhz) {
++		reg |= 0xd << 8;
++	} else {
++		reg |= 0x7 << 8;
++	}
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0018, reg);
++
++	if (clk_20mhz || clk_40mhz) {
++		reg = 0x1c7dbf48;
++	} else {
++		reg = 0x1697cc39;
++	}
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0020, reg);
++
++	/* Common setting - Set PLLGP_CTRL_4 */
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg &= ~(0x1 << 31);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 0;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 3;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 8;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 6;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 5;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 7;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 17;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++}
++
++struct rt_pcm_data {
++	u32 flags;
++	void (*refclk_setup)(void);
++};
++
++static struct rt_pcm_data mt7621_pcm_data = {
++	.refclk_setup = mt7621_refclk_setup
++};
++
++static const struct of_device_id ralink_pcm_match_table[] = {
++	{ .compatible = "mediatek,mt7621-pcm",
++		.data = (void *)&mt7621_pcm_data },
++};
++MODULE_DEVICE_TABLE(of, ralink_pcm_match_table);
++
++static int ralink_pcm_probe(struct platform_device *pdev)
++{
++	const struct of_device_id *match;
++	struct device_node *np = pdev->dev.of_node;
++	struct ralink_pcm *pcm;
++	struct resource *res;
++	int irq, ret;
++	u32 dma_req;
++	struct rt_pcm_data *data;
++
++	pcm = devm_kzalloc(&pdev->dev, sizeof(*pcm), GFP_KERNEL);
++	if (!pcm)
++		return -ENOMEM;
++
++	platform_set_drvdata(pdev, pcm);
++	pcm->dev = &pdev->dev;
++
++	match = of_match_device(ralink_pcm_match_table, &pdev->dev);
++	if (!match)
++		return -EINVAL;
++	data = (struct rt_pcm_data *)match->data;
++	pcm->flags = data->flags;
++	if (data->refclk_setup)
++		data->refclk_setup();
++
++	if (of_property_read_u32(np, "txdma-req", &dma_req)) {
++		dev_err(&pdev->dev, "no txdma-req define\n");
++		return -EINVAL;
++	}
++	pcm->txdma_req = (u16)dma_req;
++	if (of_property_read_u32(np, "rxdma-req", &dma_req)) {
++		dev_err(&pdev->dev, "no rxdma-req define\n");
++		return -EINVAL;
++	}
++	pcm->rxdma_req = (u16)dma_req;
++
++	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++	pcm->regs = devm_ioremap_resource(&pdev->dev, res);
++	if (IS_ERR(pcm->regs))
++		return PTR_ERR(pcm->regs);
++
++	pcm->regmap = devm_regmap_init_mmio(&pdev->dev, pcm->regs,
++			&ralink_pcm_regmap_config);
++	if (IS_ERR(pcm->regmap)) {
++		dev_err(&pdev->dev, "regmap init failed\n");
++		return PTR_ERR(pcm->regmap);
++	}
++
++	irq = platform_get_irq(pdev, 0);
++	if (irq < 0) {
++		dev_err(&pdev->dev, "failed to get irq\n");
++		return -EINVAL;
++	}
++
++#if (RALINK_PCM_INT_EN)
++	ret = devm_request_irq(&pdev->dev, irq, ralink_pcm_irq,
++			0, dev_name(&pdev->dev), pcm);
++	if (ret) {
++		dev_err(&pdev->dev, "failed to request irq\n");
++		return ret;
++	}
++#endif
++
++	pcm->clk = devm_clk_get(&pdev->dev, NULL);
++	if (IS_ERR(pcm->clk)) {
++		dev_err(&pdev->dev, "no clock defined\n");
++		return PTR_ERR(pcm->clk);
++	}
++
++	ret = clk_prepare_enable(pcm->clk);
++	if (ret)
++		return ret;
++
++	ralink_pcm_init_dma_data(pcm, res);
++
++	device_reset(&pdev->dev);
++
++	ret = ralink_pcm_debugfs_create(pcm);
++	if (ret) {
++		dev_err(&pdev->dev, "create debugfs failed\n");
++		goto err_clk_disable;
++	}
++
++
++	ret = devm_snd_soc_register_component(&pdev->dev, &ralink_pcm_component,
++			&ralink_pcm_dai, 1);
++	if (ret)
++		goto err_debugfs;
++
++	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
++			&ralink_dmaengine_pcm_config,
++			SND_DMAENGINE_PCM_FLAG_COMPAT);
++	if (ret)
++		goto err_debugfs;
++
++	return 0;
++
++err_debugfs:
++	ralink_pcm_debugfs_remove(pcm);
++
++err_clk_disable:
++	clk_disable_unprepare(pcm->clk);
++
++	return ret;
++}
++
++static int ralink_pcm_remove(struct platform_device *pdev)
++{
++	struct ralink_pcm *pcm = platform_get_drvdata(pdev);
++
++	ralink_pcm_debugfs_remove(pcm);
++	clk_disable_unprepare(pcm->clk);
++
++	return 0;
++}
++
++static struct platform_driver ralink_pcm_driver = {
++	.probe = ralink_pcm_probe,
++	.remove = ralink_pcm_remove,
++	.driver = {
++		.name = DRV_NAME,
++		.of_match_table = ralink_pcm_match_table,
++	},
++};
++module_platform_driver(ralink_pcm_driver);
++
++MODULE_AUTHOR("Puyou Lu <puyou.lu@gmail.com>");
++MODULE_DESCRIPTION("Ralink/MediaTek PCM driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:" DRV_NAME);
diff mbox series

Patch

diff --git a/target/linux/ramips/dts/AP-MT7621A-V60.dts b/target/linux/ramips/dts/AP-MT7621A-V60.dts
index b613c9c..c4db011 100644
--- a/target/linux/ramips/dts/AP-MT7621A-V60.dts
+++ b/target/linux/ramips/dts/AP-MT7621A-V60.dts
@@ -54,6 +54,12 @@ 
 			ralink,function = "i2s";
 		};
 	};
+	pcm_pins: pcm {
+		pcm {
+			ralink,group = "uart2";
+			ralink,function = "pcm";
+		};
+	};
 };
 
 &i2c {
@@ -78,6 +84,13 @@ 
 	pinctrl-0 = <&i2s_pins>;
 };
 
+&pcm {
+	#sound-dai-cells = <0>;
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&pcm_pins>;
+};
+
 &spi0 {
 	status = "okay";
 
diff --git a/target/linux/ramips/dts/mt7621.dtsi b/target/linux/ramips/dts/mt7621.dtsi
index 4f69e09..759a928 100644
--- a/target/linux/ramips/dts/mt7621.dtsi
+++ b/target/linux/ramips/dts/mt7621.dtsi
@@ -141,6 +141,24 @@ 
 			status = "disabled";
 		};
 
+		pcm: pcm@2000 {
+			compatible = "mediatek,mt7621-pcm";
+			reg = <0x2000 0x800>;
+
+			clocks = <&sysclock>;
+
+			resets = <&rstctrl 11>;
+			reset-names = "pcm";
+
+			interrupt-parent = <&gic>;
+			interrupts = <GIC_SHARED 10 IRQ_TYPE_LEVEL_HIGH>;
+
+			txdma-req = <6>;
+			rxdma-req = <5>;
+
+			status = "disabled";
+		};
+
 		systick: systick@500 {
 			compatible = "ralink,mt7621-systick", "ralink,cevt-systick";
 			reg = <0x500 0x10>;
diff --git a/target/linux/ramips/modules.mk b/target/linux/ramips/modules.mk
index b604110..5976a68 100644
--- a/target/linux/ramips/modules.mk
+++ b/target/linux/ramips/modules.mk
@@ -119,20 +119,23 @@  define KernelPackage/sound-mt7620
   DEPENDS:=@TARGET_ramips +kmod-sound-soc-core +kmod-regmap-i2c +kmod-dma-ralink @!TARGET_ramips_rt288x
   KCONFIG:= \
 	CONFIG_SND_RALINK_SOC_I2S \
+	CONFIG_SND_RALINK_SOC_PCM \
 	CONFIG_SND_SIMPLE_CARD \
 	CONFIG_SND_SIMPLE_CARD_UTILS \
 	CONFIG_SND_SOC_WM8960
   FILES:= \
 	$(LINUX_DIR)/sound/soc/ralink/snd-soc-ralink-i2s.ko \
+	$(LINUX_DIR)/sound/soc/ralink/snd-soc-ralink-pcm.ko \
 	$(LINUX_DIR)/sound/soc/generic/snd-soc-simple-card.ko \
 	$(LINUX_DIR)/sound/soc/generic/snd-soc-simple-card-utils.ko \
 	$(LINUX_DIR)/sound/soc/codecs/snd-soc-wm8960.ko
   AUTOLOAD:=$(call AutoLoad,90,snd-soc-wm8960 snd-soc-ralink-i2s snd-soc-simple-card)
+  AUTOLOAD:=$(call AutoLoad,90,snd-soc-wm8960 snd-soc-ralink-pcm snd-soc-simple-card)
   $(call AddDepends/sound)
 endef
 
 define KernelPackage/sound-mt7620/description
- Alsa modules for ralink i2s controller.
+ Alsa modules for ralink i2s/pcm controller.
 endef
 
 $(eval $(call KernelPackage,sound-mt7620))
diff --git a/target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch b/target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch
new file mode 100644
index 0000000..57c65d1
--- /dev/null
+++ b/target/linux/ramips/patches-4.14/0049-asoc-add-mt7620-pcm-support.patch
@@ -0,0 +1,920 @@ 
+--- a/sound/soc/ralink/Kconfig
++++ b/sound/soc/ralink/Kconfig
+@@ -6,3 +6,12 @@ config SND_RALINK_SOC_I2S
+ 	help
+ 	  Say Y if you want to use I2S protocol and I2S codec on Ralink/MediaTek
+ 	  based boards.
++
++config SND_RALINK_SOC_PCM
++	depends on RALINK && SND_SOC && !SOC_RT288X
++	select SND_SOC_GENERIC_DMAENGINE_PCM
++	select REGMAP_MMIO
++	tristate "SoC Audio (PCM protocol) for Ralink SoC"
++	help
++	  Say Y if you want to use PCM protocol and PCM codec on Ralink/MediaTek
++	  based boards.
+--- a/sound/soc/ralink/Makefile
++++ b/sound/soc/ralink/Makefile
+@@ -2,5 +2,7 @@
+ # Ralink/MediaTek Platform Support
+ #
+ snd-soc-ralink-i2s-objs := ralink-i2s.o
++snd-soc-ralink-pcm-objs := ralink-pcm.o
+ 
+ obj-$(CONFIG_SND_RALINK_SOC_I2S) += snd-soc-ralink-i2s.o
++obj-$(CONFIG_SND_RALINK_SOC_PCM) += snd-soc-ralink-pcm.o
+--- /dev/null
++++ b/sound/soc/ralink/ralink-pcm.c
+@@ -0,0 +1,892 @@
++/*
++ *  This program is free software; you can redistribute it and/or modify it
++ *  under  the terms of the GNU General  Public License as published by the
++ *  Free Software Foundation;  either version 2 of the License, or (at your
++ *  option) any later version.
++ *
++ *  You should have received a copy of the GNU General Public License along
++ *  with this program; if not, write to the Free Software Foundation, Inc.,
++ *  675 Mass Ave, Cambridge, MA 02139, USA.
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/clk.h>
++#include <linux/regmap.h>
++#include <linux/reset.h>
++#include <linux/debugfs.h>
++#include <linux/of_device.h>
++#include <sound/pcm_params.h>
++#include <sound/dmaengine_pcm.h>
++
++#include <asm/mach-ralink/ralink_regs.h>
++
++#define DRV_NAME "ralink-pcm"
++
++#define PCM_REG_GLB_CFG			0x000
++#define PCM_REG_PCM_CFG			0x004
++#define PCM_REG_INT_STATUS		0x008
++#define PCM_REG_INT_EN			0x00C
++#define PCM_REG_CHA0_FF_STATUS		0x010
++#define PCM_REG_CHB0_FF_STATUS		0x014
++#define PCM_REG_CHA0_CFG		0x020
++#define PCM_REG_CHB0_CFG		0x024
++#define PCM_REG_FSYNC_CFG		0x030
++#define PCM_REG_CHA0_CFG2		0x034
++#define PCM_REG_CHB0_CFG2		0x038
++#define PCM_REG_IP_INFO			0x040
++#define PCM_REG_RSV_REG16		0x044
++#define PCM_REG_DIVCOMP_CFG		0x050
++#define PCM_REG_DIVINT_CFG		0x054
++#define PCM_REG_DIGDELAY_CFG		0x060
++#define PCM_REG_CH0_FIFO		0x080
++#define PCM_REG_CH1_FIFO		0x084
++#define PCM_REG_CH2_FIFO		0x088
++#define PCM_REG_CH3_FIFO		0x08C
++#define PCM_REG_CHA1_FF_STATUS		0x110
++#define PCM_REG_CHB1_FF_STATUS		0x114
++#define PCM_REG_CHA1_CFG		0x120
++#define PCM_REG_CHB1_CFG		0x124
++#define PCM_REG_CHA1_CFG2		0x134
++#define PCM_REG_CHB1_CFG2		0x138
++
++/* PCM_REG_GLB_CFG */
++#define PCM_REG_GLB_CFG_EN		BIT(31)
++#define PCM_REG_GLB_CFG_DMA_EN		BIT(30)
++#define PCM_REG_GLB_CFG_RX_THRES	20
++#define PCM_REG_GLB_CFG_TX_THRES	16
++#define PCM_REG_GLB_CFG_THRES_MASK	(7 << PCM_REG_GLB_CFG_RX_THRES) | \
++					(7 << PCM_REG_GLB_CFG_TX_THRES)
++#define PCM_REG_GLB_CFG_DFT_THRES	(4 << PCM_REG_GLB_CFG_RX_THRES) | \
++					(4 << PCM_REG_GLB_CFG_TX_THRES)
++#define PCM_REG_GLB_CFG_CH		0
++#define PCM_REG_GLB_CFG_CH_MASK		(0xF << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH0		(BIT(0) << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH1		(BIT(1) << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH2		(BIT(2) << PCM_REG_GLB_CFG_CH)
++#define PCM_REG_GLB_CFG_CH3		(BIT(3) << PCM_REG_GLB_CFG_CH)
++
++/* PCM_REG_PCM_CFG */
++#define PCM_REG_PCM_CFG_CLKOUT_EN	BIT(30)
++#define PCM_REG_PCM_CFG_EXT_FSYNC	BIT(27)
++#define PCM_REG_PCM_CFG_FSYNC_POL	BIT(25)
++#define PCM_REG_PCM_CFG_DTX_TRI		BIT(24)
++#define PCM_REG_PCM_CFG_SLOT_MODE_MASK	0x7
++#define PCM_REG_PCM_CFG_SLOT_MODE_4	0x0 // FS = BCLK / 8 / 4
++#define PCM_REG_PCM_CFG_SLOT_MODE_8	0x1 // FS = BCLK / 8 / 8
++#define PCM_REG_PCM_CFG_SLOT_MODE_16	0x2 // FS = BCLK / 8 / 16
++#define PCM_REG_PCM_CFG_SLOT_MODE_32	0x3 // FS = BCLK / 8 / 32
++#define PCM_REG_PCM_CFG_SLOT_MODE_64	0x4 // FS = BCLK / 8 / 64
++#define PCM_REG_PCM_CFG_SLOT_MODE_128	0x5 // FS = BCLK / 8 / 128
++
++/* PCM_REG_INT_STATUS */
++#define PCM_REG_INT_TX_FAULT		BIT(7)
++#define PCM_REG_INT_TX_OVRUN		BIT(6)
++#define PCM_REG_INT_TX_UNRUN		BIT(5)
++#define PCM_REG_INT_TX_THRES		BIT(4)
++#define PCM_REG_INT_RX_FAULT		BIT(3)
++#define PCM_REG_INT_RX_OVRUN		BIT(2)
++#define PCM_REG_INT_RX_UNRUN		BIT(1)
++#define PCM_REG_INT_RX_THRES		BIT(0)
++#define PCM_REG_INT_TX_MASK		0xF0
++#define PCM_REG_INT_RX_MASK		0x0F
++
++/* PCM_REG_CHA0_CFG */
++#define PCM_REG_CHA0_CFG_TS_START	0
++#define PCM_REG_CHA0_CFG_TS_START_MASK	(0x3FF << PCM_REG_CHA0_CFG_TS_START)
++
++/* PCM_REG_FSYNC_CFG */
++#define PCM_REG_FSYNC_CFG_EN		BIT(31)
++#define PCM_REG_FSYNC_CFG_CAP_DT	BIT(30)
++#define PCM_REG_FSYNC_CFG_DRV_DT	BIT(29)
++#define PCM_REG_FSYNC_CFG_CAP_FSYNC	BIT(28)
++#define PCM_REG_FSYNC_CFG_DRV_FSYNC	BIT(27)
++#define PCM_REG_FSYNC_CFG_FSYNC_INTV	0
++#define PCM_REG_FSYNC_CFG_FSYNC_INTV_MASK \
++					(0x3F << PCM_REG_FSYNC_CFG_FSYNC_INTV)
++
++/* PCM_REG_DIVCOMP_CFG */
++#define PCM_REG_DIVCOMP_CFG_CLKEN	BIT(31)
++#define PCM_REG_DIVCOMP_CFG_MASK	0xFF
++
++/* PCM_REG_DIVINT_CFG */
++#define PCM_REG_DIVINT_CFG_MASK		0x3FF
++
++/* FIFO */
++#define RALINK_PCM_FIFO_SIZE		32
++
++#define RALINK_PCM_INT_EN		1
++
++struct ralink_pcm_stats {
++	u32 dmafault;
++	u32 overrun;
++	u32 underrun;
++	u32 belowthres;
++};
++
++struct ralink_pcm {
++	struct device *dev;
++	void __iomem *regs;
++	struct clk *clk;
++	struct regmap *regmap;
++	u32 flags;
++	unsigned int fmt;
++	u16 txdma_req;
++	u16 rxdma_req;
++
++	struct snd_dmaengine_dai_dma_data playback_dma_data;
++	struct snd_dmaengine_dai_dma_data capture_dma_data;
++
++	struct dentry *dbg_dir;
++	struct dentry *dbg_stats;
++	struct ralink_pcm_stats txstats;
++	struct ralink_pcm_stats rxstats;
++};
++
++#define PRINT_REG(reg) \
++		regmap_read(pcm->regmap, PCM_REG_##reg, &buf); \
++		printk(KERN_DEBUG "%3x: %08x " #reg "\n", PCM_REG_##reg, buf);
++
++static void ralink_pcm_dump_regs(struct ralink_pcm *pcm)
++{
++	u32 buf;
++
++	printk(KERN_DEBUG "mt7621 pcm regs:\n");
++	PRINT_REG(GLB_CFG);
++	PRINT_REG(PCM_CFG);
++	PRINT_REG(INT_STATUS);
++	PRINT_REG(INT_EN);
++	PRINT_REG(CHA0_FF_STATUS);
++	PRINT_REG(CHB0_FF_STATUS);
++	PRINT_REG(CHA0_CFG);
++	PRINT_REG(CHB0_CFG);
++	PRINT_REG(FSYNC_CFG);
++	PRINT_REG(CHA0_CFG2);
++	PRINT_REG(CHB0_CFG2);
++	PRINT_REG(IP_INFO);
++	PRINT_REG(RSV_REG16);
++	PRINT_REG(DIVCOMP_CFG);
++	PRINT_REG(DIVINT_CFG);
++	PRINT_REG(DIGDELAY_CFG);
++	PRINT_REG(CH0_FIFO);
++	PRINT_REG(CH1_FIFO);
++	PRINT_REG(CH2_FIFO);
++	PRINT_REG(CH3_FIFO);
++	PRINT_REG(CHA1_FF_STATUS);
++	PRINT_REG(CHB1_FF_STATUS);
++	PRINT_REG(CHA1_CFG);
++	PRINT_REG(CHB1_CFG);
++	PRINT_REG(CHA1_CFG2);
++	PRINT_REG(CHB1_CFG2);
++}
++
++static int ralink_pcm_set_sysclk(struct snd_soc_dai *dai,
++		int clk_id, unsigned int freq, int dir)
++{
++	return 0;
++}
++
++static int ralink_pcm_set_bclk(struct snd_soc_dai *dai, int width, int rate)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++	unsigned long clk;
++	unsigned long freqin = 294000000;
++	int divint, divcomp;
++
++	/* now use fixed PCM_REG_PCM_CFG_SLOT_MODE_8 */
++	if (width > 64)
++		return -EINVAL;
++	width = 64;
++
++	/* disable clock at slave mode */
++	if ((pcm->fmt & SND_SOC_DAIFMT_MASTER_MASK) ==
++			SND_SOC_DAIFMT_CBM_CFM) {
++		regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
++				PCM_REG_DIVCOMP_CFG_CLKEN, 0);
++		return 0;
++	}
++
++	/* FREQOUT = FREQIN * (1/2) * (1/(DIVINT + DIVCOMP/256)) */
++	clk = freqin / (2 * 1 * width);
++	divint = clk / rate;
++	divcomp = ((clk % rate) * 256) / rate;
++
++	if ((divint > PCM_REG_DIVINT_CFG_MASK) ||
++			(divcomp > PCM_REG_DIVCOMP_CFG_MASK))
++		return -EINVAL;
++
++	regmap_update_bits(pcm->regmap, PCM_REG_DIVINT_CFG,
++			PCM_REG_DIVINT_CFG_MASK,
++			divint);
++	regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
++			PCM_REG_DIVCOMP_CFG_MASK,
++			divcomp);
++
++	/* enable clock */
++	regmap_update_bits(pcm->regmap, PCM_REG_DIVCOMP_CFG,
++			PCM_REG_DIVCOMP_CFG_CLKEN,
++			PCM_REG_DIVCOMP_CFG_CLKEN);
++
++	dev_dbg(pcm->dev, "freqin: %lu, rate: %u, width: %u, int: %d, comp: %d\n",
++			freqin, rate, width, divint, divcomp);
++
++	return 0;
++}
++
++static int ralink_pcm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
++	case SND_SOC_DAIFMT_CBM_CFM:
++		regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
++				PCM_REG_PCM_CFG_CLKOUT_EN |
++				PCM_REG_PCM_CFG_EXT_FSYNC,
++				0 |
++				PCM_REG_PCM_CFG_EXT_FSYNC);
++		break;
++	case SND_SOC_DAIFMT_CBS_CFS:
++		regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
++				PCM_REG_PCM_CFG_CLKOUT_EN |
++				PCM_REG_PCM_CFG_EXT_FSYNC,
++				PCM_REG_PCM_CFG_CLKOUT_EN |
++				0);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	/* interface format */
++	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
++	case SND_SOC_DAIFMT_DSP_A:
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	/* clock inversion */
++	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
++	case SND_SOC_DAIFMT_IB_NF:
++		regmap_update_bits(pcm->regmap, PCM_REG_FSYNC_CFG,
++				PCM_REG_FSYNC_CFG_CAP_DT |
++				PCM_REG_FSYNC_CFG_DRV_DT |
++				PCM_REG_FSYNC_CFG_CAP_FSYNC |
++				PCM_REG_FSYNC_CFG_DRV_FSYNC,
++				0 |
++				PCM_REG_FSYNC_CFG_DRV_DT |
++				0 |
++				PCM_REG_FSYNC_CFG_DRV_FSYNC);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	pcm->fmt = fmt;
++
++	return 0;
++}
++
++static int ralink_pcm_startup(struct snd_pcm_substream *substream,
++		struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	if (dai->active)
++		return 0;
++
++	/* setup status interrupt */
++#if (RALINK_PCM_INT_EN)
++	regmap_write(pcm->regmap, PCM_REG_INT_EN, 0xff);
++#else
++	regmap_write(pcm->regmap, PCM_REG_INT_EN, 0x0);
++#endif
++
++	/* enable */
++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++			PCM_REG_GLB_CFG_EN |
++			PCM_REG_GLB_CFG_CH_MASK,
++			PCM_REG_GLB_CFG_EN |
++			PCM_REG_GLB_CFG_CH0 | PCM_REG_GLB_CFG_CH1);
++
++	return 0;
++}
++
++static void ralink_pcm_shutdown(struct snd_pcm_substream *substream,
++		struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	/* If both streams are stopped, disable module and clock */
++	if (dai->active)
++		return;
++
++	/*
++	 * datasheet mention when disable all control regs are cleared
++	 * to initial values. need reinit at startup.
++	 */
++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++			PCM_REG_GLB_CFG_EN |
++			PCM_REG_GLB_CFG_CH_MASK,
++			0 |
++			0);
++}
++
++static int ralink_pcm_hw_params(struct snd_pcm_substream *substream,
++		struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
++{
++	int width;
++	int ret;
++
++	width = params_width(params);
++	switch (width) {
++	case 16:
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	switch (params_channels(params)) {
++	case 1:
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	/* setup bclk rate */
++	ret = ralink_pcm_set_bclk(dai, width, params_rate(params));
++
++	return ret;
++}
++
++static int ralink_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
++		struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	switch (cmd) {
++	case SNDRV_PCM_TRIGGER_START:
++	case SNDRV_PCM_TRIGGER_RESUME:
++	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
++		regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++				PCM_REG_GLB_CFG_DMA_EN,
++				PCM_REG_GLB_CFG_DMA_EN);
++		break;
++	case SNDRV_PCM_TRIGGER_STOP:
++	case SNDRV_PCM_TRIGGER_SUSPEND:
++	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
++		regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++				PCM_REG_GLB_CFG_DMA_EN,
++				0);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static void ralink_pcm_init_dma_data(struct ralink_pcm *pcm,
++		struct resource *res)
++{
++	struct snd_dmaengine_dai_dma_data *dma_data;
++
++	/* channel 0 Playback */
++	dma_data = &pcm->playback_dma_data;
++	dma_data->addr = res->start + PCM_REG_CH0_FIFO;
++	dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
++	dma_data->maxburst = 1;
++	dma_data->slave_id = pcm->txdma_req;
++
++	/* channel 1 Capture */
++	dma_data = &pcm->capture_dma_data;
++	dma_data->addr = res->start + PCM_REG_CH1_FIFO;
++	dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
++	dma_data->maxburst = 1;
++	dma_data->slave_id = pcm->rxdma_req;
++}
++
++static int ralink_pcm_dai_probe(struct snd_soc_dai *dai)
++{
++	struct ralink_pcm *pcm = snd_soc_dai_get_drvdata(dai);
++
++	snd_soc_dai_init_dma_data(dai, &pcm->playback_dma_data,
++			&pcm->capture_dma_data);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_PCM_CFG,
++			PCM_REG_PCM_CFG_FSYNC_POL |
++			PCM_REG_PCM_CFG_DTX_TRI |
++			PCM_REG_PCM_CFG_SLOT_MODE_MASK,
++			PCM_REG_PCM_CFG_FSYNC_POL |
++			0 |
++			PCM_REG_PCM_CFG_SLOT_MODE_8);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_CHA0_CFG,
++			PCM_REG_CHA0_CFG_TS_START_MASK,
++			1);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_GLB_CFG,
++			PCM_REG_GLB_CFG_THRES_MASK,
++			PCM_REG_GLB_CFG_DFT_THRES);
++
++	regmap_update_bits(pcm->regmap, PCM_REG_FSYNC_CFG,
++			PCM_REG_FSYNC_CFG_EN |
++			PCM_REG_FSYNC_CFG_FSYNC_INTV_MASK,
++			PCM_REG_FSYNC_CFG_EN |
++			1);
++
++	return 0;
++}
++
++static int ralink_pcm_dai_remove(struct snd_soc_dai *dai)
++{
++	return 0;
++}
++
++static const struct snd_soc_dai_ops ralink_pcm_dai_ops = {
++	.set_sysclk = ralink_pcm_set_sysclk,
++	.set_fmt = ralink_pcm_set_fmt,
++	.startup = ralink_pcm_startup,
++	.shutdown = ralink_pcm_shutdown,
++	.hw_params = ralink_pcm_hw_params,
++	.trigger = ralink_pcm_trigger,
++};
++
++static struct snd_soc_dai_driver ralink_pcm_dai = {
++	.name = DRV_NAME,
++	.probe = ralink_pcm_dai_probe,
++	.remove = ralink_pcm_dai_remove,
++	.ops = &ralink_pcm_dai_ops,
++	.capture = {
++		.stream_name = "PCM Capture",
++		.channels_min = 1,
++		.channels_max = 1,
++		.rate_min = 5512,
++		.rate_max = 192000,
++		.rates = SNDRV_PCM_RATE_CONTINUOUS,
++		.formats = SNDRV_PCM_FMTBIT_S16_LE,
++	},
++	.playback = {
++		.stream_name = "PCM Playback",
++		.channels_min = 1,
++		.channels_max = 1,
++		.rate_min = 5512,
++		.rate_max = 192000,
++		.rates = SNDRV_PCM_RATE_CONTINUOUS,
++		.formats = SNDRV_PCM_FMTBIT_S16_LE,
++	},
++	.symmetric_rates = 1,
++};
++
++static struct snd_pcm_hardware ralink_pcm_hardware = {
++	.info = SNDRV_PCM_INFO_MMAP |
++		SNDRV_PCM_INFO_MMAP_VALID |
++		SNDRV_PCM_INFO_INTERLEAVED |
++		SNDRV_PCM_INFO_BLOCK_TRANSFER,
++	.formats = SNDRV_PCM_FMTBIT_S16_LE,
++	.channels_min		= 2,
++	.channels_max		= 2,
++	.period_bytes_min	= PAGE_SIZE,
++	.period_bytes_max	= PAGE_SIZE * 2,
++	.periods_min		= 2,
++	.periods_max		= 128,
++	.buffer_bytes_max	= 128 * 1024,
++	.fifo_size		= RALINK_PCM_FIFO_SIZE,
++};
++
++static const struct snd_dmaengine_pcm_config ralink_dmaengine_pcm_config = {
++	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
++	.pcm_hardware = &ralink_pcm_hardware,
++	.prealloc_buffer_size = 256 * PAGE_SIZE,
++};
++
++static const struct snd_soc_component_driver ralink_pcm_component = {
++	.name = DRV_NAME,
++};
++
++static bool ralink_pcm_writeable_reg(struct device *dev, unsigned int reg)
++{
++	return true;
++}
++
++static bool ralink_pcm_readable_reg(struct device *dev, unsigned int reg)
++{
++	return true;
++}
++
++static bool ralink_pcm_volatile_reg(struct device *dev, unsigned int reg)
++{
++	switch (reg) {
++	case PCM_REG_INT_STATUS:
++	case PCM_REG_CHA0_FF_STATUS:
++	case PCM_REG_CHB0_FF_STATUS:
++	case PCM_REG_CHA1_FF_STATUS:
++	case PCM_REG_CHB1_FF_STATUS:
++		return true;
++	}
++	return false;
++}
++
++static const struct regmap_config ralink_pcm_regmap_config = {
++	.reg_bits = 32,
++	.reg_stride = 4,
++	.val_bits = 32,
++	.writeable_reg = ralink_pcm_writeable_reg,
++	.readable_reg = ralink_pcm_readable_reg,
++	.volatile_reg = ralink_pcm_volatile_reg,
++	.max_register = PCM_REG_CHB1_CFG2,
++};
++
++#if (RALINK_PCM_INT_EN)
++static irqreturn_t ralink_pcm_irq(int irq, void *devid)
++{
++	struct ralink_pcm *pcm = devid;
++	u32 status;
++
++	regmap_read(pcm->regmap, PCM_REG_INT_STATUS, &status);
++	if (unlikely(!status))
++		return IRQ_NONE;
++
++	/* tx stats */
++	if (status & PCM_REG_INT_TX_MASK) {
++		if (status & PCM_REG_INT_TX_THRES)
++			pcm->txstats.belowthres++;
++		if (status & PCM_REG_INT_TX_UNRUN)
++			pcm->txstats.underrun++;
++		if (status & PCM_REG_INT_TX_OVRUN)
++			pcm->txstats.overrun++;
++		if (status & PCM_REG_INT_TX_FAULT)
++			pcm->txstats.dmafault++;
++	}
++
++	/* rx stats */
++	if (status & PCM_REG_INT_RX_MASK) {
++		if (status & PCM_REG_INT_RX_THRES)
++			pcm->rxstats.belowthres++;
++		if (status & PCM_REG_INT_RX_UNRUN)
++			pcm->rxstats.underrun++;
++		if (status & PCM_REG_INT_RX_OVRUN)
++			pcm->rxstats.overrun++;
++		if (status & PCM_REG_INT_RX_FAULT)
++			pcm->rxstats.dmafault++;
++	}
++
++	/* clean status bits */
++	regmap_write(pcm->regmap, PCM_REG_INT_STATUS, status);
++
++	return IRQ_HANDLED;
++}
++#endif
++
++#if IS_ENABLED(CONFIG_DEBUG_FS)
++static int ralink_pcm_stats_show(struct seq_file *s, void *unused)
++{
++	struct ralink_pcm *pcm = s->private;
++
++	seq_printf(s, "tx stats\n");
++	seq_printf(s, "\tbelow threshold\t%u\n", pcm->txstats.belowthres);
++	seq_printf(s, "\tunder run\t%u\n", pcm->txstats.underrun);
++	seq_printf(s, "\tover run\t%u\n", pcm->txstats.overrun);
++	seq_printf(s, "\tdma fault\t%u\n", pcm->txstats.dmafault);
++
++	seq_printf(s, "rx stats\n");
++	seq_printf(s, "\tbelow threshold\t%u\n", pcm->rxstats.belowthres);
++	seq_printf(s, "\tunder run\t%u\n", pcm->rxstats.underrun);
++	seq_printf(s, "\tover run\t%u\n", pcm->rxstats.overrun);
++	seq_printf(s, "\tdma fault\t%u\n", pcm->rxstats.dmafault);
++
++	ralink_pcm_dump_regs(pcm);
++
++	return 0;
++}
++
++static int ralink_pcm_stats_open(struct inode *inode, struct file *file)
++{
++	return single_open(file, ralink_pcm_stats_show, inode->i_private);
++}
++
++static const struct file_operations ralink_pcm_stats_ops = {
++	.open = ralink_pcm_stats_open,
++	.read = seq_read,
++	.llseek = seq_lseek,
++	.release = single_release,
++};
++
++static inline int ralink_pcm_debugfs_create(struct ralink_pcm *pcm)
++{
++	pcm->dbg_dir = debugfs_create_dir(dev_name(pcm->dev), NULL);
++	if (!pcm->dbg_dir)
++		return -ENOMEM;
++
++	pcm->dbg_stats = debugfs_create_file("stats", S_IRUGO,
++			pcm->dbg_dir, pcm, &ralink_pcm_stats_ops);
++	if (!pcm->dbg_stats) {
++		debugfs_remove(pcm->dbg_dir);
++		return -ENOMEM;
++	}
++
++	return 0;
++}
++
++static inline void ralink_pcm_debugfs_remove(struct ralink_pcm *pcm)
++{
++	debugfs_remove(pcm->dbg_stats);
++	debugfs_remove(pcm->dbg_dir);
++}
++#else
++static inline int ralink_pcm_debugfs_create(struct ralink_pcm *pcm)
++{
++	return 0;
++}
++
++static inline void ralink_pcm_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
++{
++}
++#endif
++
++#define RALINK_SYSCTL_BASE		0xBE000000
++#define RALINK_ANA_CTRL_BASE		0xBE000F00
++#define RALINK_REG_RD(addr)		(*(volatile u32 *)(addr))
++#define RALINK_REG_WR(addr, val)	(*(volatile u32 *)(addr) = (val))
++
++static void mt7621_refclk_setup(void)
++{
++	bool clk_20mhz = 0, clk_40mhz = 0;
++	u32 reg;
++
++	reg = RALINK_REG_RD(RALINK_SYSCTL_BASE + 0x10);
++	reg = (reg >> 6) & 0x7;
++	if (reg <= 2) {
++		/* 20MHz Xtal */
++		clk_20mhz = true;
++	} else if (reg >= 3 && reg <= 5) {
++		/* 40MHz Xtal */
++		clk_40mhz = true;
++	} else {
++		/* 25MHz Xtal */
++	}
++
++	/* reset required registers to default */
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008000);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0014, 0x01401d61);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0018, 0x38233d0e);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, 0x80120004);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0020, 0x1c7dbf48);
++
++	/* toggle RG_XPTL_CHG */
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008800);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0000, 0x00008c00);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x0014);
++	reg &= ~0x0000ffc0;
++	if (clk_20mhz || clk_40mhz) {
++		reg |= 0x1d << 8;
++	} else {
++		reg |= 0x17 << 8;
++	}
++	reg |= 0x1 << 6;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0014, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x0018);
++	reg &= ~0xf0773f00;
++	reg |= 0x3 << 28;
++	reg |= 0x2 << 20;
++	if (clk_20mhz || clk_40mhz) {
++		reg |= 0x3 << 16;
++	} else {
++		reg |= 0x2 << 16;
++	}
++	reg |= 0x3 << 12;
++	if (clk_20mhz || clk_40mhz) {
++		reg |= 0xd << 8;
++	} else {
++		reg |= 0x7 << 8;
++	}
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0018, reg);
++
++	if (clk_20mhz || clk_40mhz) {
++		reg = 0x1c7dbf48;
++	} else {
++		reg = 0x1697cc39;
++	}
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x0020, reg);
++
++	/* Common setting - Set PLLGP_CTRL_4 */
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg &= ~(0x1 << 31);
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 0;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 3;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 8;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 6;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 5;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 7;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++
++	reg = RALINK_REG_RD(RALINK_ANA_CTRL_BASE + 0x001c);
++	reg |= 0x1 << 17;
++	RALINK_REG_WR(RALINK_ANA_CTRL_BASE + 0x001c, reg);
++}
++
++struct rt_pcm_data {
++	u32 flags;
++	void (*refclk_setup)(void);
++};
++
++static struct rt_pcm_data mt7621_pcm_data = {
++	.refclk_setup = mt7621_refclk_setup
++};
++
++static const struct of_device_id ralink_pcm_match_table[] = {
++	{ .compatible = "mediatek,mt7621-pcm",
++		.data = (void *)&mt7621_pcm_data },
++};
++MODULE_DEVICE_TABLE(of, ralink_pcm_match_table);
++
++static int ralink_pcm_probe(struct platform_device *pdev)
++{
++	const struct of_device_id *match;
++	struct device_node *np = pdev->dev.of_node;
++	struct ralink_pcm *pcm;
++	struct resource *res;
++	int irq, ret;
++	u32 dma_req;
++	struct rt_pcm_data *data;
++
++	pcm = devm_kzalloc(&pdev->dev, sizeof(*pcm), GFP_KERNEL);
++	if (!pcm)
++		return -ENOMEM;
++
++	platform_set_drvdata(pdev, pcm);
++	pcm->dev = &pdev->dev;
++
++	match = of_match_device(ralink_pcm_match_table, &pdev->dev);
++	if (!match)
++		return -EINVAL;
++	data = (struct rt_pcm_data *)match->data;
++	pcm->flags = data->flags;
++	if (data->refclk_setup)
++		data->refclk_setup();
++
++	if (of_property_read_u32(np, "txdma-req", &dma_req)) {
++		dev_err(&pdev->dev, "no txdma-req define\n");
++		return -EINVAL;
++	}
++	pcm->txdma_req = (u16)dma_req;
++	if (of_property_read_u32(np, "rxdma-req", &dma_req)) {
++		dev_err(&pdev->dev, "no rxdma-req define\n");
++		return -EINVAL;
++	}
++	pcm->rxdma_req = (u16)dma_req;
++
++	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++	pcm->regs = devm_ioremap_resource(&pdev->dev, res);
++	if (IS_ERR(pcm->regs))
++		return PTR_ERR(pcm->regs);
++
++	pcm->regmap = devm_regmap_init_mmio(&pdev->dev, pcm->regs,
++			&ralink_pcm_regmap_config);
++	if (IS_ERR(pcm->regmap)) {
++		dev_err(&pdev->dev, "regmap init failed\n");
++		return PTR_ERR(pcm->regmap);
++	}
++
++	irq = platform_get_irq(pdev, 0);
++	if (irq < 0) {
++		dev_err(&pdev->dev, "failed to get irq\n");
++		return -EINVAL;
++	}
++
++#if (RALINK_PCM_INT_EN)
++	ret = devm_request_irq(&pdev->dev, irq, ralink_pcm_irq,
++			0, dev_name(&pdev->dev), pcm);
++	if (ret) {
++		dev_err(&pdev->dev, "failed to request irq\n");
++		return ret;
++	}
++#endif
++
++	pcm->clk = devm_clk_get(&pdev->dev, NULL);
++	if (IS_ERR(pcm->clk)) {
++		dev_err(&pdev->dev, "no clock defined\n");
++		return PTR_ERR(pcm->clk);
++	}
++
++	ret = clk_prepare_enable(pcm->clk);
++	if (ret)
++		return ret;
++
++	ralink_pcm_init_dma_data(pcm, res);
++
++	device_reset(&pdev->dev);
++
++	ret = ralink_pcm_debugfs_create(pcm);
++	if (ret) {
++		dev_err(&pdev->dev, "create debugfs failed\n");
++		goto err_clk_disable;
++	}
++
++
++	ret = devm_snd_soc_register_component(&pdev->dev, &ralink_pcm_component,
++			&ralink_pcm_dai, 1);
++	if (ret)
++		goto err_debugfs;
++
++	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
++			&ralink_dmaengine_pcm_config,
++			SND_DMAENGINE_PCM_FLAG_COMPAT);
++	if (ret)
++		goto err_debugfs;
++
++	return 0;
++
++err_debugfs:
++	ralink_pcm_debugfs_remove(pcm);
++
++err_clk_disable:
++	clk_disable_unprepare(pcm->clk);
++
++	return ret;
++}
++
++static int ralink_pcm_remove(struct platform_device *pdev)
++{
++	struct ralink_pcm *pcm = platform_get_drvdata(pdev);
++
++	ralink_pcm_debugfs_remove(pcm);
++	clk_disable_unprepare(pcm->clk);
++
++	return 0;
++}
++
++static struct platform_driver ralink_pcm_driver = {
++	.probe = ralink_pcm_probe,
++	.remove = ralink_pcm_remove,
++	.driver = {
++		.name = DRV_NAME,
++		.of_match_table = ralink_pcm_match_table,
++	},
++};
++module_platform_driver(ralink_pcm_driver);
++
++MODULE_AUTHOR("Puyou Lu <puyou.lu@gmail.com>");
++MODULE_DESCRIPTION("Ralink/MediaTek PCM driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:" DRV_NAME);