summaryrefslogtreecommitdiffstats
path: root/sound/soc/bcm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /sound/soc/bcm
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/soc/bcm')
-rw-r--r--sound/soc/bcm/Kconfig28
-rw-r--r--sound/soc/bcm/Makefile15
-rw-r--r--sound/soc/bcm/bcm2835-i2s.c931
-rw-r--r--sound/soc/bcm/bcm63xx-i2s-whistler.c318
-rw-r--r--sound/soc/bcm/bcm63xx-i2s.h89
-rw-r--r--sound/soc/bcm/bcm63xx-pcm-whistler.c414
-rw-r--r--sound/soc/bcm/cygnus-pcm.c750
-rw-r--r--sound/soc/bcm/cygnus-ssp.c1407
-rw-r--r--sound/soc/bcm/cygnus-ssp.h129
9 files changed, 4081 insertions, 0 deletions
diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig
new file mode 100644
index 000000000..4218057b0
--- /dev/null
+++ b/sound/soc/bcm/Kconfig
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_BCM2835_SOC_I2S
+ tristate "SoC Audio support for the Broadcom BCM2835 I2S module"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the BCM2835 I2S interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_SOC_CYGNUS
+ tristate "SoC platform audio for Broadcom Cygnus chips"
+ depends on ARCH_BCM_CYGNUS || COMPILE_TEST
+ help
+ Say Y if you want to add support for ASoC audio on Broadcom
+ Cygnus chips (bcm958300, bcm958305, bcm911360)
+
+ If you don't know what to do here, say N.
+
+config SND_BCM63XX_I2S_WHISTLER
+ tristate "SoC Audio support for the Broadcom BCM63XX I2S module"
+ select REGMAP_MMIO
+ help
+ Say Y if you want to add support for ASoC audio on Broadcom
+ DSL/PON chips (bcm63158, bcm63178)
+
+ If you don't know what to do here, say N
diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile
new file mode 100644
index 000000000..7c2d78996
--- /dev/null
+++ b/sound/soc/bcm/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# BCM2835 Platform Support
+snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o
+
+obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o
+
+# CYGNUS Platform Support
+snd-soc-cygnus-objs := cygnus-pcm.o cygnus-ssp.o
+
+obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o
+
+# BCM63XX Platform Support
+snd-soc-63xx-objs := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o
+
+obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o \ No newline at end of file
diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c
new file mode 100644
index 000000000..85f705afc
--- /dev/null
+++ b/sound/soc/bcm/bcm2835-i2s.c
@@ -0,0 +1,931 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALSA SoC I2S Audio Layer for Broadcom BCM2835 SoC
+ *
+ * Author: Florian Meier <florian.meier@koalo.de>
+ * Copyright 2013
+ *
+ * Based on
+ * Raspberry Pi PCM I2S ALSA Driver
+ * Copyright (c) by Phil Poole 2013
+ *
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ * Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * OMAP ALSA SoC DAI driver using McBSP port
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
+ * Peter Ujfalusi <peter.ujfalusi@ti.com>
+ *
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ * Author: Timur Tabi <timur@freescale.com>
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/* I2S registers */
+#define BCM2835_I2S_CS_A_REG 0x00
+#define BCM2835_I2S_FIFO_A_REG 0x04
+#define BCM2835_I2S_MODE_A_REG 0x08
+#define BCM2835_I2S_RXC_A_REG 0x0c
+#define BCM2835_I2S_TXC_A_REG 0x10
+#define BCM2835_I2S_DREQ_A_REG 0x14
+#define BCM2835_I2S_INTEN_A_REG 0x18
+#define BCM2835_I2S_INTSTC_A_REG 0x1c
+#define BCM2835_I2S_GRAY_REG 0x20
+
+/* I2S register settings */
+#define BCM2835_I2S_STBY BIT(25)
+#define BCM2835_I2S_SYNC BIT(24)
+#define BCM2835_I2S_RXSEX BIT(23)
+#define BCM2835_I2S_RXF BIT(22)
+#define BCM2835_I2S_TXE BIT(21)
+#define BCM2835_I2S_RXD BIT(20)
+#define BCM2835_I2S_TXD BIT(19)
+#define BCM2835_I2S_RXR BIT(18)
+#define BCM2835_I2S_TXW BIT(17)
+#define BCM2835_I2S_CS_RXERR BIT(16)
+#define BCM2835_I2S_CS_TXERR BIT(15)
+#define BCM2835_I2S_RXSYNC BIT(14)
+#define BCM2835_I2S_TXSYNC BIT(13)
+#define BCM2835_I2S_DMAEN BIT(9)
+#define BCM2835_I2S_RXTHR(v) ((v) << 7)
+#define BCM2835_I2S_TXTHR(v) ((v) << 5)
+#define BCM2835_I2S_RXCLR BIT(4)
+#define BCM2835_I2S_TXCLR BIT(3)
+#define BCM2835_I2S_TXON BIT(2)
+#define BCM2835_I2S_RXON BIT(1)
+#define BCM2835_I2S_EN (1)
+
+#define BCM2835_I2S_CLKDIS BIT(28)
+#define BCM2835_I2S_PDMN BIT(27)
+#define BCM2835_I2S_PDME BIT(26)
+#define BCM2835_I2S_FRXP BIT(25)
+#define BCM2835_I2S_FTXP BIT(24)
+#define BCM2835_I2S_CLKM BIT(23)
+#define BCM2835_I2S_CLKI BIT(22)
+#define BCM2835_I2S_FSM BIT(21)
+#define BCM2835_I2S_FSI BIT(20)
+#define BCM2835_I2S_FLEN(v) ((v) << 10)
+#define BCM2835_I2S_FSLEN(v) (v)
+
+#define BCM2835_I2S_CHWEX BIT(15)
+#define BCM2835_I2S_CHEN BIT(14)
+#define BCM2835_I2S_CHPOS(v) ((v) << 4)
+#define BCM2835_I2S_CHWID(v) (v)
+#define BCM2835_I2S_CH1(v) ((v) << 16)
+#define BCM2835_I2S_CH2(v) (v)
+#define BCM2835_I2S_CH1_POS(v) BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(v))
+#define BCM2835_I2S_CH2_POS(v) BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(v))
+
+#define BCM2835_I2S_TX_PANIC(v) ((v) << 24)
+#define BCM2835_I2S_RX_PANIC(v) ((v) << 16)
+#define BCM2835_I2S_TX(v) ((v) << 8)
+#define BCM2835_I2S_RX(v) (v)
+
+#define BCM2835_I2S_INT_RXERR BIT(3)
+#define BCM2835_I2S_INT_TXERR BIT(2)
+#define BCM2835_I2S_INT_RXR BIT(1)
+#define BCM2835_I2S_INT_TXW BIT(0)
+
+/* Frame length register is 10 bit, maximum length 1024 */
+#define BCM2835_I2S_MAX_FRAME_LENGTH 1024
+
+/* General device struct */
+struct bcm2835_i2s_dev {
+ struct device *dev;
+ struct snd_dmaengine_dai_dma_data dma_data[2];
+ unsigned int fmt;
+ unsigned int tdm_slots;
+ unsigned int rx_mask;
+ unsigned int tx_mask;
+ unsigned int slot_width;
+ unsigned int frame_length;
+
+ struct regmap *i2s_regmap;
+ struct clk *clk;
+ bool clk_prepared;
+ int clk_rate;
+};
+
+static void bcm2835_i2s_start_clock(struct bcm2835_i2s_dev *dev)
+{
+ unsigned int provider = dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
+
+ if (dev->clk_prepared)
+ return;
+
+ switch (provider) {
+ case SND_SOC_DAIFMT_BP_FP:
+ case SND_SOC_DAIFMT_BP_FC:
+ clk_prepare_enable(dev->clk);
+ dev->clk_prepared = true;
+ break;
+ default:
+ break;
+ }
+}
+
+static void bcm2835_i2s_stop_clock(struct bcm2835_i2s_dev *dev)
+{
+ if (dev->clk_prepared)
+ clk_disable_unprepare(dev->clk);
+ dev->clk_prepared = false;
+}
+
+static void bcm2835_i2s_clear_fifos(struct bcm2835_i2s_dev *dev,
+ bool tx, bool rx)
+{
+ int timeout = 1000;
+ uint32_t syncval;
+ uint32_t csreg;
+ uint32_t i2s_active_state;
+ bool clk_was_prepared;
+ uint32_t off;
+ uint32_t clr;
+
+ off = tx ? BCM2835_I2S_TXON : 0;
+ off |= rx ? BCM2835_I2S_RXON : 0;
+
+ clr = tx ? BCM2835_I2S_TXCLR : 0;
+ clr |= rx ? BCM2835_I2S_RXCLR : 0;
+
+ /* Backup the current state */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
+ i2s_active_state = csreg & (BCM2835_I2S_RXON | BCM2835_I2S_TXON);
+
+ /* Start clock if not running */
+ clk_was_prepared = dev->clk_prepared;
+ if (!clk_was_prepared)
+ bcm2835_i2s_start_clock(dev);
+
+ /* Stop I2S module */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, off, 0);
+
+ /*
+ * Clear the FIFOs
+ * Requires at least 2 PCM clock cycles to take effect
+ */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, clr, clr);
+
+ /* Wait for 2 PCM clock cycles */
+
+ /*
+ * Toggle the SYNC flag. After 2 PCM clock cycles it can be read back
+ * FIXME: This does not seem to work for slave mode!
+ */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &syncval);
+ syncval &= BCM2835_I2S_SYNC;
+
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_SYNC, ~syncval);
+
+ /* Wait for the SYNC flag changing it's state */
+ while (--timeout) {
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
+ if ((csreg & BCM2835_I2S_SYNC) != syncval)
+ break;
+ }
+
+ if (!timeout)
+ dev_err(dev->dev, "I2S SYNC error!\n");
+
+ /* Stop clock if it was not running before */
+ if (!clk_was_prepared)
+ bcm2835_i2s_stop_clock(dev);
+
+ /* Restore I2S state */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_RXON | BCM2835_I2S_TXON, i2s_active_state);
+}
+
+static int bcm2835_i2s_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ dev->fmt = fmt;
+ return 0;
+}
+
+static int bcm2835_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai,
+ unsigned int ratio)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (!ratio) {
+ dev->tdm_slots = 0;
+ return 0;
+ }
+
+ if (ratio > BCM2835_I2S_MAX_FRAME_LENGTH)
+ return -EINVAL;
+
+ dev->tdm_slots = 2;
+ dev->rx_mask = 0x03;
+ dev->tx_mask = 0x03;
+ dev->slot_width = ratio / 2;
+ dev->frame_length = ratio;
+
+ return 0;
+}
+
+static int bcm2835_i2s_set_dai_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int width)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (slots) {
+ if (slots < 0 || width < 0)
+ return -EINVAL;
+
+ /* Limit masks to available slots */
+ rx_mask &= GENMASK(slots - 1, 0);
+ tx_mask &= GENMASK(slots - 1, 0);
+
+ /*
+ * The driver is limited to 2-channel setups.
+ * Check that exactly 2 bits are set in the masks.
+ */
+ if (hweight_long((unsigned long) rx_mask) != 2
+ || hweight_long((unsigned long) tx_mask) != 2)
+ return -EINVAL;
+
+ if (slots * width > BCM2835_I2S_MAX_FRAME_LENGTH)
+ return -EINVAL;
+ }
+
+ dev->tdm_slots = slots;
+
+ dev->rx_mask = rx_mask;
+ dev->tx_mask = tx_mask;
+ dev->slot_width = width;
+ dev->frame_length = slots * width;
+
+ return 0;
+}
+
+/*
+ * Convert logical slot number into physical slot number.
+ *
+ * If odd_offset is 0 sequential number is identical to logical number.
+ * This is used for DSP modes with slot numbering 0 1 2 3 ...
+ *
+ * Otherwise odd_offset defines the physical offset for odd numbered
+ * slots. This is used for I2S and left/right justified modes to
+ * translate from logical slot numbers 0 1 2 3 ... into physical slot
+ * numbers 0 2 ... 3 4 ...
+ */
+static int bcm2835_i2s_convert_slot(unsigned int slot, unsigned int odd_offset)
+{
+ if (!odd_offset)
+ return slot;
+
+ if (slot & 1)
+ return (slot >> 1) + odd_offset;
+
+ return slot >> 1;
+}
+
+/*
+ * Calculate channel position from mask and slot width.
+ *
+ * Mask must contain exactly 2 set bits.
+ * Lowest set bit is channel 1 position, highest set bit channel 2.
+ * The constant offset is added to both channel positions.
+ *
+ * If odd_offset is > 0 slot positions are translated to
+ * I2S-style TDM slot numbering ( 0 2 ... 3 4 ...) with odd
+ * logical slot numbers starting at physical slot odd_offset.
+ */
+static void bcm2835_i2s_calc_channel_pos(
+ unsigned int *ch1_pos, unsigned int *ch2_pos,
+ unsigned int mask, unsigned int width,
+ unsigned int bit_offset, unsigned int odd_offset)
+{
+ *ch1_pos = bcm2835_i2s_convert_slot((ffs(mask) - 1), odd_offset)
+ * width + bit_offset;
+ *ch2_pos = bcm2835_i2s_convert_slot((fls(mask) - 1), odd_offset)
+ * width + bit_offset;
+}
+
+static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ unsigned int data_length, data_delay, framesync_length;
+ unsigned int slots, slot_width, odd_slot_offset;
+ int frame_length, bclk_rate;
+ unsigned int rx_mask, tx_mask;
+ unsigned int rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos;
+ unsigned int mode, format;
+ bool bit_clock_provider = false;
+ bool frame_sync_provider = false;
+ bool frame_start_falling_edge = false;
+ uint32_t csreg;
+ int ret = 0;
+
+ /*
+ * If a stream is already enabled,
+ * the registers are already set properly.
+ */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
+
+ if (csreg & (BCM2835_I2S_TXON | BCM2835_I2S_RXON))
+ return 0;
+
+ data_length = params_width(params);
+ data_delay = 0;
+ odd_slot_offset = 0;
+ mode = 0;
+
+ if (dev->tdm_slots) {
+ slots = dev->tdm_slots;
+ slot_width = dev->slot_width;
+ frame_length = dev->frame_length;
+ rx_mask = dev->rx_mask;
+ tx_mask = dev->tx_mask;
+ bclk_rate = dev->frame_length * params_rate(params);
+ } else {
+ slots = 2;
+ slot_width = params_width(params);
+ rx_mask = 0x03;
+ tx_mask = 0x03;
+
+ frame_length = snd_soc_params_to_frame_size(params);
+ if (frame_length < 0)
+ return frame_length;
+
+ bclk_rate = snd_soc_params_to_bclk(params);
+ if (bclk_rate < 0)
+ return bclk_rate;
+ }
+
+ /* Check if data fits into slots */
+ if (data_length > slot_width)
+ return -EINVAL;
+
+ /* Check if CPU is bit clock provider */
+ switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ case SND_SOC_DAIFMT_BP_FC:
+ bit_clock_provider = true;
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ case SND_SOC_DAIFMT_BC_FC:
+ bit_clock_provider = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if CPU is frame sync provider */
+ switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ case SND_SOC_DAIFMT_BC_FP:
+ frame_sync_provider = true;
+ break;
+ case SND_SOC_DAIFMT_BP_FC:
+ case SND_SOC_DAIFMT_BC_FC:
+ frame_sync_provider = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Clock should only be set up here if CPU is clock master */
+ if (bit_clock_provider &&
+ (!dev->clk_prepared || dev->clk_rate != bclk_rate)) {
+ if (dev->clk_prepared)
+ bcm2835_i2s_stop_clock(dev);
+
+ if (dev->clk_rate != bclk_rate) {
+ ret = clk_set_rate(dev->clk, bclk_rate);
+ if (ret)
+ return ret;
+ dev->clk_rate = bclk_rate;
+ }
+
+ bcm2835_i2s_start_clock(dev);
+ }
+
+ /* Setup the frame format */
+ format = BCM2835_I2S_CHEN;
+
+ if (data_length >= 24)
+ format |= BCM2835_I2S_CHWEX;
+
+ format |= BCM2835_I2S_CHWID((data_length-8)&0xf);
+
+ /* CH2 format is the same as for CH1 */
+ format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format);
+
+ switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* I2S mode needs an even number of slots */
+ if (slots & 1)
+ return -EINVAL;
+
+ /*
+ * Use I2S-style logical slot numbering: even slots
+ * are in first half of frame, odd slots in second half.
+ */
+ odd_slot_offset = slots >> 1;
+
+ /* MSB starts one cycle after frame start */
+ data_delay = 1;
+
+ /* Setup frame sync signal for 50% duty cycle */
+ framesync_length = frame_length / 2;
+ frame_start_falling_edge = true;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ if (slots & 1)
+ return -EINVAL;
+
+ odd_slot_offset = slots >> 1;
+ data_delay = 0;
+ framesync_length = frame_length / 2;
+ frame_start_falling_edge = false;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ if (slots & 1)
+ return -EINVAL;
+
+ /* Odd frame lengths aren't supported */
+ if (frame_length & 1)
+ return -EINVAL;
+
+ odd_slot_offset = slots >> 1;
+ data_delay = slot_width - data_length;
+ framesync_length = frame_length / 2;
+ frame_start_falling_edge = false;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ data_delay = 1;
+ framesync_length = 1;
+ frame_start_falling_edge = false;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ data_delay = 0;
+ framesync_length = 1;
+ frame_start_falling_edge = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ bcm2835_i2s_calc_channel_pos(&rx_ch1_pos, &rx_ch2_pos,
+ rx_mask, slot_width, data_delay, odd_slot_offset);
+ bcm2835_i2s_calc_channel_pos(&tx_ch1_pos, &tx_ch2_pos,
+ tx_mask, slot_width, data_delay, odd_slot_offset);
+
+ /*
+ * Transmitting data immediately after frame start, eg
+ * in left-justified or DSP mode A, only works stable
+ * if bcm2835 is the frame clock provider.
+ */
+ if ((!rx_ch1_pos || !tx_ch1_pos) && !frame_sync_provider)
+ dev_warn(dev->dev,
+ "Unstable consumer config detected, L/R may be swapped");
+
+ /*
+ * Set format for both streams.
+ * We cannot set another frame length
+ * (and therefore word length) anyway,
+ * so the format will be the same.
+ */
+ regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG,
+ format
+ | BCM2835_I2S_CH1_POS(rx_ch1_pos)
+ | BCM2835_I2S_CH2_POS(rx_ch2_pos));
+ regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG,
+ format
+ | BCM2835_I2S_CH1_POS(tx_ch1_pos)
+ | BCM2835_I2S_CH2_POS(tx_ch2_pos));
+
+ /* Setup the I2S mode */
+
+ if (data_length <= 16) {
+ /*
+ * Use frame packed mode (2 channels per 32 bit word)
+ * We cannot set another frame length in the second stream
+ * (and therefore word length) anyway,
+ * so the format will be the same.
+ */
+ mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP;
+ }
+
+ mode |= BCM2835_I2S_FLEN(frame_length - 1);
+ mode |= BCM2835_I2S_FSLEN(framesync_length);
+
+ /* CLKM selects bcm2835 clock slave mode */
+ if (!bit_clock_provider)
+ mode |= BCM2835_I2S_CLKM;
+
+ /* FSM selects bcm2835 frame sync slave mode */
+ if (!frame_sync_provider)
+ mode |= BCM2835_I2S_FSM;
+
+ /* CLKI selects normal clocking mode, sampling on rising edge */
+ switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_NB_IF:
+ mode |= BCM2835_I2S_CLKI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* FSI selects frame start on falling edge */
+ switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_IB_NF:
+ if (frame_start_falling_edge)
+ mode |= BCM2835_I2S_FSI;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ case SND_SOC_DAIFMT_IB_IF:
+ if (!frame_start_falling_edge)
+ mode |= BCM2835_I2S_FSI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(dev->i2s_regmap, BCM2835_I2S_MODE_A_REG, mode);
+
+ /* Setup the DMA parameters */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_RXTHR(1)
+ | BCM2835_I2S_TXTHR(1)
+ | BCM2835_I2S_DMAEN, 0xffffffff);
+
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_DREQ_A_REG,
+ BCM2835_I2S_TX_PANIC(0x10)
+ | BCM2835_I2S_RX_PANIC(0x30)
+ | BCM2835_I2S_TX(0x30)
+ | BCM2835_I2S_RX(0x20), 0xffffffff);
+
+ /* Clear FIFOs */
+ bcm2835_i2s_clear_fifos(dev, true, true);
+
+ dev_dbg(dev->dev,
+ "slots: %d width: %d rx mask: 0x%02x tx_mask: 0x%02x\n",
+ slots, slot_width, rx_mask, tx_mask);
+
+ dev_dbg(dev->dev, "frame len: %d sync len: %d data len: %d\n",
+ frame_length, framesync_length, data_length);
+
+ dev_dbg(dev->dev, "rx pos: %d,%d tx pos: %d,%d\n",
+ rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos);
+
+ dev_dbg(dev->dev, "sampling rate: %d bclk rate: %d\n",
+ params_rate(params), bclk_rate);
+
+ dev_dbg(dev->dev, "CLKM: %d CLKI: %d FSM: %d FSI: %d frame start: %s edge\n",
+ !!(mode & BCM2835_I2S_CLKM),
+ !!(mode & BCM2835_I2S_CLKI),
+ !!(mode & BCM2835_I2S_FSM),
+ !!(mode & BCM2835_I2S_FSI),
+ (mode & BCM2835_I2S_FSI) ? "falling" : "rising");
+
+ return ret;
+}
+
+static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ uint32_t cs_reg;
+
+ /*
+ * Clear both FIFOs if the one that should be started
+ * is not empty at the moment. This should only happen
+ * after overrun. Otherwise, hw_params would have cleared
+ * the FIFO.
+ */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &cs_reg);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ && !(cs_reg & BCM2835_I2S_TXE))
+ bcm2835_i2s_clear_fifos(dev, true, false);
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
+ && (cs_reg & BCM2835_I2S_RXD))
+ bcm2835_i2s_clear_fifos(dev, false, true);
+
+ return 0;
+}
+
+static void bcm2835_i2s_stop(struct bcm2835_i2s_dev *dev,
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ uint32_t mask;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ mask = BCM2835_I2S_RXON;
+ else
+ mask = BCM2835_I2S_TXON;
+
+ regmap_update_bits(dev->i2s_regmap,
+ BCM2835_I2S_CS_A_REG, mask, 0);
+
+ /* Stop also the clock when not SND_SOC_DAIFMT_CONT */
+ if (!snd_soc_dai_active(dai) && !(dev->fmt & SND_SOC_DAIFMT_CONT))
+ bcm2835_i2s_stop_clock(dev);
+}
+
+static int bcm2835_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ uint32_t mask;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ bcm2835_i2s_start_clock(dev);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ mask = BCM2835_I2S_RXON;
+ else
+ mask = BCM2835_I2S_TXON;
+
+ regmap_update_bits(dev->i2s_regmap,
+ BCM2835_I2S_CS_A_REG, mask, mask);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ bcm2835_i2s_stop(dev, substream, dai);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (snd_soc_dai_active(dai))
+ return 0;
+
+ /* Should this still be running stop it */
+ bcm2835_i2s_stop_clock(dev);
+
+ /* Enable PCM block */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_EN, BCM2835_I2S_EN);
+
+ /*
+ * Disable STBY.
+ * Requires at least 4 PCM clock cycles to take effect.
+ */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_STBY, BCM2835_I2S_STBY);
+
+ return 0;
+}
+
+static void bcm2835_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ bcm2835_i2s_stop(dev, substream, dai);
+
+ /* If both streams are stopped, disable module and clock */
+ if (snd_soc_dai_active(dai))
+ return;
+
+ /* Disable the module */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_EN, 0);
+
+ /*
+ * Stopping clock is necessary, because stop does
+ * not stop the clock when SND_SOC_DAIFMT_CONT
+ */
+ bcm2835_i2s_stop_clock(dev);
+}
+
+static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = {
+ .startup = bcm2835_i2s_startup,
+ .shutdown = bcm2835_i2s_shutdown,
+ .prepare = bcm2835_i2s_prepare,
+ .trigger = bcm2835_i2s_trigger,
+ .hw_params = bcm2835_i2s_hw_params,
+ .set_fmt = bcm2835_i2s_set_dai_fmt,
+ .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio,
+ .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot,
+};
+
+static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai,
+ &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK],
+ &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver bcm2835_i2s_dai = {
+ .name = "bcm2835-i2s",
+ .probe = bcm2835_i2s_dai_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 8000,
+ .rate_max = 384000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
+ | SNDRV_PCM_FMTBIT_S24_LE
+ | SNDRV_PCM_FMTBIT_S32_LE
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 8000,
+ .rate_max = 384000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
+ | SNDRV_PCM_FMTBIT_S24_LE
+ | SNDRV_PCM_FMTBIT_S32_LE
+ },
+ .ops = &bcm2835_i2s_dai_ops,
+ .symmetric_rate = 1,
+ .symmetric_sample_bits = 1,
+};
+
+static bool bcm2835_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BCM2835_I2S_CS_A_REG:
+ case BCM2835_I2S_FIFO_A_REG:
+ case BCM2835_I2S_INTSTC_A_REG:
+ case BCM2835_I2S_GRAY_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool bcm2835_i2s_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BCM2835_I2S_FIFO_A_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config bcm2835_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = BCM2835_I2S_GRAY_REG,
+ .precious_reg = bcm2835_i2s_precious_reg,
+ .volatile_reg = bcm2835_i2s_volatile_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct snd_soc_component_driver bcm2835_i2s_component = {
+ .name = "bcm2835-i2s-comp",
+ .legacy_dai_naming = 1,
+};
+
+static int bcm2835_i2s_probe(struct platform_device *pdev)
+{
+ struct bcm2835_i2s_dev *dev;
+ int ret;
+ void __iomem *base;
+ const __be32 *addr;
+ dma_addr_t dma_base;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
+ GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* get the clock */
+ dev->clk_prepared = false;
+ dev->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(dev->clk),
+ "could not get clk\n");
+
+ /* Request ioarea */
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ dev->i2s_regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &bcm2835_regmap_config);
+ if (IS_ERR(dev->i2s_regmap))
+ return PTR_ERR(dev->i2s_regmap);
+
+ /* Set the DMA address - we have to parse DT ourselves */
+ addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
+ if (!addr) {
+ dev_err(&pdev->dev, "could not get DMA-register address\n");
+ return -EINVAL;
+ }
+ dma_base = be32_to_cpup(addr);
+
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
+ dma_base + BCM2835_I2S_FIFO_A_REG;
+
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
+ dma_base + BCM2835_I2S_FIFO_A_REG;
+
+ /* Set the bus width */
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
+ DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr_width =
+ DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ /* Set burst */
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].maxburst = 2;
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].maxburst = 2;
+
+ /*
+ * Set the PACK flag to enable S16_LE support (2 S16_LE values
+ * packed into 32-bit transfers).
+ */
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].flags =
+ SND_DMAENGINE_PCM_DAI_FLAG_PACK;
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].flags =
+ SND_DMAENGINE_PCM_DAI_FLAG_PACK;
+
+ /* Store the pdev */
+ dev->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &bcm2835_i2s_component, &bcm2835_i2s_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm2835_i2s_of_match[] = {
+ { .compatible = "brcm,bcm2835-i2s", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_i2s_of_match);
+
+static struct platform_driver bcm2835_i2s_driver = {
+ .probe = bcm2835_i2s_probe,
+ .driver = {
+ .name = "bcm2835-i2s",
+ .of_match_table = bcm2835_i2s_of_match,
+ },
+};
+
+module_platform_driver(bcm2835_i2s_driver);
+
+MODULE_ALIAS("platform:bcm2835-i2s");
+MODULE_DESCRIPTION("BCM2835 I2S interface");
+MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/bcm63xx-i2s-whistler.c b/sound/soc/bcm/bcm63xx-i2s-whistler.c
new file mode 100644
index 000000000..2da1384ff
--- /dev/null
+++ b/sound/soc/bcm/bcm63xx-i2s-whistler.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// linux/sound/bcm/bcm63xx-i2s-whistler.c
+// BCM63xx whistler i2s driver
+// Copyright (c) 2020 Broadcom Corporation
+// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com>
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "bcm63xx-i2s.h"
+
+#define DRV_NAME "brcm-i2s"
+
+static bool brcm_i2s_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case I2S_TX_CFG ... I2S_TX_DESC_IFF_LEN:
+ case I2S_TX_CFG_2 ... I2S_RX_DESC_IFF_LEN:
+ case I2S_RX_CFG_2 ... I2S_REG_MAX:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool brcm_i2s_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case I2S_TX_CFG ... I2S_REG_MAX:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool brcm_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case I2S_TX_CFG:
+ case I2S_TX_IRQ_CTL:
+ case I2S_TX_DESC_IFF_ADDR:
+ case I2S_TX_DESC_IFF_LEN:
+ case I2S_TX_DESC_OFF_ADDR:
+ case I2S_TX_DESC_OFF_LEN:
+ case I2S_TX_CFG_2:
+ case I2S_RX_CFG:
+ case I2S_RX_IRQ_CTL:
+ case I2S_RX_DESC_OFF_ADDR:
+ case I2S_RX_DESC_OFF_LEN:
+ case I2S_RX_DESC_IFF_LEN:
+ case I2S_RX_DESC_IFF_ADDR:
+ case I2S_RX_CFG_2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config brcm_i2s_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = I2S_REG_MAX,
+ .writeable_reg = brcm_i2s_wr_reg,
+ .readable_reg = brcm_i2s_rd_reg,
+ .volatile_reg = brcm_i2s_volatile_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int bcm63xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai);
+
+ ret = clk_set_rate(i2s_priv->i2s_clk, params_rate(params));
+ if (ret < 0)
+ dev_err(i2s_priv->dev,
+ "Can't set sample rate, err: %d\n", ret);
+
+ return ret;
+}
+
+static int bcm63xx_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ unsigned int slavemode;
+ struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai);
+ struct regmap *regmap_i2s = i2s_priv->regmap_i2s;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_update_bits(regmap_i2s, I2S_TX_CFG,
+ I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT |
+ I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE,
+ I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT |
+ I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE);
+ regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 0);
+ regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 0);
+ regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 1);
+
+ /* TX and RX block each have an independent bit to indicate
+ * if it is generating the clock for the I2S bus. The bus
+ * clocks need to be generated from either the TX or RX block,
+ * but not both
+ */
+ regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode);
+ if (slavemode & I2S_RX_SLAVE_MODE_MASK)
+ regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
+ I2S_TX_SLAVE_MODE_MASK,
+ I2S_TX_MASTER_MODE);
+ else
+ regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
+ I2S_TX_SLAVE_MODE_MASK,
+ I2S_TX_SLAVE_MODE);
+ } else {
+ regmap_update_bits(regmap_i2s, I2S_RX_CFG,
+ I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT |
+ I2S_RX_CLOCK_ENABLE,
+ I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT |
+ I2S_RX_CLOCK_ENABLE);
+ regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 0);
+ regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 0);
+ regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 1);
+
+ regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode);
+ if (slavemode & I2S_TX_SLAVE_MODE_MASK)
+ regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
+ I2S_RX_SLAVE_MODE_MASK, 0);
+ else
+ regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
+ I2S_RX_SLAVE_MODE_MASK,
+ I2S_RX_SLAVE_MODE);
+ }
+ return 0;
+}
+
+static void bcm63xx_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ unsigned int enabled, slavemode;
+ struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai);
+ struct regmap *regmap_i2s = i2s_priv->regmap_i2s;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_update_bits(regmap_i2s, I2S_TX_CFG,
+ I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT |
+ I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, 0);
+ regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 1);
+ regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 4);
+ regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 4);
+
+ regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode);
+ slavemode = slavemode & I2S_TX_SLAVE_MODE_MASK;
+ if (!slavemode) {
+ regmap_read(regmap_i2s, I2S_RX_CFG, &enabled);
+ enabled = enabled & I2S_RX_ENABLE_MASK;
+ if (enabled)
+ regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
+ I2S_RX_SLAVE_MODE_MASK,
+ I2S_RX_MASTER_MODE);
+ }
+ regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
+ I2S_TX_SLAVE_MODE_MASK,
+ I2S_TX_SLAVE_MODE);
+ } else {
+ regmap_update_bits(regmap_i2s, I2S_RX_CFG,
+ I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT |
+ I2S_RX_CLOCK_ENABLE, 0);
+ regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 1);
+ regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 4);
+ regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 4);
+
+ regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode);
+ slavemode = slavemode & I2S_RX_SLAVE_MODE_MASK;
+ if (!slavemode) {
+ regmap_read(regmap_i2s, I2S_TX_CFG, &enabled);
+ enabled = enabled & I2S_TX_ENABLE_MASK;
+ if (enabled)
+ regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
+ I2S_TX_SLAVE_MODE_MASK,
+ I2S_TX_MASTER_MODE);
+ }
+
+ regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
+ I2S_RX_SLAVE_MODE_MASK, I2S_RX_SLAVE_MODE);
+ }
+}
+
+static const struct snd_soc_dai_ops bcm63xx_i2s_dai_ops = {
+ .startup = bcm63xx_i2s_startup,
+ .shutdown = bcm63xx_i2s_shutdown,
+ .hw_params = bcm63xx_i2s_hw_params,
+};
+
+static struct snd_soc_dai_driver bcm63xx_i2s_dai = {
+ .name = DRV_NAME,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &bcm63xx_i2s_dai_ops,
+ .symmetric_rate = 1,
+ .symmetric_channels = 1,
+};
+
+static const struct snd_soc_component_driver bcm63xx_i2s_component = {
+ .name = "bcm63xx",
+ .legacy_dai_naming = 1,
+};
+
+static int bcm63xx_i2s_dev_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ void __iomem *regs;
+ struct resource *r_mem, *region;
+ struct bcm_i2s_priv *i2s_priv;
+ struct regmap *regmap_i2s;
+ struct clk *i2s_clk;
+
+ i2s_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_priv), GFP_KERNEL);
+ if (!i2s_priv)
+ return -ENOMEM;
+
+ i2s_clk = devm_clk_get(&pdev->dev, "i2sclk");
+ if (IS_ERR(i2s_clk)) {
+ dev_err(&pdev->dev, "%s: cannot get a brcm clock: %ld\n",
+ __func__, PTR_ERR(i2s_clk));
+ return PTR_ERR(i2s_clk);
+ }
+
+ r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r_mem) {
+ dev_err(&pdev->dev, "Unable to get register resource.\n");
+ return -ENODEV;
+ }
+
+ region = devm_request_mem_region(&pdev->dev, r_mem->start,
+ resource_size(r_mem), DRV_NAME);
+ if (!region) {
+ dev_err(&pdev->dev, "Memory region already claimed\n");
+ return -EBUSY;
+ }
+
+ regs = devm_ioremap_resource(&pdev->dev, r_mem);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ return ret;
+ }
+
+ regmap_i2s = devm_regmap_init_mmio(&pdev->dev,
+ regs, &brcm_i2s_regmap_config);
+ if (IS_ERR(regmap_i2s))
+ return PTR_ERR(regmap_i2s);
+
+ regmap_update_bits(regmap_i2s, I2S_MISC_CFG,
+ I2S_PAD_LVL_LOOP_DIS_MASK,
+ I2S_PAD_LVL_LOOP_DIS_ENABLE);
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &bcm63xx_i2s_component,
+ &bcm63xx_i2s_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register the dai\n");
+ return ret;
+ }
+
+ i2s_priv->dev = &pdev->dev;
+ i2s_priv->i2s_clk = i2s_clk;
+ i2s_priv->regmap_i2s = regmap_i2s;
+ dev_set_drvdata(&pdev->dev, i2s_priv);
+
+ ret = bcm63xx_soc_platform_probe(pdev, i2s_priv);
+ if (ret)
+ dev_err(&pdev->dev, "failed to register the pcm\n");
+
+ return ret;
+}
+
+static int bcm63xx_i2s_dev_remove(struct platform_device *pdev)
+{
+ bcm63xx_soc_platform_remove(pdev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id snd_soc_bcm_audio_match[] = {
+ {.compatible = "brcm,bcm63xx-i2s"},
+ { }
+};
+#endif
+
+static struct platform_driver bcm63xx_i2s_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = of_match_ptr(snd_soc_bcm_audio_match),
+ },
+ .probe = bcm63xx_i2s_dev_probe,
+ .remove = bcm63xx_i2s_dev_remove,
+};
+
+module_platform_driver(bcm63xx_i2s_driver);
+
+MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom DSL XPON ASOC I2S Interface");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/bcm63xx-i2s.h b/sound/soc/bcm/bcm63xx-i2s.h
new file mode 100644
index 000000000..f30556bec
--- /dev/null
+++ b/sound/soc/bcm/bcm63xx-i2s.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// linux/sound/soc/bcm/bcm63xx-i2s.h
+// Copyright (c) 2020 Broadcom Corporation
+// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com>
+
+#ifndef __BCM63XX_I2S_H
+#define __BCM63XX_I2S_H
+
+#define I2S_DESC_FIFO_DEPTH 8
+#define I2S_MISC_CFG (0x003C)
+#define I2S_PAD_LVL_LOOP_DIS_MASK (1 << 2)
+#define I2S_PAD_LVL_LOOP_DIS_ENABLE I2S_PAD_LVL_LOOP_DIS_MASK
+
+#define I2S_TX_ENABLE_MASK (1 << 31)
+#define I2S_TX_ENABLE I2S_TX_ENABLE_MASK
+#define I2S_TX_OUT_R (1 << 19)
+#define I2S_TX_DATA_ALIGNMENT (1 << 2)
+#define I2S_TX_DATA_ENABLE (1 << 1)
+#define I2S_TX_CLOCK_ENABLE (1 << 0)
+
+#define I2S_TX_DESC_OFF_LEVEL_SHIFT 12
+#define I2S_TX_DESC_OFF_LEVEL_MASK (0x0F << I2S_TX_DESC_OFF_LEVEL_SHIFT)
+#define I2S_TX_DESC_IFF_LEVEL_SHIFT 8
+#define I2S_TX_DESC_IFF_LEVEL_MASK (0x0F << I2S_TX_DESC_IFF_LEVEL_SHIFT)
+#define I2S_TX_DESC_OFF_INTR_EN_MSK (1 << 1)
+#define I2S_TX_DESC_OFF_INTR_EN I2S_TX_DESC_OFF_INTR_EN_MSK
+
+#define I2S_TX_CFG (0x0000)
+#define I2S_TX_IRQ_CTL (0x0004)
+#define I2S_TX_IRQ_EN (0x0008)
+#define I2S_TX_IRQ_IFF_THLD (0x000c)
+#define I2S_TX_IRQ_OFF_THLD (0x0010)
+#define I2S_TX_DESC_IFF_ADDR (0x0014)
+#define I2S_TX_DESC_IFF_LEN (0x0018)
+#define I2S_TX_DESC_OFF_ADDR (0x001C)
+#define I2S_TX_DESC_OFF_LEN (0x0020)
+#define I2S_TX_CFG_2 (0x0024)
+#define I2S_TX_SLAVE_MODE_SHIFT 13
+#define I2S_TX_SLAVE_MODE_MASK (1 << I2S_TX_SLAVE_MODE_SHIFT)
+#define I2S_TX_SLAVE_MODE I2S_TX_SLAVE_MODE_MASK
+#define I2S_TX_MASTER_MODE 0
+#define I2S_TX_INTR_MASK 0x0F
+
+#define I2S_RX_ENABLE_MASK (1 << 31)
+#define I2S_RX_ENABLE I2S_RX_ENABLE_MASK
+#define I2S_RX_IN_R (1 << 19)
+#define I2S_RX_DATA_ALIGNMENT (1 << 2)
+#define I2S_RX_CLOCK_ENABLE (1 << 0)
+
+#define I2S_RX_DESC_OFF_LEVEL_SHIFT 12
+#define I2S_RX_DESC_OFF_LEVEL_MASK (0x0F << I2S_RX_DESC_OFF_LEVEL_SHIFT)
+#define I2S_RX_DESC_IFF_LEVEL_SHIFT 8
+#define I2S_RX_DESC_IFF_LEVEL_MASK (0x0F << I2S_RX_DESC_IFF_LEVEL_SHIFT)
+#define I2S_RX_DESC_OFF_INTR_EN_MSK (1 << 1)
+#define I2S_RX_DESC_OFF_INTR_EN I2S_RX_DESC_OFF_INTR_EN_MSK
+
+#define I2S_RX_CFG (0x0040) /* 20c0 */
+#define I2S_RX_IRQ_CTL (0x0044)
+#define I2S_RX_IRQ_EN (0x0048)
+#define I2S_RX_IRQ_IFF_THLD (0x004C)
+#define I2S_RX_IRQ_OFF_THLD (0x0050)
+#define I2S_RX_DESC_IFF_ADDR (0x0054)
+#define I2S_RX_DESC_IFF_LEN (0x0058)
+#define I2S_RX_DESC_OFF_ADDR (0x005C)
+#define I2S_RX_DESC_OFF_LEN (0x0060)
+#define I2S_RX_CFG_2 (0x0064)
+#define I2S_RX_SLAVE_MODE_SHIFT 13
+#define I2S_RX_SLAVE_MODE_MASK (1 << I2S_RX_SLAVE_MODE_SHIFT)
+#define I2S_RX_SLAVE_MODE I2S_RX_SLAVE_MODE_MASK
+#define I2S_RX_MASTER_MODE 0
+#define I2S_RX_INTR_MASK 0x0F
+
+#define I2S_REG_MAX 0x007C
+
+struct bcm_i2s_priv {
+ struct device *dev;
+ struct regmap *regmap_i2s;
+ struct clk *i2s_clk;
+ struct snd_pcm_substream *play_substream;
+ struct snd_pcm_substream *capture_substream;
+ struct i2s_dma_desc *play_dma_desc;
+ struct i2s_dma_desc *capture_dma_desc;
+};
+
+extern int bcm63xx_soc_platform_probe(struct platform_device *pdev,
+ struct bcm_i2s_priv *i2s_priv);
+extern int bcm63xx_soc_platform_remove(struct platform_device *pdev);
+
+#endif
diff --git a/sound/soc/bcm/bcm63xx-pcm-whistler.c b/sound/soc/bcm/bcm63xx-pcm-whistler.c
new file mode 100644
index 000000000..2c600b017
--- /dev/null
+++ b/sound/soc/bcm/bcm63xx-pcm-whistler.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// linux/sound/bcm/bcm63xx-pcm-whistler.c
+// BCM63xx whistler pcm interface
+// Copyright (c) 2020 Broadcom Corporation
+// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com>
+
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <sound/pcm_params.h>
+#include <linux/regmap.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include "bcm63xx-i2s.h"
+
+
+struct i2s_dma_desc {
+ unsigned char *dma_area;
+ dma_addr_t dma_addr;
+ unsigned int dma_len;
+};
+
+struct bcm63xx_runtime_data {
+ int dma_len;
+ dma_addr_t dma_addr;
+ dma_addr_t dma_addr_next;
+};
+
+static const struct snd_pcm_hardware bcm63xx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE, /* support S32 only */
+ .period_bytes_max = 8192 - 32,
+ .periods_min = 1,
+ .periods_max = PAGE_SIZE/sizeof(struct i2s_dma_desc),
+ .buffer_bytes_max = 128 * 1024,
+ .fifo_size = 32,
+};
+
+static int bcm63xx_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct i2s_dma_desc *dma_desc;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+
+ dma_desc = kzalloc(sizeof(*dma_desc), GFP_NOWAIT);
+ if (!dma_desc)
+ return -ENOMEM;
+
+ snd_soc_dai_set_dma_data(asoc_rtd_to_cpu(rtd, 0), substream, dma_desc);
+
+ return 0;
+}
+
+static int bcm63xx_pcm_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct i2s_dma_desc *dma_desc;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+
+ dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+ kfree(dma_desc);
+
+ return 0;
+}
+
+static int bcm63xx_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd;
+ struct bcm_i2s_priv *i2s_priv;
+ struct regmap *regmap_i2s;
+
+ rtd = asoc_substream_to_rtd(substream);
+ i2s_priv = dev_get_drvdata(asoc_rtd_to_cpu(rtd, 0)->dev);
+ regmap_i2s = i2s_priv->regmap_i2s;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ regmap_update_bits(regmap_i2s,
+ I2S_TX_IRQ_EN,
+ I2S_TX_DESC_OFF_INTR_EN,
+ I2S_TX_DESC_OFF_INTR_EN);
+ regmap_update_bits(regmap_i2s,
+ I2S_TX_CFG,
+ I2S_TX_ENABLE_MASK,
+ I2S_TX_ENABLE);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ regmap_write(regmap_i2s,
+ I2S_TX_IRQ_EN,
+ 0);
+ regmap_update_bits(regmap_i2s,
+ I2S_TX_CFG,
+ I2S_TX_ENABLE_MASK,
+ 0);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ } else {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ regmap_update_bits(regmap_i2s,
+ I2S_RX_IRQ_EN,
+ I2S_RX_DESC_OFF_INTR_EN_MSK,
+ I2S_RX_DESC_OFF_INTR_EN);
+ regmap_update_bits(regmap_i2s,
+ I2S_RX_CFG,
+ I2S_RX_ENABLE_MASK,
+ I2S_RX_ENABLE);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ regmap_update_bits(regmap_i2s,
+ I2S_RX_IRQ_EN,
+ I2S_RX_DESC_OFF_INTR_EN_MSK,
+ 0);
+ regmap_update_bits(regmap_i2s,
+ I2S_RX_CFG,
+ I2S_RX_ENABLE_MASK,
+ 0);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ }
+ return ret;
+}
+
+static int bcm63xx_pcm_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct i2s_dma_desc *dma_desc;
+ struct regmap *regmap_i2s;
+ struct bcm_i2s_priv *i2s_priv;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ uint32_t regaddr_desclen, regaddr_descaddr;
+
+ dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+ dma_desc->dma_len = snd_pcm_lib_period_bytes(substream);
+ dma_desc->dma_addr = runtime->dma_addr;
+ dma_desc->dma_area = runtime->dma_area;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regaddr_desclen = I2S_TX_DESC_IFF_LEN;
+ regaddr_descaddr = I2S_TX_DESC_IFF_ADDR;
+ } else {
+ regaddr_desclen = I2S_RX_DESC_IFF_LEN;
+ regaddr_descaddr = I2S_RX_DESC_IFF_ADDR;
+ }
+
+ i2s_priv = dev_get_drvdata(asoc_rtd_to_cpu(rtd, 0)->dev);
+ regmap_i2s = i2s_priv->regmap_i2s;
+
+ regmap_write(regmap_i2s, regaddr_desclen, dma_desc->dma_len);
+ regmap_write(regmap_i2s, regaddr_descaddr, dma_desc->dma_addr);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+bcm63xx_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ snd_pcm_uframes_t x;
+ struct bcm63xx_runtime_data *prtd = substream->runtime->private_data;
+
+ if (!prtd->dma_addr_next)
+ prtd->dma_addr_next = substream->runtime->dma_addr;
+
+ x = bytes_to_frames(substream->runtime,
+ prtd->dma_addr_next - substream->runtime->dma_addr);
+
+ return x == substream->runtime->buffer_size ? 0 : x;
+}
+
+static int bcm63xx_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm63xx_runtime_data *prtd;
+
+ runtime->hw = bcm63xx_pcm_hardware;
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ ret = -ENOMEM;
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (!prtd)
+ goto out;
+
+ runtime->private_data = prtd;
+ return 0;
+out:
+ return ret;
+}
+
+static int bcm63xx_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm63xx_runtime_data *prtd = runtime->private_data;
+
+ kfree(prtd);
+ return 0;
+}
+
+static irqreturn_t i2s_dma_isr(int irq, void *bcm_i2s_priv)
+{
+ unsigned int availdepth, ifflevel, offlevel, int_status, val_1, val_2;
+ struct bcm63xx_runtime_data *prtd;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ struct regmap *regmap_i2s;
+ struct i2s_dma_desc *dma_desc;
+ struct snd_soc_pcm_runtime *rtd;
+ struct bcm_i2s_priv *i2s_priv;
+
+ i2s_priv = (struct bcm_i2s_priv *)bcm_i2s_priv;
+ regmap_i2s = i2s_priv->regmap_i2s;
+
+ /* rx */
+ regmap_read(regmap_i2s, I2S_RX_IRQ_CTL, &int_status);
+
+ if (int_status & I2S_RX_DESC_OFF_INTR_EN_MSK) {
+ substream = i2s_priv->capture_substream;
+ runtime = substream->runtime;
+ rtd = asoc_substream_to_rtd(substream);
+ prtd = runtime->private_data;
+ dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+
+ offlevel = (int_status & I2S_RX_DESC_OFF_LEVEL_MASK) >>
+ I2S_RX_DESC_OFF_LEVEL_SHIFT;
+ while (offlevel) {
+ regmap_read(regmap_i2s, I2S_RX_DESC_OFF_ADDR, &val_1);
+ regmap_read(regmap_i2s, I2S_RX_DESC_OFF_LEN, &val_2);
+ offlevel--;
+ }
+ prtd->dma_addr_next = val_1 + val_2;
+ ifflevel = (int_status & I2S_RX_DESC_IFF_LEVEL_MASK) >>
+ I2S_RX_DESC_IFF_LEVEL_SHIFT;
+
+ availdepth = I2S_DESC_FIFO_DEPTH - ifflevel;
+ while (availdepth) {
+ dma_desc->dma_addr +=
+ snd_pcm_lib_period_bytes(substream);
+ dma_desc->dma_area +=
+ snd_pcm_lib_period_bytes(substream);
+ if (dma_desc->dma_addr - runtime->dma_addr >=
+ runtime->dma_bytes) {
+ dma_desc->dma_addr = runtime->dma_addr;
+ dma_desc->dma_area = runtime->dma_area;
+ }
+
+ prtd->dma_addr = dma_desc->dma_addr;
+ regmap_write(regmap_i2s, I2S_RX_DESC_IFF_LEN,
+ snd_pcm_lib_period_bytes(substream));
+ regmap_write(regmap_i2s, I2S_RX_DESC_IFF_ADDR,
+ dma_desc->dma_addr);
+ availdepth--;
+ }
+
+ snd_pcm_period_elapsed(substream);
+
+ /* Clear interrupt by writing 0 */
+ regmap_update_bits(regmap_i2s, I2S_RX_IRQ_CTL,
+ I2S_RX_INTR_MASK, 0);
+ }
+
+ /* tx */
+ regmap_read(regmap_i2s, I2S_TX_IRQ_CTL, &int_status);
+
+ if (int_status & I2S_TX_DESC_OFF_INTR_EN_MSK) {
+ substream = i2s_priv->play_substream;
+ runtime = substream->runtime;
+ rtd = asoc_substream_to_rtd(substream);
+ prtd = runtime->private_data;
+ dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+
+ offlevel = (int_status & I2S_TX_DESC_OFF_LEVEL_MASK) >>
+ I2S_TX_DESC_OFF_LEVEL_SHIFT;
+ while (offlevel) {
+ regmap_read(regmap_i2s, I2S_TX_DESC_OFF_ADDR, &val_1);
+ regmap_read(regmap_i2s, I2S_TX_DESC_OFF_LEN, &val_2);
+ prtd->dma_addr_next = val_1 + val_2;
+ offlevel--;
+ }
+
+ ifflevel = (int_status & I2S_TX_DESC_IFF_LEVEL_MASK) >>
+ I2S_TX_DESC_IFF_LEVEL_SHIFT;
+ availdepth = I2S_DESC_FIFO_DEPTH - ifflevel;
+
+ while (availdepth) {
+ dma_desc->dma_addr +=
+ snd_pcm_lib_period_bytes(substream);
+ dma_desc->dma_area +=
+ snd_pcm_lib_period_bytes(substream);
+
+ if (dma_desc->dma_addr - runtime->dma_addr >=
+ runtime->dma_bytes) {
+ dma_desc->dma_addr = runtime->dma_addr;
+ dma_desc->dma_area = runtime->dma_area;
+ }
+
+ prtd->dma_addr = dma_desc->dma_addr;
+ regmap_write(regmap_i2s, I2S_TX_DESC_IFF_LEN,
+ snd_pcm_lib_period_bytes(substream));
+ regmap_write(regmap_i2s, I2S_TX_DESC_IFF_ADDR,
+ dma_desc->dma_addr);
+ availdepth--;
+ }
+
+ snd_pcm_period_elapsed(substream);
+
+ /* Clear interrupt by writing 0 */
+ regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL,
+ I2S_TX_INTR_MASK, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int bcm63xx_soc_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ struct bcm_i2s_priv *i2s_priv;
+ int ret;
+
+ i2s_priv = dev_get_drvdata(asoc_rtd_to_cpu(rtd, 0)->dev);
+
+ of_dma_configure(pcm->card->dev, pcm->card->dev->of_node, 1);
+
+ ret = dma_coerce_mask_and_coherent(pcm->card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+ i2s_priv->play_substream =
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+ i2s_priv->capture_substream =
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
+ pcm->card->dev,
+ bcm63xx_pcm_hardware.buffer_bytes_max);
+}
+
+static const struct snd_soc_component_driver bcm63xx_soc_platform = {
+ .open = bcm63xx_pcm_open,
+ .close = bcm63xx_pcm_close,
+ .hw_params = bcm63xx_pcm_hw_params,
+ .hw_free = bcm63xx_pcm_hw_free,
+ .prepare = bcm63xx_pcm_prepare,
+ .trigger = bcm63xx_pcm_trigger,
+ .pointer = bcm63xx_pcm_pointer,
+ .pcm_construct = bcm63xx_soc_pcm_new,
+};
+
+int bcm63xx_soc_platform_probe(struct platform_device *pdev,
+ struct bcm_i2s_priv *i2s_priv)
+{
+ int ret;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_irq(&pdev->dev, ret, i2s_dma_isr,
+ irq_get_trigger_type(ret), "i2s_dma", (void *)i2s_priv);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "i2s_init: failed to request interrupt.ret=%d\n", ret);
+ return ret;
+ }
+
+ return devm_snd_soc_register_component(&pdev->dev,
+ &bcm63xx_soc_platform, NULL, 0);
+}
+
+int bcm63xx_soc_platform_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom DSL XPON ASOC PCM Interface");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c
new file mode 100644
index 000000000..8f488f929
--- /dev/null
+++ b/sound/soc/bcm/cygnus-pcm.c
@@ -0,0 +1,750 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2014-2015 Broadcom Corporation
+#include <linux/debugfs.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "cygnus-ssp.h"
+
+/* Register offset needed for ASoC PCM module */
+
+#define INTH_R5F_STATUS_OFFSET 0x040
+#define INTH_R5F_CLEAR_OFFSET 0x048
+#define INTH_R5F_MASK_SET_OFFSET 0x050
+#define INTH_R5F_MASK_CLEAR_OFFSET 0x054
+
+#define BF_REARM_FREE_MARK_OFFSET 0x344
+#define BF_REARM_FULL_MARK_OFFSET 0x348
+
+/* Ring Buffer Ctrl Regs --- Start */
+/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */
+#define SRC_RBUF_0_RDADDR_OFFSET 0x500
+#define SRC_RBUF_1_RDADDR_OFFSET 0x518
+#define SRC_RBUF_2_RDADDR_OFFSET 0x530
+#define SRC_RBUF_3_RDADDR_OFFSET 0x548
+#define SRC_RBUF_4_RDADDR_OFFSET 0x560
+#define SRC_RBUF_5_RDADDR_OFFSET 0x578
+#define SRC_RBUF_6_RDADDR_OFFSET 0x590
+
+/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */
+#define SRC_RBUF_0_WRADDR_OFFSET 0x504
+#define SRC_RBUF_1_WRADDR_OFFSET 0x51c
+#define SRC_RBUF_2_WRADDR_OFFSET 0x534
+#define SRC_RBUF_3_WRADDR_OFFSET 0x54c
+#define SRC_RBUF_4_WRADDR_OFFSET 0x564
+#define SRC_RBUF_5_WRADDR_OFFSET 0x57c
+#define SRC_RBUF_6_WRADDR_OFFSET 0x594
+
+/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */
+#define SRC_RBUF_0_BASEADDR_OFFSET 0x508
+#define SRC_RBUF_1_BASEADDR_OFFSET 0x520
+#define SRC_RBUF_2_BASEADDR_OFFSET 0x538
+#define SRC_RBUF_3_BASEADDR_OFFSET 0x550
+#define SRC_RBUF_4_BASEADDR_OFFSET 0x568
+#define SRC_RBUF_5_BASEADDR_OFFSET 0x580
+#define SRC_RBUF_6_BASEADDR_OFFSET 0x598
+
+/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */
+#define SRC_RBUF_0_ENDADDR_OFFSET 0x50c
+#define SRC_RBUF_1_ENDADDR_OFFSET 0x524
+#define SRC_RBUF_2_ENDADDR_OFFSET 0x53c
+#define SRC_RBUF_3_ENDADDR_OFFSET 0x554
+#define SRC_RBUF_4_ENDADDR_OFFSET 0x56c
+#define SRC_RBUF_5_ENDADDR_OFFSET 0x584
+#define SRC_RBUF_6_ENDADDR_OFFSET 0x59c
+
+/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */
+#define SRC_RBUF_0_FREE_MARK_OFFSET 0x510
+#define SRC_RBUF_1_FREE_MARK_OFFSET 0x528
+#define SRC_RBUF_2_FREE_MARK_OFFSET 0x540
+#define SRC_RBUF_3_FREE_MARK_OFFSET 0x558
+#define SRC_RBUF_4_FREE_MARK_OFFSET 0x570
+#define SRC_RBUF_5_FREE_MARK_OFFSET 0x588
+#define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0
+
+/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */
+#define DST_RBUF_0_RDADDR_OFFSET 0x5c0
+#define DST_RBUF_1_RDADDR_OFFSET 0x5d8
+#define DST_RBUF_2_RDADDR_OFFSET 0x5f0
+#define DST_RBUF_3_RDADDR_OFFSET 0x608
+#define DST_RBUF_4_RDADDR_OFFSET 0x620
+#define DST_RBUF_5_RDADDR_OFFSET 0x638
+
+/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */
+#define DST_RBUF_0_WRADDR_OFFSET 0x5c4
+#define DST_RBUF_1_WRADDR_OFFSET 0x5dc
+#define DST_RBUF_2_WRADDR_OFFSET 0x5f4
+#define DST_RBUF_3_WRADDR_OFFSET 0x60c
+#define DST_RBUF_4_WRADDR_OFFSET 0x624
+#define DST_RBUF_5_WRADDR_OFFSET 0x63c
+
+/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */
+#define DST_RBUF_0_BASEADDR_OFFSET 0x5c8
+#define DST_RBUF_1_BASEADDR_OFFSET 0x5e0
+#define DST_RBUF_2_BASEADDR_OFFSET 0x5f8
+#define DST_RBUF_3_BASEADDR_OFFSET 0x610
+#define DST_RBUF_4_BASEADDR_OFFSET 0x628
+#define DST_RBUF_5_BASEADDR_OFFSET 0x640
+
+/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */
+#define DST_RBUF_0_ENDADDR_OFFSET 0x5cc
+#define DST_RBUF_1_ENDADDR_OFFSET 0x5e4
+#define DST_RBUF_2_ENDADDR_OFFSET 0x5fc
+#define DST_RBUF_3_ENDADDR_OFFSET 0x614
+#define DST_RBUF_4_ENDADDR_OFFSET 0x62c
+#define DST_RBUF_5_ENDADDR_OFFSET 0x644
+
+/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */
+#define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0
+#define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8
+#define DST_RBUF_2_FULL_MARK_OFFSET 0x600
+#define DST_RBUF_3_FULL_MARK_OFFSET 0x618
+#define DST_RBUF_4_FULL_MARK_OFFSET 0x630
+#define DST_RBUF_5_FULL_MARK_OFFSET 0x648
+/* Ring Buffer Ctrl Regs --- End */
+
+/* Error Status Regs --- Start */
+/* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */
+#define ESR0_STATUS_OFFSET 0x900
+#define ESR1_STATUS_OFFSET 0x918
+#define ESR2_STATUS_OFFSET 0x930
+#define ESR3_STATUS_OFFSET 0x948
+#define ESR4_STATUS_OFFSET 0x960
+
+/* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */
+#define ESR0_STATUS_CLR_OFFSET 0x908
+#define ESR1_STATUS_CLR_OFFSET 0x920
+#define ESR2_STATUS_CLR_OFFSET 0x938
+#define ESR3_STATUS_CLR_OFFSET 0x950
+#define ESR4_STATUS_CLR_OFFSET 0x968
+
+/* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */
+#define ESR0_MASK_STATUS_OFFSET 0x90c
+#define ESR1_MASK_STATUS_OFFSET 0x924
+#define ESR2_MASK_STATUS_OFFSET 0x93c
+#define ESR3_MASK_STATUS_OFFSET 0x954
+#define ESR4_MASK_STATUS_OFFSET 0x96c
+
+/* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */
+#define ESR0_MASK_SET_OFFSET 0x910
+#define ESR1_MASK_SET_OFFSET 0x928
+#define ESR2_MASK_SET_OFFSET 0x940
+#define ESR3_MASK_SET_OFFSET 0x958
+#define ESR4_MASK_SET_OFFSET 0x970
+
+/* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */
+#define ESR0_MASK_CLR_OFFSET 0x914
+#define ESR1_MASK_CLR_OFFSET 0x92c
+#define ESR2_MASK_CLR_OFFSET 0x944
+#define ESR3_MASK_CLR_OFFSET 0x95c
+#define ESR4_MASK_CLR_OFFSET 0x974
+/* Error Status Regs --- End */
+
+#define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */
+#define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */
+#define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */
+#define R5F_ESR3_SHIFT 3 /* esr3 = freemark */
+#define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */
+
+
+/* Mask for R5F register. Set all relevant interrupt for playback handler */
+#define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \
+ BIT(R5F_ESR1_SHIFT) | \
+ BIT(R5F_ESR3_SHIFT))
+
+/* Mask for R5F register. Set all relevant interrupt for capture handler */
+#define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT))
+
+/*
+ * PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick.
+ * This number should be a multiple of 256. Minimum value is 256
+ */
+#define PERIOD_BYTES_MIN 0x100
+
+static const struct snd_pcm_hardware cygnus_pcm_hw = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+
+ /* A period is basically an interrupt */
+ .period_bytes_min = PERIOD_BYTES_MIN,
+ .period_bytes_max = 0x10000,
+
+ /* period_min/max gives range of approx interrupts per buffer */
+ .periods_min = 2,
+ .periods_max = 8,
+
+ /*
+ * maximum buffer size in bytes = period_bytes_max * periods_max
+ * We allocate this amount of data for each enabled channel
+ */
+ .buffer_bytes_max = 4 * 0x8000,
+};
+
+static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32);
+
+static struct cygnus_aio_port *cygnus_dai_get_dma_data(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
+
+ return snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(soc_runtime, 0), substream);
+}
+
+static void ringbuf_set_initial(void __iomem *audio_io,
+ struct ringbuf_regs *p_rbuf,
+ bool is_playback,
+ u32 start,
+ u32 periodsize,
+ u32 bufsize)
+{
+ u32 initial_rd;
+ u32 initial_wr;
+ u32 end;
+ u32 fmark_val; /* free or full mark */
+
+ p_rbuf->period_bytes = periodsize;
+ p_rbuf->buf_size = bufsize;
+
+ if (is_playback) {
+ /* Set the pointers to indicate full (flip uppermost bit) */
+ initial_rd = start;
+ initial_wr = initial_rd ^ BIT(31);
+ } else {
+ /* Set the pointers to indicate empty */
+ initial_wr = start;
+ initial_rd = initial_wr;
+ }
+
+ end = start + bufsize - 1;
+
+ /*
+ * The interrupt will fire when free/full mark is *exceeded*
+ * The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark
+ * to be PERIOD_BYTES_MIN less than the period size.
+ */
+ fmark_val = periodsize - PERIOD_BYTES_MIN;
+
+ writel(start, audio_io + p_rbuf->baseaddr);
+ writel(end, audio_io + p_rbuf->endaddr);
+ writel(fmark_val, audio_io + p_rbuf->fmark);
+ writel(initial_rd, audio_io + p_rbuf->rdaddr);
+ writel(initial_wr, audio_io + p_rbuf->wraddr);
+}
+
+static int configure_ringbuf_regs(struct snd_pcm_substream *substream)
+{
+ struct cygnus_aio_port *aio;
+ struct ringbuf_regs *p_rbuf;
+ int status = 0;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ /* Map the ssp portnum to a set of ring buffers. */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ p_rbuf = &aio->play_rb_regs;
+
+ switch (aio->portnum) {
+ case 0:
+ *p_rbuf = RINGBUF_REG_PLAYBACK(0);
+ break;
+ case 1:
+ *p_rbuf = RINGBUF_REG_PLAYBACK(2);
+ break;
+ case 2:
+ *p_rbuf = RINGBUF_REG_PLAYBACK(4);
+ break;
+ case 3: /* SPDIF */
+ *p_rbuf = RINGBUF_REG_PLAYBACK(6);
+ break;
+ default:
+ status = -EINVAL;
+ }
+ } else {
+ p_rbuf = &aio->capture_rb_regs;
+
+ switch (aio->portnum) {
+ case 0:
+ *p_rbuf = RINGBUF_REG_CAPTURE(0);
+ break;
+ case 1:
+ *p_rbuf = RINGBUF_REG_CAPTURE(2);
+ break;
+ case 2:
+ *p_rbuf = RINGBUF_REG_CAPTURE(4);
+ break;
+ default:
+ status = -EINVAL;
+ }
+ }
+
+ return status;
+}
+
+static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream)
+{
+ struct cygnus_aio_port *aio;
+ struct ringbuf_regs *p_rbuf = NULL;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ p_rbuf = &aio->play_rb_regs;
+ else
+ p_rbuf = &aio->capture_rb_regs;
+
+ return p_rbuf;
+}
+
+static void enable_intr(struct snd_pcm_substream *substream)
+{
+ struct cygnus_aio_port *aio;
+ u32 clear_mask;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ /* The port number maps to the bit position to be cleared */
+ clear_mask = BIT(aio->portnum);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* Clear interrupt status before enabling them */
+ writel(clear_mask, aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET);
+ /* Unmask the interrupts of the given port*/
+ writel(clear_mask, aio->cygaud->audio + ESR0_MASK_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR1_MASK_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR3_MASK_CLR_OFFSET);
+
+ writel(ANY_PLAYBACK_IRQ,
+ aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET);
+ } else {
+ writel(clear_mask, aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR2_MASK_CLR_OFFSET);
+ writel(clear_mask, aio->cygaud->audio + ESR4_MASK_CLR_OFFSET);
+
+ writel(ANY_CAPTURE_IRQ,
+ aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET);
+ }
+
+}
+
+static void disable_intr(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct cygnus_aio_port *aio;
+ u32 set_mask;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "%s on port %d\n", __func__, aio->portnum);
+
+ /* The port number maps to the bit position to be set */
+ set_mask = BIT(aio->portnum);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* Mask the interrupts of the given port*/
+ writel(set_mask, aio->cygaud->audio + ESR0_MASK_SET_OFFSET);
+ writel(set_mask, aio->cygaud->audio + ESR1_MASK_SET_OFFSET);
+ writel(set_mask, aio->cygaud->audio + ESR3_MASK_SET_OFFSET);
+ } else {
+ writel(set_mask, aio->cygaud->audio + ESR2_MASK_SET_OFFSET);
+ writel(set_mask, aio->cygaud->audio + ESR4_MASK_SET_OFFSET);
+ }
+
+}
+
+static int cygnus_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ enable_intr(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ disable_intr(substream);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream)
+{
+ struct cygnus_aio_port *aio;
+ struct ringbuf_regs *p_rbuf = NULL;
+ u32 regval;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ p_rbuf = get_ringbuf(substream);
+
+ /*
+ * If free/full mark interrupt occurs, provide timestamp
+ * to ALSA and update appropriate idx by period_bytes
+ */
+ snd_pcm_period_elapsed(substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* Set the ring buffer to full */
+ regval = readl(aio->cygaud->audio + p_rbuf->rdaddr);
+ regval = regval ^ BIT(31);
+ writel(regval, aio->cygaud->audio + p_rbuf->wraddr);
+ } else {
+ /* Set the ring buffer to empty */
+ regval = readl(aio->cygaud->audio + p_rbuf->wraddr);
+ writel(regval, aio->cygaud->audio + p_rbuf->rdaddr);
+ }
+}
+
+/*
+ * ESR0/1/3 status Description
+ * 0x1 I2S0_out port caused interrupt
+ * 0x2 I2S1_out port caused interrupt
+ * 0x4 I2S2_out port caused interrupt
+ * 0x8 SPDIF_out port caused interrupt
+ */
+static void handle_playback_irq(struct cygnus_audio *cygaud)
+{
+ void __iomem *audio_io;
+ u32 port;
+ u32 esr_status0, esr_status1, esr_status3;
+
+ audio_io = cygaud->audio;
+
+ /*
+ * ESR status gets updates with/without interrupts enabled.
+ * So, check the ESR mask, which provides interrupt enable/
+ * disable status and use it to determine which ESR status
+ * should be serviced.
+ */
+ esr_status0 = readl(audio_io + ESR0_STATUS_OFFSET);
+ esr_status0 &= ~readl(audio_io + ESR0_MASK_STATUS_OFFSET);
+ esr_status1 = readl(audio_io + ESR1_STATUS_OFFSET);
+ esr_status1 &= ~readl(audio_io + ESR1_MASK_STATUS_OFFSET);
+ esr_status3 = readl(audio_io + ESR3_STATUS_OFFSET);
+ esr_status3 &= ~readl(audio_io + ESR3_MASK_STATUS_OFFSET);
+
+ for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) {
+ u32 esrmask = BIT(port);
+
+ /*
+ * Ringbuffer or FIFO underflow
+ * If we get this interrupt then, it is also true that we have
+ * not yet responded to the freemark interrupt.
+ * Log a debug message. The freemark handler below will
+ * handle getting everything going again.
+ */
+ if ((esrmask & esr_status1) || (esrmask & esr_status0)) {
+ dev_dbg(cygaud->dev,
+ "Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n",
+ esr_status0, esr_status1, esr_status3);
+ }
+
+ /*
+ * Freemark is hit. This is the normal interrupt.
+ * In typical operation the read and write regs will be equal
+ */
+ if (esrmask & esr_status3) {
+ struct snd_pcm_substream *playstr;
+
+ playstr = cygaud->portinfo[port].play_stream;
+ cygnus_pcm_period_elapsed(playstr);
+ }
+ }
+
+ /* Clear ESR interrupt */
+ writel(esr_status0, audio_io + ESR0_STATUS_CLR_OFFSET);
+ writel(esr_status1, audio_io + ESR1_STATUS_CLR_OFFSET);
+ writel(esr_status3, audio_io + ESR3_STATUS_CLR_OFFSET);
+ /* Rearm freemark logic by writing 1 to the correct bit */
+ writel(esr_status3, audio_io + BF_REARM_FREE_MARK_OFFSET);
+}
+
+/*
+ * ESR2/4 status Description
+ * 0x1 I2S0_in port caused interrupt
+ * 0x2 I2S1_in port caused interrupt
+ * 0x4 I2S2_in port caused interrupt
+ */
+static void handle_capture_irq(struct cygnus_audio *cygaud)
+{
+ void __iomem *audio_io;
+ u32 port;
+ u32 esr_status2, esr_status4;
+
+ audio_io = cygaud->audio;
+
+ /*
+ * ESR status gets updates with/without interrupts enabled.
+ * So, check the ESR mask, which provides interrupt enable/
+ * disable status and use it to determine which ESR status
+ * should be serviced.
+ */
+ esr_status2 = readl(audio_io + ESR2_STATUS_OFFSET);
+ esr_status2 &= ~readl(audio_io + ESR2_MASK_STATUS_OFFSET);
+ esr_status4 = readl(audio_io + ESR4_STATUS_OFFSET);
+ esr_status4 &= ~readl(audio_io + ESR4_MASK_STATUS_OFFSET);
+
+ for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) {
+ u32 esrmask = BIT(port);
+
+ /*
+ * Ringbuffer or FIFO overflow
+ * If we get this interrupt then, it is also true that we have
+ * not yet responded to the fullmark interrupt.
+ * Log a debug message. The fullmark handler below will
+ * handle getting everything going again.
+ */
+ if (esrmask & esr_status2)
+ dev_dbg(cygaud->dev,
+ "Overflow: esr2=0x%x\n", esr_status2);
+
+ if (esrmask & esr_status4) {
+ struct snd_pcm_substream *capstr;
+
+ capstr = cygaud->portinfo[port].capture_stream;
+ cygnus_pcm_period_elapsed(capstr);
+ }
+ }
+
+ writel(esr_status2, audio_io + ESR2_STATUS_CLR_OFFSET);
+ writel(esr_status4, audio_io + ESR4_STATUS_CLR_OFFSET);
+ /* Rearm fullmark logic by writing 1 to the correct bit */
+ writel(esr_status4, audio_io + BF_REARM_FULL_MARK_OFFSET);
+}
+
+static irqreturn_t cygnus_dma_irq(int irq, void *data)
+{
+ u32 r5_status;
+ struct cygnus_audio *cygaud = data;
+
+ /*
+ * R5 status bits Description
+ * 0 ESR0 (playback FIFO interrupt)
+ * 1 ESR1 (playback rbuf interrupt)
+ * 2 ESR2 (capture rbuf interrupt)
+ * 3 ESR3 (Freemark play. interrupt)
+ * 4 ESR4 (Fullmark capt. interrupt)
+ */
+ r5_status = readl(cygaud->audio + INTH_R5F_STATUS_OFFSET);
+
+ if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ)))
+ return IRQ_NONE;
+
+ /* If playback interrupt happened */
+ if (ANY_PLAYBACK_IRQ & r5_status) {
+ handle_playback_irq(cygaud);
+ writel(ANY_PLAYBACK_IRQ & r5_status,
+ cygaud->audio + INTH_R5F_CLEAR_OFFSET);
+ }
+
+ /* If capture interrupt happened */
+ if (ANY_CAPTURE_IRQ & r5_status) {
+ handle_capture_irq(cygaud);
+ writel(ANY_CAPTURE_IRQ & r5_status,
+ cygaud->audio + INTH_R5F_CLEAR_OFFSET);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int cygnus_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct cygnus_aio_port *aio;
+ int ret;
+
+ aio = cygnus_dai_get_dma_data(substream);
+ if (!aio)
+ return -ENODEV;
+
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n", __func__, aio->portnum);
+
+ snd_soc_set_runtime_hwparams(substream, &cygnus_pcm_hw);
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN);
+ if (ret < 0)
+ return ret;
+ /*
+ * Keep track of which substream belongs to which port.
+ * This info is needed by snd_pcm_period_elapsed() in irq_handler
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ aio->play_stream = substream;
+ else
+ aio->capture_stream = substream;
+
+ return 0;
+}
+
+static int cygnus_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct cygnus_aio_port *aio;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n", __func__, aio->portnum);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ aio->play_stream = NULL;
+ else
+ aio->capture_stream = NULL;
+
+ if (!aio->play_stream && !aio->capture_stream)
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "freed port %d\n", aio->portnum);
+
+ return 0;
+}
+
+static int cygnus_pcm_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct cygnus_aio_port *aio;
+ unsigned long bufsize, periodsize;
+ bool is_play;
+ u32 start;
+ struct ringbuf_regs *p_rbuf = NULL;
+
+ aio = cygnus_dai_get_dma_data(substream);
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n", __func__, aio->portnum);
+
+ bufsize = snd_pcm_lib_buffer_bytes(substream);
+ periodsize = snd_pcm_lib_period_bytes(substream);
+
+ dev_dbg(asoc_rtd_to_cpu(rtd, 0)->dev, "%s (buf_size %lu) (period_size %lu)\n",
+ __func__, bufsize, periodsize);
+
+ configure_ringbuf_regs(substream);
+
+ p_rbuf = get_ringbuf(substream);
+
+ start = runtime->dma_addr;
+
+ is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0;
+
+ ringbuf_set_initial(aio->cygaud->audio, p_rbuf, is_play, start,
+ periodsize, bufsize);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct cygnus_aio_port *aio;
+ unsigned int res = 0, cur = 0, base = 0;
+ struct ringbuf_regs *p_rbuf = NULL;
+
+ aio = cygnus_dai_get_dma_data(substream);
+
+ /*
+ * Get the offset of the current read (for playack) or write
+ * index (for capture). Report this value back to the asoc framework.
+ */
+ p_rbuf = get_ringbuf(substream);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cur = readl(aio->cygaud->audio + p_rbuf->rdaddr);
+ else
+ cur = readl(aio->cygaud->audio + p_rbuf->wraddr);
+
+ base = readl(aio->cygaud->audio + p_rbuf->baseaddr);
+
+ /*
+ * Mask off the MSB of the rdaddr,wraddr and baseaddr
+ * since MSB is not part of the address
+ */
+ res = (cur & 0x7fffffff) - (base & 0x7fffffff);
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int cygnus_dma_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ size_t size = cygnus_pcm_hw.buffer_bytes_max;
+ struct snd_card *card = rtd->card->snd_card;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &cygnus_dma_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, size, size);
+
+ return 0;
+}
+
+static struct snd_soc_component_driver cygnus_soc_platform = {
+ .open = cygnus_pcm_open,
+ .close = cygnus_pcm_close,
+ .prepare = cygnus_pcm_prepare,
+ .trigger = cygnus_pcm_trigger,
+ .pointer = cygnus_pcm_pointer,
+ .pcm_construct = cygnus_dma_new,
+};
+
+int cygnus_soc_platform_register(struct device *dev,
+ struct cygnus_audio *cygaud)
+{
+ int rc;
+
+ dev_dbg(dev, "%s Enter\n", __func__);
+
+ rc = devm_request_irq(dev, cygaud->irq_num, cygnus_dma_irq,
+ IRQF_SHARED, "cygnus-audio", cygaud);
+ if (rc) {
+ dev_err(dev, "%s request_irq error %d\n", __func__, rc);
+ return rc;
+ }
+
+ rc = devm_snd_soc_register_component(dev, &cygnus_soc_platform,
+ NULL, 0);
+ if (rc) {
+ dev_err(dev, "%s failed\n", __func__);
+ return rc;
+ }
+
+ return 0;
+}
+
+int cygnus_soc_platform_unregister(struct device *dev)
+{
+ return 0;
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Cygnus ASoC PCM module");
diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c
new file mode 100644
index 000000000..2a92e33e1
--- /dev/null
+++ b/sound/soc/bcm/cygnus-ssp.c
@@ -0,0 +1,1407 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2014-2015 Broadcom Corporation
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "cygnus-ssp.h"
+
+#define DEFAULT_VCO 1354750204
+
+#define CAPTURE_FCI_ID_BASE 0x180
+#define CYGNUS_SSP_TRISTATE_MASK 0x001fff
+#define CYGNUS_PLLCLKSEL_MASK 0xf
+
+/* Used with stream_on field to indicate which streams are active */
+#define PLAYBACK_STREAM_MASK BIT(0)
+#define CAPTURE_STREAM_MASK BIT(1)
+
+#define I2S_STREAM_CFG_MASK 0xff003ff
+#define I2S_CAP_STREAM_CFG_MASK 0xf0
+#define SPDIF_STREAM_CFG_MASK 0x3ff
+#define CH_GRP_STEREO 0x1
+
+/* Begin register offset defines */
+#define AUD_MISC_SEROUT_OE_REG_BASE 0x01c
+#define AUD_MISC_SEROUT_SPDIF_OE 12
+#define AUD_MISC_SEROUT_MCLK_OE 3
+#define AUD_MISC_SEROUT_LRCK_OE 2
+#define AUD_MISC_SEROUT_SCLK_OE 1
+#define AUD_MISC_SEROUT_SDAT_OE 0
+
+/* AUD_FMM_BF_CTRL_xxx regs */
+#define BF_DST_CFG0_OFFSET 0x100
+#define BF_DST_CFG1_OFFSET 0x104
+#define BF_DST_CFG2_OFFSET 0x108
+
+#define BF_DST_CTRL0_OFFSET 0x130
+#define BF_DST_CTRL1_OFFSET 0x134
+#define BF_DST_CTRL2_OFFSET 0x138
+
+#define BF_SRC_CFG0_OFFSET 0x148
+#define BF_SRC_CFG1_OFFSET 0x14c
+#define BF_SRC_CFG2_OFFSET 0x150
+#define BF_SRC_CFG3_OFFSET 0x154
+
+#define BF_SRC_CTRL0_OFFSET 0x1c0
+#define BF_SRC_CTRL1_OFFSET 0x1c4
+#define BF_SRC_CTRL2_OFFSET 0x1c8
+#define BF_SRC_CTRL3_OFFSET 0x1cc
+
+#define BF_SRC_GRP0_OFFSET 0x1fc
+#define BF_SRC_GRP1_OFFSET 0x200
+#define BF_SRC_GRP2_OFFSET 0x204
+#define BF_SRC_GRP3_OFFSET 0x208
+
+#define BF_SRC_GRP_EN_OFFSET 0x320
+#define BF_SRC_GRP_FLOWON_OFFSET 0x324
+#define BF_SRC_GRP_SYNC_DIS_OFFSET 0x328
+
+/* AUD_FMM_IOP_OUT_I2S_xxx regs */
+#define OUT_I2S_0_STREAM_CFG_OFFSET 0xa00
+#define OUT_I2S_0_CFG_OFFSET 0xa04
+#define OUT_I2S_0_MCLK_CFG_OFFSET 0xa0c
+
+#define OUT_I2S_1_STREAM_CFG_OFFSET 0xa40
+#define OUT_I2S_1_CFG_OFFSET 0xa44
+#define OUT_I2S_1_MCLK_CFG_OFFSET 0xa4c
+
+#define OUT_I2S_2_STREAM_CFG_OFFSET 0xa80
+#define OUT_I2S_2_CFG_OFFSET 0xa84
+#define OUT_I2S_2_MCLK_CFG_OFFSET 0xa8c
+
+/* AUD_FMM_IOP_OUT_SPDIF_xxx regs */
+#define SPDIF_STREAM_CFG_OFFSET 0xac0
+#define SPDIF_CTRL_OFFSET 0xac4
+#define SPDIF_FORMAT_CFG_OFFSET 0xad8
+#define SPDIF_MCLK_CFG_OFFSET 0xadc
+
+/* AUD_FMM_IOP_PLL_0_xxx regs */
+#define IOP_PLL_0_MACRO_OFFSET 0xb00
+#define IOP_PLL_0_MDIV_Ch0_OFFSET 0xb14
+#define IOP_PLL_0_MDIV_Ch1_OFFSET 0xb18
+#define IOP_PLL_0_MDIV_Ch2_OFFSET 0xb1c
+
+#define IOP_PLL_0_ACTIVE_MDIV_Ch0_OFFSET 0xb30
+#define IOP_PLL_0_ACTIVE_MDIV_Ch1_OFFSET 0xb34
+#define IOP_PLL_0_ACTIVE_MDIV_Ch2_OFFSET 0xb38
+
+/* AUD_FMM_IOP_xxx regs */
+#define IOP_PLL_0_CONTROL_OFFSET 0xb04
+#define IOP_PLL_0_USER_NDIV_OFFSET 0xb08
+#define IOP_PLL_0_ACTIVE_NDIV_OFFSET 0xb20
+#define IOP_PLL_0_RESET_OFFSET 0xb5c
+
+/* AUD_FMM_IOP_IN_I2S_xxx regs */
+#define IN_I2S_0_STREAM_CFG_OFFSET 0x00
+#define IN_I2S_0_CFG_OFFSET 0x04
+#define IN_I2S_1_STREAM_CFG_OFFSET 0x40
+#define IN_I2S_1_CFG_OFFSET 0x44
+#define IN_I2S_2_STREAM_CFG_OFFSET 0x80
+#define IN_I2S_2_CFG_OFFSET 0x84
+
+/* AUD_FMM_IOP_MISC_xxx regs */
+#define IOP_SW_INIT_LOGIC 0x1c0
+
+/* End register offset defines */
+
+
+/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_0_REG */
+#define I2S_OUT_MCLKRATE_SHIFT 16
+
+/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_REG */
+#define I2S_OUT_PLLCLKSEL_SHIFT 0
+
+/* AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG */
+#define I2S_OUT_STREAM_ENA 31
+#define I2S_OUT_STREAM_CFG_GROUP_ID 20
+#define I2S_OUT_STREAM_CFG_CHANNEL_GROUPING 24
+
+/* AUD_FMM_IOP_IN_I2S_x_CAP */
+#define I2S_IN_STREAM_CFG_CAP_ENA 31
+#define I2S_IN_STREAM_CFG_0_GROUP_ID 4
+
+/* AUD_FMM_IOP_OUT_I2S_x_I2S_CFG_REG */
+#define I2S_OUT_CFGX_CLK_ENA 0
+#define I2S_OUT_CFGX_DATA_ENABLE 1
+#define I2S_OUT_CFGX_DATA_ALIGNMENT 6
+#define I2S_OUT_CFGX_BITS_PER_SLOT 13
+#define I2S_OUT_CFGX_VALID_SLOT 14
+#define I2S_OUT_CFGX_FSYNC_WIDTH 18
+#define I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32 26
+#define I2S_OUT_CFGX_SLAVE_MODE 30
+#define I2S_OUT_CFGX_TDM_MODE 31
+
+/* AUD_FMM_BF_CTRL_SOURCECH_CFGx_REG */
+#define BF_SRC_CFGX_SFIFO_ENA 0
+#define BF_SRC_CFGX_BUFFER_PAIR_ENABLE 1
+#define BF_SRC_CFGX_SAMPLE_CH_MODE 2
+#define BF_SRC_CFGX_SFIFO_SZ_DOUBLE 5
+#define BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY 10
+#define BF_SRC_CFGX_BIT_RES 20
+#define BF_SRC_CFGX_PROCESS_SEQ_ID_VALID 31
+
+/* AUD_FMM_BF_CTRL_DESTCH_CFGx_REG */
+#define BF_DST_CFGX_CAP_ENA 0
+#define BF_DST_CFGX_BUFFER_PAIR_ENABLE 1
+#define BF_DST_CFGX_DFIFO_SZ_DOUBLE 2
+#define BF_DST_CFGX_NOT_PAUSE_WHEN_FULL 11
+#define BF_DST_CFGX_FCI_ID 12
+#define BF_DST_CFGX_CAP_MODE 24
+#define BF_DST_CFGX_PROC_SEQ_ID_VALID 31
+
+/* AUD_FMM_IOP_OUT_SPDIF_xxx */
+#define SPDIF_0_OUT_DITHER_ENA 3
+#define SPDIF_0_OUT_STREAM_ENA 31
+
+/* AUD_FMM_IOP_PLL_0_USER */
+#define IOP_PLL_0_USER_NDIV_FRAC 10
+
+/* AUD_FMM_IOP_PLL_0_ACTIVE */
+#define IOP_PLL_0_ACTIVE_NDIV_FRAC 10
+
+
+#define INIT_SSP_REGS(num) (struct cygnus_ssp_regs){ \
+ .i2s_stream_cfg = OUT_I2S_ ##num## _STREAM_CFG_OFFSET, \
+ .i2s_cap_stream_cfg = IN_I2S_ ##num## _STREAM_CFG_OFFSET, \
+ .i2s_cfg = OUT_I2S_ ##num## _CFG_OFFSET, \
+ .i2s_cap_cfg = IN_I2S_ ##num## _CFG_OFFSET, \
+ .i2s_mclk_cfg = OUT_I2S_ ##num## _MCLK_CFG_OFFSET, \
+ .bf_destch_ctrl = BF_DST_CTRL ##num## _OFFSET, \
+ .bf_destch_cfg = BF_DST_CFG ##num## _OFFSET, \
+ .bf_sourcech_ctrl = BF_SRC_CTRL ##num## _OFFSET, \
+ .bf_sourcech_cfg = BF_SRC_CFG ##num## _OFFSET, \
+ .bf_sourcech_grp = BF_SRC_GRP ##num## _OFFSET \
+}
+
+struct pll_macro_entry {
+ u32 mclk;
+ u32 pll_ch_num;
+};
+
+/*
+ * PLL has 3 output channels (1x, 2x, and 4x). Below are
+ * the common MCLK frequencies used by audio driver
+ */
+static const struct pll_macro_entry pll_predef_mclk[] = {
+ { 4096000, 0},
+ { 8192000, 1},
+ {16384000, 2},
+
+ { 5644800, 0},
+ {11289600, 1},
+ {22579200, 2},
+
+ { 6144000, 0},
+ {12288000, 1},
+ {24576000, 2},
+
+ {12288000, 0},
+ {24576000, 1},
+ {49152000, 2},
+
+ {22579200, 0},
+ {45158400, 1},
+ {90316800, 2},
+
+ {24576000, 0},
+ {49152000, 1},
+ {98304000, 2},
+};
+
+#define CYGNUS_RATE_MIN 8000
+#define CYGNUS_RATE_MAX 384000
+
+/* List of valid frame sizes for tdm mode */
+static const int ssp_valid_tdm_framesize[] = {32, 64, 128, 256, 512};
+
+static const unsigned int cygnus_rates[] = {
+ 8000, 11025, 16000, 22050, 32000, 44100, 48000,
+ 88200, 96000, 176400, 192000, 352800, 384000
+};
+
+static const struct snd_pcm_hw_constraint_list cygnus_rate_constraint = {
+ .count = ARRAY_SIZE(cygnus_rates),
+ .list = cygnus_rates,
+};
+
+static struct cygnus_aio_port *cygnus_dai_get_portinfo(struct snd_soc_dai *dai)
+{
+ struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai);
+
+ return &cygaud->portinfo[dai->id];
+}
+
+static int audio_ssp_init_portregs(struct cygnus_aio_port *aio)
+{
+ u32 value, fci_id;
+ int status = 0;
+
+ switch (aio->port_type) {
+ case PORT_TDM:
+ value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+ value &= ~I2S_STREAM_CFG_MASK;
+
+ /* Set Group ID */
+ writel(aio->portnum,
+ aio->cygaud->audio + aio->regs.bf_sourcech_grp);
+
+ /* Configure the AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG reg */
+ value |= aio->portnum << I2S_OUT_STREAM_CFG_GROUP_ID;
+ value |= aio->portnum; /* FCI ID is the port num */
+ value |= CH_GRP_STEREO << I2S_OUT_STREAM_CFG_CHANNEL_GROUPING;
+ writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+
+ /* Configure the AUD_FMM_BF_CTRL_SOURCECH_CFGX reg */
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY);
+ value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE);
+ value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ /* Configure the AUD_FMM_IOP_IN_I2S_x_CAP_STREAM_CFG_0 reg */
+ value = readl(aio->cygaud->i2s_in +
+ aio->regs.i2s_cap_stream_cfg);
+ value &= ~I2S_CAP_STREAM_CFG_MASK;
+ value |= aio->portnum << I2S_IN_STREAM_CFG_0_GROUP_ID;
+ writel(value, aio->cygaud->i2s_in +
+ aio->regs.i2s_cap_stream_cfg);
+
+ /* Configure the AUD_FMM_BF_CTRL_DESTCH_CFGX_REG_BASE reg */
+ fci_id = CAPTURE_FCI_ID_BASE + aio->portnum;
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg);
+ value |= BIT(BF_DST_CFGX_DFIFO_SZ_DOUBLE);
+ value &= ~BIT(BF_DST_CFGX_NOT_PAUSE_WHEN_FULL);
+ value |= (fci_id << BF_DST_CFGX_FCI_ID);
+ value |= BIT(BF_DST_CFGX_PROC_SEQ_ID_VALID);
+ writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
+
+ /* Enable the transmit pin for this port */
+ value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+ value &= ~BIT((aio->portnum * 4) + AUD_MISC_SEROUT_SDAT_OE);
+ writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+ break;
+ case PORT_SPDIF:
+ writel(aio->portnum, aio->cygaud->audio + BF_SRC_GRP3_OFFSET);
+
+ value = readl(aio->cygaud->audio + SPDIF_CTRL_OFFSET);
+ value |= BIT(SPDIF_0_OUT_DITHER_ENA);
+ writel(value, aio->cygaud->audio + SPDIF_CTRL_OFFSET);
+
+ /* Enable and set the FCI ID for the SPDIF channel */
+ value = readl(aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET);
+ value &= ~SPDIF_STREAM_CFG_MASK;
+ value |= aio->portnum; /* FCI ID is the port num */
+ value |= BIT(SPDIF_0_OUT_STREAM_ENA);
+ writel(value, aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET);
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY);
+ value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE);
+ value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ /* Enable the spdif output pin */
+ value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+ value &= ~BIT(AUD_MISC_SEROUT_SPDIF_OE);
+ writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+ break;
+ default:
+ dev_err(aio->cygaud->dev, "Port not supported\n");
+ status = -EINVAL;
+ }
+
+ return status;
+}
+
+static void audio_ssp_in_enable(struct cygnus_aio_port *aio)
+{
+ u32 value;
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg);
+ value |= BIT(BF_DST_CFGX_CAP_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
+
+ writel(0x1, aio->cygaud->audio + aio->regs.bf_destch_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value |= BIT(I2S_OUT_CFGX_CLK_ENA);
+ value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+
+ value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
+ value |= BIT(I2S_IN_STREAM_CFG_CAP_ENA);
+ writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
+
+ aio->streams_on |= CAPTURE_STREAM_MASK;
+}
+
+static void audio_ssp_in_disable(struct cygnus_aio_port *aio)
+{
+ u32 value;
+
+ value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
+ value &= ~BIT(I2S_IN_STREAM_CFG_CAP_ENA);
+ writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg);
+
+ aio->streams_on &= ~CAPTURE_STREAM_MASK;
+
+ /* If both playback and capture are off */
+ if (!aio->streams_on) {
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
+ value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+ }
+
+ writel(0x0, aio->cygaud->audio + aio->regs.bf_destch_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg);
+ value &= ~BIT(BF_DST_CFGX_CAP_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg);
+}
+
+static int audio_ssp_out_enable(struct cygnus_aio_port *aio)
+{
+ u32 value;
+ int status = 0;
+
+ switch (aio->port_type) {
+ case PORT_TDM:
+ value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+ value |= BIT(I2S_OUT_STREAM_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+
+ writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value |= BIT(I2S_OUT_CFGX_CLK_ENA);
+ value |= BIT(I2S_OUT_CFGX_DATA_ENABLE);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ aio->streams_on |= PLAYBACK_STREAM_MASK;
+ break;
+ case PORT_SPDIF:
+ value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
+ value |= 0x3;
+ writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
+
+ writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value |= BIT(BF_SRC_CFGX_SFIFO_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ break;
+ default:
+ dev_err(aio->cygaud->dev,
+ "Port not supported %d\n", aio->portnum);
+ status = -EINVAL;
+ }
+
+ return status;
+}
+
+static int audio_ssp_out_disable(struct cygnus_aio_port *aio)
+{
+ u32 value;
+ int status = 0;
+
+ switch (aio->port_type) {
+ case PORT_TDM:
+ aio->streams_on &= ~PLAYBACK_STREAM_MASK;
+
+ /* If both playback and capture are off */
+ if (!aio->streams_on) {
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value &= ~BIT(I2S_OUT_CFGX_CLK_ENA);
+ value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+ }
+
+ /* set group_sync_dis = 1 */
+ value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
+ value |= BIT(aio->portnum);
+ writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
+
+ writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ /* set group_sync_dis = 0 */
+ value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
+ value &= ~BIT(aio->portnum);
+ writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET);
+
+ value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+ value &= ~BIT(I2S_OUT_STREAM_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg);
+
+ /* IOP SW INIT on OUT_I2S_x */
+ value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value |= BIT(aio->portnum);
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ value &= ~BIT(aio->portnum);
+ writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC);
+ break;
+ case PORT_SPDIF:
+ value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
+ value &= ~0x3;
+ writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET);
+ writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl);
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ break;
+ default:
+ dev_err(aio->cygaud->dev,
+ "Port not supported %d\n", aio->portnum);
+ status = -EINVAL;
+ }
+
+ return status;
+}
+
+static int pll_configure_mclk(struct cygnus_audio *cygaud, u32 mclk,
+ struct cygnus_aio_port *aio)
+{
+ int i = 0, error;
+ bool found = false;
+ const struct pll_macro_entry *p_entry;
+ struct clk *ch_clk;
+
+ for (i = 0; i < ARRAY_SIZE(pll_predef_mclk); i++) {
+ p_entry = &pll_predef_mclk[i];
+ if (p_entry->mclk == mclk) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ dev_err(cygaud->dev,
+ "%s No valid mclk freq (%u) found!\n", __func__, mclk);
+ return -EINVAL;
+ }
+
+ ch_clk = cygaud->audio_clk[p_entry->pll_ch_num];
+
+ if ((aio->clk_trace.cap_en) && (!aio->clk_trace.cap_clk_en)) {
+ error = clk_prepare_enable(ch_clk);
+ if (error) {
+ dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n",
+ __func__, error);
+ return error;
+ }
+ aio->clk_trace.cap_clk_en = true;
+ }
+
+ if ((aio->clk_trace.play_en) && (!aio->clk_trace.play_clk_en)) {
+ error = clk_prepare_enable(ch_clk);
+ if (error) {
+ dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n",
+ __func__, error);
+ return error;
+ }
+ aio->clk_trace.play_clk_en = true;
+ }
+
+ error = clk_set_rate(ch_clk, mclk);
+ if (error) {
+ dev_err(cygaud->dev, "%s Set MCLK rate failed: %d\n",
+ __func__, error);
+ return error;
+ }
+
+ return p_entry->pll_ch_num;
+}
+
+static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio)
+{
+ u32 value;
+ u32 mask = 0xf;
+ u32 sclk;
+ u32 mclk_rate;
+ unsigned int bit_rate;
+ unsigned int ratio;
+
+ bit_rate = aio->bit_per_frame * aio->lrclk;
+
+ /*
+ * Check if the bit clock can be generated from the given MCLK.
+ * MCLK must be a perfect multiple of bit clock and must be one of the
+ * following values... (2,4,6,8,10,12,14)
+ */
+ if ((aio->mclk % bit_rate) != 0)
+ return -EINVAL;
+
+ ratio = aio->mclk / bit_rate;
+ switch (ratio) {
+ case 2:
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ case 12:
+ case 14:
+ mclk_rate = ratio / 2;
+ break;
+
+ default:
+ dev_err(aio->cygaud->dev,
+ "Invalid combination of MCLK and BCLK\n");
+ dev_err(aio->cygaud->dev, "lrclk = %u, bits/frame = %u, mclk = %u\n",
+ aio->lrclk, aio->bit_per_frame, aio->mclk);
+ return -EINVAL;
+ }
+
+ /* Set sclk rate */
+ switch (aio->port_type) {
+ case PORT_TDM:
+ sclk = aio->bit_per_frame;
+ if (sclk == 512)
+ sclk = 0;
+
+ /* sclks_per_1fs_div = sclk cycles/32 */
+ sclk /= 32;
+
+ /* Set number of bitclks per frame */
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value &= ~(mask << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32);
+ value |= sclk << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32;
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+ dev_dbg(aio->cygaud->dev,
+ "SCLKS_PER_1FS_DIV32 = 0x%x\n", value);
+ break;
+ case PORT_SPDIF:
+ break;
+ default:
+ dev_err(aio->cygaud->dev, "Unknown port type\n");
+ return -EINVAL;
+ }
+
+ /* Set MCLK_RATE ssp port (spdif and ssp are the same) */
+ value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
+ value &= ~(0xf << I2S_OUT_MCLKRATE_SHIFT);
+ value |= (mclk_rate << I2S_OUT_MCLKRATE_SHIFT);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
+
+ dev_dbg(aio->cygaud->dev, "mclk cfg reg = 0x%x\n", value);
+ dev_dbg(aio->cygaud->dev, "bits per frame = %u, mclk = %u Hz, lrclk = %u Hz\n",
+ aio->bit_per_frame, aio->mclk, aio->lrclk);
+ return 0;
+}
+
+static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai);
+ int rate, bitres;
+ u32 value;
+ u32 mask = 0x1f;
+ int ret = 0;
+
+ dev_dbg(aio->cygaud->dev, "%s port = %d\n", __func__, aio->portnum);
+ dev_dbg(aio->cygaud->dev, "params_channels %d\n",
+ params_channels(params));
+ dev_dbg(aio->cygaud->dev, "rate %d\n", params_rate(params));
+ dev_dbg(aio->cygaud->dev, "format %d\n", params_format(params));
+
+ rate = params_rate(params);
+
+ switch (aio->mode) {
+ case CYGNUS_SSPMODE_TDM:
+ if ((rate == 192000) && (params_channels(params) > 4)) {
+ dev_err(aio->cygaud->dev, "Cannot run %d channels at %dHz\n",
+ params_channels(params), rate);
+ return -EINVAL;
+ }
+ break;
+ case CYGNUS_SSPMODE_I2S:
+ aio->bit_per_frame = 64; /* I2S must be 64 bit per frame */
+ break;
+ default:
+ dev_err(aio->cygaud->dev,
+ "%s port running in unknown mode\n", __func__);
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value &= ~BIT(BF_SRC_CFGX_BUFFER_PAIR_ENABLE);
+ value &= ~BIT(BF_SRC_CFGX_SAMPLE_CH_MODE);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bitres = 16;
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ /* 32 bit mode is coded as 0 */
+ bitres = 0;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+ value &= ~(mask << BF_SRC_CFGX_BIT_RES);
+ value |= (bitres << BF_SRC_CFGX_BIT_RES);
+ writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg);
+
+ } else {
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ value = readl(aio->cygaud->audio +
+ aio->regs.bf_destch_cfg);
+ value |= BIT(BF_DST_CFGX_CAP_MODE);
+ writel(value, aio->cygaud->audio +
+ aio->regs.bf_destch_cfg);
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ value = readl(aio->cygaud->audio +
+ aio->regs.bf_destch_cfg);
+ value &= ~BIT(BF_DST_CFGX_CAP_MODE);
+ writel(value, aio->cygaud->audio +
+ aio->regs.bf_destch_cfg);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ aio->lrclk = rate;
+
+ if (!aio->is_slave)
+ ret = cygnus_ssp_set_clocks(aio);
+
+ return ret;
+}
+
+/*
+ * This function sets the mclk frequency for pll clock
+ */
+static int cygnus_ssp_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ int sel;
+ u32 value;
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai);
+ struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai);
+
+ dev_dbg(aio->cygaud->dev,
+ "%s Enter port = %d\n", __func__, aio->portnum);
+ sel = pll_configure_mclk(cygaud, freq, aio);
+ if (sel < 0) {
+ dev_err(aio->cygaud->dev,
+ "%s Setting mclk failed.\n", __func__);
+ return -EINVAL;
+ }
+
+ aio->mclk = freq;
+
+ dev_dbg(aio->cygaud->dev, "%s Setting MCLKSEL to %d\n", __func__, sel);
+ value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
+ value &= ~(0xf << I2S_OUT_PLLCLKSEL_SHIFT);
+ value |= (sel << I2S_OUT_PLLCLKSEL_SHIFT);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
+
+ return 0;
+}
+
+static int cygnus_ssp_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai);
+
+ snd_soc_dai_set_dma_data(dai, substream, aio);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ aio->clk_trace.play_en = true;
+ else
+ aio->clk_trace.cap_en = true;
+
+ substream->runtime->hw.rate_min = CYGNUS_RATE_MIN;
+ substream->runtime->hw.rate_max = CYGNUS_RATE_MAX;
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &cygnus_rate_constraint);
+ return 0;
+}
+
+static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ aio->clk_trace.play_en = false;
+ else
+ aio->clk_trace.cap_en = false;
+
+ if (!aio->is_slave) {
+ u32 val;
+
+ val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
+ val &= CYGNUS_PLLCLKSEL_MASK;
+ if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) {
+ dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n",
+ val);
+ return;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (aio->clk_trace.play_clk_en) {
+ clk_disable_unprepare(aio->cygaud->
+ audio_clk[val]);
+ aio->clk_trace.play_clk_en = false;
+ }
+ } else {
+ if (aio->clk_trace.cap_clk_en) {
+ clk_disable_unprepare(aio->cygaud->
+ audio_clk[val]);
+ aio->clk_trace.cap_clk_en = false;
+ }
+ }
+ }
+}
+
+/*
+ * Bit Update Notes
+ * 31 Yes TDM Mode (1 = TDM, 0 = i2s)
+ * 30 Yes Slave Mode (1 = Slave, 0 = Master)
+ * 29:26 No Sclks per frame
+ * 25:18 Yes FS Width
+ * 17:14 No Valid Slots
+ * 13 No Bits (1 = 16 bits, 0 = 32 bits)
+ * 12:08 No Bits per samp
+ * 07 Yes Justifcation (1 = LSB, 0 = MSB)
+ * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay
+ * 05 Yes SCLK polarity (1 = Rising, 0 = Falling)
+ * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left)
+ * 03:02 Yes Reserved - write as zero
+ * 01 No Data Enable
+ * 00 No CLK Enable
+ */
+#define I2S_OUT_CFG_REG_UPDATE_MASK 0x3C03FF03
+
+/* Input cfg is same as output, but the FS width is not a valid field */
+#define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03FC0000)
+
+int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
+
+ if ((len > 0) && (len < 256)) {
+ aio->fsync_width = len;
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL_GPL(cygnus_ssp_set_custom_fsync_width);
+
+static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
+ u32 ssp_curcfg;
+ u32 ssp_newcfg;
+ u32 ssp_outcfg;
+ u32 ssp_incfg;
+ u32 val;
+ u32 mask;
+
+ dev_dbg(aio->cygaud->dev, "%s Enter fmt: %x\n", __func__, fmt);
+
+ if (aio->port_type == PORT_SPDIF)
+ return -EINVAL;
+
+ ssp_newcfg = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE);
+ aio->is_slave = 1;
+ break;
+ case SND_SOC_DAIFMT_BP_FP:
+ ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE);
+ aio->is_slave = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH);
+ aio->mode = CYGNUS_SSPMODE_I2S;
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE);
+
+ /* DSP_A = data after FS, DSP_B = data during FS */
+ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A)
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT);
+
+ if ((aio->fsync_width > 0) && (aio->fsync_width < 256))
+ ssp_newcfg |=
+ (aio->fsync_width << I2S_OUT_CFGX_FSYNC_WIDTH);
+ else
+ ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH);
+
+ aio->mode = CYGNUS_SSPMODE_TDM;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * SSP out cfg.
+ * Retain bits we do not want to update, then OR in new bits
+ */
+ ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg;
+ writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg);
+
+ /*
+ * SSP in cfg.
+ * Retain bits we do not want to update, then OR in new bits
+ */
+ ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+ ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg;
+ writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+
+ val = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+
+ /*
+ * Configure the word clk and bit clk as output or tristate
+ * Each port has 4 bits for controlling its pins.
+ * Shift the mask based upon port number.
+ */
+ mask = BIT(AUD_MISC_SEROUT_LRCK_OE)
+ | BIT(AUD_MISC_SEROUT_SCLK_OE)
+ | BIT(AUD_MISC_SEROUT_MCLK_OE);
+ mask = mask << (aio->portnum * 4);
+ if (aio->is_slave)
+ /* Set bit for tri-state */
+ val |= mask;
+ else
+ /* Clear bit for drive */
+ val &= ~mask;
+
+ dev_dbg(aio->cygaud->dev, "%s Set OE bits 0x%x\n", __func__, val);
+ writel(val, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+
+ return 0;
+}
+
+static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai);
+ struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai);
+
+ dev_dbg(aio->cygaud->dev,
+ "%s cmd %d at port = %d\n", __func__, cmd, aio->portnum);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ audio_ssp_out_enable(aio);
+ else
+ audio_ssp_in_enable(aio);
+ cygaud->active_ports++;
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ audio_ssp_out_disable(aio);
+ else
+ audio_ssp_in_disable(aio);
+ cygaud->active_ports--;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
+ u32 value;
+ int bits_per_slot = 0; /* default to 32-bits per slot */
+ int frame_bits;
+ unsigned int active_slots;
+ bool found = false;
+ int i;
+
+ if (tx_mask != rx_mask) {
+ dev_err(aio->cygaud->dev,
+ "%s tx_mask must equal rx_mask\n", __func__);
+ return -EINVAL;
+ }
+
+ active_slots = hweight32(tx_mask);
+
+ if (active_slots > 16)
+ return -EINVAL;
+
+ /* Slot value must be even */
+ if (active_slots % 2)
+ return -EINVAL;
+
+ /* We encode 16 slots as 0 in the reg */
+ if (active_slots == 16)
+ active_slots = 0;
+
+ /* Slot Width is either 16 or 32 */
+ switch (slot_width) {
+ case 16:
+ bits_per_slot = 1;
+ break;
+ case 32:
+ bits_per_slot = 0;
+ break;
+ default:
+ bits_per_slot = 0;
+ dev_warn(aio->cygaud->dev,
+ "%s Defaulting Slot Width to 32\n", __func__);
+ }
+
+ frame_bits = slots * slot_width;
+
+ for (i = 0; i < ARRAY_SIZE(ssp_valid_tdm_framesize); i++) {
+ if (ssp_valid_tdm_framesize[i] == frame_bits) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_err(aio->cygaud->dev,
+ "%s In TDM mode, frame bits INVALID (%d)\n",
+ __func__, frame_bits);
+ return -EINVAL;
+ }
+
+ aio->bit_per_frame = frame_bits;
+
+ dev_dbg(aio->cygaud->dev, "%s active_slots %u, bits per frame %d\n",
+ __func__, active_slots, frame_bits);
+
+ /* Set capture side of ssp port */
+ value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+ value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
+ value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT);
+ value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
+ value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT);
+ writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg);
+
+ /* Set playback side of ssp port */
+ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg);
+ value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT);
+ value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT);
+ value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT);
+ value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT);
+ writel(value, aio->cygaud->audio + aio->regs.i2s_cfg);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int __cygnus_ssp_suspend(struct snd_soc_dai *cpu_dai)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
+
+ if (!snd_soc_dai_active(cpu_dai))
+ return 0;
+
+ if (!aio->is_slave) {
+ u32 val;
+
+ val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg);
+ val &= CYGNUS_PLLCLKSEL_MASK;
+ if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) {
+ dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n",
+ val);
+ return -EINVAL;
+ }
+
+ if (aio->clk_trace.cap_clk_en)
+ clk_disable_unprepare(aio->cygaud->audio_clk[val]);
+ if (aio->clk_trace.play_clk_en)
+ clk_disable_unprepare(aio->cygaud->audio_clk[val]);
+
+ aio->pll_clk_num = val;
+ }
+
+ return 0;
+}
+
+static int cygnus_ssp_suspend(struct snd_soc_component *component)
+{
+ struct snd_soc_dai *dai;
+ int ret = 0;
+
+ for_each_component_dais(component, dai)
+ ret |= __cygnus_ssp_suspend(dai);
+
+ return ret;
+}
+
+static int __cygnus_ssp_resume(struct snd_soc_dai *cpu_dai)
+{
+ struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai);
+ int error;
+
+ if (!snd_soc_dai_active(cpu_dai))
+ return 0;
+
+ if (!aio->is_slave) {
+ if (aio->clk_trace.cap_clk_en) {
+ error = clk_prepare_enable(aio->cygaud->
+ audio_clk[aio->pll_clk_num]);
+ if (error) {
+ dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+ if (aio->clk_trace.play_clk_en) {
+ error = clk_prepare_enable(aio->cygaud->
+ audio_clk[aio->pll_clk_num]);
+ if (error) {
+ if (aio->clk_trace.cap_clk_en)
+ clk_disable_unprepare(aio->cygaud->
+ audio_clk[aio->pll_clk_num]);
+ dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int cygnus_ssp_resume(struct snd_soc_component *component)
+{
+ struct snd_soc_dai *dai;
+ int ret = 0;
+
+ for_each_component_dais(component, dai)
+ ret |= __cygnus_ssp_resume(dai);
+
+ return ret;
+}
+
+#else
+#define cygnus_ssp_suspend NULL
+#define cygnus_ssp_resume NULL
+#endif
+
+static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = {
+ .startup = cygnus_ssp_startup,
+ .shutdown = cygnus_ssp_shutdown,
+ .trigger = cygnus_ssp_trigger,
+ .hw_params = cygnus_ssp_hw_params,
+ .set_fmt = cygnus_ssp_set_fmt,
+ .set_sysclk = cygnus_ssp_set_sysclk,
+ .set_tdm_slot = cygnus_set_dai_tdm_slot,
+};
+
+static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = {
+ .startup = cygnus_ssp_startup,
+ .shutdown = cygnus_ssp_shutdown,
+ .trigger = cygnus_ssp_trigger,
+ .hw_params = cygnus_ssp_hw_params,
+ .set_sysclk = cygnus_ssp_set_sysclk,
+};
+
+#define INIT_CPU_DAI(num) { \
+ .name = "cygnus-ssp" #num, \
+ .playback = { \
+ .channels_min = 2, \
+ .channels_max = 16, \
+ .rates = SNDRV_PCM_RATE_KNOT, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .capture = { \
+ .channels_min = 2, \
+ .channels_max = 16, \
+ .rates = SNDRV_PCM_RATE_KNOT, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .ops = &cygnus_ssp_dai_ops, \
+}
+
+static const struct snd_soc_dai_driver cygnus_ssp_dai_info[] = {
+ INIT_CPU_DAI(0),
+ INIT_CPU_DAI(1),
+ INIT_CPU_DAI(2),
+};
+
+static const struct snd_soc_dai_driver cygnus_spdif_dai_info = {
+ .name = "cygnus-spdif",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &cygnus_spdif_dai_ops,
+};
+
+static struct snd_soc_dai_driver cygnus_ssp_dai[CYGNUS_MAX_PORTS];
+
+static const struct snd_soc_component_driver cygnus_ssp_component = {
+ .name = "cygnus-audio",
+ .suspend = cygnus_ssp_suspend,
+ .resume = cygnus_ssp_resume,
+ .legacy_dai_naming = 1,
+};
+
+/*
+ * Return < 0 if error
+ * Return 0 if disabled
+ * Return 1 if enabled and node is parsed successfully
+ */
+static int parse_ssp_child_node(struct platform_device *pdev,
+ struct device_node *dn,
+ struct cygnus_audio *cygaud,
+ struct snd_soc_dai_driver *p_dai)
+{
+ struct cygnus_aio_port *aio;
+ struct cygnus_ssp_regs ssp_regs[3];
+ u32 rawval;
+ int portnum = -1;
+ enum cygnus_audio_port_type port_type;
+
+ if (of_property_read_u32(dn, "reg", &rawval)) {
+ dev_err(&pdev->dev, "Missing reg property\n");
+ return -EINVAL;
+ }
+
+ portnum = rawval;
+ switch (rawval) {
+ case 0:
+ ssp_regs[0] = INIT_SSP_REGS(0);
+ port_type = PORT_TDM;
+ break;
+ case 1:
+ ssp_regs[1] = INIT_SSP_REGS(1);
+ port_type = PORT_TDM;
+ break;
+ case 2:
+ ssp_regs[2] = INIT_SSP_REGS(2);
+ port_type = PORT_TDM;
+ break;
+ case 3:
+ port_type = PORT_SPDIF;
+ break;
+ default:
+ dev_err(&pdev->dev, "Bad value for reg %u\n", rawval);
+ return -EINVAL;
+ }
+
+ aio = &cygaud->portinfo[portnum];
+ aio->cygaud = cygaud;
+ aio->portnum = portnum;
+ aio->port_type = port_type;
+ aio->fsync_width = -1;
+
+ switch (port_type) {
+ case PORT_TDM:
+ aio->regs = ssp_regs[portnum];
+ *p_dai = cygnus_ssp_dai_info[portnum];
+ aio->mode = CYGNUS_SSPMODE_UNKNOWN;
+ break;
+
+ case PORT_SPDIF:
+ aio->regs.bf_sourcech_cfg = BF_SRC_CFG3_OFFSET;
+ aio->regs.bf_sourcech_ctrl = BF_SRC_CTRL3_OFFSET;
+ aio->regs.i2s_mclk_cfg = SPDIF_MCLK_CFG_OFFSET;
+ aio->regs.i2s_stream_cfg = SPDIF_STREAM_CFG_OFFSET;
+ *p_dai = cygnus_spdif_dai_info;
+
+ /* For the purposes of this code SPDIF can be I2S mode */
+ aio->mode = CYGNUS_SSPMODE_I2S;
+ break;
+ default:
+ dev_err(&pdev->dev, "Bad value for port_type %d\n", port_type);
+ return -EINVAL;
+ }
+
+ dev_dbg(&pdev->dev, "%s portnum = %d\n", __func__, aio->portnum);
+ aio->streams_on = 0;
+ aio->cygaud->dev = &pdev->dev;
+ aio->clk_trace.play_en = false;
+ aio->clk_trace.cap_en = false;
+
+ audio_ssp_init_portregs(aio);
+ return 0;
+}
+
+static int audio_clk_init(struct platform_device *pdev,
+ struct cygnus_audio *cygaud)
+{
+ int i;
+ char clk_name[PROP_LEN_MAX];
+
+ for (i = 0; i < ARRAY_SIZE(cygaud->audio_clk); i++) {
+ snprintf(clk_name, PROP_LEN_MAX, "ch%d_audio", i);
+
+ cygaud->audio_clk[i] = devm_clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(cygaud->audio_clk[i]))
+ return PTR_ERR(cygaud->audio_clk[i]);
+ }
+
+ return 0;
+}
+
+static int cygnus_ssp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *child_node;
+ struct cygnus_audio *cygaud;
+ int err;
+ int node_count;
+ int active_port_count;
+
+ cygaud = devm_kzalloc(dev, sizeof(struct cygnus_audio), GFP_KERNEL);
+ if (!cygaud)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, cygaud);
+
+ cygaud->audio = devm_platform_ioremap_resource_byname(pdev, "aud");
+ if (IS_ERR(cygaud->audio))
+ return PTR_ERR(cygaud->audio);
+
+ cygaud->i2s_in = devm_platform_ioremap_resource_byname(pdev, "i2s_in");
+ if (IS_ERR(cygaud->i2s_in))
+ return PTR_ERR(cygaud->i2s_in);
+
+ /* Tri-state all controlable pins until we know that we need them */
+ writel(CYGNUS_SSP_TRISTATE_MASK,
+ cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE);
+
+ node_count = of_get_child_count(pdev->dev.of_node);
+ if ((node_count < 1) || (node_count > CYGNUS_MAX_PORTS)) {
+ dev_err(dev, "child nodes is %d. Must be between 1 and %d\n",
+ node_count, CYGNUS_MAX_PORTS);
+ return -EINVAL;
+ }
+
+ active_port_count = 0;
+
+ for_each_available_child_of_node(pdev->dev.of_node, child_node) {
+ err = parse_ssp_child_node(pdev, child_node, cygaud,
+ &cygnus_ssp_dai[active_port_count]);
+
+ /* negative is err, 0 is active and good, 1 is disabled */
+ if (err < 0) {
+ of_node_put(child_node);
+ return err;
+ }
+ else if (!err) {
+ dev_dbg(dev, "Activating DAI: %s\n",
+ cygnus_ssp_dai[active_port_count].name);
+ active_port_count++;
+ }
+ }
+
+ cygaud->dev = dev;
+ cygaud->active_ports = 0;
+
+ dev_dbg(dev, "Registering %d DAIs\n", active_port_count);
+ err = devm_snd_soc_register_component(dev, &cygnus_ssp_component,
+ cygnus_ssp_dai, active_port_count);
+ if (err) {
+ dev_err(dev, "snd_soc_register_dai failed\n");
+ return err;
+ }
+
+ cygaud->irq_num = platform_get_irq(pdev, 0);
+ if (cygaud->irq_num <= 0)
+ return cygaud->irq_num;
+
+ err = audio_clk_init(pdev, cygaud);
+ if (err) {
+ dev_err(dev, "audio clock initialization failed\n");
+ return err;
+ }
+
+ err = cygnus_soc_platform_register(dev, cygaud);
+ if (err) {
+ dev_err(dev, "platform reg error %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int cygnus_ssp_remove(struct platform_device *pdev)
+{
+ cygnus_soc_platform_unregister(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id cygnus_ssp_of_match[] = {
+ { .compatible = "brcm,cygnus-audio" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cygnus_ssp_of_match);
+
+static struct platform_driver cygnus_ssp_driver = {
+ .probe = cygnus_ssp_probe,
+ .remove = cygnus_ssp_remove,
+ .driver = {
+ .name = "cygnus-ssp",
+ .of_match_table = cygnus_ssp_of_match,
+ },
+};
+
+module_platform_driver(cygnus_ssp_driver);
+
+MODULE_ALIAS("platform:cygnus-ssp");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Cygnus ASoC SSP Interface");
diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h
new file mode 100644
index 000000000..74152b2d7
--- /dev/null
+++ b/sound/soc/bcm/cygnus-ssp.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2014-2015 Broadcom Corporation */
+#ifndef __CYGNUS_SSP_H__
+#define __CYGNUS_SSP_H__
+
+#define CYGNUS_TDM_DAI_MAX_SLOTS 16
+
+#define CYGNUS_MAX_PLAYBACK_PORTS 4
+#define CYGNUS_MAX_CAPTURE_PORTS 3
+#define CYGNUS_MAX_I2S_PORTS 3
+#define CYGNUS_MAX_PORTS CYGNUS_MAX_PLAYBACK_PORTS
+#define CYGNUS_AUIDO_MAX_NUM_CLKS 3
+
+#define CYGNUS_SSP_FRAMEBITS_DIV 1
+
+#define CYGNUS_SSPMODE_I2S 0
+#define CYGNUS_SSPMODE_TDM 1
+#define CYGNUS_SSPMODE_UNKNOWN -1
+
+#define CYGNUS_SSP_CLKSRC_PLL 0
+
+/* Max string length of our dt property names */
+#define PROP_LEN_MAX 40
+
+struct ringbuf_regs {
+ unsigned rdaddr;
+ unsigned wraddr;
+ unsigned baseaddr;
+ unsigned endaddr;
+ unsigned fmark; /* freemark for play, fullmark for caputure */
+ unsigned period_bytes;
+ unsigned buf_size;
+};
+
+#define RINGBUF_REG_PLAYBACK(num) ((struct ringbuf_regs) { \
+ .rdaddr = SRC_RBUF_ ##num## _RDADDR_OFFSET, \
+ .wraddr = SRC_RBUF_ ##num## _WRADDR_OFFSET, \
+ .baseaddr = SRC_RBUF_ ##num## _BASEADDR_OFFSET, \
+ .endaddr = SRC_RBUF_ ##num## _ENDADDR_OFFSET, \
+ .fmark = SRC_RBUF_ ##num## _FREE_MARK_OFFSET, \
+ .period_bytes = 0, \
+ .buf_size = 0, \
+})
+
+#define RINGBUF_REG_CAPTURE(num) ((struct ringbuf_regs) { \
+ .rdaddr = DST_RBUF_ ##num## _RDADDR_OFFSET, \
+ .wraddr = DST_RBUF_ ##num## _WRADDR_OFFSET, \
+ .baseaddr = DST_RBUF_ ##num## _BASEADDR_OFFSET, \
+ .endaddr = DST_RBUF_ ##num## _ENDADDR_OFFSET, \
+ .fmark = DST_RBUF_ ##num## _FULL_MARK_OFFSET, \
+ .period_bytes = 0, \
+ .buf_size = 0, \
+})
+
+enum cygnus_audio_port_type {
+ PORT_TDM,
+ PORT_SPDIF,
+};
+
+struct cygnus_ssp_regs {
+ u32 i2s_stream_cfg;
+ u32 i2s_cfg;
+ u32 i2s_cap_stream_cfg;
+ u32 i2s_cap_cfg;
+ u32 i2s_mclk_cfg;
+
+ u32 bf_destch_ctrl;
+ u32 bf_destch_cfg;
+ u32 bf_sourcech_ctrl;
+ u32 bf_sourcech_cfg;
+ u32 bf_sourcech_grp;
+};
+
+struct cygnus_track_clk {
+ bool cap_en;
+ bool play_en;
+ bool cap_clk_en;
+ bool play_clk_en;
+};
+
+struct cygnus_aio_port {
+ int portnum;
+ int mode;
+ bool is_slave;
+ int streams_on; /* will be 0 if both capture and play are off */
+ int fsync_width;
+ int port_type;
+
+ u32 mclk;
+ u32 lrclk;
+ u32 bit_per_frame;
+ u32 pll_clk_num;
+
+ struct cygnus_audio *cygaud;
+ struct cygnus_ssp_regs regs;
+
+ struct ringbuf_regs play_rb_regs;
+ struct ringbuf_regs capture_rb_regs;
+
+ struct snd_pcm_substream *play_stream;
+ struct snd_pcm_substream *capture_stream;
+
+ struct cygnus_track_clk clk_trace;
+};
+
+
+struct cygnus_audio {
+ struct cygnus_aio_port portinfo[CYGNUS_MAX_PORTS];
+
+ int irq_num;
+ void __iomem *audio;
+ struct device *dev;
+ void __iomem *i2s_in;
+
+ struct clk *audio_clk[CYGNUS_AUIDO_MAX_NUM_CLKS];
+ int active_ports;
+ unsigned long vco_rate;
+};
+
+extern int cygnus_ssp_get_mode(struct snd_soc_dai *cpu_dai);
+extern int cygnus_ssp_add_pll_tweak_controls(struct snd_soc_pcm_runtime *rtd);
+extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai,
+ int len);
+extern int cygnus_soc_platform_register(struct device *dev,
+ struct cygnus_audio *cygaud);
+extern int cygnus_soc_platform_unregister(struct device *dev);
+extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai,
+ int len);
+#endif