[PATCH 1/1] ramips: mt7621: add ralink pcm controller driver
Puyou Lu
puyou.lu at gmail.com
Mon Jul 6 04:05:13 EDT 2020
On Fri, Jul 3, 2020 at 10:09 PM Daniel Golle <daniel at makrotopia.org> wrote:
>
> Hi Puyou,
>
>
> On Fri, Jul 03, 2020 at 09:51:37PM +0800, puyou.lu at gmail.com wrote:
> > From: Puyou Lu <puyou.lu at gmail.com>
> >
> > Only tested on mt7621 currently.
> >
> > Signed-off-by: Puyou Lu <puyou.lu at 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 at 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 at 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 at gmail.com>");
++MODULE_DESCRIPTION("Ralink/MediaTek PCM driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:" DRV_NAME);
--
2.7.4
More information about the openwrt-devel
mailing list