From ad2f087d4dab77f26776c54575c9740fbae72fe4 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 18 Jan 2012 13:22:40 +0000 Subject: 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 --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sa11x0/Kconfig | 4 + sound/soc/sa11x0/Makefile | 2 + sound/soc/sa11x0/ssp.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/sa11x0/ssp.h | 11 ++ 6 files changed, 380 insertions(+) create mode 100644 sound/soc/sa11x0/Kconfig create mode 100644 sound/soc/sa11x0/Makefile create mode 100644 sound/soc/sa11x0/ssp.c create mode 100644 sound/soc/sa11x0/ssp.h diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index ea2ad134386f..e6fdd7c149a8 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -84,6 +84,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/sof/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 17a8b6e149c4..f263ebb09743 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -50,6 +50,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) += sof/ 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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, ¶ms->rate_num, + ¶ms->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 "); +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 -- cgit