summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2012-01-18 13:22:40 +0000
committerRussell King <rmk+kernel@armlinux.org.uk>2020-10-12 21:52:48 +0100
commitffe4f617976af2767c80f4bde71ba0ead245bfd8 (patch)
tree10eba399c9f65bbde13b3a4decdb31051eaa193c
parentc029406d6cf0d37164a31cd34f1199144e4e7652 (diff)
ALSA: ASoC: add SA-11x0 SSP cpu_dai driver
Add a SA11x0 SSP SoC audio interface driver, which contains the SoC specifics for handing audio data via the SSP interface. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r--sound/soc/Kconfig1
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/sa11x0/Kconfig4
-rw-r--r--sound/soc/sa11x0/Makefile2
-rw-r--r--sound/soc/sa11x0/ssp.c361
-rw-r--r--sound/soc/sa11x0/ssp.h11
6 files changed, 380 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 7792fd5eb3dc..f03e4359934b 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -66,6 +66,7 @@ source "sound/soc/mxs/Kconfig"
source "sound/soc/pxa/Kconfig"
source "sound/soc/qcom/Kconfig"
source "sound/soc/rockchip/Kconfig"
+source "sound/soc/sa11x0/Kconfig"
source "sound/soc/samsung/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/sirf/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 035015352c4d..980489376d9e 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_SND_SOC) += kirkwood/
obj-$(CONFIG_SND_SOC) += pxa/
obj-$(CONFIG_SND_SOC) += qcom/
obj-$(CONFIG_SND_SOC) += rockchip/
+obj-$(CONFIG_SND_SOC) += sa11x0/
obj-$(CONFIG_SND_SOC) += samsung/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += sirf/
diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
new file mode 100644
index 000000000000..7a400f914c52
--- /dev/null
+++ b/sound/soc/sa11x0/Kconfig
@@ -0,0 +1,4 @@
+config SND_SOC_SA11X0_SSP
+ tristate
+ depends on DMA_SA11X0
+ select SND_SOC_DMAENGINE
diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile
new file mode 100644
index 000000000000..770ca0647343
--- /dev/null
+++ b/sound/soc/sa11x0/Makefile
@@ -0,0 +1,2 @@
+snd-soc-sa11x0-ssp-objs := ssp.o
+obj-$(CONFIG_SND_SOC_SA11X0_SSP) += snd-soc-sa11x0-ssp.o
diff --git a/sound/soc/sa11x0/ssp.c b/sound/soc/sa11x0/ssp.c
new file mode 100644
index 000000000000..1bdd4310a39d
--- /dev/null
+++ b/sound/soc/sa11x0/ssp.c
@@ -0,0 +1,361 @@
+/*
+ * ASoC SA11x0 SSP DAI driver
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * The SA11x0 SSP interface gives us four signals: TXD, RXD, SFRM and SCLK,
+ * and SFRM pulses for each and every word transmitted. These signals can
+ * be routed to GPIO10(TXD), GPIO11(RXD), GPIO12(SCLK), and GPIO13(SFRM)
+ * when the PPAR_SPR bit it set, along with the appropriate GAFR and GPDR
+ * configuration. If PPAR_SPR is clear, the SSP shares the same pins with
+ * the MCP.
+ */
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dmaengine.h>
+#include <sound/soc.h>
+
+#include "ssp.h"
+
+#define SA11X0_SSP_CHANNELS_MIN 1
+#define SA11X0_SSP_CHANNELS_MAX 8
+
+/*
+ * This isn't really up to us - it depends how the board implements the
+ * clocking, whether the board uses the on-board clock source, or whether
+ * the SSP is clocked via GPIO19.
+ */
+#define SA11X0_SSP_RATES \
+ (SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_CONTINUOUS)
+#define SA11X0_SSP_FORMATS \
+ (SNDRV_PCM_FORMAT_S8 | SNDRV_PCM_FORMAT_U8 | \
+ SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_U16_LE)
+
+#define SSCR0 0x60
+#define SSCR0_DSS (15 << 0) /* s */
+#define SSCR0_FRF_MOT (0 << 4)
+#define SSCR0_FRF_TI (1 << 4)
+#define SSCR0_FRF_NAT (2 << 4)
+#define SSCR0_SSE (1 << 7) /* s */
+#define SSCR0_SCR (0xff << 8) /* s */
+#define SSCR1 0x64
+#define SSCR1_RIE (1 << 0)
+#define SSCR1_TIE (1 << 1)
+#define SSCR1_LBM (1 << 2)
+#define SSCR1_SPO (1 << 3)
+#define SSCR1_SP (1 << 4)
+#define SSCR1_ECS (1 << 5) /* s */
+#define SSDR 0x6c
+#define SSSR 0x74
+
+struct ssp_priv {
+ void __iomem *base;
+ unsigned long clk_hz;
+ u32 cr0;
+ u32 cr1;
+};
+
+static struct soc_dma_config sa11x0_tx_cfg = {
+ .filter = sa11x0_dma_filter_fn,
+ .data = "Ser4SSPTr",
+ .reg = 0x80070000 + SSDR,
+ .width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+ .maxburst = 4,
+ .align = 32,
+ .fifo_size = 2 * 8,
+};
+
+static struct soc_dma_config sa11x0_rx_cfg = {
+ .filter = sa11x0_dma_filter_fn,
+ .data = "Ser4SSPRc",
+ .reg = 0x80070000 + SSDR,
+ .width = DMA_SLAVE_BUSWIDTH_2_BYTES,
+ .maxburst = 4,
+ .align = 32,
+ .fifo_size = 2 * 12,
+};
+
+/*
+ * Constrain the rate according to the bitrate(s) that the interface can
+ * do, which is determined by its input clock and the range of divisors
+ * that we can program (1 to 256 with an initial /2).
+ */
+static int
+sa11x0_ssp_rate_constraint(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_interval *p = hw_param_interval(params, rule->var);
+ struct ssp_priv *ssp = rule->private;
+ snd_pcm_format_t fmt = params_format(params);
+ struct snd_ratnum ratnum;
+
+ ratnum.num = ssp->clk_hz /
+ (2 * snd_pcm_format_width(fmt) * params_channels(params));
+ ratnum.den_min = 1;
+ ratnum.den_max = 256;
+ ratnum.den_step = 1;
+
+ return snd_interval_ratnum(p, 1, &ratnum, &params->rate_num,
+ &params->rate_den);
+}
+
+static int
+sa11x0_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ if (!dai->active) {
+ writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+ writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+ ssp->cr0 |= SSCR0_SSE;
+ writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+ }
+
+ snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ sa11x0_ssp_rate_constraint, ssp,
+ SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+
+ return 0;
+}
+
+static void sa11x0_ssp_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (!dai->active) {
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ ssp->cr0 &= ~SSCR0_SSE;
+ writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+ }
+}
+
+static int sa11x0_ssp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+ snd_pcm_format_t fmt = params_format(params);
+ int width = snd_pcm_format_width(fmt);
+ unsigned int divisor;
+ u32 cr0;
+
+ if (width < 0)
+ return width;
+ if (width < 4 || width > 16)
+ return -EINVAL;
+
+ divisor = ssp->clk_hz / (2 * width * params_channels(params) *
+ params_rate(params));
+ if (divisor == 0)
+ return -EINVAL;
+
+ /* Set the bit-width for the format and the divisor */
+ cr0 = ssp->cr0 & ~(SSCR0_DSS | SSCR0_SCR);
+ /* rate_den is calculated by snd_interval_ratnum() above. */
+ cr0 |= (width - 1) | (divisor - 1) << 8;
+ if (cr0 != ssp->cr0) {
+ ssp->cr0 = cr0;
+ writel_relaxed(cr0, ssp->base + SSCR0);
+ }
+ writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+
+ return 0;
+}
+
+static int sa11x0_ssp_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ switch (clk_id) {
+ case SA11X0_SSP_CLK_INT:
+ ssp->cr1 &= ~SSCR1_ECS;
+ ssp->clk_hz = 3686400;
+ break;
+
+ case SA11X0_SSP_CLK_EXT:
+ /*
+ * The SA11x0 developer's manual section 11.12.10.6 says the
+ * external clock can be up to 3.6864MHz, but iPAQs supply
+ * this with up to a 12.288MHz clock, which appears to work.
+ * We trust that the frequency is correct.
+ */
+ if (dir != SND_SOC_CLOCK_IN)
+ return -EINVAL;
+ ssp->cr1 |= SSCR1_ECS;
+ ssp->clk_hz = freq;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sa11x0_ssp_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ /* We always generate the clock and frm signals */
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ ssp->cr1 &= ~SSCR1_SPO;
+ break;
+
+ case SND_SOC_DAIFMT_IB_NF:
+ ssp->cr1 |= SSCR1_SPO;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops sa11x0_ssp_ops = {
+ .startup = sa11x0_ssp_startup,
+ .shutdown = sa11x0_ssp_shutdown,
+ .hw_params = sa11x0_ssp_hw_params,
+ .set_sysclk = sa11x0_ssp_set_sysclk,
+ .set_fmt = sa11x0_ssp_set_fmt,
+};
+
+static int sa11x0_ssp_probe(struct snd_soc_dai *dai)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ /* Default to TI mode and a bit-width of 16 */
+ ssp->cr0 = SSCR0_FRF_TI | 15;
+
+ /*
+ * Set the DMA data now - it's needed for the dmaengine backend to
+ * obtain its DMA channel, in turn its struct device, and therefore
+ * a struct device to allocate DMA memory against.
+ */
+ dai->playback_dma_data = &sa11x0_tx_cfg;
+ dai->capture_dma_data = &sa11x0_rx_cfg;
+
+ return 0;
+}
+
+static int sa11x0_ssp_remove(struct snd_soc_dai *dai)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int sa11x0_ssp_suspend(struct snd_soc_dai *dai)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ writel_relaxed(ssp->cr0 & ~SSCR0_SSE, ssp->base + SSCR0);
+
+ return 0;
+}
+
+static int sa11x0_ssp_resume(struct snd_soc_dai *dai)
+{
+ struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+ writel_relaxed(ssp->cr0 & ~SSCR0_SSE, ssp->base + SSCR0);
+ writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+ if (ssp->cr0 & SSCR0_SSE)
+ writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+
+ return 0;
+}
+#else
+#define sa11x0_ssp_suspend NULL
+#define sa11x0_ssp_resume NULL
+#endif
+
+static struct snd_soc_dai_driver sa11x0_ssp_driver = {
+ .probe = sa11x0_ssp_probe,
+ .remove = sa11x0_ssp_remove,
+ .suspend = sa11x0_ssp_suspend,
+ .resume = sa11x0_ssp_resume,
+ .ops = &sa11x0_ssp_ops,
+ .capture = {
+ .channels_min = SA11X0_SSP_CHANNELS_MIN,
+ .channels_max = SA11X0_SSP_CHANNELS_MAX,
+ .rates = SA11X0_SSP_RATES,
+ .formats = SA11X0_SSP_FORMATS,
+ },
+ .playback = {
+ .channels_min = SA11X0_SSP_CHANNELS_MIN,
+ .channels_max = SA11X0_SSP_CHANNELS_MAX,
+ .rates = SA11X0_SSP_RATES,
+ .formats = SA11X0_SSP_FORMATS,
+ },
+ .symmetric_rates = 1,
+};
+
+static const struct snd_soc_component_driver sa11x0_ssp_component = {
+ .name = "sa11x0-ssp",
+};
+
+static int sa11x0_ssp_plat_probe(struct platform_device *pdev)
+{
+ struct ssp_priv *ssp;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ ssp = devm_kzalloc(&pdev->dev, sizeof(*ssp), GFP_KERNEL);
+ if (!ssp)
+ return -ENOMEM;
+
+ /* Assume internal clock, as that's what we program initially */
+ ssp->clk_hz = 3686400;
+
+ ssp->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ssp->base))
+ return PTR_ERR(ssp->base);
+
+ writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+ writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+
+ platform_set_drvdata(pdev, ssp);
+ return snd_soc_register_component(&pdev->dev, &sa11x0_ssp_component,
+ &sa11x0_ssp_driver, 1);
+}
+
+static int sa11x0_ssp_plat_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver sa11x0_ssp_plat_driver = {
+ .driver = {
+ .name = "sa11x0-ssp",
+ .owner = THIS_MODULE,
+ },
+ .probe = sa11x0_ssp_plat_probe,
+ .remove = sa11x0_ssp_plat_remove,
+};
+module_platform_driver(sa11x0_ssp_plat_driver);
+
+MODULE_AUTHOR("Russell King <linux@arm.linux.org.uk>");
+MODULE_DESCRIPTION("SA11x0 SSP/PCM SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sa11x0-ssp");
diff --git a/sound/soc/sa11x0/ssp.h b/sound/soc/sa11x0/ssp.h
new file mode 100644
index 000000000000..22f1e5b8f45c
--- /dev/null
+++ b/sound/soc/sa11x0/ssp.h
@@ -0,0 +1,11 @@
+#ifndef SA11X0_SSP_H
+#define SA11X0_SSP_H
+
+/* SSP clock sources */
+#define SA11X0_SSP_CLK_INT 0x11000000
+#define SA11X0_SSP_CLK_EXT 0x11000001
+
+/* SSP audio dividers */
+#define SA11X0_SSP_DIV_SCR 0x11000100
+
+#endif