Message ID | 1593784297-31453-2-git-send-email-puyou.lu@gmail.com |
---|---|
State | Changes Requested |
Headers | show |
Series | ralink pcm controller driver | expand |
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
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 --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);