summaryrefslogtreecommitdiffstats
path: root/sound/soc/pxa
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /sound/soc/pxa
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/soc/pxa')
-rw-r--r--sound/soc/pxa/Kconfig58
-rw-r--r--sound/soc/pxa/Makefile17
-rw-r--r--sound/soc/pxa/mmp-sspa.c585
-rw-r--r--sound/soc/pxa/mmp-sspa.h70
-rw-r--r--sound/soc/pxa/pxa-ssp.c887
-rw-r--r--sound/soc/pxa/pxa-ssp.h36
-rw-r--r--sound/soc/pxa/pxa2xx-ac97.c306
-rw-r--r--sound/soc/pxa/pxa2xx-i2s.c412
-rw-r--r--sound/soc/pxa/pxa2xx-i2s.h12
-rw-r--r--sound/soc/pxa/pxa2xx-pcm.c49
-rw-r--r--sound/soc/pxa/spitz.c322
11 files changed, 2754 insertions, 0 deletions
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
new file mode 100644
index 0000000000..e6bca90709
--- /dev/null
+++ b/sound/soc/pxa/Kconfig
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_PXA2XX_SOC
+ tristate "SoC Audio for the Intel PXA2xx chip"
+ depends on ARCH_PXA || COMPILE_TEST
+ select SND_PXA2XX_LIB
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the PXA2xx AC97, I2S or SSP interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_PXA2XX_AC97
+ tristate
+
+config SND_PXA2XX_SOC_AC97
+ tristate "SoC AC97 support for PXA2xx"
+ depends on SND_PXA2XX_SOC
+ depends on AC97_BUS=n
+ default y
+ select AC97_BUS_NEW
+ select SND_PXA2XX_LIB
+ select SND_PXA2XX_LIB_AC97
+ select SND_SOC_AC97_BUS_NEW
+
+config SND_PXA2XX_SOC_I2S
+ select SND_PXA2XX_LIB
+ tristate
+
+config SND_PXA_SOC_SSP
+ tristate "Soc Audio via PXA2xx/PXA3xx SSP ports"
+ depends on PLAT_PXA
+ select PXA_SSP
+ select SND_PXA2XX_LIB
+
+config SND_MMP_SOC_SSPA
+ tristate "SoC Audio via MMP SSPA ports"
+ depends on ARCH_MMP
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_ARM
+ help
+ Say Y if you want to add support for codecs attached to
+ the MMP SSPA interface.
+
+config SND_PXA2XX_SOC_SPITZ
+ tristate "SoC Audio support for Sharp Zaurus SL-Cxx00"
+ depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00 && I2C
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8750
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita).
+
+config SND_PXA910_SOC
+ tristate "SoC Audio for Marvell PXA910 chip"
+ depends on ARCH_MMP && SND
+ select SND_PCM
+ help
+ Say Y if you want to add support for SoC audio on the
+ Marvell PXA910 reference platform.
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
new file mode 100644
index 0000000000..406605fc74
--- /dev/null
+++ b/sound/soc/pxa/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+# PXA Platform Support
+snd-soc-pxa2xx-objs := pxa2xx-pcm.o
+snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
+snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
+snd-soc-pxa-ssp-objs := pxa-ssp.o
+snd-soc-mmp-sspa-objs := mmp-sspa.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
+obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
+obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
+obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
+obj-$(CONFIG_SND_MMP_SOC_SSPA) += snd-soc-mmp-sspa.o
+
+# PXA Machine Support
+snd-soc-spitz-objs := spitz.o
+obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c
new file mode 100644
index 0000000000..abfaf3cdf5
--- /dev/null
+++ b/sound/soc/pxa/mmp-sspa.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/sound/soc/pxa/mmp-sspa.c
+ * Base on pxa2xx-ssp.c
+ *
+ * Copyright (C) 2011 Marvell International Ltd.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/dmaengine.h>
+#include <linux/pm_runtime.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+#include <sound/dmaengine_pcm.h>
+#include "mmp-sspa.h"
+
+/*
+ * SSPA audio private data
+ */
+struct sspa_priv {
+ void __iomem *tx_base;
+ void __iomem *rx_base;
+
+ struct snd_dmaengine_dai_dma_data playback_dma_data;
+ struct snd_dmaengine_dai_dma_data capture_dma_data;
+ struct clk *clk;
+ struct clk *audio_clk;
+ struct clk *sysclk;
+
+ int running_cnt;
+ u32 sp;
+ u32 ctrl;
+};
+
+static void mmp_sspa_tx_enable(struct sspa_priv *sspa)
+{
+ unsigned int sspa_sp = sspa->sp;
+
+ sspa_sp &= ~SSPA_SP_MSL;
+ sspa_sp |= SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ __raw_writel(sspa_sp, sspa->tx_base + SSPA_SP);
+}
+
+static void mmp_sspa_tx_disable(struct sspa_priv *sspa)
+{
+ unsigned int sspa_sp = sspa->sp;
+
+ sspa_sp &= ~SSPA_SP_MSL;
+ sspa_sp &= ~SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ __raw_writel(sspa_sp, sspa->tx_base + SSPA_SP);
+}
+
+static void mmp_sspa_rx_enable(struct sspa_priv *sspa)
+{
+ unsigned int sspa_sp = sspa->sp;
+
+ sspa_sp |= SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ __raw_writel(sspa_sp, sspa->rx_base + SSPA_SP);
+}
+
+static void mmp_sspa_rx_disable(struct sspa_priv *sspa)
+{
+ unsigned int sspa_sp = sspa->sp;
+
+ sspa_sp &= ~SSPA_SP_S_EN;
+ sspa_sp |= SSPA_SP_WEN;
+ __raw_writel(sspa_sp, sspa->rx_base + SSPA_SP);
+}
+
+static int mmp_sspa_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai);
+
+ clk_prepare_enable(sspa->sysclk);
+ clk_prepare_enable(sspa->clk);
+
+ return 0;
+}
+
+static void mmp_sspa_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai);
+
+ clk_disable_unprepare(sspa->clk);
+ clk_disable_unprepare(sspa->sysclk);
+}
+
+/*
+ * Set the SSP ports SYSCLK.
+ */
+static int mmp_sspa_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(cpu_dai);
+ struct device *dev = cpu_dai->component->dev;
+ int ret = 0;
+
+ if (dev->of_node)
+ return -ENOTSUPP;
+
+ switch (clk_id) {
+ case MMP_SSPA_CLK_AUDIO:
+ ret = clk_set_rate(sspa->audio_clk, freq);
+ if (ret)
+ return ret;
+ break;
+ case MMP_SSPA_CLK_PLL:
+ case MMP_SSPA_CLK_VCXO:
+ /* not support yet */
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mmp_sspa_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id,
+ int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(cpu_dai);
+ struct device *dev = cpu_dai->component->dev;
+ int ret = 0;
+
+ if (dev->of_node)
+ return -ENOTSUPP;
+
+ switch (pll_id) {
+ case MMP_SYSCLK:
+ ret = clk_set_rate(sspa->sysclk, freq_out);
+ if (ret)
+ return ret;
+ break;
+ case MMP_SSPA_CLK:
+ ret = clk_set_rate(sspa->clk, freq_out);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/*
+ * Set up the sspa dai format.
+ */
+static int mmp_sspa_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(cpu_dai);
+
+ /* reset port settings */
+ sspa->sp = SSPA_SP_WEN | SSPA_SP_S_RST | SSPA_SP_FFLUSH;
+ sspa->ctrl = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ sspa->sp |= SSPA_SP_MSL;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ sspa->sp |= SSPA_SP_FSP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sspa->ctrl |= SSPA_CTL_XDATDLY(1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Since we are configuring the timings for the format by hand
+ * we have to defer some things until hw_params() where we
+ * know parameters like the sample size.
+ */
+ return 0;
+}
+
+/*
+ * Set the SSPA audio DMA parameters and sample size.
+ * Can be called multiple times by oss emulation.
+ */
+static int mmp_sspa_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = dai->component->dev;
+ u32 sspa_ctrl = sspa->ctrl;
+ int bits;
+ int bitval;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ bits = 8;
+ bitval = SSPA_CTL_8_BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bits = 16;
+ bitval = SSPA_CTL_16_BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ bits = 24;
+ bitval = SSPA_CTL_24_BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bits = 32;
+ bitval = SSPA_CTL_32_BITS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ sspa_ctrl &= ~SSPA_CTL_XPH;
+ if (dev->of_node || params_channels(params) == 2)
+ sspa_ctrl |= SSPA_CTL_XPH;
+
+ sspa_ctrl &= ~SSPA_CTL_XWDLEN1_MASK;
+ sspa_ctrl |= SSPA_CTL_XWDLEN1(bitval);
+
+ sspa_ctrl &= ~SSPA_CTL_XWDLEN2_MASK;
+ sspa_ctrl |= SSPA_CTL_XWDLEN2(bitval);
+
+ sspa_ctrl &= ~SSPA_CTL_XSSZ1_MASK;
+ sspa_ctrl |= SSPA_CTL_XSSZ1(bitval);
+
+ sspa_ctrl &= ~SSPA_CTL_XSSZ2_MASK;
+ sspa_ctrl |= SSPA_CTL_XSSZ2(bitval);
+
+ sspa->sp &= ~SSPA_SP_FWID_MASK;
+ sspa->sp |= SSPA_SP_FWID(bits - 1);
+
+ sspa->sp &= ~SSPA_TXSP_FPER_MASK;
+ sspa->sp |= SSPA_TXSP_FPER(bits * 2 - 1);
+
+ if (dev->of_node) {
+ clk_set_rate(sspa->clk, params_rate(params) *
+ params_channels(params) * bits);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ __raw_writel(sspa_ctrl, sspa->tx_base + SSPA_CTL);
+ __raw_writel(0x1, sspa->tx_base + SSPA_FIFO_UL);
+ } else {
+ __raw_writel(sspa_ctrl, sspa->rx_base + SSPA_CTL);
+ __raw_writel(0x0, sspa->rx_base + SSPA_FIFO_UL);
+ }
+
+ return 0;
+}
+
+static int mmp_sspa_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /*
+ * whatever playback or capture, must enable rx.
+ * this is a hw issue, so need check if rx has been
+ * enabled or not; if has been enabled by another
+ * stream, do not enable again.
+ */
+ if (!sspa->running_cnt)
+ mmp_sspa_rx_enable(sspa);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mmp_sspa_tx_enable(sspa);
+
+ sspa->running_cnt++;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ sspa->running_cnt--;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mmp_sspa_tx_disable(sspa);
+
+ /* have no capture stream, disable rx port */
+ if (!sspa->running_cnt)
+ mmp_sspa_rx_disable(sspa);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mmp_sspa_probe(struct snd_soc_dai *dai)
+{
+ struct sspa_priv *sspa = dev_get_drvdata(dai->dev);
+
+ snd_soc_dai_init_dma_data(dai,
+ &sspa->playback_dma_data,
+ &sspa->capture_dma_data);
+
+ return 0;
+}
+
+#define MMP_SSPA_RATES SNDRV_PCM_RATE_8000_192000
+#define MMP_SSPA_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dai_ops mmp_sspa_dai_ops = {
+ .probe = mmp_sspa_probe,
+ .startup = mmp_sspa_startup,
+ .shutdown = mmp_sspa_shutdown,
+ .trigger = mmp_sspa_trigger,
+ .hw_params = mmp_sspa_hw_params,
+ .set_sysclk = mmp_sspa_set_dai_sysclk,
+ .set_pll = mmp_sspa_set_dai_pll,
+ .set_fmt = mmp_sspa_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver mmp_sspa_dai = {
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 128,
+ .rates = MMP_SSPA_RATES,
+ .formats = MMP_SSPA_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MMP_SSPA_RATES,
+ .formats = MMP_SSPA_FORMATS,
+ },
+ .ops = &mmp_sspa_dai_ops,
+};
+
+#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
+ SNDRV_PCM_INFO_MMAP_VALID | \
+ SNDRV_PCM_INFO_INTERLEAVED | \
+ SNDRV_PCM_INFO_PAUSE | \
+ SNDRV_PCM_INFO_RESUME | \
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP)
+
+static const struct snd_pcm_hardware mmp_pcm_hardware[] = {
+ {
+ .info = MMP_PCM_INFO,
+ .period_bytes_min = 1024,
+ .period_bytes_max = 2048,
+ .periods_min = 2,
+ .periods_max = 32,
+ .buffer_bytes_max = 4096,
+ .fifo_size = 32,
+ },
+ {
+ .info = MMP_PCM_INFO,
+ .period_bytes_min = 1024,
+ .period_bytes_max = 2048,
+ .periods_min = 2,
+ .periods_max = 32,
+ .buffer_bytes_max = 4096,
+ .fifo_size = 32,
+ },
+};
+
+static const struct snd_dmaengine_pcm_config mmp_pcm_config = {
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+ .pcm_hardware = mmp_pcm_hardware,
+ .prealloc_buffer_size = 4096,
+};
+
+static int mmp_pcm_mmap(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ return remap_pfn_range(vma, vma->vm_start,
+ substream->dma_buffer.addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static int mmp_sspa_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct sspa_priv *sspa = snd_soc_component_get_drvdata(component);
+
+ pm_runtime_get_sync(component->dev);
+
+ /* we can only change the settings if the port is not in use */
+ if ((__raw_readl(sspa->tx_base + SSPA_SP) & SSPA_SP_S_EN) ||
+ (__raw_readl(sspa->rx_base + SSPA_SP) & SSPA_SP_S_EN)) {
+ dev_err(component->dev,
+ "can't change hardware dai format: stream is in use\n");
+ return -EBUSY;
+ }
+
+ __raw_writel(sspa->sp, sspa->tx_base + SSPA_SP);
+ __raw_writel(sspa->sp, sspa->rx_base + SSPA_SP);
+
+ sspa->sp &= ~(SSPA_SP_S_RST | SSPA_SP_FFLUSH);
+ __raw_writel(sspa->sp, sspa->tx_base + SSPA_SP);
+ __raw_writel(sspa->sp, sspa->rx_base + SSPA_SP);
+
+ /*
+ * FIXME: hw issue, for the tx serial port,
+ * can not config the master/slave mode;
+ * so must clean this bit.
+ * The master/slave mode has been set in the
+ * rx port.
+ */
+ __raw_writel(sspa->sp & ~SSPA_SP_MSL, sspa->tx_base + SSPA_SP);
+
+ __raw_writel(sspa->ctrl, sspa->tx_base + SSPA_CTL);
+ __raw_writel(sspa->ctrl, sspa->rx_base + SSPA_CTL);
+
+ return 0;
+}
+
+static int mmp_sspa_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ pm_runtime_put_sync(component->dev);
+ return 0;
+}
+
+static const struct snd_soc_component_driver mmp_sspa_component = {
+ .name = "mmp-sspa",
+ .mmap = mmp_pcm_mmap,
+ .open = mmp_sspa_open,
+ .close = mmp_sspa_close,
+ .legacy_dai_naming = 1,
+};
+
+static int asoc_mmp_sspa_probe(struct platform_device *pdev)
+{
+ struct sspa_priv *sspa;
+ int ret;
+
+ sspa = devm_kzalloc(&pdev->dev,
+ sizeof(struct sspa_priv), GFP_KERNEL);
+ if (!sspa)
+ return -ENOMEM;
+
+ if (pdev->dev.of_node) {
+ sspa->rx_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sspa->rx_base))
+ return PTR_ERR(sspa->rx_base);
+
+ sspa->tx_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(sspa->tx_base))
+ return PTR_ERR(sspa->tx_base);
+
+ sspa->clk = devm_clk_get(&pdev->dev, "bitclk");
+ if (IS_ERR(sspa->clk))
+ return PTR_ERR(sspa->clk);
+
+ sspa->audio_clk = devm_clk_get(&pdev->dev, "audio");
+ if (IS_ERR(sspa->audio_clk))
+ return PTR_ERR(sspa->audio_clk);
+ } else {
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL)
+ return -ENODEV;
+
+ sspa->rx_base = devm_ioremap(&pdev->dev, res->start, 0x30);
+ if (!sspa->rx_base)
+ return -ENOMEM;
+
+ sspa->tx_base = devm_ioremap(&pdev->dev,
+ res->start + 0x80, 0x30);
+ if (!sspa->tx_base)
+ return -ENOMEM;
+
+ sspa->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sspa->clk))
+ return PTR_ERR(sspa->clk);
+
+ sspa->audio_clk = clk_get(NULL, "mmp-audio");
+ if (IS_ERR(sspa->audio_clk))
+ return PTR_ERR(sspa->audio_clk);
+
+ sspa->sysclk = clk_get(NULL, "mmp-sysclk");
+ if (IS_ERR(sspa->sysclk)) {
+ clk_put(sspa->audio_clk);
+ return PTR_ERR(sspa->sysclk);
+ }
+ }
+ platform_set_drvdata(pdev, sspa);
+
+ sspa->playback_dma_data.maxburst = 4;
+ sspa->capture_dma_data.maxburst = 4;
+ /* You know, these addresses are actually ignored. */
+ sspa->capture_dma_data.addr = SSPA_D;
+ sspa->playback_dma_data.addr = 0x80 + SSPA_D;
+
+ if (pdev->dev.of_node) {
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
+ &mmp_pcm_config, 0);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &mmp_sspa_component,
+ &mmp_sspa_dai, 1);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+ clk_prepare_enable(sspa->audio_clk);
+
+ return 0;
+}
+
+static void asoc_mmp_sspa_remove(struct platform_device *pdev)
+{
+ struct sspa_priv *sspa = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(sspa->audio_clk);
+ pm_runtime_disable(&pdev->dev);
+
+ if (pdev->dev.of_node)
+ return;
+
+ clk_put(sspa->audio_clk);
+ clk_put(sspa->sysclk);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id mmp_sspa_of_match[] = {
+ { .compatible = "marvell,mmp-sspa" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, mmp_sspa_of_match);
+#endif
+
+static struct platform_driver asoc_mmp_sspa_driver = {
+ .driver = {
+ .name = "mmp-sspa-dai",
+ .of_match_table = of_match_ptr(mmp_sspa_of_match),
+ },
+ .probe = asoc_mmp_sspa_probe,
+ .remove_new = asoc_mmp_sspa_remove,
+};
+
+module_platform_driver(asoc_mmp_sspa_driver);
+
+MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
+MODULE_DESCRIPTION("MMP SSPA SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mmp-sspa-dai");
diff --git a/sound/soc/pxa/mmp-sspa.h b/sound/soc/pxa/mmp-sspa.h
new file mode 100644
index 0000000000..938ef2f667
--- /dev/null
+++ b/sound/soc/pxa/mmp-sspa.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/sound/soc/pxa/mmp-sspa.h
+ *
+ * Copyright (C) 2011 Marvell International Ltd.
+ */
+#ifndef _MMP_SSPA_H
+#define _MMP_SSPA_H
+
+/*
+ * SSPA Registers
+ */
+#define SSPA_D (0x00)
+#define SSPA_ID (0x04)
+#define SSPA_CTL (0x08)
+#define SSPA_SP (0x0c)
+#define SSPA_FIFO_UL (0x10)
+#define SSPA_INT_MASK (0x14)
+#define SSPA_C (0x18)
+#define SSPA_FIFO_NOFS (0x1c)
+#define SSPA_FIFO_SIZE (0x20)
+
+/* SSPA Control Register */
+#define SSPA_CTL_XPH (1 << 31) /* Read Phase */
+#define SSPA_CTL_XFIG (1 << 15) /* Transmit Zeros when FIFO Empty */
+#define SSPA_CTL_JST (1 << 3) /* Audio Sample Justification */
+#define SSPA_CTL_XFRLEN2_MASK (7 << 24)
+#define SSPA_CTL_XFRLEN2(x) ((x) << 24) /* Transmit Frame Length in Phase 2 */
+#define SSPA_CTL_XWDLEN2_MASK (7 << 21)
+#define SSPA_CTL_XWDLEN2(x) ((x) << 21) /* Transmit Word Length in Phase 2 */
+#define SSPA_CTL_XDATDLY(x) ((x) << 19) /* Transmit Data Delay */
+#define SSPA_CTL_XSSZ2_MASK (7 << 16)
+#define SSPA_CTL_XSSZ2(x) ((x) << 16) /* Transmit Sample Audio Size */
+#define SSPA_CTL_XFRLEN1_MASK (7 << 8)
+#define SSPA_CTL_XFRLEN1(x) ((x) << 8) /* Transmit Frame Length in Phase 1 */
+#define SSPA_CTL_XWDLEN1_MASK (7 << 5)
+#define SSPA_CTL_XWDLEN1(x) ((x) << 5) /* Transmit Word Length in Phase 1 */
+#define SSPA_CTL_XSSZ1_MASK (7 << 0)
+#define SSPA_CTL_XSSZ1(x) ((x) << 0) /* XSSZ1 */
+
+#define SSPA_CTL_8_BITS (0x0) /* Sample Size */
+#define SSPA_CTL_12_BITS (0x1)
+#define SSPA_CTL_16_BITS (0x2)
+#define SSPA_CTL_20_BITS (0x3)
+#define SSPA_CTL_24_BITS (0x4)
+#define SSPA_CTL_32_BITS (0x5)
+
+/* SSPA Serial Port Register */
+#define SSPA_SP_WEN (1 << 31) /* Write Configuration Enable */
+#define SSPA_SP_MSL (1 << 18) /* Master Slave Configuration */
+#define SSPA_SP_CLKP (1 << 17) /* CLKP Polarity Clock Edge Select */
+#define SSPA_SP_FSP (1 << 16) /* FSP Polarity Clock Edge Select */
+#define SSPA_SP_FFLUSH (1 << 2) /* FIFO Flush */
+#define SSPA_SP_S_RST (1 << 1) /* Active High Reset Signal */
+#define SSPA_SP_S_EN (1 << 0) /* Serial Clock Domain Enable */
+#define SSPA_SP_FWID_MASK (0x3f << 20)
+#define SSPA_SP_FWID(x) ((x) << 20) /* Frame-Sync Width */
+#define SSPA_TXSP_FPER_MASK (0x3f << 4)
+#define SSPA_TXSP_FPER(x) ((x) << 4) /* Frame-Sync Active */
+
+/* sspa clock sources */
+#define MMP_SSPA_CLK_PLL 0
+#define MMP_SSPA_CLK_VCXO 1
+#define MMP_SSPA_CLK_AUDIO 3
+
+/* sspa pll id */
+#define MMP_SYSCLK 0
+#define MMP_SSPA_CLK 1
+
+#endif /* _MMP_SSPA_H */
diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c
new file mode 100644
index 0000000000..b8a3cb8b75
--- /dev/null
+++ b/sound/soc/pxa/pxa-ssp.c
@@ -0,0 +1,887 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * pxa-ssp.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005,2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * TODO:
+ * o Test network mode for > 16bit sample size
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/pxa2xx_ssp.h>
+#include <linux/of.h>
+#include <linux/dmaengine.h>
+
+#include <asm/irq.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "pxa-ssp.h"
+
+/*
+ * SSP audio private data
+ */
+struct ssp_priv {
+ struct ssp_device *ssp;
+ struct clk *extclk;
+ unsigned long ssp_clk;
+ unsigned int sysclk;
+ unsigned int dai_fmt;
+ unsigned int configured_dai_fmt;
+#ifdef CONFIG_PM
+ uint32_t cr0;
+ uint32_t cr1;
+ uint32_t to;
+ uint32_t psp;
+#endif
+};
+
+static void dump_registers(struct ssp_device *ssp)
+{
+ dev_dbg(ssp->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n",
+ pxa_ssp_read_reg(ssp, SSCR0), pxa_ssp_read_reg(ssp, SSCR1),
+ pxa_ssp_read_reg(ssp, SSTO));
+
+ dev_dbg(ssp->dev, "SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x\n",
+ pxa_ssp_read_reg(ssp, SSPSP), pxa_ssp_read_reg(ssp, SSSR),
+ pxa_ssp_read_reg(ssp, SSACD));
+}
+
+static void pxa_ssp_set_dma_params(struct ssp_device *ssp, int width4,
+ int out, struct snd_dmaengine_dai_dma_data *dma)
+{
+ dma->addr_width = width4 ? DMA_SLAVE_BUSWIDTH_4_BYTES :
+ DMA_SLAVE_BUSWIDTH_2_BYTES;
+ dma->maxburst = 16;
+ dma->addr = ssp->phys_base + SSDR;
+}
+
+static int pxa_ssp_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ struct snd_dmaengine_dai_dma_data *dma;
+ int ret = 0;
+
+ if (!snd_soc_dai_active(cpu_dai)) {
+ clk_prepare_enable(ssp->clk);
+ pxa_ssp_disable(ssp);
+ }
+
+ clk_prepare_enable(priv->extclk);
+
+ dma = kzalloc(sizeof(struct snd_dmaengine_dai_dma_data), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+ dma->chan_name = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "tx" : "rx";
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma);
+
+ return ret;
+}
+
+static void pxa_ssp_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+
+ if (!snd_soc_dai_active(cpu_dai)) {
+ pxa_ssp_disable(ssp);
+ clk_disable_unprepare(ssp->clk);
+ }
+
+ clk_disable_unprepare(priv->extclk);
+
+ kfree(snd_soc_dai_get_dma_data(cpu_dai, substream));
+ snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+}
+
+#ifdef CONFIG_PM
+
+static int pxa_ssp_suspend(struct snd_soc_component *component)
+{
+ struct ssp_priv *priv = snd_soc_component_get_drvdata(component);
+ struct ssp_device *ssp = priv->ssp;
+
+ if (!snd_soc_component_active(component))
+ clk_prepare_enable(ssp->clk);
+
+ priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0);
+ priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1);
+ priv->to = __raw_readl(ssp->mmio_base + SSTO);
+ priv->psp = __raw_readl(ssp->mmio_base + SSPSP);
+
+ pxa_ssp_disable(ssp);
+ clk_disable_unprepare(ssp->clk);
+ return 0;
+}
+
+static int pxa_ssp_resume(struct snd_soc_component *component)
+{
+ struct ssp_priv *priv = snd_soc_component_get_drvdata(component);
+ struct ssp_device *ssp = priv->ssp;
+ uint32_t sssr = SSSR_ROR | SSSR_TUR | SSSR_BCE;
+
+ clk_prepare_enable(ssp->clk);
+
+ __raw_writel(sssr, ssp->mmio_base + SSSR);
+ __raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0);
+ __raw_writel(priv->cr1, ssp->mmio_base + SSCR1);
+ __raw_writel(priv->to, ssp->mmio_base + SSTO);
+ __raw_writel(priv->psp, ssp->mmio_base + SSPSP);
+
+ if (snd_soc_component_active(component))
+ pxa_ssp_enable(ssp);
+ else
+ clk_disable_unprepare(ssp->clk);
+
+ return 0;
+}
+
+#else
+#define pxa_ssp_suspend NULL
+#define pxa_ssp_resume NULL
+#endif
+
+/*
+ * ssp_set_clkdiv - set SSP clock divider
+ * @div: serial clock rate divider
+ */
+static void pxa_ssp_set_scr(struct ssp_device *ssp, u32 div)
+{
+ u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+
+ if (ssp->type == PXA25x_SSP) {
+ sscr0 &= ~0x0000ff00;
+ sscr0 |= ((div - 2)/2) << 8; /* 2..512 */
+ } else {
+ sscr0 &= ~0x000fff00;
+ sscr0 |= (div - 1) << 8; /* 1..4096 */
+ }
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+}
+
+/*
+ * Set the SSP ports SYSCLK.
+ */
+static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+
+ u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
+ ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
+
+ if (priv->extclk) {
+ int ret;
+
+ /*
+ * For DT based boards, if an extclk is given, use it
+ * here and configure PXA_SSP_CLK_EXT.
+ */
+
+ ret = clk_set_rate(priv->extclk, freq);
+ if (ret < 0)
+ return ret;
+
+ clk_id = PXA_SSP_CLK_EXT;
+ }
+
+ dev_dbg(ssp->dev,
+ "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n",
+ cpu_dai->id, clk_id, freq);
+
+ switch (clk_id) {
+ case PXA_SSP_CLK_NET_PLL:
+ sscr0 |= SSCR0_MOD;
+ break;
+ case PXA_SSP_CLK_PLL:
+ /* Internal PLL is fixed */
+ if (ssp->type == PXA25x_SSP)
+ priv->sysclk = 1843200;
+ else
+ priv->sysclk = 13000000;
+ break;
+ case PXA_SSP_CLK_EXT:
+ priv->sysclk = freq;
+ sscr0 |= SSCR0_ECS;
+ break;
+ case PXA_SSP_CLK_NET:
+ priv->sysclk = freq;
+ sscr0 |= SSCR0_NCS | SSCR0_MOD;
+ break;
+ case PXA_SSP_CLK_AUDIO:
+ priv->sysclk = 0;
+ pxa_ssp_set_scr(ssp, 1);
+ sscr0 |= SSCR0_ACS;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ /* The SSP clock must be disabled when changing SSP clock mode
+ * on PXA2xx. On PXA3xx it must be enabled when doing so. */
+ if (ssp->type != PXA3xx_SSP)
+ clk_disable_unprepare(ssp->clk);
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+ if (ssp->type != PXA3xx_SSP)
+ clk_prepare_enable(ssp->clk);
+
+ return 0;
+}
+
+/*
+ * Configure the PLL frequency pxa27x and (afaik - pxa320 only)
+ */
+static int pxa_ssp_set_pll(struct ssp_priv *priv, unsigned int freq)
+{
+ struct ssp_device *ssp = priv->ssp;
+ u32 ssacd = pxa_ssp_read_reg(ssp, SSACD) & ~0x70;
+
+ if (ssp->type == PXA3xx_SSP)
+ pxa_ssp_write_reg(ssp, SSACDD, 0);
+
+ switch (freq) {
+ case 5622000:
+ break;
+ case 11345000:
+ ssacd |= (0x1 << 4);
+ break;
+ case 12235000:
+ ssacd |= (0x2 << 4);
+ break;
+ case 14857000:
+ ssacd |= (0x3 << 4);
+ break;
+ case 32842000:
+ ssacd |= (0x4 << 4);
+ break;
+ case 48000000:
+ ssacd |= (0x5 << 4);
+ break;
+ case 0:
+ /* Disable */
+ break;
+
+ default:
+ /* PXA3xx has a clock ditherer which can be used to generate
+ * a wider range of frequencies - calculate a value for it.
+ */
+ if (ssp->type == PXA3xx_SSP) {
+ u32 val;
+ u64 tmp = 19968;
+
+ tmp *= 1000000;
+ do_div(tmp, freq);
+ val = tmp;
+
+ val = (val << 16) | 64;
+ pxa_ssp_write_reg(ssp, SSACDD, val);
+
+ ssacd |= (0x6 << 4);
+
+ dev_dbg(ssp->dev,
+ "Using SSACDD %x to supply %uHz\n",
+ val, freq);
+ break;
+ }
+
+ return -EINVAL;
+ }
+
+ pxa_ssp_write_reg(ssp, SSACD, ssacd);
+
+ return 0;
+}
+
+/*
+ * Set the active slots in TDM/Network mode
+ */
+static int pxa_ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ u32 sscr0;
+
+ sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+ sscr0 &= ~(SSCR0_MOD | SSCR0_SlotsPerFrm(8) | SSCR0_EDSS | SSCR0_DSS);
+
+ /* set slot width */
+ if (slot_width > 16)
+ sscr0 |= SSCR0_EDSS | SSCR0_DataSize(slot_width - 16);
+ else
+ sscr0 |= SSCR0_DataSize(slot_width);
+
+ if (slots > 1) {
+ /* enable network mode */
+ sscr0 |= SSCR0_MOD;
+
+ /* set number of active slots */
+ sscr0 |= SSCR0_SlotsPerFrm(slots);
+
+ /* set active slot mask */
+ pxa_ssp_write_reg(ssp, SSTSA, tx_mask);
+ pxa_ssp_write_reg(ssp, SSRSA, rx_mask);
+ }
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+
+ return 0;
+}
+
+/*
+ * Tristate the SSP DAI lines
+ */
+static int pxa_ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai,
+ int tristate)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ u32 sscr1;
+
+ sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
+ if (tristate)
+ sscr1 &= ~SSCR1_TTE;
+ else
+ sscr1 |= SSCR1_TTE;
+ pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+
+ return 0;
+}
+
+static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ case SND_SOC_DAIFMT_BC_FP:
+ case SND_SOC_DAIFMT_BP_FP:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_NB_IF:
+ case SND_SOC_DAIFMT_IB_IF:
+ case SND_SOC_DAIFMT_IB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Settings will be applied in hw_params() */
+ priv->dai_fmt = fmt;
+
+ return 0;
+}
+
+/*
+ * Set up the SSP DAI format.
+ * The SSP Port must be inactive before calling this function as the
+ * physical interface format is changed.
+ */
+static int pxa_ssp_configure_dai_fmt(struct ssp_priv *priv)
+{
+ struct ssp_device *ssp = priv->ssp;
+ u32 sscr0, sscr1, sspsp, scfr;
+
+ /* check if we need to change anything at all */
+ if (priv->configured_dai_fmt == priv->dai_fmt)
+ return 0;
+
+ /* reset port settings */
+ sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
+ ~(SSCR0_PSP | SSCR0_MOD);
+ sscr1 = pxa_ssp_read_reg(ssp, SSCR1) &
+ ~(SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR |
+ SSCR1_RWOT | SSCR1_TRAIL | SSCR1_TFT | SSCR1_RFT);
+ sspsp = pxa_ssp_read_reg(ssp, SSPSP) &
+ ~(SSPSP_SFRMP | SSPSP_SCMODE(3));
+
+ sscr1 |= SSCR1_RxTresh(8) | SSCR1_TxTresh(7);
+
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR;
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ sscr1 |= SSCR1_SCLKDIR | SSCR1_SCFR;
+ break;
+ case SND_SOC_DAIFMT_BP_FP:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ sspsp |= SSPSP_SFRMP;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ sspsp |= SSPSP_SCMODE(2);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sscr0 |= SSCR0_PSP;
+ sscr1 |= SSCR1_RWOT | SSCR1_TRAIL;
+ /* See hw_params() */
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A:
+ sspsp |= SSPSP_FSRT;
+ fallthrough;
+ case SND_SOC_DAIFMT_DSP_B:
+ sscr0 |= SSCR0_MOD | SSCR0_PSP;
+ sscr1 |= SSCR1_TRAIL | SSCR1_RWOT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+ pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+ pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ case SND_SOC_DAIFMT_BC_FP:
+ scfr = pxa_ssp_read_reg(ssp, SSCR1) | SSCR1_SCFR;
+ pxa_ssp_write_reg(ssp, SSCR1, scfr);
+
+ while (pxa_ssp_read_reg(ssp, SSSR) & SSSR_BSY)
+ cpu_relax();
+ break;
+ }
+
+ dump_registers(ssp);
+
+ /* Since we are configuring the timings for the format by hand
+ * we have to defer some things until hw_params() where we
+ * know parameters like the sample size.
+ */
+ priv->configured_dai_fmt = priv->dai_fmt;
+
+ return 0;
+}
+
+struct pxa_ssp_clock_mode {
+ int rate;
+ int pll;
+ u8 acds;
+ u8 scdb;
+};
+
+static const struct pxa_ssp_clock_mode pxa_ssp_clock_modes[] = {
+ { .rate = 8000, .pll = 32842000, .acds = SSACD_ACDS_32, .scdb = SSACD_SCDB_4X },
+ { .rate = 11025, .pll = 5622000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_4X },
+ { .rate = 16000, .pll = 32842000, .acds = SSACD_ACDS_16, .scdb = SSACD_SCDB_4X },
+ { .rate = 22050, .pll = 5622000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X },
+ { .rate = 44100, .pll = 11345000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X },
+ { .rate = 48000, .pll = 12235000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X },
+ { .rate = 96000, .pll = 12235000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_1X },
+ {}
+};
+
+/*
+ * Set the SSP audio DMA parameters and sample size.
+ * Can be called multiple times by oss emulation.
+ */
+static int pxa_ssp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int chn = params_channels(params);
+ u32 sscr0, sspsp;
+ int width = snd_pcm_format_physical_width(params_format(params));
+ int ttsa = pxa_ssp_read_reg(ssp, SSTSA) & 0xf;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ int rate = params_rate(params);
+ int bclk = rate * chn * (width / 8);
+ int ret;
+
+ dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ /* Network mode with one active slot (ttsa == 1) can be used
+ * to force 16-bit frame width on the wire (for S16_LE), even
+ * with two channels. Use 16-bit DMA transfers for this case.
+ */
+ pxa_ssp_set_dma_params(ssp,
+ ((chn == 2) && (ttsa != 1)) || (width == 32),
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK, dma_data);
+
+ /* we can only change the settings if the port is not in use */
+ if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE)
+ return 0;
+
+ ret = pxa_ssp_configure_dai_fmt(priv);
+ if (ret < 0)
+ return ret;
+
+ /* clear selected SSP bits */
+ sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & ~(SSCR0_DSS | SSCR0_EDSS);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (ssp->type == PXA3xx_SSP)
+ sscr0 |= SSCR0_FPCKE;
+ sscr0 |= SSCR0_DataSize(16);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(8));
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(16));
+ break;
+ }
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+
+ if (sscr0 & SSCR0_ACS) {
+ ret = pxa_ssp_set_pll(priv, bclk);
+
+ /*
+ * If we were able to generate the bclk directly,
+ * all is fine. Otherwise, look up the closest rate
+ * from the table and also set the dividers.
+ */
+
+ if (ret < 0) {
+ const struct pxa_ssp_clock_mode *m;
+ int ssacd;
+
+ for (m = pxa_ssp_clock_modes; m->rate; m++) {
+ if (m->rate == rate)
+ break;
+ }
+
+ if (!m->rate)
+ return -EINVAL;
+
+ ret = pxa_ssp_set_pll(priv, bclk);
+ if (ret < 0)
+ return ret;
+
+ ssacd = pxa_ssp_read_reg(ssp, SSACD);
+ ssacd &= ~(SSACD_ACDS(7) | SSACD_SCDB_1X);
+ ssacd |= SSACD_ACDS(m->acds);
+ ssacd |= m->scdb;
+ pxa_ssp_write_reg(ssp, SSACD, ssacd);
+ }
+ } else if (sscr0 & SSCR0_ECS) {
+ /*
+ * For setups with external clocking, the PLL and its diviers
+ * are not active. Instead, the SCR bits in SSCR0 can be used
+ * to divide the clock.
+ */
+ pxa_ssp_set_scr(ssp, bclk / rate);
+ }
+
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ sspsp = pxa_ssp_read_reg(ssp, SSPSP);
+
+ if (((priv->sysclk / bclk) == 64) && (width == 16)) {
+ /* This is a special case where the bitclk is 64fs
+ * and we're not dealing with 2*32 bits of audio
+ * samples.
+ *
+ * The SSP values used for that are all found out by
+ * trying and failing a lot; some of the registers
+ * needed for that mode are only available on PXA3xx.
+ */
+ if (ssp->type != PXA3xx_SSP)
+ return -EINVAL;
+
+ sspsp |= SSPSP_SFRMWDTH(width * 2);
+ sspsp |= SSPSP_SFRMDLY(width * 4);
+ sspsp |= SSPSP_EDMYSTOP(3);
+ sspsp |= SSPSP_DMYSTOP(3);
+ sspsp |= SSPSP_DMYSTRT(1);
+ } else {
+ /* The frame width is the width the LRCLK is
+ * asserted for; the delay is expressed in
+ * half cycle units. We need the extra cycle
+ * because the data starts clocking out one BCLK
+ * after LRCLK changes polarity.
+ */
+ sspsp |= SSPSP_SFRMWDTH(width + 1);
+ sspsp |= SSPSP_SFRMDLY((width + 1) * 2);
+ sspsp |= SSPSP_DMYSTRT(1);
+ }
+
+ pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+ break;
+ default:
+ break;
+ }
+
+ /* When we use a network mode, we always require TDM slots
+ * - complain loudly and fail if they've not been set up yet.
+ */
+ if ((sscr0 & SSCR0_MOD) && !ttsa) {
+ dev_err(ssp->dev, "No TDM timeslot configured\n");
+ return -EINVAL;
+ }
+
+ dump_registers(ssp);
+
+ return 0;
+}
+
+static void pxa_ssp_set_running_bit(struct snd_pcm_substream *substream,
+ struct ssp_device *ssp, int value)
+{
+ uint32_t sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+ uint32_t sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
+ uint32_t sspsp = pxa_ssp_read_reg(ssp, SSPSP);
+ uint32_t sssr = pxa_ssp_read_reg(ssp, SSSR);
+
+ if (value && (sscr0 & SSCR0_SSE))
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0 & ~SSCR0_SSE);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (value)
+ sscr1 |= SSCR1_TSRE;
+ else
+ sscr1 &= ~SSCR1_TSRE;
+ } else {
+ if (value)
+ sscr1 |= SSCR1_RSRE;
+ else
+ sscr1 &= ~SSCR1_RSRE;
+ }
+
+ pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+
+ if (value) {
+ pxa_ssp_write_reg(ssp, SSSR, sssr);
+ pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+ pxa_ssp_write_reg(ssp, SSCR0, sscr0 | SSCR0_SSE);
+ }
+}
+
+static int pxa_ssp_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ int ret = 0;
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct ssp_device *ssp = priv->ssp;
+ int val;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ pxa_ssp_enable(ssp);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pxa_ssp_set_running_bit(substream, ssp, 1);
+ val = pxa_ssp_read_reg(ssp, SSSR);
+ pxa_ssp_write_reg(ssp, SSSR, val);
+ break;
+ case SNDRV_PCM_TRIGGER_START:
+ pxa_ssp_set_running_bit(substream, ssp, 1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pxa_ssp_set_running_bit(substream, ssp, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ pxa_ssp_disable(ssp);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ pxa_ssp_set_running_bit(substream, ssp, 0);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ dump_registers(ssp);
+
+ return ret;
+}
+
+static int pxa_ssp_probe(struct snd_soc_dai *dai)
+{
+ struct device *dev = dai->dev;
+ struct ssp_priv *priv;
+ int ret;
+
+ priv = kzalloc(sizeof(struct ssp_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ if (dev->of_node) {
+ struct device_node *ssp_handle;
+
+ ssp_handle = of_parse_phandle(dev->of_node, "port", 0);
+ if (!ssp_handle) {
+ dev_err(dev, "unable to get 'port' phandle\n");
+ ret = -ENODEV;
+ goto err_priv;
+ }
+
+ priv->ssp = pxa_ssp_request_of(ssp_handle, "SoC audio");
+ if (priv->ssp == NULL) {
+ ret = -ENODEV;
+ goto err_priv;
+ }
+
+ priv->extclk = devm_clk_get(dev, "extclk");
+ if (IS_ERR(priv->extclk)) {
+ ret = PTR_ERR(priv->extclk);
+ if (ret == -EPROBE_DEFER)
+ goto err_priv;
+
+ priv->extclk = NULL;
+ }
+ } else {
+ priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio");
+ if (priv->ssp == NULL) {
+ ret = -ENODEV;
+ goto err_priv;
+ }
+ }
+
+ priv->dai_fmt = (unsigned int) -1;
+ snd_soc_dai_set_drvdata(dai, priv);
+
+ return 0;
+
+err_priv:
+ kfree(priv);
+ return ret;
+}
+
+static int pxa_ssp_remove(struct snd_soc_dai *dai)
+{
+ struct ssp_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+ pxa_ssp_free(priv->ssp);
+ kfree(priv);
+ return 0;
+}
+
+#define PXA_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dai_ops pxa_ssp_dai_ops = {
+ .probe = pxa_ssp_probe,
+ .remove = pxa_ssp_remove,
+ .startup = pxa_ssp_startup,
+ .shutdown = pxa_ssp_shutdown,
+ .trigger = pxa_ssp_trigger,
+ .hw_params = pxa_ssp_hw_params,
+ .set_sysclk = pxa_ssp_set_dai_sysclk,
+ .set_fmt = pxa_ssp_set_dai_fmt,
+ .set_tdm_slot = pxa_ssp_set_dai_tdm_slot,
+ .set_tristate = pxa_ssp_set_dai_tristate,
+};
+
+static struct snd_soc_dai_driver pxa_ssp_dai = {
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = PXA_SSP_RATES,
+ .formats = PXA_SSP_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = PXA_SSP_RATES,
+ .formats = PXA_SSP_FORMATS,
+ },
+ .ops = &pxa_ssp_dai_ops,
+};
+
+static const struct snd_soc_component_driver pxa_ssp_component = {
+ .name = "pxa-ssp",
+ .pcm_construct = pxa2xx_soc_pcm_new,
+ .open = pxa2xx_soc_pcm_open,
+ .close = pxa2xx_soc_pcm_close,
+ .hw_params = pxa2xx_soc_pcm_hw_params,
+ .prepare = pxa2xx_soc_pcm_prepare,
+ .trigger = pxa2xx_soc_pcm_trigger,
+ .pointer = pxa2xx_soc_pcm_pointer,
+ .suspend = pxa_ssp_suspend,
+ .resume = pxa_ssp_resume,
+ .legacy_dai_naming = 1,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id pxa_ssp_of_ids[] = {
+ { .compatible = "mrvl,pxa-ssp-dai" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pxa_ssp_of_ids);
+#endif
+
+static int asoc_ssp_probe(struct platform_device *pdev)
+{
+ return devm_snd_soc_register_component(&pdev->dev, &pxa_ssp_component,
+ &pxa_ssp_dai, 1);
+}
+
+static struct platform_driver asoc_ssp_driver = {
+ .driver = {
+ .name = "pxa-ssp-dai",
+ .of_match_table = of_match_ptr(pxa_ssp_of_ids),
+ },
+
+ .probe = asoc_ssp_probe,
+};
+
+module_platform_driver(asoc_ssp_driver);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa-ssp-dai");
diff --git a/sound/soc/pxa/pxa-ssp.h b/sound/soc/pxa/pxa-ssp.h
new file mode 100644
index 0000000000..d3b05109df
--- /dev/null
+++ b/sound/soc/pxa/pxa-ssp.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ASoC PXA SSP port support
+ */
+
+#ifndef _PXA_SSP_H
+#define _PXA_SSP_H
+
+/* SSP clock sources */
+#define PXA_SSP_CLK_PLL 0
+#define PXA_SSP_CLK_EXT 1
+#define PXA_SSP_CLK_NET 2
+#define PXA_SSP_CLK_AUDIO 3
+#define PXA_SSP_CLK_NET_PLL 4
+
+/* SSP audio dividers */
+#define PXA_SSP_AUDIO_DIV_ACDS 0
+#define PXA_SSP_AUDIO_DIV_SCDB 1
+#define PXA_SSP_DIV_SCR 2
+
+/* SSP ACDS audio dividers values */
+#define PXA_SSP_CLK_AUDIO_DIV_1 0
+#define PXA_SSP_CLK_AUDIO_DIV_2 1
+#define PXA_SSP_CLK_AUDIO_DIV_4 2
+#define PXA_SSP_CLK_AUDIO_DIV_8 3
+#define PXA_SSP_CLK_AUDIO_DIV_16 4
+#define PXA_SSP_CLK_AUDIO_DIV_32 5
+
+/* SSP divider bypass */
+#define PXA_SSP_CLK_SCDB_4 0
+#define PXA_SSP_CLK_SCDB_1 1
+#define PXA_SSP_CLK_SCDB_8 2
+
+#define PXA_SSP_PLL_OUT 0
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c
new file mode 100644
index 0000000000..e73bd62c03
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-ac97.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
+ *
+ * Author: Nicolas Pitre
+ * Created: Dec 02, 2004
+ * Copyright: MontaVista Software Inc.
+ */
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma/pxa-dma.h>
+
+#include <sound/ac97/controller.h>
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+#include <sound/dmaengine_pcm.h>
+
+#include <linux/platform_data/asoc-pxa.h>
+
+#define PCDR 0x0040 /* PCM FIFO Data Register */
+#define MODR 0x0140 /* Modem FIFO Data Register */
+#define MCDR 0x0060 /* Mic-in FIFO Data Register */
+
+static void pxa2xx_ac97_warm_reset(struct ac97_controller *adrv)
+{
+ pxa2xx_ac97_try_warm_reset();
+
+ pxa2xx_ac97_finish_reset();
+}
+
+static void pxa2xx_ac97_cold_reset(struct ac97_controller *adrv)
+{
+ pxa2xx_ac97_try_cold_reset();
+
+ pxa2xx_ac97_finish_reset();
+}
+
+static int pxa2xx_ac97_read_actrl(struct ac97_controller *adrv, int slot,
+ unsigned short reg)
+{
+ return pxa2xx_ac97_read(slot, reg);
+}
+
+static int pxa2xx_ac97_write_actrl(struct ac97_controller *adrv, int slot,
+ unsigned short reg, unsigned short val)
+{
+ return pxa2xx_ac97_write(slot, reg, val);
+}
+
+static struct ac97_controller_ops pxa2xx_ac97_ops = {
+ .read = pxa2xx_ac97_read_actrl,
+ .write = pxa2xx_ac97_write_actrl,
+ .warm_reset = pxa2xx_ac97_warm_reset,
+ .reset = pxa2xx_ac97_cold_reset,
+};
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_stereo_in = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .chan_name = "pcm_pcm_stereo_in",
+ .maxburst = 32,
+};
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_stereo_out = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .chan_name = "pcm_pcm_stereo_out",
+ .maxburst = 32,
+};
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_aux_mono_out = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+ .chan_name = "pcm_aux_mono_out",
+ .maxburst = 16,
+};
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_aux_mono_in = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+ .chan_name = "pcm_aux_mono_in",
+ .maxburst = 16,
+};
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_mic_mono_in = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+ .chan_name = "pcm_aux_mic_mono",
+ .maxburst = 16,
+};
+
+static int pxa2xx_ac97_hifi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &pxa2xx_ac97_pcm_stereo_out;
+ else
+ dma_data = &pxa2xx_ac97_pcm_stereo_in;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ return 0;
+}
+
+static int pxa2xx_ac97_aux_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &pxa2xx_ac97_pcm_aux_mono_out;
+ else
+ dma_data = &pxa2xx_ac97_pcm_aux_mono_in;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+ return 0;
+}
+
+static int pxa2xx_ac97_mic_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+ snd_soc_dai_set_dma_data(cpu_dai, substream,
+ &pxa2xx_ac97_pcm_mic_mono_in);
+
+ return 0;
+}
+
+#define PXA2XX_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+static const struct snd_soc_dai_ops pxa_ac97_hifi_dai_ops = {
+ .startup = pxa2xx_ac97_hifi_startup,
+};
+
+static const struct snd_soc_dai_ops pxa_ac97_aux_dai_ops = {
+ .startup = pxa2xx_ac97_aux_startup,
+};
+
+static const struct snd_soc_dai_ops pxa_ac97_mic_dai_ops = {
+ .startup = pxa2xx_ac97_mic_startup,
+};
+
+/*
+ * There is only 1 physical AC97 interface for pxa2xx, but it
+ * has extra fifo's that can be used for aux DACs and ADCs.
+ */
+static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = {
+{
+ .name = "pxa2xx-ac97",
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_ac97_hifi_dai_ops,
+},
+{
+ .name = "pxa2xx-ac97-aux",
+ .playback = {
+ .stream_name = "AC97 Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Aux Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_ac97_aux_dai_ops,
+},
+{
+ .name = "pxa2xx-ac97-mic",
+ .capture = {
+ .stream_name = "AC97 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_ac97_mic_dai_ops,
+},
+};
+
+static const struct snd_soc_component_driver pxa_ac97_component = {
+ .name = "pxa-ac97",
+ .pcm_construct = pxa2xx_soc_pcm_new,
+ .open = pxa2xx_soc_pcm_open,
+ .close = pxa2xx_soc_pcm_close,
+ .hw_params = pxa2xx_soc_pcm_hw_params,
+ .prepare = pxa2xx_soc_pcm_prepare,
+ .trigger = pxa2xx_soc_pcm_trigger,
+ .pointer = pxa2xx_soc_pcm_pointer,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id pxa2xx_ac97_dt_ids[] = {
+ { .compatible = "marvell,pxa250-ac97", },
+ { .compatible = "marvell,pxa270-ac97", },
+ { .compatible = "marvell,pxa300-ac97", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pxa2xx_ac97_dt_ids);
+
+#endif
+
+static int pxa2xx_ac97_dev_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct ac97_controller *ctrl;
+ pxa2xx_audio_ops_t *pdata = pdev->dev.platform_data;
+ struct resource *regs;
+ void **codecs_pdata;
+
+ if (pdev->id != -1) {
+ dev_err(&pdev->dev, "PXA2xx has only one AC97 port.\n");
+ return -ENXIO;
+ }
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs)
+ return -ENXIO;
+
+ pxa2xx_ac97_pcm_stereo_in.addr = regs->start + PCDR;
+ pxa2xx_ac97_pcm_stereo_out.addr = regs->start + PCDR;
+ pxa2xx_ac97_pcm_aux_mono_out.addr = regs->start + MODR;
+ pxa2xx_ac97_pcm_aux_mono_in.addr = regs->start + MODR;
+ pxa2xx_ac97_pcm_mic_mono_in.addr = regs->start + MCDR;
+
+ ret = pxa2xx_ac97_hw_probe(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "PXA2xx AC97 hw probe error (%d)\n", ret);
+ return ret;
+ }
+
+ codecs_pdata = pdata ? pdata->codec_pdata : NULL;
+ ctrl = snd_ac97_controller_register(&pxa2xx_ac97_ops, &pdev->dev,
+ AC97_SLOTS_AVAILABLE_ALL,
+ codecs_pdata);
+ if (IS_ERR(ctrl))
+ return PTR_ERR(ctrl);
+
+ platform_set_drvdata(pdev, ctrl);
+ /* Punt most of the init to the SoC probe; we may need the machine
+ * driver to do interesting things with the clocking to get us up
+ * and running.
+ */
+ return devm_snd_soc_register_component(&pdev->dev, &pxa_ac97_component,
+ pxa_ac97_dai_driver, ARRAY_SIZE(pxa_ac97_dai_driver));
+}
+
+static void pxa2xx_ac97_dev_remove(struct platform_device *pdev)
+{
+ struct ac97_controller *ctrl = platform_get_drvdata(pdev);
+
+ snd_ac97_controller_unregister(ctrl);
+ pxa2xx_ac97_hw_remove(pdev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pxa2xx_ac97_dev_suspend(struct device *dev)
+{
+ return pxa2xx_ac97_hw_suspend();
+}
+
+static int pxa2xx_ac97_dev_resume(struct device *dev)
+{
+ return pxa2xx_ac97_hw_resume();
+}
+
+static SIMPLE_DEV_PM_OPS(pxa2xx_ac97_pm_ops,
+ pxa2xx_ac97_dev_suspend, pxa2xx_ac97_dev_resume);
+#endif
+
+static struct platform_driver pxa2xx_ac97_driver = {
+ .probe = pxa2xx_ac97_dev_probe,
+ .remove_new = pxa2xx_ac97_dev_remove,
+ .driver = {
+ .name = "pxa2xx-ac97",
+#ifdef CONFIG_PM_SLEEP
+ .pm = &pxa2xx_ac97_pm_ops,
+#endif
+ .of_match_table = of_match_ptr(pxa2xx_ac97_dt_ids),
+ },
+};
+
+module_platform_driver(pxa2xx_ac97_driver);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-ac97");
diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c
new file mode 100644
index 0000000000..437bfccd04
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-i2s.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * pxa2xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * lrg@slimlogic.co.uk
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+#include <sound/dmaengine_pcm.h>
+
+#include <linux/platform_data/asoc-pxa.h>
+
+#include "pxa2xx-i2s.h"
+
+/*
+ * I2S Controller Register and Bit Definitions
+ */
+#define SACR0 (0x0000) /* Global Control Register */
+#define SACR1 (0x0004) /* Serial Audio I 2 S/MSB-Justified Control Register */
+#define SASR0 (0x000C) /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */
+#define SAIMR (0x0014) /* Serial Audio Interrupt Mask Register */
+#define SAICR (0x0018) /* Serial Audio Interrupt Clear Register */
+#define SADIV (0x0060) /* Audio Clock Divider Register. */
+#define SADR (0x0080) /* Serial Audio Data Register (TX and RX FIFO access Register). */
+
+#define SACR0_RFTH(x) ((x) << 12) /* Rx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_TFTH(x) ((x) << 8) /* Tx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */
+#define SACR0_EFWR (1 << 4) /* Enable EFWR Function */
+#define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */
+#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */
+#define SACR0_ENB (1 << 0) /* Enable I2S Link */
+#define SACR1_ENLBF (1 << 5) /* Enable Loopback */
+#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */
+#define SACR1_DREC (1 << 3) /* Disable Recording Function */
+#define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */
+
+#define SASR0_I2SOFF (1 << 7) /* Controller Status */
+#define SASR0_ROR (1 << 6) /* Rx FIFO Overrun */
+#define SASR0_TUR (1 << 5) /* Tx FIFO Underrun */
+#define SASR0_RFS (1 << 4) /* Rx FIFO Service Request */
+#define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */
+#define SASR0_BSY (1 << 2) /* I2S Busy */
+#define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */
+#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */
+
+#define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */
+#define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */
+
+#define SAIMR_ROR (1 << 6) /* Enable Rx FIFO Overrun Condition Interrupt */
+#define SAIMR_TUR (1 << 5) /* Enable Tx FIFO Underrun Condition Interrupt */
+#define SAIMR_RFS (1 << 4) /* Enable Rx FIFO Service Interrupt */
+#define SAIMR_TFS (1 << 3) /* Enable Tx FIFO Service Interrupt */
+
+struct pxa_i2s_port {
+ u32 sadiv;
+ u32 sacr0;
+ u32 sacr1;
+ u32 saimr;
+ int master;
+ u32 fmt;
+};
+static struct pxa_i2s_port pxa_i2s;
+static struct clk *clk_i2s;
+static int clk_ena = 0;
+static void __iomem *i2s_reg_base;
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_i2s_pcm_stereo_out = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .chan_name = "tx",
+ .maxburst = 32,
+};
+
+static struct snd_dmaengine_dai_dma_data pxa2xx_i2s_pcm_stereo_in = {
+ .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .chan_name = "rx",
+ .maxburst = 32,
+};
+
+static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+
+ if (IS_ERR(clk_i2s))
+ return PTR_ERR(clk_i2s);
+
+ if (!snd_soc_dai_active(cpu_dai))
+ writel(0, i2s_reg_base + SACR0);
+
+ return 0;
+}
+
+/* wait for I2S controller to be ready */
+static int pxa_i2s_wait(void)
+{
+ int i;
+
+ /* flush the Rx FIFO */
+ for (i = 0; i < 16; i++)
+ readl(i2s_reg_base + SADR);
+ return 0;
+}
+
+static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ pxa_i2s.fmt = 0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ pxa_i2s.fmt = SACR1_AMSL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ pxa_i2s.master = 1;
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ pxa_i2s.master = 0;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ if (clk_id != PXA2XX_I2S_SYSCLK)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ if (WARN_ON(IS_ERR(clk_i2s)))
+ return -EINVAL;
+ clk_prepare_enable(clk_i2s);
+ clk_ena = 1;
+ pxa_i2s_wait();
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = &pxa2xx_i2s_pcm_stereo_out;
+ else
+ dma_data = &pxa2xx_i2s_pcm_stereo_in;
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+ /* is port used by another stream */
+ if (!(SACR0 & SACR0_ENB)) {
+ writel(0, i2s_reg_base + SACR0);
+ if (pxa_i2s.master)
+ writel(readl(i2s_reg_base + SACR0) | (SACR0_BCKD), i2s_reg_base + SACR0);
+
+ writel(readl(i2s_reg_base + SACR0) | (SACR0_RFTH(14) | SACR0_TFTH(1)), i2s_reg_base + SACR0);
+ writel(readl(i2s_reg_base + SACR1) | (pxa_i2s.fmt), i2s_reg_base + SACR1);
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ writel(readl(i2s_reg_base + SAIMR) | (SAIMR_TFS), i2s_reg_base + SAIMR);
+ else
+ writel(readl(i2s_reg_base + SAIMR) | (SAIMR_RFS), i2s_reg_base + SAIMR);
+
+ switch (params_rate(params)) {
+ case 8000:
+ writel(0x48, i2s_reg_base + SADIV);
+ break;
+ case 11025:
+ writel(0x34, i2s_reg_base + SADIV);
+ break;
+ case 16000:
+ writel(0x24, i2s_reg_base + SADIV);
+ break;
+ case 22050:
+ writel(0x1a, i2s_reg_base + SADIV);
+ break;
+ case 44100:
+ writel(0xd, i2s_reg_base + SADIV);
+ break;
+ case 48000:
+ writel(0xc, i2s_reg_base + SADIV);
+ break;
+ case 96000: /* not in manual and possibly slightly inaccurate */
+ writel(0x6, i2s_reg_base + SADIV);
+ break;
+ }
+
+ return 0;
+}
+
+static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ writel(readl(i2s_reg_base + SACR1) & (~SACR1_DRPL), i2s_reg_base + SACR1);
+ else
+ writel(readl(i2s_reg_base + SACR1) & (~SACR1_DREC), i2s_reg_base + SACR1);
+ writel(readl(i2s_reg_base + SACR0) | (SACR0_ENB), i2s_reg_base + SACR0);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ writel(readl(i2s_reg_base + SACR1) | (SACR1_DRPL), i2s_reg_base + SACR1);
+ writel(readl(i2s_reg_base + SAIMR) & (~SAIMR_TFS), i2s_reg_base + SAIMR);
+ } else {
+ writel(readl(i2s_reg_base + SACR1) | (SACR1_DREC), i2s_reg_base + SACR1);
+ writel(readl(i2s_reg_base + SAIMR) & (~SAIMR_RFS), i2s_reg_base + SAIMR);
+ }
+
+ if ((readl(i2s_reg_base + SACR1) & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) {
+ writel(readl(i2s_reg_base + SACR0) & (~SACR0_ENB), i2s_reg_base + SACR0);
+ pxa_i2s_wait();
+ if (clk_ena) {
+ clk_disable_unprepare(clk_i2s);
+ clk_ena = 0;
+ }
+ }
+}
+
+#ifdef CONFIG_PM
+static int pxa2xx_soc_pcm_suspend(struct snd_soc_component *component)
+{
+ /* store registers */
+ pxa_i2s.sacr0 = readl(i2s_reg_base + SACR0);
+ pxa_i2s.sacr1 = readl(i2s_reg_base + SACR1);
+ pxa_i2s.saimr = readl(i2s_reg_base + SAIMR);
+ pxa_i2s.sadiv = readl(i2s_reg_base + SADIV);
+
+ /* deactivate link */
+ writel(readl(i2s_reg_base + SACR0) & (~SACR0_ENB), i2s_reg_base + SACR0);
+ pxa_i2s_wait();
+ return 0;
+}
+
+static int pxa2xx_soc_pcm_resume(struct snd_soc_component *component)
+{
+ pxa_i2s_wait();
+
+ writel(pxa_i2s.sacr0 & ~SACR0_ENB, i2s_reg_base + SACR0);
+ writel(pxa_i2s.sacr1, i2s_reg_base + SACR1);
+ writel(pxa_i2s.saimr, i2s_reg_base + SAIMR);
+ writel(pxa_i2s.sadiv, i2s_reg_base + SADIV);
+
+ writel(pxa_i2s.sacr0, i2s_reg_base + SACR0);
+
+ return 0;
+}
+
+#else
+#define pxa2xx_soc_pcm_suspend NULL
+#define pxa2xx_soc_pcm_resume NULL
+#endif
+
+static int pxa2xx_i2s_probe(struct snd_soc_dai *dai)
+{
+ clk_i2s = clk_get(dai->dev, "I2SCLK");
+ if (IS_ERR(clk_i2s))
+ return PTR_ERR(clk_i2s);
+
+ /*
+ * PXA Developer's Manual:
+ * If SACR0[ENB] is toggled in the middle of a normal operation,
+ * the SACR0[RST] bit must also be set and cleared to reset all
+ * I2S controller registers.
+ */
+ writel(SACR0_RST, i2s_reg_base + SACR0);
+ writel(0, i2s_reg_base + SACR0);
+ /* Make sure RPL and REC are disabled */
+ writel(SACR1_DRPL | SACR1_DREC, i2s_reg_base + SACR1);
+ /* Along with FIFO servicing */
+ writel(readl(i2s_reg_base + SAIMR) & (~(SAIMR_RFS | SAIMR_TFS)), i2s_reg_base + SAIMR);
+
+ snd_soc_dai_init_dma_data(dai, &pxa2xx_i2s_pcm_stereo_out,
+ &pxa2xx_i2s_pcm_stereo_in);
+
+ return 0;
+}
+
+static int pxa2xx_i2s_remove(struct snd_soc_dai *dai)
+{
+ clk_put(clk_i2s);
+ clk_i2s = ERR_PTR(-ENOENT);
+ return 0;
+}
+
+#define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+
+static const struct snd_soc_dai_ops pxa_i2s_dai_ops = {
+ .probe = pxa2xx_i2s_probe,
+ .remove = pxa2xx_i2s_remove,
+ .startup = pxa2xx_i2s_startup,
+ .shutdown = pxa2xx_i2s_shutdown,
+ .trigger = pxa2xx_i2s_trigger,
+ .hw_params = pxa2xx_i2s_hw_params,
+ .set_fmt = pxa2xx_i2s_set_dai_fmt,
+ .set_sysclk = pxa2xx_i2s_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver pxa_i2s_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &pxa_i2s_dai_ops,
+ .symmetric_rate = 1,
+};
+
+static const struct snd_soc_component_driver pxa_i2s_component = {
+ .name = "pxa-i2s",
+ .pcm_construct = pxa2xx_soc_pcm_new,
+ .open = pxa2xx_soc_pcm_open,
+ .close = pxa2xx_soc_pcm_close,
+ .hw_params = pxa2xx_soc_pcm_hw_params,
+ .prepare = pxa2xx_soc_pcm_prepare,
+ .trigger = pxa2xx_soc_pcm_trigger,
+ .pointer = pxa2xx_soc_pcm_pointer,
+ .suspend = pxa2xx_soc_pcm_suspend,
+ .resume = pxa2xx_soc_pcm_resume,
+ .legacy_dai_naming = 1,
+};
+
+static int pxa2xx_i2s_drv_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ i2s_reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(i2s_reg_base))
+ return PTR_ERR(i2s_reg_base);
+
+ pxa2xx_i2s_pcm_stereo_out.addr = res->start + SADR;
+ pxa2xx_i2s_pcm_stereo_in.addr = res->start + SADR;
+
+ return devm_snd_soc_register_component(&pdev->dev, &pxa_i2s_component,
+ &pxa_i2s_dai, 1);
+}
+
+static struct platform_driver pxa2xx_i2s_driver = {
+ .probe = pxa2xx_i2s_drv_probe,
+
+ .driver = {
+ .name = "pxa2xx-i2s",
+ },
+};
+
+static int __init pxa2xx_i2s_init(void)
+{
+ clk_i2s = ERR_PTR(-ENOENT);
+ return platform_driver_register(&pxa2xx_i2s_driver);
+}
+
+static void __exit pxa2xx_i2s_exit(void)
+{
+ platform_driver_unregister(&pxa2xx_i2s_driver);
+}
+
+module_init(pxa2xx_i2s_init);
+module_exit(pxa2xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("pxa2xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-i2s");
diff --git a/sound/soc/pxa/pxa2xx-i2s.h b/sound/soc/pxa/pxa2xx-i2s.h
new file mode 100644
index 0000000000..263568d445
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-i2s.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * linux/sound/soc/pxa/pxa2xx-i2s.h
+ */
+
+#ifndef _PXA2XX_I2S_H
+#define _PXA2XX_I2S_H
+
+/* I2S clock */
+#define PXA2XX_I2S_SYSCLK 0
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c
new file mode 100644
index 0000000000..9d6c41f775
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-pcm.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: (C) 2004 MontaVista Software, Inc.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/dmaengine.h>
+#include <linux/of.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+#include <sound/dmaengine_pcm.h>
+
+static const struct snd_soc_component_driver pxa2xx_soc_platform = {
+ .pcm_construct = pxa2xx_soc_pcm_new,
+ .open = pxa2xx_soc_pcm_open,
+ .close = pxa2xx_soc_pcm_close,
+ .hw_params = pxa2xx_soc_pcm_hw_params,
+ .prepare = pxa2xx_soc_pcm_prepare,
+ .trigger = pxa2xx_soc_pcm_trigger,
+ .pointer = pxa2xx_soc_pcm_pointer,
+};
+
+static int pxa2xx_soc_platform_probe(struct platform_device *pdev)
+{
+ return devm_snd_soc_register_component(&pdev->dev, &pxa2xx_soc_platform,
+ NULL, 0);
+}
+
+static struct platform_driver pxa_pcm_driver = {
+ .driver = {
+ .name = "pxa-pcm-audio",
+ },
+
+ .probe = pxa2xx_soc_platform_probe,
+};
+
+module_platform_driver(pxa_pcm_driver);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa-pcm-audio");
diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c
new file mode 100644
index 0000000000..70442315f5
--- /dev/null
+++ b/sound/soc/pxa/spitz.c
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * spitz.c -- SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include "../codecs/wm8750.h"
+#include "pxa2xx-i2s.h"
+
+#define SPITZ_HP 0
+#define SPITZ_MIC 1
+#define SPITZ_LINE 2
+#define SPITZ_HEADSET 3
+#define SPITZ_HP_OFF 4
+#define SPITZ_SPK_ON 0
+#define SPITZ_SPK_OFF 1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define SPITZ_AUDIO_CLOCK 12288000
+
+static int spitz_jack_func;
+static int spitz_spk_func;
+static struct gpio_desc *gpiod_mic, *gpiod_mute_l, *gpiod_mute_r;
+
+static void spitz_ext_control(struct snd_soc_dapm_context *dapm)
+{
+ snd_soc_dapm_mutex_lock(dapm);
+
+ if (spitz_spk_func == SPITZ_SPK_ON)
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk");
+
+ /* set up jack connection */
+ switch (spitz_jack_func) {
+ case SPITZ_HP:
+ /* enable and unmute hp jack, disable mic bias */
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack");
+ gpiod_set_value(gpiod_mute_l, 1);
+ gpiod_set_value(gpiod_mute_r, 1);
+ break;
+ case SPITZ_MIC:
+ /* enable mic jack and bias, mute hp */
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack");
+ gpiod_set_value(gpiod_mute_l, 0);
+ gpiod_set_value(gpiod_mute_r, 0);
+ break;
+ case SPITZ_LINE:
+ /* enable line jack, disable mic bias and mute hp */
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Line Jack");
+ gpiod_set_value(gpiod_mute_l, 0);
+ gpiod_set_value(gpiod_mute_r, 0);
+ break;
+ case SPITZ_HEADSET:
+ /* enable and unmute headset jack enable mic bias, mute L hp */
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack");
+ gpiod_set_value(gpiod_mute_l, 0);
+ gpiod_set_value(gpiod_mute_r, 1);
+ break;
+ case SPITZ_HP_OFF:
+
+ /* jack removed, everything off */
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
+ gpiod_set_value(gpiod_mute_l, 0);
+ gpiod_set_value(gpiod_mute_r, 0);
+ break;
+ }
+
+ snd_soc_dapm_sync_unlocked(dapm);
+
+ snd_soc_dapm_mutex_unlock(dapm);
+}
+
+static int spitz_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+
+ /* check the jack status at stream startup */
+ spitz_ext_control(&rtd->card->dapm);
+
+ return 0;
+}
+
+static int spitz_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_ops spitz_ops = {
+ .startup = spitz_startup,
+ .hw_params = spitz_hw_params,
+};
+
+static int spitz_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = spitz_jack_func;
+ return 0;
+}
+
+static int spitz_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+
+ if (spitz_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ spitz_jack_func = ucontrol->value.enumerated.item[0];
+ spitz_ext_control(&card->dapm);
+ return 1;
+}
+
+static int spitz_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = spitz_spk_func;
+ return 0;
+}
+
+static int spitz_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+
+ if (spitz_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ spitz_spk_func = ucontrol->value.enumerated.item[0];
+ spitz_ext_control(&card->dapm);
+ return 1;
+}
+
+static int spitz_mic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpiod_set_value_cansleep(gpiod_mic, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* spitz machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_LINE("Line Jack", NULL),
+
+ /* headset is a mic and mono headphone */
+ SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Spitz machine audio_map */
+static const struct snd_soc_dapm_route spitz_audio_map[] = {
+
+ /* headphone connected to LOUT1, ROUT1 */
+ {"Headphone Jack", NULL, "LOUT1"},
+ {"Headphone Jack", NULL, "ROUT1"},
+
+ /* headset connected to ROUT1 and LINPUT1 with bias (def below) */
+ {"Headset Jack", NULL, "ROUT1"},
+
+ /* ext speaker connected to LOUT2, ROUT2 */
+ {"Ext Spk", NULL, "ROUT2"},
+ {"Ext Spk", NULL, "LOUT2"},
+
+ /* mic is connected to input 1 - with bias */
+ {"LINPUT1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic Jack"},
+
+ /* line is connected to input 1 - no bias */
+ {"LINPUT1", NULL, "Line Jack"},
+};
+
+static const char * const jack_function[] = {"Headphone", "Mic", "Line",
+ "Headset", "Off"};
+static const char * const spk_function[] = {"On", "Off"};
+static const struct soc_enum spitz_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8750_spitz_controls[] = {
+ SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack,
+ spitz_set_jack),
+ SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk,
+ spitz_set_spk),
+};
+
+/* spitz digital audio interface glue - connects codec <--> CPU */
+SND_SOC_DAILINK_DEFS(wm8750,
+ DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-i2s")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8750.0-001b", "wm8750-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
+
+static struct snd_soc_dai_link spitz_dai = {
+ .name = "wm8750",
+ .stream_name = "WM8750",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &spitz_ops,
+ SND_SOC_DAILINK_REG(wm8750),
+};
+
+/* spitz audio machine driver */
+static struct snd_soc_card snd_soc_spitz = {
+ .name = "Spitz",
+ .owner = THIS_MODULE,
+ .dai_link = &spitz_dai,
+ .num_links = 1,
+
+ .controls = wm8750_spitz_controls,
+ .num_controls = ARRAY_SIZE(wm8750_spitz_controls),
+ .dapm_widgets = wm8750_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets),
+ .dapm_routes = spitz_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(spitz_audio_map),
+ .fully_routed = true,
+};
+
+static int spitz_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_spitz;
+ int ret;
+
+ gpiod_mic = devm_gpiod_get(&pdev->dev, "mic", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod_mic))
+ return PTR_ERR(gpiod_mic);
+ gpiod_mute_l = devm_gpiod_get(&pdev->dev, "mute-l", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod_mute_l))
+ return PTR_ERR(gpiod_mute_l);
+ gpiod_mute_r = devm_gpiod_get(&pdev->dev, "mute-r", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod_mute_r))
+ return PTR_ERR(gpiod_mute_r);
+
+ card->dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+static struct platform_driver spitz_driver = {
+ .driver = {
+ .name = "spitz-audio",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = spitz_probe,
+};
+
+module_platform_driver(spitz_driver);
+
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Spitz");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:spitz-audio");