[alsa-devel] [PATCH v3 0/5] ASoC: add CSR SiRFSoC sound drivers
From: Rongjun Ying rongjun.ying@csr.com
This patchset adds CSR SiRFSoC sound drivers including: 1. the platform DMA driver which will be shard by all DAI 2. the I2S CPU DAI driver 3. the USP-based PCM CPU DAI driver 4. CPU DAI and Codec driver for internal chip codec 5. the mach driver for EVB board using internal codec
-v3: 1. Use devm_* API for driver 2. Remove the extcon stuff code 3. Calculated automatically the div vaule at runtime based on the sample rate 4. Automatically discovering the configuration of pcm hardware from the dmaengine driver 5. Add binding documents
Rongjun Ying (5): ASoC: sirf: add sirf platform driver which provides DMA ASoC: sirf: add I2S CPU DAI driver ASoC: usp-pcm: add CPU DAI driver for PCM simulated from USP ASoC: sirf-soc-inner: add drivers for both CPU and Codec DAIs ASoC: sirf-inner: add mach driver for SiRFSoC internal codec
.../bindings/sound/sirf,inner-audio-codec.txt | 41 ++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sirf/Kconfig | 18 + sound/soc/sirf/Makefile | 11 + sound/soc/sirf/sirf-audio.h | 268 ++++++++ sound/soc/sirf/sirf-i2s.c | 435 +++++++++++++ sound/soc/sirf/sirf-inner.c | 155 +++++ sound/soc/sirf/sirf-pcm.c | 68 ++ sound/soc/sirf/sirf-soc-inner.c | 653 ++++++++++++++++++++ sound/soc/sirf/sirf-usp.c | 463 ++++++++++++++ sound/soc/sirf/sirf-usp.h | 276 +++++++++ 12 files changed, 2390 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/sirf,inner-audio-codec.txt create mode 100644 sound/soc/sirf/Kconfig create mode 100644 sound/soc/sirf/Makefile create mode 100644 sound/soc/sirf/sirf-audio.h create mode 100644 sound/soc/sirf/sirf-i2s.c create mode 100644 sound/soc/sirf/sirf-inner.c create mode 100644 sound/soc/sirf/sirf-pcm.c create mode 100644 sound/soc/sirf/sirf-soc-inner.c create mode 100644 sound/soc/sirf/sirf-usp.c create mode 100644 sound/soc/sirf/sirf-usp.h
From: Rongjun Ying Rongjun.Ying@csr.com
this driver uses dmaengine APIs and provides DMA to the CPU DAIs of I2S, USP and SiRF-soc-inner. SiRFSoC has 3 audio DAIs: I2S, USP(Universal Serial Ports) and DAI connected to soc-inner-codec, all of them will use the same DMA driver here.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com --- -v3: Automatically discovering the configuration of pcm hardware from the dmaengine driver
sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sirf/Kconfig | 4 ++ sound/soc/sirf/Makefile | 3 ++ sound/soc/sirf/sirf-pcm.c | 68 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 0 deletions(-) create mode 100644 sound/soc/sirf/Kconfig create mode 100644 sound/soc/sirf/Makefile create mode 100644 sound/soc/sirf/sirf-pcm.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index d62ce48..0060b31 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -50,6 +50,7 @@ source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 62a1822..5f1df02 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig new file mode 100644 index 0000000..1637089 --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC + tristate "Platform DMA driver for the SiRF SoC chips" + depends on ARCH_SIRF && SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile new file mode 100644 index 0000000..f268b83 --- /dev/null +++ b/sound/soc/sirf/Makefile @@ -0,0 +1,3 @@ +snd-soc-sirf-objs := sirf-pcm.o + +obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o diff --git a/sound/soc/sirf/sirf-pcm.c b/sound/soc/sirf/sirf-pcm.c new file mode 100644 index 0000000..e4cb3a6 --- /dev/null +++ b/sound/soc/sirf/sirf-pcm.c @@ -0,0 +1,68 @@ +/* + * ALSA PCM interface for the SiRF SoC + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/module.h> +#include <sound/dmaengine_pcm.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +static struct dma_chan *sirf_pcm_request_chan(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + return dma_request_slave_channel(rtd->cpu_dai->dev, + dma_data->chan_name); +} + +static const struct snd_dmaengine_pcm_config sirf_dmaengine_pcm_config = { + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .compat_request_channel = sirf_pcm_request_chan, +}; + +static int sirf_pcm_probe(struct platform_device *pdev) +{ + return devm_snd_dmaengine_pcm_register(&pdev->dev, + &sirf_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_COMPAT); +} + +static struct platform_driver sirf_pcm_driver = { + .driver = { + .name = "sirf-pcm-audio", + .owner = THIS_MODULE, + }, + .probe = sirf_pcm_probe, +}; + +static int __init sirf_pcm_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&sirf_pcm_driver); + if (ret) + pr_err("failed to register platform driver\n"); + return ret; +} + +static void __exit sirf_pcm_exit(void) +{ + platform_driver_unregister(&sirf_pcm_driver); +} + +module_init(sirf_pcm_init); +module_exit(sirf_pcm_exit); + +MODULE_DESCRIPTION("SiRF PCM audio interface driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2");
On 01/03/2014 07:05 AM, RongJun Ying wrote:
From: Rongjun Ying Rongjun.Ying@csr.com
this driver uses dmaengine APIs and provides DMA to the CPU DAIs of I2S, USP and SiRF-soc-inner. SiRFSoC has 3 audio DAIs: I2S, USP(Universal Serial Ports) and DAI connected to soc-inner-codec, all of them will use the same DMA driver here.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com
-v3: Automatically discovering the configuration of pcm hardware from the dmaengine driver
sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sirf/Kconfig | 4 ++ sound/soc/sirf/Makefile | 3 ++ sound/soc/sirf/sirf-pcm.c | 68 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 0 deletions(-) create mode 100644 sound/soc/sirf/Kconfig create mode 100644 sound/soc/sirf/Makefile create mode 100644 sound/soc/sirf/sirf-pcm.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index d62ce48..0060b31 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -50,6 +50,7 @@ source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 62a1822..5f1df02 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig new file mode 100644 index 0000000..1637089 --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC
- tristate "Platform DMA driver for the SiRF SoC chips"
- depends on ARCH_SIRF && SND_SOC
- select SND_SOC_GENERIC_DMAENGINE_PCM
diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile new file mode 100644 index 0000000..f268b83 --- /dev/null +++ b/sound/soc/sirf/Makefile @@ -0,0 +1,3 @@ +snd-soc-sirf-objs := sirf-pcm.o
+obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o diff --git a/sound/soc/sirf/sirf-pcm.c b/sound/soc/sirf/sirf-pcm.c new file mode 100644 index 0000000..e4cb3a6 --- /dev/null +++ b/sound/soc/sirf/sirf-pcm.c @@ -0,0 +1,68 @@ +/*
- ALSA PCM interface for the SiRF SoC
- Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company.
- Licensed under GPLv2 or later.
- */
+#include <linux/module.h> +#include <sound/dmaengine_pcm.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h>
+static struct dma_chan *sirf_pcm_request_chan(struct snd_soc_pcm_runtime *rtd,
- struct snd_pcm_substream *substream)
+{
- struct snd_dmaengine_dai_dma_data *dma_data;
- dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
- return dma_request_slave_channel(rtd->cpu_dai->dev,
dma_data->chan_name);
+}
+static const struct snd_dmaengine_pcm_config sirf_dmaengine_pcm_config = {
- .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
- .compat_request_channel = sirf_pcm_request_chan,
+};
+static int sirf_pcm_probe(struct platform_device *pdev) +{
- return devm_snd_dmaengine_pcm_register(&pdev->dev,
&sirf_dmaengine_pcm_config,
SND_DMAENGINE_PCM_FLAG_NO_DT |
SND_DMAENGINE_PCM_FLAG_COMPAT);
Since your platform is using DT you should drop the NO_DT and COMPAT flags. Your sirf_pcm_request_chan is essentially doing the same as what the DT path in the generic driver does.
+}
+static struct platform_driver sirf_pcm_driver = {
- .driver = {
.name = "sirf-pcm-audio",
.owner = THIS_MODULE,
- },
- .probe = sirf_pcm_probe,
+};
Also I wouldn't bother to register a extra device for the PCM, just call devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0) from the DAI drivers, that's what everybody else is doing.
- Lars
On Fri, Jan 03, 2014 at 02:05:00PM +0800, RongJun Ying wrote:
index 0000000..1637089 --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC
- tristate "Platform DMA driver for the SiRF SoC chips"
- depends on ARCH_SIRF && SND_SOC
- select SND_SOC_GENERIC_DMAENGINE_PCM
No need for the SND_SOC dependency, the subdirectories are only enumerated if it is selected, and looking at the code I don't see a build time dependency on ARCH_SIRF so it should be ARCH_SIRF || COMPILE_TEST.
+static struct dma_chan *sirf_pcm_request_chan(struct snd_soc_pcm_runtime *rtd,
- struct snd_pcm_substream *substream)
+{
- struct snd_dmaengine_dai_dma_data *dma_data;
- dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
- return dma_request_slave_channel(rtd->cpu_dai->dev,
dma_data->chan_name);
+}
+static const struct snd_dmaengine_pcm_config sirf_dmaengine_pcm_config = {
- .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
- .compat_request_channel = sirf_pcm_request_chan,
Do you need the compat_request_chan - I'd expect a newish ARM system to be DT only and the code doesn't look like it's doing anything that the core couldn't handle?
+static struct platform_driver sirf_pcm_driver = {
- .driver = {
.name = "sirf-pcm-audio",
.owner = THIS_MODULE,
- },
- .probe = sirf_pcm_probe,
+};
This doesn't look to ever access any hardware directly so it probably shouldn't be a standalone driver as Lars-Peter says.
+static int __init sirf_pcm_init(void) +{
- int ret = 0;
- ret = platform_driver_register(&sirf_pcm_driver);
- if (ret)
pr_err("failed to register platform driver\n");
- return ret;
+}
module_platform_driver() if there is some real hardware here.
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: Tuesday, January 07, 2014 1:07 AM To: RongJun Ying Cc: Liam Girdwood; Jaroslav Kysela; Takashi Iwai; alsa-devel@alsa- project.org; DL-SHA-WorkGroupLinux; Rongjun Ying Subject: Re: [PATCH v3 1/5] ASoC: sirf: add sirf platform driver which provides DMA
On Fri, Jan 03, 2014 at 02:05:00PM +0800, RongJun Ying wrote:
index 0000000..1637089 --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC
- tristate "Platform DMA driver for the SiRF SoC chips"
- depends on ARCH_SIRF && SND_SOC
- select SND_SOC_GENERIC_DMAENGINE_PCM
No need for the SND_SOC dependency, the subdirectories are only enumerated if it is selected, and looking at the code I don't see a build time dependency on ARCH_SIRF so it should be ARCH_SIRF || COMPILE_TEST.
+static struct dma_chan *sirf_pcm_request_chan(struct
snd_soc_pcm_runtime *rtd,
- struct snd_pcm_substream *substream) {
- struct snd_dmaengine_dai_dma_data *dma_data;
- dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
- return dma_request_slave_channel(rtd->cpu_dai->dev,
dma_data->chan_name);
+}
+static const struct snd_dmaengine_pcm_config
sirf_dmaengine_pcm_config = {
- .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
- .compat_request_channel = sirf_pcm_request_chan,
Do you need the compat_request_chan - I'd expect a newish ARM system to be DT only and the code doesn't look like it's doing anything that the core couldn't handle?
+static struct platform_driver sirf_pcm_driver = {
- .driver = {
.name = "sirf-pcm-audio",
.owner = THIS_MODULE,
- },
- .probe = sirf_pcm_probe,
+};
This doesn't look to ever access any hardware directly so it probably shouldn't be a standalone driver as Lars-Peter says.
+static int __init sirf_pcm_init(void) {
- int ret = 0;
- ret = platform_driver_register(&sirf_pcm_driver);
- if (ret)
pr_err("failed to register platform driver\n");
- return ret;
+}
module_platform_driver() if there is some real hardware here.
Do you mean to add pcm device into dts?
pcm { compatible = "sirf,pcm-audio"; }
------------------- BR RongJun Ying
Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom More information can be found at www.csr.com. Keep up to date with CSR on our technical blog, www.csr.com/blog, CSR people blog, www.csr.com/people, YouTube, www.youtube.com/user/CSRplc, Facebook, www.facebook.com/pages/CSR/191038434253534, or follow us on Twitter at www.twitter.com/CSR_plc. New for 2014, you can now access the wide range of products powered by aptX at www.aptx.com.
On Wed, Jan 08, 2014 at 09:01:15AM +0000, Rongjun Ying wrote:
module_platform_driver() if there is some real hardware here.
Do you mean to add pcm device into dts?
pcm { compatible = "sirf,pcm-audio"; }
Only if this is an actual dedicated hardware block which as myself and Lars said doesn't seem to be the case.
From: Rongjun Ying Rongjun.Ying@csr.com
This patch adds I2S DAI driver for SiRFprima2 and SiRFatlas6. The hardware supports: I2S bus specification compliant (Released by Philips Semiconductors in June, 1996) Supports I2S master and I2S slave mode Provides MCLK to I2S CODEC in I2S master mode Supports 16-bit resolution playback Supports 16-bit resolution record Supports 2 channel record Supports 2 channel and 6 channel mode playback Frame length, left channel length and right channel length are all programmable Supports two DMA channels for TXFIFO and RXFIFO Supports floating mode (x_ac97_dout can be configured as input)
headfile sound/soc/sirf/sirf-audio.h will be shard by all I2S and soc-inner DAIs.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com --- -v3: Calculated automatically the div vaule at runtime based on the sample rate Move the master/slave mode setting from set_fmt to hw_parames
sound/soc/sirf/Kconfig | 3 + sound/soc/sirf/Makefile | 2 + sound/soc/sirf/sirf-audio.h | 268 ++++++++++++++++++++++++++ sound/soc/sirf/sirf-i2s.c | 435 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 708 insertions(+), 0 deletions(-) create mode 100644 sound/soc/sirf/sirf-audio.h create mode 100644 sound/soc/sirf/sirf-i2s.c
diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index 1637089..5064cfc 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -2,3 +2,6 @@ config SND_SIRF_SOC tristate "Platform DMA driver for the SiRF SoC chips" depends on ARCH_SIRF && SND_SOC select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_SOC_SIRF_I2S + tristate diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index f268b83..9f754fe 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,3 +1,5 @@ snd-soc-sirf-objs := sirf-pcm.o +snd-soc-sirf-i2s-objs := sirf-i2s.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o +obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o diff --git a/sound/soc/sirf/sirf-audio.h b/sound/soc/sirf/sirf-audio.h new file mode 100644 index 0000000..b6fdf06 --- /dev/null +++ b/sound/soc/sirf/sirf-audio.h @@ -0,0 +1,268 @@ +/* + * SiRF inner codec controllers define + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#ifndef _SIRF_INNER_AUDIO_CTRL_H +#define _SIRF_INNER_AUDIO_CTRL_H + +#define AUDIO_CTRL_TX_FIFO_LEVEL_CHECK_MASK 0x3F +#define AUDIO_CTRL_TX_FIFO_SC_OFFSET 0 +#define AUDIO_CTRL_TX_FIFO_LC_OFFSET 10 +#define AUDIO_CTRL_TX_FIFO_HC_OFFSET 20 + +#define TX_FIFO_SC(x) (((x) & AUDIO_CTRL_TX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_CTRL_TX_FIFO_SC_OFFSET) +#define TX_FIFO_LC(x) (((x) & AUDIO_CTRL_TX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_CTRL_TX_FIFO_LC_OFFSET) +#define TX_FIFO_HC(x) (((x) & AUDIO_CTRL_TX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_CTRL_TX_FIFO_HC_OFFSET) + +#define AUDIO_CTRL_RX_FIFO_LEVEL_CHECK_MASK 0x0F +#define AUDIO_CTRL_RX_FIFO_SC_OFFSET 0 +#define AUDIO_CTRL_RX_FIFO_LC_OFFSET 10 +#define AUDIO_CTRL_RX_FIFO_HC_OFFSET 20 + +#define RX_FIFO_SC(x) (((x) & AUDIO_CTRL_RX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_CTRL_RX_FIFO_SC_OFFSET) +#define RX_FIFO_LC(x) (((x) & AUDIO_CTRL_RX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_CTRL_RX_FIFO_LC_OFFSET) +#define RX_FIFO_HC(x) (((x) & AUDIO_CTRL_RX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_CTRL_RX_FIFO_HC_OFFSET) + +#define AUDIO_CTRL_MODE_SEL (0x0000) +#define AUDIO_CTRL_AC97_CTRL (0x0004) +#define AUDIO_CTRL_AC97_CMD (0x0008) +#define AUDIO_CTRL_AC97_OP_STATUS (0x000C) +#define AUDIO_CTRL_AC97_RD_CODEC_REG (0x0010) +#define AUDIO_CTRL_TXSLOT_EN (0x0014) +#define AUDIO_CTRL_RXSLOT_EN (0x0018) +#define AUDIO_CTRL_AC97_AUX_SLOT_EN (0x001c) +#define AUDIO_CTRL_I2S_CTRL (0x0020) +#define AUDIO_CTRL_I2S_TX_RX_EN (0x0024) + +#define AUDIO_CTRL_EXT_TXFIFO1_OP (0x0040) +#define AUDIO_CTRL_EXT_TXFIFO1_LEV_CHK (0x0044) +#define AUDIO_CTRL_EXT_TXFIFO1_STS (0x0048) +#define AUDIO_CTRL_EXT_TXFIFO1_INT (0x004C) +#define AUDIO_CTRL_EXT_TXFIFO1_INT_MSK (0x0050) + +#define AUDIO_CTRL_EXT_TXFIFO2_OP (0x0054) +#define AUDIO_CTRL_EXT_TXFIFO2_LEV_CHK (0x0058) +#define AUDIO_CTRL_EXT_TXFIFO2_STS (0x005C) +#define AUDIO_CTRL_EXT_TXFIFO2_INT (0x0060) +#define AUDIO_CTRL_EXT_TXFIFO2_INT_MSK (0x0064) + +#define AUDIO_CTRL_EXT_TXFIFO3_OP (0x0068) +#define AUDIO_CTRL_EXT_TXFIFO3_LEV_CHK (0x006C) +#define AUDIO_CTRL_EXT_TXFIFO3_STS (0x0070) +#define AUDIO_CTRL_EXT_TXFIFO3_INT (0x0074) +#define AUDIO_CTRL_EXT_TXFIFO3_INT_MSK (0x0078) + +#define AUDIO_CTRL_EXT_TXFIFO4_OP (0x007C) +#define AUDIO_CTRL_EXT_TXFIFO4_LEV_CHK (0x0080) +#define AUDIO_CTRL_EXT_TXFIFO4_STS (0x0084) +#define AUDIO_CTRL_EXT_TXFIFO4_INT (0x0088) +#define AUDIO_CTRL_EXT_TXFIFO4_INT_MSK (0x008C) + +#define AUDIO_CTRL_EXT_TXFIFO5_OP (0x0090) +#define AUDIO_CTRL_EXT_TXFIFO5_LEV_CHK (0x0094) +#define AUDIO_CTRL_EXT_TXFIFO5_STS (0x0098) +#define AUDIO_CTRL_EXT_TXFIFO5_INT (0x009C) +#define AUDIO_CTRL_EXT_TXFIFO5_INT_MSK (0x00A0) + +#define AUDIO_CTRL_EXT_TXFIFO6_OP (0x00A4) +#define AUDIO_CTRL_EXT_TXFIFO6_LEV_CHK (0x00A8) +#define AUDIO_CTRL_EXT_TXFIFO6_STS (0x00AC) +#define AUDIO_CTRL_EXT_TXFIFO6_INT (0x00B0) +#define AUDIO_CTRL_EXT_TXFIFO6_INT_MSK (0x00B4) + +#define AUDIO_CTRL_RXFIFO_OP (0x00B8) +#define AUDIO_CTRL_RXFIFO_LEV_CHK (0x00BC) +#define AUDIO_CTRL_RXFIFO_STS (0x00C0) +#define AUDIO_CTRL_RXFIFO_INT (0x00C4) +#define AUDIO_CTRL_RXFIFO_INT_MSK (0x00C8) + +#define AUDIO_CTRL_AUXFIFO_OP (0x00CC) +#define AUDIO_CTRL_AUXFIFO_LEV_CHK (0x00D0) +#define AUDIO_CTRL_AUXFIFO_STS (0x00D4) +#define AUDIO_CTRL_AUXFIFO_INT (0x00D8) +#define AUDIO_CTRL_AUXFIFO_INT_MSK (0x00DC) + +#define AUDIO_IC_CODEC_PWR (0x00E0) +#define AUDIO_IC_CODEC_CTRL0 (0x00E4) +#define AUDIO_IC_CODEC_CTRL1 (0x00E8) +#define AUDIO_IC_CODEC_CTRL2 (0x00EC) +#define AUDIO_IC_CODEC_CTRL3 (0x00F0) + +#define AUDIO_CTRL_IC_CODEC_TX_CTRL (0x00F4) +#define AUDIO_CTRL_IC_CODEC_RX_CTRL (0x00F8) + +#define AUDIO_CTRL_IC_TXFIFO_OP (0x00FC) +#define AUDIO_CTRL_IC_TXFIFO_LEV_CHK (0x0100) +#define AUDIO_CTRL_IC_TXFIFO_STS (0x0104) +#define AUDIO_CTRL_IC_TXFIFO_INT (0x0108) +#define AUDIO_CTRL_IC_TXFIFO_INT_MSK (0x010C) + +#define AUDIO_CTRL_IC_RXFIFO_OP (0x0110) +#define AUDIO_CTRL_IC_RXFIFO_LEV_CHK (0x0114) +#define AUDIO_CTRL_IC_RXFIFO_STS (0x0118) +#define AUDIO_CTRL_IC_RXFIFO_INT (0x011C) +#define AUDIO_CTRL_IC_RXFIFO_INT_MSK (0x0120) + +#define I2S_MODE (1<<0) +#define AC97_TX_SLOT3_WIDTH_MASK (3<<1) +#define AC97_TX_SLOT4_WIDTH_MASK (3<<3) +#define AC97_TX_SLOT6_WIDTH_MASK (3<<5) +#define AC97_TX_SLOT7_WIDTH_MASK (3<<7) +#define AC97_TX_SLOT8_WIDTH_MASK (3<<9) +#define AC97_TX_SLOT9_WIDTH_MASK (3<<11) +#define AC97_FIFO_SYNC_MASK (3<<13) +#define AC97_FIFO_SYNC_ALL (1<<13) + +#define SYNC_START (1<<0) +#define AC97_START (1<<1) +#define WARM_WAKEUP (1<<2) + +#define AC97_CMD_TYPE_MASK (1<<7) +#define AC97_CMD_ADDR_MASK (0x7F) +#define AC97_CMD_TYPE_WRITE (0<<7) +#define AC97_CMD_TYPE_READ (1<<7) + +#define CMD_ISSUE_BIT (1<<0) +#define RD_CMD_FINISH_BIT (1<<1) +#define CODEC_READY_BIT (1<<2) + +#define AC97_RDBACK_ADDR_BITS (0x7F) +#define AC97_RDBACK_DATA_BITS (0xFF<<16) + +#define AC97_TX_SLOT3_EN (1<<0) +#define AC97_TX_SLOT4_EN (1<<1) +#define AC97_TX_SLOT6_EN (1<<2) +#define AC97_TX_SLOT7_EN (1<<3) +#define AC97_TX_SLOT8_EN (1<<4) +#define AC97_TX_SLOT9_EN (1<<5) + +#define AC97_RX_SLOT3_EN (1<<0) +#define AC97_RX_SLOT4_EN (1<<1) +#define AC97_RX_SLOT5_EN (1<<2) +#define AC97_RX_SLOT6_EN (1<<3) +#define AC97_RX_SLOT7_EN (1<<4) +#define AC97_RX_SLOT8_EN (1<<5) +#define AC97_RX_SLOT9_EN (1<<6) +#define AC97_RX_SLOT10_EN (1<<7) +#define AC97_RX_SLOT11_EN (1<<8) +#define AC97_RX_SLOT12_EN (1<<9) + +#define AC97_AUX_SLOT3_EN (1<<0) +#define AC97_AUX_SLOT4_EN (1<<1) +#define AC97_AUX_SLOT5_EN (1<<2) +#define AC97_AUX_SLOT6_EN (1<<3) +#define AC97_AUX_SLOT7_EN (1<<4) +#define AC97_AUX_SLOT8_EN (1<<5) +#define AC97_AUX_SLOT9_EN (1<<6) +#define AC97_AUX_SLOT10_EN (1<<7) +#define AC97_AUX_SLOT11_EN (1<<8) +#define AC97_AUX_SLOT12_EN (1<<9) + +#define I2S_LOOP_BACK (1<<3) +#define I2S_MCLK_DIV_SHIFT 15 +#define I2S_MCLK_DIV_MASK (0x1FF<<I2S_MCLK_DIV_SHIFT) +#define I2S_BITCLK_DIV_SHIFT 24 +#define I2S_BITCLK_DIV_MASK (0xFF<<I2S_BITCLK_DIV_SHIFT) + +#define I2S_MCLK_EN (1<<2) +#define I2S_REF_CLK_SEL_EXT (1<<3) +#define I2S_DOUT_OE (1<<4) +#define i2s_R2X_LP_TO_TX0 (1<<30) +#define i2s_R2X_LP_TO_TX1 (2<<30) +#define i2s_R2X_LP_TO_TX2 (3<<30) + +#define AUDIO_FIFO_START (1 << 0) +#define AUDIO_FIFO_RESET (1 << 1) + +#define AUDIO_FIFO_FULL (1 << 0) +#define AUDIO_FIFO_EMPTY (1 << 1) +#define AUDIO_FIFO_OFLOW (1 << 2) +#define AUDIO_FIFO_UFLOW (1 << 3) + +#define I2S_RX_ENABLE (1 << 0) +#define I2S_TX_ENABLE (1 << 1) + +/* Codec I2S Control Register defines */ +#define I2S_SLAVE_MODE (1 << 0) +#define I2S_SIX_CHANNELS (1 << 1) +#define I2S_L_CHAN_LEN_MASK (0x1f << 4) +#define I2S_FRAME_LEN_MASK (0x3f << 9) + +#define AC97_WRITE_FRAME_VALID 0X10 +#define AC97_WRITE_SLOT1_VALID 0X08 +#define AC97_WRITE_SLOT2_VALID 0X04 +#define AC97_WRITE_SLOT3_VALID 0X02 +#define AC97_WRITE_SLOT4_VALID 0X01 + +#define IC_TX_ENABLE (0x03) +#define IC_RX_ENABLE (0x03) + +#define MICBIASEN (1 << 3) + +#define IC_RDACEN (1 << 0) +#define IC_LDACEN (1 << 1) +#define IC_HSREN (1 << 2) +#define IC_HSLEN (1 << 3) +#define IC_SPEN (1 << 4) +#define IC_CPEN (1 << 5) + +#define IC_HPRSELR (1 << 6) +#define IC_HPLSELR (1 << 7) +#define IC_HPRSELL (1 << 8) +#define IC_HPLSELL (1 << 9) +#define IC_SPSELR (1 << 10) +#define IC_SPSELL (1 << 11) + +#define IC_MONOR (1 << 12) +#define IC_MONOL (1 << 13) + +#define IC_RXOSRSEL (1 << 28) +#define IC_CPFREQ (1 << 29) +#define IC_HSINVEN (1 << 30) + +#define IC_MICINREN (1 << 0) +#define IC_MICINLEN (1 << 1) +#define IC_MICIN1SEL (1 << 2) +#define IC_MICIN2SEL (1 << 3) +#define IC_MICDIFSEL (1 << 4) +#define IC_LINEIN1SEL (1 << 5) +#define IC_LINEIN2SEL (1 << 6) +#define IC_RADCEN (1 << 7) +#define IC_LADCEN (1 << 8) +#define IC_ALM (1 << 9) + +#define IC_DIGMICEN (1 << 22) +#define IC_DIGMICFREQ (1 << 23) +#define IC_ADC14B_12 (1 << 24) +#define IC_FIRDAC_HSL_EN (1 << 25) +#define IC_FIRDAC_HSR_EN (1 << 26) +#define IC_FIRDAC_LOUT_EN (1 << 27) +#define IC_POR (1 << 28) +#define IC_CODEC_CLK_EN (1 << 29) +#define IC_HP_3DB_BOOST (1 << 30) + +#define IC_ADC_LEFT_GAIN_SHIFT 16 +#define IC_ADC_RIGHT_GAIN_SHIFT 10 +#define IC_ADC_GAIN_MASK 0x3F +#define IC_MIC_MAX_GAIN 0x39 + +#define IC_RXPGAR_MASK 0x3F +#define IC_RXPGAR_SHIFT 14 +#define IC_RXPGAL_MASK 0x3F +#define IC_RXPGAL_SHIFT 21 +#define IC_RXPGAR 0x7B +#define IC_RXPGAL 0x7B + +#define SIRF_I2S_EXT_CLK 0x0 +#define SIRF_I2S_PWM_CLK 0x1 +#endif /*__SIRF_INNER_AUDIO_CTRL_H*/ diff --git a/sound/soc/sirf/sirf-i2s.c b/sound/soc/sirf/sirf-i2s.c new file mode 100644 index 0000000..d8b8732 --- /dev/null +++ b/sound/soc/sirf/sirf-i2s.c @@ -0,0 +1,435 @@ +/* + * SiRF I2S driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> + +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> + +#include "sirf-audio.h" + +struct sirf_i2s { + void __iomem *base; + struct clk *clk; + u32 i2s_ctrl; + u32 i2s_ctrl_tx_rx_en; + spinlock_t lock; + struct platform_device *sirf_pcm_pdev; + bool master; + int ext_clk; + int src_clk_rate; +}; + +static struct snd_dmaengine_dai_dma_data dma_data[2]; + +static int sirf_i2s_dai_probe(struct snd_soc_dai *dai) +{ + dai->playback_dma_data = &dma_data[0]; + dai->capture_dma_data = &dma_data[1]; + return 0; +} + +static int sirf_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock(&si2s->lock); + + if (playback) { + /* First start the FIFO, then enable the tx/rx */ + writel(AUDIO_FIFO_RESET, + si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP); + writel(AUDIO_FIFO_START, + si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP); + + writel(readl(si2s->base+AUDIO_CTRL_I2S_TX_RX_EN) + | I2S_TX_ENABLE | I2S_DOUT_OE, + si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + + } else { + /* First start the FIFO, then enable the tx/rx */ + writel(AUDIO_FIFO_RESET, + si2s->base + AUDIO_CTRL_RXFIFO_OP); + writel(AUDIO_FIFO_START, + si2s->base + AUDIO_CTRL_RXFIFO_OP); + + writel(readl(si2s->base+AUDIO_CTRL_I2S_TX_RX_EN) + | I2S_RX_ENABLE, + si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + } + + spin_unlock(&si2s->lock); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock(&si2s->lock); + + if (playback) { + writel(readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN) + & ~(I2S_TX_ENABLE), + si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + /* First disable the tx/rx, then stop the FIFO */ + writel(0, si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP); + } else { + writel(readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN) + & ~(I2S_RX_ENABLE), + si2s->base+AUDIO_CTRL_I2S_TX_RX_EN); + + /* First disable the tx/rx, then stop the FIFO */ + writel(0, si2s->base + AUDIO_CTRL_RXFIFO_OP); + } + + spin_unlock(&si2s->lock); + break; + default: + return -EINVAL; + } + return 0; +} + +static int sirf_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + u32 i2s_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL); + u32 i2s_tx_rx_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + u32 left_len, frame_len; + int channels = params_channels(params); + u32 bitclk; + u32 bclk_div; + u32 div; + + /* + * SiRFSoC I2S controller only support 2 and 6 channells output. + * I2S_SIX_CHANNELS bit clear: select 2 channels mode. + * I2S_SIX_CHANNELS bit set: select 6 channels mode. + */ + switch (channels) { + case 2: + i2s_ctrl &= ~I2S_SIX_CHANNELS; + break; + case 6: + i2s_ctrl |= I2S_SIX_CHANNELS; + break; + default: + dev_err(dai->dev, "%d channels unsupported\n", channels); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + left_len = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + left_len = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + left_len = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + left_len = 32; + break; + default: + dev_err(dai->dev, "Format unsupported\n"); + return -EINVAL; + } + + frame_len = left_len * 2; + i2s_ctrl &= ~(I2S_L_CHAN_LEN_MASK | I2S_FRAME_LEN_MASK); + /* Fill the actual len - 1 */ + i2s_ctrl |= ((frame_len - 1) << 9) | ((left_len - 1) << 4) + | (0 << 15) | (3 << 24); + + if (si2s->master) { + i2s_ctrl &= ~I2S_SLAVE_MODE; + i2s_tx_rx_ctrl |= I2S_MCLK_EN; + bitclk = params_rate(params) * frame_len; + div = si2s->src_clk_rate / bitclk; + /* MCLK divide-by-2 from source clk */ + div /= 2; + bclk_div = div / 2 - 1; + i2s_ctrl |= (bclk_div << 24); + /* + * MCLK coefficient must set to 0, means + * divide-by-two from reference clock. + */ + i2s_ctrl &= ~(((1 << 10) - 1) << 15); + } else { + i2s_ctrl |= I2S_SLAVE_MODE; + i2s_tx_rx_ctrl &= ~I2S_MCLK_EN; + } + + if (si2s->ext_clk) + i2s_tx_rx_ctrl |= I2S_REF_CLK_SEL_EXT; + else + i2s_tx_rx_ctrl &= ~I2S_REF_CLK_SEL_EXT; + + writel(i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL); + writel(i2s_tx_rx_ctrl, si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + writel(readl(si2s->base + AUDIO_CTRL_MODE_SEL) + | I2S_MODE, + si2s->base + AUDIO_CTRL_MODE_SEL); + + return 0; +} + +static int sirf_i2s_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + si2s->master = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + si2s->master = true; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + dev_err(dai->dev, "Only I2S format supported\n"); + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_err(dai->dev, "Only normal bit clock, normal frame clock supported\n"); + return -EINVAL; + } + + return 0; +} + +static int sirf_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int src_rate) +{ + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + + switch (div_id) { + case SIRF_I2S_EXT_CLK: + si2s->ext_clk = 1; + break; + case SIRF_I2S_PWM_CLK: + si2s->ext_clk = 0; + break; + default: + return -EINVAL; + } + + si2s->src_clk_rate = src_rate; + return 0; +} + +struct snd_soc_dai_ops sirfsoc_i2s_dai_ops = { + .trigger = sirf_i2s_trigger, + .hw_params = sirf_i2s_hw_params, + .set_fmt = sirf_i2s_set_dai_fmt, + .set_clkdiv = sirf_i2s_set_clkdiv, +}; + +static struct snd_soc_dai_driver sirf_i2s_dai = { + .probe = sirf_i2s_dai_probe, + .name = "sirf-i2s", + .id = 0, + .playback = { + .stream_name = "SiRF I2S Playback", + .channels_min = 2, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "SiRF I2S Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &sirfsoc_i2s_dai_ops, +}; +#ifdef CONFIG_PM_RUNTIME +static int sirf_i2s_runtime_suspend(struct device *dev) +{ + struct sirf_i2s *si2s = dev_get_drvdata(dev); + clk_disable_unprepare(si2s->clk); + + return 0; +} + +static int sirf_i2s_runtime_resume(struct device *dev) +{ + struct sirf_i2s *si2s = dev_get_drvdata(dev); + int ret; + ret = clk_prepare_enable(si2s->clk); + if (ret) + return ret; + return ret; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int sirf_i2s_suspend(struct device *dev) +{ + struct sirf_i2s *si2s = dev_get_drvdata(dev); + + if (!pm_runtime_status_suspended(dev)) { + si2s->i2s_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL); + si2s->i2s_ctrl_tx_rx_en = + readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + sirf_i2s_runtime_suspend(dev); + } + return 0; +} + +static int sirf_i2s_resume(struct device *dev) +{ + struct sirf_i2s *si2s = dev_get_drvdata(dev); + int ret; + if (!pm_runtime_status_suspended(dev)) { + ret = sirf_i2s_runtime_resume(dev); + if (ret) + return ret; + writel(readl(si2s->base + AUDIO_CTRL_MODE_SEL) + | I2S_MODE, + si2s->base + AUDIO_CTRL_MODE_SEL); + writel(si2s->i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL); + /*Restore MCLK enable and reference clock select bits.*/ + writel(si2s->i2s_ctrl_tx_rx_en & + (I2S_MCLK_EN | I2S_REF_CLK_SEL_EXT), + si2s->base + AUDIO_CTRL_I2S_TX_RX_EN); + + writel(0, si2s->base + AUDIO_CTRL_EXT_TXFIFO1_INT_MSK); + writel(0, si2s->base + AUDIO_CTRL_RXFIFO_INT_MSK); + } + + return 0; +} +#endif + +static const struct snd_soc_component_driver sirf_i2s_component = { + .name = "sirf-i2s", +}; + +static int sirf_i2s_probe(struct platform_device *pdev) +{ + struct sirf_i2s *si2s; + int ret; + struct resource mem_res; + + si2s = devm_kzalloc(&pdev->dev, sizeof(struct sirf_i2s), + GFP_KERNEL); + if (!si2s) + return -ENOMEM; + + si2s->sirf_pcm_pdev = platform_device_register_simple("sirf-pcm-audio", + 0, NULL, 0); + if (IS_ERR(si2s->sirf_pcm_pdev)) + return PTR_ERR(si2s->sirf_pcm_pdev); + + platform_set_drvdata(pdev, si2s); + + spin_lock_init(&si2s->lock); + + dma_data[0].chan_name = "tx"; + dma_data[1].chan_name = "rx"; + + ret = of_address_to_resource(pdev->dev.of_node, 0, &mem_res); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to get i2s memory resource.\n"); + return ret; + } + si2s->base = devm_ioremap(&pdev->dev, mem_res.start, + resource_size(&mem_res)); + if (!si2s->base) + return -ENOMEM; + + si2s->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(si2s->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + ret = PTR_ERR(si2s->clk); + goto err; + } + + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &sirf_i2s_component, + &sirf_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio SoC dai failed.\n"); + goto err; + } + + return 0; + +err: + return ret; +} + +static int sirf_i2s_remove(struct platform_device *pdev) +{ + struct sirf_i2s *si2s = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + platform_device_unregister(si2s->sirf_pcm_pdev); + return 0; +} + +static const struct of_device_id sirf_i2s_of_match[] = { + { .compatible = "sirf,prima2-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_i2s_of_match); + +static const struct dev_pm_ops sirf_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(sirf_i2s_runtime_suspend, sirf_i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(sirf_i2s_suspend, sirf_i2s_resume) +}; + +static struct platform_driver sirf_i2s_driver = { + .driver = { + .name = "sirf-i2s", + .owner = THIS_MODULE, + .of_match_table = sirf_i2s_of_match, + .pm = &sirf_i2s_pm_ops, + }, + .probe = sirf_i2s_probe, + .remove = sirf_i2s_remove, +}; + +module_platform_driver(sirf_i2s_driver); + +MODULE_DESCRIPTION("SiRF SoC I2S driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2");
On 01/03/2014 07:05 AM, RongJun Ying wrote:
diff --git a/sound/soc/sirf/sirf-audio.h b/sound/soc/sirf/sirf-audio.h new file mode 100644 index 0000000..b6fdf06 --- /dev/null +++ b/sound/soc/sirf/sirf-audio.h @@ -0,0 +1,268 @@ +#ifndef _SIRF_INNER_AUDIO_CTRL_H +#define _SIRF_INNER_AUDIO_CTRL_H
+#define AUDIO_CTRL_TX_FIFO_LEVEL_CHECK_MASK 0x3F
[...]
Adding a prefix like SIRF_ to all those defines wouldn't hurt.
+#define SIRF_I2S_EXT_CLK 0x0 +#define SIRF_I2S_PWM_CLK 0x1 +#endif /*__SIRF_INNER_AUDIO_CTRL_H*/ diff --git a/sound/soc/sirf/sirf-i2s.c b/sound/soc/sirf/sirf-i2s.c new file mode 100644 index 0000000..d8b8732 --- /dev/null +++ b/sound/soc/sirf/sirf-i2s.c @@ -0,0 +1,435 @@ +/*
- SiRF I2S driver
- Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company.
- Licensed under GPLv2 or later.
- */
+#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h>
+#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h>
+#include "sirf-audio.h"
+struct sirf_i2s {
- void __iomem *base;
- struct clk *clk;
- u32 i2s_ctrl;
- u32 i2s_ctrl_tx_rx_en;
- spinlock_t lock;
- struct platform_device *sirf_pcm_pdev;
- bool master;
- int ext_clk;
- int src_clk_rate;
+};
+static struct snd_dmaengine_dai_dma_data dma_data[2];
This can be removed since you only use it to specify the channel names. But since you use the default names there is no need to do that.
+static int sirf_i2s_dai_probe(struct snd_soc_dai *dai) +{
- dai->playback_dma_data = &dma_data[0];
- dai->capture_dma_data = &dma_data[1];
- return 0;
+}
+static int sirf_i2s_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
+{
- struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai);
- int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
spin_lock(&si2s->lock);
This is the only place where you use the spinlock. The trigger callback is already protected by the ALSA core and can not race against itself, so you do not need the lock.
if (playback) {
/* First start the FIFO, then enable the tx/rx */
writel(AUDIO_FIFO_RESET,
si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP);
writel(AUDIO_FIFO_START,
si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP);
writel(readl(si2s->base+AUDIO_CTRL_I2S_TX_RX_EN)
| I2S_TX_ENABLE | I2S_DOUT_OE,
si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
For better legibility this should probably be split into multiple statements, like
val = read() val |= ... write(val);
} else {
/* First start the FIFO, then enable the tx/rx */
writel(AUDIO_FIFO_RESET,
si2s->base + AUDIO_CTRL_RXFIFO_OP);
writel(AUDIO_FIFO_START,
si2s->base + AUDIO_CTRL_RXFIFO_OP);
writel(readl(si2s->base+AUDIO_CTRL_I2S_TX_RX_EN)
| I2S_RX_ENABLE,
si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
Same here
}
spin_unlock(&si2s->lock);
break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock(&si2s->lock);
if (playback) {
writel(readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN)
& ~(I2S_TX_ENABLE),
si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
and here
/* First disable the tx/rx, then stop the FIFO */
writel(0, si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP);
} else {
writel(readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN)
& ~(I2S_RX_ENABLE),
si2s->base+AUDIO_CTRL_I2S_TX_RX_EN);
and here
/* First disable the tx/rx, then stop the FIFO */
writel(0, si2s->base + AUDIO_CTRL_RXFIFO_OP);
}
spin_unlock(&si2s->lock);
break;
- default:
return -EINVAL;
- }
- return 0;
+}
+static int sirf_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
- struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai);
- u32 i2s_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL);
- u32 i2s_tx_rx_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
- u32 left_len, frame_len;
- int channels = params_channels(params);
- u32 bitclk;
- u32 bclk_div;
- u32 div;
- /*
* SiRFSoC I2S controller only support 2 and 6 channells output.
* I2S_SIX_CHANNELS bit clear: select 2 channels mode.
* I2S_SIX_CHANNELS bit set: select 6 channels mode.
*/
- switch (channels) {
- case 2:
i2s_ctrl &= ~I2S_SIX_CHANNELS;
break;
- case 6:
i2s_ctrl |= I2S_SIX_CHANNELS;
break;
- default:
dev_err(dai->dev, "%d channels unsupported\n", channels);
return -EINVAL;
Since you only support 2 and 6 for the number of channels you should add a constraint to that in your hwparams callback. Otherwise userspace will think you support any number of channels between 2 and 6. You can do this using this snippet:
static unsigned int sirf_i2s_channels[] = {2, 6}; static struct snd_pcm_hw_constraint_list sirf_i2s_channel_constraints = { .count = ARRAY_SIZE(sirf_i2s_channels), .list = sirf_i2s_channels, };
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &sirf_i2s_channel_constraints);
- }
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S8:
left_len = 8;
break;
- case SNDRV_PCM_FORMAT_S16_LE:
left_len = 16;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
left_len = 24;
break;
- case SNDRV_PCM_FORMAT_S32_LE:
left_len = 32;
break;
- default:
dev_err(dai->dev, "Format unsupported\n");
return -EINVAL;
- }
- frame_len = left_len * 2;
- i2s_ctrl &= ~(I2S_L_CHAN_LEN_MASK | I2S_FRAME_LEN_MASK);
- /* Fill the actual len - 1 */
- i2s_ctrl |= ((frame_len - 1) << 9) | ((left_len - 1) << 4)
| (0 << 15) | (3 << 24);
You set the bclk_div to 3 here
- if (si2s->master) {
i2s_ctrl &= ~I2S_SLAVE_MODE;
i2s_tx_rx_ctrl |= I2S_MCLK_EN;
bitclk = params_rate(params) * frame_len;
div = si2s->src_clk_rate / bitclk;
/* MCLK divide-by-2 from source clk */
div /= 2;
bclk_div = div / 2 - 1;
i2s_ctrl |= (bclk_div << 24);
and never clear it before setting it here again, this will probably not work.
/*
* MCLK coefficient must set to 0, means
* divide-by-two from reference clock.
*/
i2s_ctrl &= ~(((1 << 10) - 1) << 15);
- } else {
i2s_ctrl |= I2S_SLAVE_MODE;
i2s_tx_rx_ctrl &= ~I2S_MCLK_EN;
- }
- if (si2s->ext_clk)
i2s_tx_rx_ctrl |= I2S_REF_CLK_SEL_EXT;
- else
i2s_tx_rx_ctrl &= ~I2S_REF_CLK_SEL_EXT;
- writel(i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL);
- writel(i2s_tx_rx_ctrl, si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
- writel(readl(si2s->base + AUDIO_CTRL_MODE_SEL)
| I2S_MODE,
si2s->base + AUDIO_CTRL_MODE_SEL);
again, break this into multiple statements.
- return 0;
+}
+static int sirf_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int src_rate)
I think this should be set_sysclk not set_clkdiv.
+{
- struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai);
- switch (div_id) {
- case SIRF_I2S_EXT_CLK:
si2s->ext_clk = 1;
break;
- case SIRF_I2S_PWM_CLK:
si2s->ext_clk = 0;
break;
- default:
return -EINVAL;
- }
- si2s->src_clk_rate = src_rate;
- return 0;
+}
+struct snd_soc_dai_ops sirfsoc_i2s_dai_ops = {
static const
- .trigger = sirf_i2s_trigger,
- .hw_params = sirf_i2s_hw_params,
- .set_fmt = sirf_i2s_set_dai_fmt,
- .set_clkdiv = sirf_i2s_set_clkdiv,
+};
[...]
+#ifdef CONFIG_PM_SLEEP +static int sirf_i2s_suspend(struct device *dev) +{
- struct sirf_i2s *si2s = dev_get_drvdata(dev);
- if (!pm_runtime_status_suspended(dev)) {
si2s->i2s_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL);
si2s->i2s_ctrl_tx_rx_en =
readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
sirf_i2s_runtime_suspend(dev);
This will yield a compile error if CONFIG_PM_SLEEP is set, but CONFIG_PM_RUNTIME is not set
- }
- return 0;
+}
+static int sirf_i2s_resume(struct device *dev) +{
- struct sirf_i2s *si2s = dev_get_drvdata(dev);
- int ret;
- if (!pm_runtime_status_suspended(dev)) {
ret = sirf_i2s_runtime_resume(dev);
Same here
if (ret)
return ret;
writel(readl(si2s->base + AUDIO_CTRL_MODE_SEL)
| I2S_MODE,
si2s->base + AUDIO_CTRL_MODE_SEL);
writel(si2s->i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL);
/*Restore MCLK enable and reference clock select bits.*/
writel(si2s->i2s_ctrl_tx_rx_en &
(I2S_MCLK_EN | I2S_REF_CLK_SEL_EXT),
si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
writel(0, si2s->base + AUDIO_CTRL_EXT_TXFIFO1_INT_MSK);
writel(0, si2s->base + AUDIO_CTRL_RXFIFO_INT_MSK);
- }
- return 0;
+} +#endif
+static const struct snd_soc_component_driver sirf_i2s_component = {
- .name = "sirf-i2s",
+};
+static int sirf_i2s_probe(struct platform_device *pdev) +{
- struct sirf_i2s *si2s;
- int ret;
- struct resource mem_res;
- si2s = devm_kzalloc(&pdev->dev, sizeof(struct sirf_i2s),
sizeof(*si2s) is prefered by the coding style guidelines
GFP_KERNEL);
- if (!si2s)
return -ENOMEM;
- si2s->sirf_pcm_pdev = platform_device_register_simple("sirf-pcm-audio",
0, NULL, 0);
- if (IS_ERR(si2s->sirf_pcm_pdev))
return PTR_ERR(si2s->sirf_pcm_pdev);
- platform_set_drvdata(pdev, si2s);
- spin_lock_init(&si2s->lock);
- dma_data[0].chan_name = "tx";
- dma_data[1].chan_name = "rx";
- ret = of_address_to_resource(pdev->dev.of_node, 0, &mem_res);
- if (ret < 0) {
dev_err(&pdev->dev, "Unable to get i2s memory resource.\n");
return ret;
- }
- si2s->base = devm_ioremap(&pdev->dev, mem_res.start,
resource_size(&mem_res));
I think using devm_ioremap_resource() is better here. It will first request the region before remapping it.
- if (!si2s->base)
return -ENOMEM;
- si2s->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(si2s->clk)) {
dev_err(&pdev->dev, "Get clock failed.\n");
ret = PTR_ERR(si2s->clk);
goto err;
- }
- pm_runtime_enable(&pdev->dev);
- ret = devm_snd_soc_register_component(&pdev->dev, &sirf_i2s_component,
&sirf_i2s_dai, 1);
- if (ret) {
dev_err(&pdev->dev, "Register Audio SoC dai failed.\n");
goto err;
- }
- return 0;
+err:
- return ret;
+}
[...]
+static const struct of_device_id sirf_i2s_of_match[] = {
- { .compatible = "sirf,prima2-i2s", },
- {}
+}; +MODULE_DEVICE_TABLE(of, sirf_i2s_of_match);
Binding documentation is missing.
On Fri, Jan 03, 2014 at 02:05:01PM +0800, RongJun Ying wrote:
+static int sirf_i2s_dai_probe(struct snd_soc_dai *dai) +{
- dai->playback_dma_data = &dma_data[0];
- dai->capture_dma_data = &dma_data[1];
- return 0;
+}
Call snd_soc_dai_init_dma_data() rather than assigning directly.
- },
- .ops = &sirfsoc_i2s_dai_ops,
+}; +#ifdef CONFIG_PM_RUNTIME
Blank line after the struct please.
writel(si2s->i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL);
/*Restore MCLK enable and reference clock select bits.*/
/* text */
- si2s->sirf_pcm_pdev = platform_device_register_simple("sirf-pcm-audio",
0, NULL, 0);
- if (IS_ERR(si2s->sirf_pcm_pdev))
return PTR_ERR(si2s->sirf_pcm_pdev);
This should just be a call to register the DMA directly rather than create a device to do it.
- ret = of_address_to_resource(pdev->dev.of_node, 0, &mem_res);
- if (ret < 0) {
dev_err(&pdev->dev, "Unable to get i2s memory resource.\n");
return ret;
- }
It is more normal to use platform_get_resource(), it works fine with OF resources and is more idiomatic than using the OF specific stuff.
From: Rongjun Ying Rongjun.Ying@csr.com
Universal Serial Ports (USP) can be used as PCM. this patch adds support for this functionality.
The USP provides 128-byte data FIFO which supports DMA and I/O modes. Here we use the common DMA driver.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com --- -v3: Use the generic dma dt-binding to get playback/capture dma channels Calculated automatically the div vaule at runtime based on the sample rate Move the master/slave mode setting from set_fmt to hw_parames Check the return value Remove the pm_runtime_get_sync and pm_runtime_put invoke
sound/soc/sirf/Kconfig | 3 + sound/soc/sirf/Makefile | 2 + sound/soc/sirf/sirf-usp.c | 463 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/sirf/sirf-usp.h | 276 +++++++++++++++++++++++++++ 4 files changed, 744 insertions(+), 0 deletions(-) create mode 100644 sound/soc/sirf/sirf-usp.c create mode 100644 sound/soc/sirf/sirf-usp.h
diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index 5064cfc..5e395d3 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -5,3 +5,6 @@ config SND_SIRF_SOC
config SND_SOC_SIRF_I2S tristate + +config SND_SOC_SIRF_USP + tristate diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index 9f754fe..630c9be 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,5 +1,7 @@ snd-soc-sirf-objs := sirf-pcm.o snd-soc-sirf-i2s-objs := sirf-i2s.o +snd-soc-sirf-usp-objs := sirf-usp.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o +obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-usp.c b/sound/soc/sirf/sirf-usp.c new file mode 100644 index 0000000..ed2410f --- /dev/null +++ b/sound/soc/sirf/sirf-usp.c @@ -0,0 +1,463 @@ +/* + * SiRF USP audio transfer interface like I2S + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ +#include <linux/module.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "sirf-usp.h" + +#define FIFO_RESET 0 +#define FIFO_START 1 +#define FIFO_STOP 2 + +#define AUDIO_WORD_SIZE 16 + +struct sirf_usp { + void __iomem *base; + struct clk *clk; + u32 mode1_reg; + u32 mode2_reg; + struct platform_device *sirf_pcm_pdev; + int master; +}; + +static struct snd_dmaengine_dai_dma_data dma_data[2]; + +static void sirf_usp_tx_fifo_op(struct sirf_usp *susp, int cmd) +{ + switch (cmd) { + case FIFO_RESET: + writel(USP_TX_FIFO_RESET, susp->base + USP_TX_FIFO_OP); + writel(0, susp->base + USP_TX_FIFO_OP); + break; + case FIFO_START: + writel(USP_TX_FIFO_START, susp->base + USP_TX_FIFO_OP); + break; + case FIFO_STOP: + writel(0, susp->base + USP_TX_FIFO_OP); + break; + } +} + +static void sirf_usp_rx_fifo_op(struct sirf_usp *susp, int cmd) +{ + switch (cmd) { + case FIFO_RESET: + writel(USP_RX_FIFO_RESET, susp->base + USP_RX_FIFO_OP); + writel(0, susp->base + USP_RX_FIFO_OP); + break; + case FIFO_START: + writel(USP_RX_FIFO_START, susp->base + USP_RX_FIFO_OP); + break; + case FIFO_STOP: + writel(0, susp->base + USP_RX_FIFO_OP); + break; + } +} + +static inline void sirf_usp_tx_enable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) | USP_TX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static inline void sirf_usp_tx_disable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) & ~USP_TX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static inline void sirf_usp_rx_enable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) | USP_RX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static inline void sirf_usp_rx_disable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) & ~USP_RX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static int sirf_usp_pcm_dai_probe(struct snd_soc_dai *dai) +{ + dai->playback_dma_data = &dma_data[0]; + dai->capture_dma_data = &dma_data[1]; + return 0; +} + +static int sirf_usp_pcm_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + susp->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + susp->master = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sirf_usp_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + u32 mode1 = readl(susp->base + USP_MODE1); + u32 mode2 = readl(susp->base + USP_MODE2); + u32 rate = params_rate(params); + u32 clk_rate, clk_div, clk_div_hi, clk_div_lo; + + if (susp->master) { + mode1 &= ~USP_CLOCK_MODE_SLAVE; + mode2 &= ~USP_TFS_CLK_SLAVE_MODE; + mode2 &= ~USP_RFS_CLK_SLAVE_MODE; + } else { + mode1 |= USP_CLOCK_MODE_SLAVE; + mode2 |= USP_TFS_CLK_SLAVE_MODE; + mode2 |= USP_RFS_CLK_SLAVE_MODE; + } + writel(mode1, susp->base + USP_MODE1); + writel(mode2, susp->base + USP_MODE2); + + clk_rate = clk_get_rate(susp->clk); + if (clk_rate < rate * 2) { + dev_err(dai->dev, "Can't get rate(%d) by need.\n", rate); + return -EINVAL; + } + + clk_div = (clk_rate / (2 * rate)) - 1; + clk_div_hi = (clk_div & 0xC00) >> 10; + clk_div_lo = (clk_div & 0x3FF); + + writel((clk_div_lo << 21) | readl(susp->base + USP_MODE2), + susp->base + USP_MODE2); + writel((clk_div_hi << 30) | readl(susp->base + USP_TX_FRAME_CTRL), + susp->base + USP_TX_FRAME_CTRL); + + return 0; +} + +static int sirf_usp_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (playback) { + sirf_usp_tx_fifo_op(susp, FIFO_RESET); + sirf_usp_tx_fifo_op(susp, FIFO_START); + sirf_usp_tx_enable(susp); + } else { + sirf_usp_rx_fifo_op(susp, FIFO_RESET); + sirf_usp_rx_fifo_op(susp, FIFO_START); + sirf_usp_rx_enable(susp); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (playback) { + sirf_usp_tx_disable(susp); + sirf_usp_tx_fifo_op(susp, FIFO_STOP); + } else { + sirf_usp_rx_disable(susp); + sirf_usp_rx_fifo_op(susp, FIFO_STOP); + } + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops sirf_usp_pcm_dai_ops = { + .trigger = sirf_usp_pcm_trigger, + .set_fmt = sirf_usp_pcm_set_dai_fmt, + .hw_params = sirf_usp_pcm_hw_params, +}; + +static struct snd_soc_dai_driver sirf_usp_pcm_dai = { + .probe = sirf_usp_pcm_dai_probe, + .name = "sirf-usp-pcm", + .id = 0, + .playback = { + .stream_name = "SiRF USP PCM Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_48000 + | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_32000 + | SNDRV_PCM_RATE_22050 + | SNDRV_PCM_RATE_16000 + | SNDRV_PCM_RATE_11025 + | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "SiRF USP PCM Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_48000 + | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_32000 + | SNDRV_PCM_RATE_22050 + | SNDRV_PCM_RATE_16000 + | SNDRV_PCM_RATE_11025 + | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sirf_usp_pcm_dai_ops, +}; + +static void sirf_usp_controller_init(struct sirf_usp *susp) +{ + u32 val; + + /* Configure RISC mode */ + writel(readl(susp->base + USP_RISC_DSP_MODE) & ~USP_RISC_DSP_SEL, + susp->base + USP_RISC_DSP_MODE); + + /* Disable all interrupts status */ + writel(readl(susp->base + USP_INT_STATUS), susp->base + USP_INT_STATUS); + + /* Configure DMA IO Length register */ + writel(0, susp->base + USP_TX_DMA_IO_LEN); + writel(0, susp->base + USP_RX_DMA_IO_LEN); + + /* Configure RX Frame Control */ + val = (AUDIO_WORD_SIZE * 2 - 1) << USP_RXC_DATA_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE * 2 - 1) << USP_RXC_FRAME_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE * 2 - 1) << USP_RXC_SHIFTER_LEN_OFFSET; + val |= USP_SINGLE_SYNC_MODE; + writel(val, susp->base + USP_RX_FRAME_CTRL); + + /* Configure TX Frame Control */ + val = (AUDIO_WORD_SIZE * 2 - 1) << USP_TXC_DATA_LEN_OFFSET; + val |= 0 << USP_TXC_SYNC_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE * 2 - 1) << USP_TXC_FRAME_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE * 2 - 1) << USP_TXC_SHIFTER_LEN_OFFSET; + val |= USP_TXC_SLAVE_CLK_SAMPLE; + writel(val, susp->base + USP_TX_FRAME_CTRL); + + /* Configure Mode2 register */ + val = (1 << USP_RXD_DELAY_LEN_OFFSET) | (0 << USP_TXD_DELAY_LEN_OFFSET); + val &= ~USP_ENA_CTRL_MODE; + val &= ~USP_FRAME_CTRL_MODE; + val &= ~USP_TFS_SOURCE_MODE; + writel(val, susp->base + USP_MODE2); + + /* Configure Mode1 register */ + val = 0; + val |= USP_SYNC_MODE; + val |= USP_ENDIAN_CTRL_LSBF; + val |= USP_EN; + val |= USP_RXD_ACT_EDGE_FALLING; + val &= ~USP_TXD_ACT_EDGE_FALLING; + val |= USP_RFS_ACT_LEVEL_LOGIC1; + val |= USP_TFS_ACT_LEVEL_LOGIC1; + val |= USP_SCLK_IDLE_MODE_TOGGLE; + val |= USP_SCLK_IDLE_LEVEL_LOGIC1; + val &= ~USP_SCLK_PIN_MODE_IO; + val &= ~USP_RFS_PIN_MODE_IO; + val &= ~USP_TFS_PIN_MODE_IO; + val &= ~USP_RXD_PIN_MODE_IO; + val &= ~USP_TXD_PIN_MODE_IO; + val |= USP_TX_UFLOW_REPEAT_ZERO; + writel(val, susp->base + USP_MODE1); + + /* Configure RX DMA IO Control register */ + writel(0x0, susp->base + USP_RX_DMA_IO_CTRL); + + /* Congiure RX FIFO Control register */ + writel((USP_RX_FIFO_THRESHOLD << USP_RX_FIFO_THD_OFFSET) | + (USP_TX_RX_FIFO_WIDTH_DWORD << USP_RX_FIFO_WIDTH_OFFSET), + susp->base + USP_RX_FIFO_CTRL); + + /* Congiure RX FIFO Level Check register */ + writel(RX_FIFO_SC(0x04) | RX_FIFO_LC(0x0E) | RX_FIFO_HC(0x1B), + susp->base + USP_RX_FIFO_LEVEL_CHK); + + /* Configure TX DMA IO Control register*/ + writel(0x0, susp->base + USP_TX_DMA_IO_CTRL); + + /* Configure TX FIFO Control register */ + writel((USP_TX_FIFO_THRESHOLD << USP_TX_FIFO_THD_OFFSET) | + (USP_TX_RX_FIFO_WIDTH_DWORD << USP_TX_FIFO_WIDTH_OFFSET), + susp->base + USP_TX_FIFO_CTRL); + + /* Congiure TX FIFO Level Check register */ + writel(TX_FIFO_SC(0x1B) | TX_FIFO_LC(0x0E) | TX_FIFO_HC(0x04), + susp->base + USP_TX_FIFO_LEVEL_CHK); + + /* Configure RX FIFO */ + writel(USP_RX_FIFO_RESET, susp->base + USP_RX_FIFO_OP); + writel(0, susp->base + USP_RX_FIFO_OP); + + /* Configure TX FIFO */ + writel(USP_TX_FIFO_RESET, susp->base + USP_TX_FIFO_OP); + writel(0, susp->base + USP_TX_FIFO_OP); +} + +static void sirf_usp_controller_uninit(struct sirf_usp *susp) +{ + /* Disable RX/TX */ + writel(0, susp->base + USP_INT_ENABLE); + writel(0, susp->base + USP_TX_RX_ENABLE); +} + +#ifdef CONFIG_PM_RUNTIME +static int sirf_usp_pcm_runtime_suspend(struct device *dev) +{ + struct sirf_usp *susp = dev_get_drvdata(dev); + sirf_usp_controller_uninit(susp); + clk_disable_unprepare(susp->clk); + return 0; +} + +static int sirf_usp_pcm_runtime_resume(struct device *dev) +{ + struct sirf_usp *susp = dev_get_drvdata(dev); + int ret; + ret = clk_prepare_enable(susp->clk); + if (ret) + return ret; + sirf_usp_controller_init(susp); + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int sirf_usp_pcm_suspend(struct device *dev) +{ + struct sirf_usp *susp = dev_get_drvdata(dev); + + if (!pm_runtime_status_suspended(dev)) { + susp->mode1_reg = readl(susp->base + USP_MODE1); + susp->mode2_reg = readl(susp->base + USP_MODE2); + sirf_usp_pcm_runtime_suspend(dev); + } + return 0; +} + +static int sirf_usp_pcm_resume(struct device *dev) +{ + struct sirf_usp *susp = dev_get_drvdata(dev); + int ret; + + if (!pm_runtime_status_suspended(dev)) { + ret = sirf_usp_pcm_runtime_resume(dev); + if (ret) + return ret; + writel(susp->mode1_reg, susp->base + USP_MODE1); + writel(susp->mode2_reg, susp->base + USP_MODE2); + } + return 0; +} +#endif + +static const struct snd_soc_component_driver sirf_usp_component = { + .name = "sirf-usp", +}; + +static int sirf_usp_pcm_probe(struct platform_device *pdev) +{ + struct sirf_usp *susp; + int ret; + struct resource *mem_res; + + susp = devm_kzalloc(&pdev->dev, sizeof(struct sirf_usp), + GFP_KERNEL); + if (!susp) + return -ENOMEM; + + susp->sirf_pcm_pdev = platform_device_register_simple("sirf-pcm-audio", + 2, NULL, 0); + if (IS_ERR(susp->sirf_pcm_pdev)) + return PTR_ERR(susp->sirf_pcm_pdev); + + platform_set_drvdata(pdev, susp); + + dma_data[0].chan_name = "tx"; + dma_data[1].chan_name = "rx"; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + susp->base = devm_ioremap_resource(&pdev->dev, mem_res); + if (susp->base == NULL) + return -ENOMEM; + + susp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(susp->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + return PTR_ERR(susp->clk); + } + + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &sirf_usp_component, + &sirf_usp_pcm_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio SoC dai failed.\n"); + goto err; + } + return 0; + +err: + return ret; +} + +static int sirf_usp_pcm_remove(struct platform_device *pdev) +{ + struct sirf_usp *susp = platform_get_drvdata(pdev); + pm_runtime_disable(&pdev->dev); + platform_device_unregister(susp->sirf_pcm_pdev); + + return 0; +} + +static const struct of_device_id sirf_usp_pcm_of_match[] = { + { .compatible = "sirf,prima2-usp-pcm", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_usp_pcm_of_match); + +static const struct dev_pm_ops sirf_usp_pcm_pm_ops = { + SET_RUNTIME_PM_OPS(sirf_usp_pcm_runtime_suspend, sirf_usp_pcm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(sirf_usp_pcm_suspend, sirf_usp_pcm_resume) +}; + +static struct platform_driver sirf_usp_pcm_driver = { + .driver = { + .name = "sirf-usp-pcm", + .owner = THIS_MODULE, + .of_match_table = sirf_usp_pcm_of_match, + .pm = &sirf_usp_pcm_pm_ops, + }, + .probe = sirf_usp_pcm_probe, + .remove = sirf_usp_pcm_remove, +}; + +module_platform_driver(sirf_usp_pcm_driver); + +MODULE_DESCRIPTION("SiRF SoC USP PCM bus driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sirf/sirf-usp.h b/sound/soc/sirf/sirf-usp.h new file mode 100644 index 0000000..cb9085f --- /dev/null +++ b/sound/soc/sirf/sirf-usp.h @@ -0,0 +1,276 @@ +/* + * arch/arm/mach-prima2/include/mach/sirfsoc_usp.h + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#ifndef __SIRFSOC_USP__ +#define __SIRFSOC_USP__ + +/* USP Registers */ +#define USP_MODE1 0x00 +#define USP_MODE2 0x04 +#define USP_TX_FRAME_CTRL 0x08 +#define USP_RX_FRAME_CTRL 0x0C +#define USP_TX_RX_ENABLE 0x10 +#define USP_INT_ENABLE 0x14 +#define USP_INT_STATUS 0x18 +#define USP_PIN_IO_DATA 0x1C +#define USP_RISC_DSP_MODE 0x20 +#define USP_AYSNC_PARAM_REG 0x24 +#define USP_IRDA_X_MODE_DIV 0x28 +#define USP_SM_CFG 0x2C +#define USP_TX_DMA_IO_CTRL 0x100 +#define USP_TX_DMA_IO_LEN 0x104 +#define USP_TX_FIFO_CTRL 0x108 +#define USP_TX_FIFO_LEVEL_CHK 0x10C +#define USP_TX_FIFO_OP 0x110 +#define USP_TX_FIFO_STATUS 0x114 +#define USP_TX_FIFO_DATA 0x118 +#define USP_RX_DMA_IO_CTRL 0x120 +#define USP_RX_DMA_IO_LEN 0x124 +#define USP_RX_FIFO_CTRL 0x128 +#define USP_RX_FIFO_LEVEL_CHK 0x12C +#define USP_RX_FIFO_OP 0x130 +#define USP_RX_FIFO_STATUS 0x134 +#define USP_RX_FIFO_DATA 0x138 + +/* USP MODE register-1 */ +#define USP_SYNC_MODE 0x00000001 +#define USP_CLOCK_MODE_SLAVE 0x00000002 +#define USP_LOOP_BACK_EN 0x00000004 +#define USP_HPSIR_EN 0x00000008 +#define USP_ENDIAN_CTRL_LSBF 0x00000010 +#define USP_EN 0x00000020 +#define USP_RXD_ACT_EDGE_FALLING 0x00000040 +#define USP_TXD_ACT_EDGE_FALLING 0x00000080 +#define USP_RFS_ACT_LEVEL_LOGIC1 0x00000100 +#define USP_TFS_ACT_LEVEL_LOGIC1 0x00000200 +#define USP_SCLK_IDLE_MODE_TOGGLE 0x00000400 +#define USP_SCLK_IDLE_LEVEL_LOGIC1 0x00000800 +#define USP_SCLK_PIN_MODE_IO 0x00001000 +#define USP_RFS_PIN_MODE_IO 0x00002000 +#define USP_TFS_PIN_MODE_IO 0x00004000 +#define USP_RXD_PIN_MODE_IO 0x00008000 +#define USP_TXD_PIN_MODE_IO 0x00010000 +#define USP_SCLK_IO_MODE_INPUT 0x00020000 +#define USP_RFS_IO_MODE_INPUT 0x00040000 +#define USP_TFS_IO_MODE_INPUT 0x00080000 +#define USP_RXD_IO_MODE_INPUT 0x00100000 +#define USP_TXD_IO_MODE_INPUT 0x00200000 +#define USP_IRDA_WIDTH_DIV_MASK 0x3FC00000 +#define USP_IRDA_WIDTH_DIV_OFFSET 0 +#define USP_IRDA_IDLE_LEVEL_HIGH 0x40000000 +#define USP_TX_UFLOW_REPEAT_ZERO 0x80000000 +#define USP_TX_ENDIAN_MODE 0x00000020 +#define USP_RX_ENDIAN_MODE 0x00000020 + +/* USP Mode Register-2 */ +#define USP_RXD_DELAY_LEN_MASK 0x000000FF +#define USP_RXD_DELAY_LEN_OFFSET 0 + +#define USP_TXD_DELAY_LEN_MASK 0x0000FF00 +#define USP_TXD_DELAY_LEN_OFFSET 8 + +#define USP_ENA_CTRL_MODE 0x00010000 +#define USP_FRAME_CTRL_MODE 0x00020000 +#define USP_TFS_SOURCE_MODE 0x00040000 +#define USP_TFS_MS_MODE 0x00080000 +#define USP_CLK_DIVISOR_MASK 0x7FE00000 +#define USP_CLK_DIVISOR_OFFSET 21 + +#define USP_TFS_CLK_SLAVE_MODE (1<<20) +#define USP_RFS_CLK_SLAVE_MODE (1<<19) + +#define USP_IRDA_DATA_WIDTH 0x80000000 + +/* USP Transmit Frame Control Register */ + +#define USP_TXC_DATA_LEN_MASK 0x000000FF +#define USP_TXC_DATA_LEN_OFFSET 0 + +#define USP_TXC_SYNC_LEN_MASK 0x0000FF00 +#define USP_TXC_SYNC_LEN_OFFSET 8 + +#define USP_TXC_FRAME_LEN_MASK 0x00FF0000 +#define USP_TXC_FRAME_LEN_OFFSET 16 + +#define USP_TXC_SHIFTER_LEN_MASK 0x1F000000 +#define USP_TXC_SHIFTER_LEN_OFFSET 24 + +#define USP_TXC_SLAVE_CLK_SAMPLE 0x20000000 + +#define USP_TXC_CLK_DIVISOR_MASK 0xC0000000 +#define USP_TXC_CLK_DIVISOR_OFFSET 30 + +/* USP Receive Frame Control Register */ + +#define USP_RXC_DATA_LEN_MASK 0x000000FF +#define USP_RXC_DATA_LEN_OFFSET 0 + +#define USP_RXC_FRAME_LEN_MASK 0x0000FF00 +#define USP_RXC_FRAME_LEN_OFFSET 8 + +#define USP_RXC_SHIFTER_LEN_MASK 0x001F0000 +#define USP_RXC_SHIFTER_LEN_OFFSET 16 + +#define USP_I2S_SYNC_CHG 0x00200000 + +#define USP_RXC_CLK_DIVISOR_MASK 0x0F000000 +#define USP_RXC_CLK_DIVISOR_OFFSET 24 +#define USP_SINGLE_SYNC_MODE 0x00400000 + +/* Tx - RX Enable Register */ + +#define USP_RX_ENA 0x00000001 +#define USP_TX_ENA 0x00000002 + +/* USP Interrupt Enable and status Register */ +#define USP_RX_DONE_INT 0x00000001 +#define USP_TX_DONE_INT 0x00000002 +#define USP_RX_OFLOW_INT 0x00000004 +#define USP_TX_UFLOW_INT 0x00000008 +#define USP_RX_IO_DMA_INT 0x00000010 +#define USP_TX_IO_DMA_INT 0x00000020 +#define USP_RXFIFO_FULL_INT 0x00000040 +#define USP_TXFIFO_EMPTY_INT 0x00000080 +#define USP_RXFIFO_THD_INT 0x00000100 +#define USP_TXFIFO_THD_INT 0x00000200 +#define USP_UART_FRM_ERR_INT 0x00000400 +#define USP_RX_TIMEOUT_INT 0x00000800 +#define USP_TX_ALLOUT_INT 0x00001000 +#define USP_RXD_BREAK_INT 0x00008000 + +/* All possible TX interruots */ +#define USP_TX_INTERRUPT (USP_TX_DONE_INT|USP_TX_UFLOW_INT|USP_TX_IO_DMA_INT|\ + USP_TXFIFO_EMPTY_INT|USP_TXFIFO_THD_INT) +/* All possible RX interruots */ +#define USP_RX_INTERRUPT (USP_RX_DONE_INT|USP_RX_OFLOW_INT|USP_RX_IO_DMA_INT|\ + USP_RXFIFO_FULL_INT|USP_RXFIFO_THD_INT|USP_RXFIFO_THD_INT|USP_RX_TIMEOUT_INT) + +#define USP_INT_ALL 0x1FFF + +/* USP Pin I/O Data Register */ + +#define USP_RFS_PIN_VALUE_MASK 0x00000001 +#define USP_TFS_PIN_VALUE_MASK 0x00000002 +#define USP_RXD_PIN_VALUE_MASK 0x00000004 +#define USP_TXD_PIN_VALUE_MASK 0x00000008 +#define USP_SCLK_PIN_VALUE_MASK 0x00000010 + +/* USP RISC/DSP Mode Register */ +#define USP_RISC_DSP_SEL 0x00000001 + +/* USP ASYNC PARAMETER Register*/ + +#define USP_ASYNC_TIMEOUT_MASK 0x0000FFFF +#define USP_ASYNC_TIMEOUT_OFFSET 0 +#define USP_ASYNC_TIMEOUT(x) (((x)&USP_ASYNC_TIMEOUT_MASK)<<USP_ASYNC_TIMEOUT_OFFSET) + +#define USP_ASYNC_DIV2_MASK 0x003F0000 +#define USP_ASYNC_DIV2_OFFSET 16 + +/* USP TX DMA I/O MODE Register */ +#define USP_TX_MODE_IO 0x00000001 + +/* USP TX DMA I/O Length Register */ +#define USP_TX_DATA_LEN_MASK 0xFFFFFFFF +#define USP_TX_DATA_LEN_OFFSET 0 + +/* USP TX FIFO Control Register */ +#define USP_TX_FIFO_WIDTH_MASK 0x00000003 +#define USP_TX_FIFO_WIDTH_OFFSET 0 + +#define USP_TX_FIFO_THD_MASK 0x000001FC +#define USP_TX_FIFO_THD_OFFSET 2 + +/* USP TX FIFO Level Check Register */ +#define USP_TX_FIFO_LEVEL_CHECK_MASK 0x1F +#define USP_TX_FIFO_SC_OFFSET 0 +#define USP_TX_FIFO_LC_OFFSET 10 +#define USP_TX_FIFO_HC_OFFSET 20 + +#define TX_FIFO_SC(x) (((x) & USP_TX_FIFO_LEVEL_CHECK_MASK) << USP_TX_FIFO_SC_OFFSET) +#define TX_FIFO_LC(x) (((x) & USP_TX_FIFO_LEVEL_CHECK_MASK) << USP_TX_FIFO_LC_OFFSET) +#define TX_FIFO_HC(x) (((x) & USP_TX_FIFO_LEVEL_CHECK_MASK) << USP_TX_FIFO_HC_OFFSET) + +/* USP TX FIFO Operation Register */ +#define USP_TX_FIFO_RESET 0x00000001 +#define USP_TX_FIFO_START 0x00000002 + +/* USP TX FIFO Status Register */ +#define USP_TX_FIFO_LEVEL_MASK 0x0000007F +#define USP_TX_FIFO_LEVEL_OFFSET 0 + +#define USP_TX_FIFO_FULL 0x00000080 +#define USP_TX_FIFO_EMPTY 0x00000100 + +/* USP TX FIFO Data Register */ +#define USP_TX_FIFO_DATA_MASK 0xFFFFFFFF +#define USP_TX_FIFO_DATA_OFFSET 0 + +/* USP RX DMA I/O MODE Register */ +#define USP_RX_MODE_IO 0x00000001 +#define USP_RX_DMA_FLUSH 0x00000004 + +/* USP RX DMA I/O Length Register */ +#define USP_RX_DATA_LEN_MASK 0xFFFFFFFF +#define USP_RX_DATA_LEN_OFFSET 0 + +/* USP RX FIFO Control Register */ +#define USP_RX_FIFO_WIDTH_MASK 0x00000003 +#define USP_RX_FIFO_WIDTH_OFFSET 0 + +#define USP_RX_FIFO_THD_MASK 0x000001FC +#define USP_RX_FIFO_THD_OFFSET 2 + +/* USP RX FIFO Level Check Register */ + +#define USP_RX_FIFO_LEVEL_CHECK_MASK 0x1F +#define USP_RX_FIFO_SC_OFFSET 0 +#define USP_RX_FIFO_LC_OFFSET 10 +#define USP_RX_FIFO_HC_OFFSET 20 + +#define RX_FIFO_SC(x) (((x) & USP_RX_FIFO_LEVEL_CHECK_MASK) << USP_RX_FIFO_SC_OFFSET) +#define RX_FIFO_LC(x) (((x) & USP_RX_FIFO_LEVEL_CHECK_MASK) << USP_RX_FIFO_LC_OFFSET) +#define RX_FIFO_HC(x) (((x) & USP_RX_FIFO_LEVEL_CHECK_MASK) << USP_RX_FIFO_HC_OFFSET) + +/* USP RX FIFO Operation Register */ +#define USP_RX_FIFO_RESET 0x00000001 +#define USP_RX_FIFO_START 0x00000002 + +/* USP RX FIFO Status Register */ + +#define USP_RX_FIFO_LEVEL_MASK 0x0000007F +#define USP_RX_FIFO_LEVEL_OFFSET 0 + +#define USP_RX_FIFO_FULL 0x00000080 +#define USP_RX_FIFO_EMPTY 0x00000100 + +/* USP RX FIFO Data Register */ + +#define USP_RX_FIFO_DATA_MASK 0xFFFFFFFF +#define USP_RX_FIFO_DATA_OFFSET 0 + +/* + * When rx thd irq occur, sender just disable tx empty irq, + * Remaining data in tx fifo wil also be sent out. + */ +#define USP_FIFO_SIZE 128 +#define USP_TX_FIFO_THRESHOLD (USP_FIFO_SIZE/2) +#define USP_RX_FIFO_THRESHOLD (USP_FIFO_SIZE/2) + +/* FIFO_WIDTH for the USP_TX_FIFO_CTRL and USP_RX_FIFO_CTRL registers */ +#define USP_FIFO_WIDTH_BYTE 0x00 +#define USP_FIFO_WIDTH_WORD 0x01 +#define USP_FIFO_WIDTH_DWORD 0x02 + +#define USP_ASYNC_DIV2 16 + +#define USP_PLUGOUT_RETRY_CNT 2 + +#define USP_TX_RX_FIFO_WIDTH_DWORD 2 + +#endif
On Fri, Jan 03, 2014 at 02:05:02PM +0800, RongJun Ying wrote:
+static void sirf_usp_tx_fifo_op(struct sirf_usp *susp, int cmd) +{
- switch (cmd) {
- case FIFO_RESET:
writel(USP_TX_FIFO_RESET, susp->base + USP_TX_FIFO_OP);
writel(0, susp->base + USP_TX_FIFO_OP);
break;
- case FIFO_START:
writel(USP_TX_FIFO_START, susp->base + USP_TX_FIFO_OP);
break;
- case FIFO_STOP:
writel(0, susp->base + USP_TX_FIFO_OP);
break;
- }
+}
I'm not sure these functions and the switch statements in them are adding anything - none of the statements share any code. Either refactor this so that the callers pass in USP_TX_FIFO_blah for the argument or just inline the register operations.
+static inline void sirf_usp_tx_enable(struct sirf_usp *susp) +{
- writel(readl(susp->base + USP_TX_RX_ENABLE) | USP_TX_ENA,
susp->base + USP_TX_RX_ENABLE);
+}
Similar thing here, just inline.
+static int sirf_usp_pcm_dai_probe(struct snd_soc_dai *dai) +{
- dai->playback_dma_data = &dma_data[0];
- dai->capture_dma_data = &dma_data[1];
snd_soc_dai_init_dma_data().
From: Rongjun Ying Rongjun.Ying@csr.com
there is an internal codec embedded in the SiRF SoC. this is not a typical user scenerios of ASoC. but we can still get benefit by sharing platform DMA codes instead of implementing a pure ALSA driver. This driver adds DAI drivers for this internal codec.
The features of Internal Codec Controller include: Support two channel 16-bit resolution playback with fix 48KHz sample rate Support two channel 16-bit resolution record with fix 48KHz sample rate Use dedicated Internal Codec TXFIFO and Internal Codec RXFIFO Supports two DMA channels for Internal Codec TXFIFO and Internal Codec RXFIFO
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com --- -v3: Remove the pm_runtime_get_sync and pm_runtime_put invoke Use the generic dma dt-binding to get playback/capture dma channels Check the return value Use devm_snd_soc_register_component instead of snd_soc_register_component for inner, usp-pcm and i2s platform interface
sound/soc/sirf/Kconfig | 3 + sound/soc/sirf/Makefile | 2 + sound/soc/sirf/sirf-soc-inner.c | 653 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 658 insertions(+), 0 deletions(-) create mode 100644 sound/soc/sirf/sirf-soc-inner.c
diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index 5e395d3..afa3952 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -6,5 +6,8 @@ config SND_SIRF_SOC config SND_SOC_SIRF_I2S tristate
+config SND_SIRF_SOC_INNER + tristate + config SND_SOC_SIRF_USP tristate diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index 630c9be..8517c67 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,7 +1,9 @@ snd-soc-sirf-objs := sirf-pcm.o +snd-soc-sirf-soc-inner-objs := sirf-soc-inner.o snd-soc-sirf-i2s-objs := sirf-i2s.o snd-soc-sirf-usp-objs := sirf-usp.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o +obj-$(CONFIG_SND_SIRF_SOC_INNER) += snd-soc-sirf-soc-inner.o obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-soc-inner.c b/sound/soc/sirf/sirf-soc-inner.c new file mode 100644 index 0000000..d542a6e --- /dev/null +++ b/sound/soc/sirf/sirf-soc-inner.c @@ -0,0 +1,653 @@ +/* + * SiRF inner audio codec driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/rtc/sirfsoc_rtciobrg.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "sirf-audio.h" + +struct sirf_soc_inner_audio_reg_bits { + u32 dig_mic_en_bits; + u32 dig_mic_freq_bits; + u32 adc14b_12_bits; + u32 firdac_hsl_en_bits; + u32 firdac_hsr_en_bits; + u32 firdac_lout_en_bits; + u32 por_bits; + u32 codec_clk_en_bits; +}; + +struct sirf_soc_inner_audio { + void __iomem *base; + struct clk *clk; + spinlock_t lock; + u32 sys_pwrc_reg_base; + struct sirf_soc_inner_audio_reg_bits *reg_bits; + u32 reg_ctrl0, reg_ctrl1; + struct platform_device *sirf_pcm_pdev; +}; + +static struct sirf_soc_inner_audio_reg_bits sirf_soc_inner_audio_reg_bits_prima2 = { + 20, 21, 22, 23, 24, 25, 26, 27, +}; + +static struct sirf_soc_inner_audio_reg_bits sirf_soc_inner_audio_reg_bits_atlas6 = { + 22, 23, 24, 25, 26, 27, 28, 29, +}; +static const char * const input_mode_mux[] = {"Single-ended", + "Differential"}; + +static const struct soc_enum sirf_inner_enum[] = { + SOC_ENUM_SINGLE(AUDIO_IC_CODEC_CTRL1, 4, 2, input_mode_mux), +}; + +static const struct snd_kcontrol_new sirf_inner_input_mode_control = + SOC_DAPM_ENUM("Route", sirf_inner_enum[0]); + +static struct snd_kcontrol_new volume_controls_atlas6[] = { + SOC_DOUBLE("Playback Volume", AUDIO_IC_CODEC_CTRL0, 21, 14, + 0x7F, 0), + SOC_DOUBLE("Capture Volume", AUDIO_IC_CODEC_CTRL1, 16, 10, + 0x3F, 0), +}; + +static struct snd_kcontrol_new volume_controls_prima2[] = { + SOC_DOUBLE("Speaker Volume", AUDIO_IC_CODEC_CTRL0, 21, 14, + 0x7F, 0), + SOC_DOUBLE("Capture Volume", AUDIO_IC_CODEC_CTRL1, 15, 10, + 0x1F, 0), +}; + +static struct snd_kcontrol_new left_input_path_controls[] = { + SOC_DAPM_SINGLE("Line left Switch", AUDIO_IC_CODEC_CTRL1, 6, 1, 0), + SOC_DAPM_SINGLE("Mic left Switch", AUDIO_IC_CODEC_CTRL1, 3, 1, 0), +}; + +static struct snd_kcontrol_new right_input_path_controls[] = { + SOC_DAPM_SINGLE("Line right Switch", AUDIO_IC_CODEC_CTRL1, 5, 1, 0), + SOC_DAPM_SINGLE("Mic right Switch", AUDIO_IC_CODEC_CTRL1, 2, 1, 0), +}; + +static struct snd_kcontrol_new left_dac_to_hp_left_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 9, 1, 0); + +static struct snd_kcontrol_new left_dac_to_hp_right_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 8, 1, 0); + +static struct snd_kcontrol_new right_dac_to_hp_left_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 7, 1, 0); + +static struct snd_kcontrol_new right_dac_to_hp_right_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 6, 1, 0); + +static struct snd_kcontrol_new left_dac_to_speaker_lineout_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 11, 1, 0); + +static struct snd_kcontrol_new right_dac_to_speaker_lineout_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 10, 1, 0); + +static int adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + u32 val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable capture power of codec*/ + val = sirfsoc_rtc_iobrg_readl(sinner_audio->sys_pwrc_reg_base + + PWRC_PDN_CTRL_OFFSET); + val |= (1 << AUDIO_POWER_EN_BIT); + sirfsoc_rtc_iobrg_writel(val, + sinner_audio->sys_pwrc_reg_base + PWRC_PDN_CTRL_OFFSET); + break; + case SND_SOC_DAPM_POST_PMU: + /*After enable adc, Delay 200ms to avoid pop noise*/ + msleep(200); + default: + break; + } + + return 0; +} + +static int hp_amp_left_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + u32 val; + + val = snd_soc_read(codec, AUDIO_IC_CODEC_CTRL1); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + val |= (1 << sinner_audio->reg_bits->firdac_hsl_en_bits); + break; + case SND_SOC_DAPM_POST_PMD: + val &= ~(1 << sinner_audio->reg_bits->firdac_hsl_en_bits); + default: + break; + } + snd_soc_write(codec, AUDIO_IC_CODEC_CTRL1, val); + return 0; +} + +static int hp_amp_right_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + u32 val; + + val = snd_soc_read(codec, AUDIO_IC_CODEC_CTRL1); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + val |= (1 << sinner_audio->reg_bits->firdac_hsr_en_bits); + break; + case SND_SOC_DAPM_POST_PMD: + val &= ~(1 << sinner_audio->reg_bits->firdac_hsr_en_bits); + default: + break; + } + snd_soc_write(codec, AUDIO_IC_CODEC_CTRL1, val); + return 0; +} + +static int speaker_output_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + u32 val; + + val = snd_soc_read(codec, AUDIO_IC_CODEC_CTRL1); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + val |= (1 << sinner_audio->reg_bits->firdac_lout_en_bits); + break; + case SND_SOC_DAPM_POST_PMD: + val &= ~(1 << sinner_audio->reg_bits->firdac_lout_en_bits); + default: + break; + } + snd_soc_write(codec, AUDIO_IC_CODEC_CTRL1, val); + return 0; +} + +static const struct snd_soc_dapm_widget sirf_inner_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC left", NULL, AUDIO_IC_CODEC_CTRL0, 1, 0), + SND_SOC_DAPM_DAC("DAC right", NULL, AUDIO_IC_CODEC_CTRL0, 0, 0), + SND_SOC_DAPM_SWITCH("Left dac to hp left amp", SND_SOC_NOPM, 0, 0, + &left_dac_to_hp_left_amp_switch_control), + SND_SOC_DAPM_SWITCH("Left dac to hp right amp", SND_SOC_NOPM, 0, 0, + &left_dac_to_hp_right_amp_switch_control), + SND_SOC_DAPM_SWITCH("Right dac to hp left amp", SND_SOC_NOPM, 0, 0, + &right_dac_to_hp_left_amp_switch_control), + SND_SOC_DAPM_SWITCH("Right dac to hp right amp", SND_SOC_NOPM, 0, 0, + &right_dac_to_hp_right_amp_switch_control), + SND_SOC_DAPM_OUT_DRV_E("HP amp left driver", AUDIO_IC_CODEC_CTRL0, 3, 0, + NULL, 0, hp_amp_left_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("HP amp right driver", AUDIO_IC_CODEC_CTRL0, 2, 0, + NULL, 0, hp_amp_right_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SWITCH("Left dac to speaker lineout", SND_SOC_NOPM, 0, 0, + &left_dac_to_speaker_lineout_switch_control), + SND_SOC_DAPM_SWITCH("Right dac to speaker lineout", SND_SOC_NOPM, 0, 0, + &right_dac_to_speaker_lineout_switch_control), + SND_SOC_DAPM_OUT_DRV_E("Speaker output driver", AUDIO_IC_CODEC_CTRL0, 4, 0, + NULL, 0, speaker_output_enable_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("SPKOUT"), + + SND_SOC_DAPM_ADC_E("ADC left", NULL, AUDIO_IC_CODEC_CTRL1, 8, 0, + adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC_E("ADC right", NULL, AUDIO_IC_CODEC_CTRL1, 7, 0, + adc_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER("Left PGA mixer", AUDIO_IC_CODEC_CTRL1, 1, 0, + &left_input_path_controls[0], + ARRAY_SIZE(left_input_path_controls)), + SND_SOC_DAPM_MIXER("Right PGA mixer", AUDIO_IC_CODEC_CTRL1, 0, 0, + &right_input_path_controls[0], + ARRAY_SIZE(right_input_path_controls)), + + SND_SOC_DAPM_MUX("Mic input mode mux", SND_SOC_NOPM, 0, 0, + &sirf_inner_input_mode_control), + SND_SOC_DAPM_MICBIAS("Mic Bias", AUDIO_IC_CODEC_PWR, 3, 0), + SND_SOC_DAPM_INPUT("MICIN1"), + SND_SOC_DAPM_INPUT("MICIN2"), + SND_SOC_DAPM_INPUT("LINEIN1"), + SND_SOC_DAPM_INPUT("LINEIN2"), + + SND_SOC_DAPM_SUPPLY("HSL Phase Opposite", AUDIO_IC_CODEC_CTRL0, + 30, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route sirf_inner_audio_map[] = { + {"SPKOUT", NULL, "Speaker output driver"}, + {"Speaker output driver", NULL, "Left dac to speaker lineout"}, + {"Speaker output driver", NULL, "Right dac to speaker lineout"}, + {"Left dac to speaker lineout", "Switch", "DAC left"}, + {"Right dac to speaker lineout", "Switch", "DAC right"}, + {"HPOUTL", NULL, "HP amp left driver"}, + {"HPOUTR", NULL, "HP amp right driver"}, + {"HP amp left driver", NULL, "Right dac to hp left amp"}, + {"HP amp right driver", NULL , "Right dac to hp right amp"}, + {"HP amp left driver", NULL, "Left dac to hp left amp"}, + {"HP amp right driver", NULL , "Right dac to hp right amp"}, + {"Right dac to hp left amp", "Switch", "DAC left"}, + {"Right dac to hp right amp", "Switch", "DAC right"}, + {"Left dac to hp left amp", "Switch", "DAC left"}, + {"Left dac to hp right amp", "Switch", "DAC right"}, + {"DAC left", NULL, "Playback"}, + {"DAC right", NULL, "Playback"}, + {"DAC left", NULL, "HSL Phase Opposite"}, + {"DAC right", NULL, "HSL Phase Opposite"}, + + {"Capture", NULL, "ADC left"}, + {"Capture", NULL, "ADC right"}, + {"ADC left", NULL, "Left PGA mixer"}, + {"ADC right", NULL, "Right PGA mixer"}, + {"Left PGA mixer", "Line left Switch", "LINEIN2"}, + {"Right PGA mixer", "Line right Switch", "LINEIN1"}, + {"Left PGA mixer", "Mic left Switch", "MICIN2"}, + {"Right PGA mixer", "Mic right Switch", "Mic input mode mux"}, + {"Mic input mode mux", "Single-ended", "MICIN1"}, + {"Mic input mode mux", "Differential", "MICIN1"}, +}; + +static int sirf_inner_codec_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct sirf_soc_inner_audio *sinner_audio = snd_soc_dai_get_drvdata(dai); + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_codec *codec = dai->codec; + u32 val; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock(&sinner_audio->lock); + if (playback) { + /*Disconnect HP amp connect to avoid pop noise*/ + val = snd_soc_read(codec, AUDIO_IC_CODEC_CTRL0); + val &= ~(IC_HSLEN | IC_HSREN); + snd_soc_write(codec, AUDIO_IC_CODEC_CTRL0, val); + + snd_soc_write(codec, AUDIO_CTRL_IC_TXFIFO_OP, 0x0); + + val = snd_soc_read(codec, AUDIO_CTRL_IC_CODEC_TX_CTRL); + val &= ~IC_TX_ENABLE; + snd_soc_write(codec, AUDIO_CTRL_IC_CODEC_TX_CTRL, val); + } else { + val = snd_soc_read(codec, AUDIO_CTRL_IC_CODEC_RX_CTRL); + val &= ~IC_RX_ENABLE; + snd_soc_write(codec, AUDIO_CTRL_IC_CODEC_RX_CTRL, val); + } + spin_unlock(&sinner_audio->lock); + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock(&sinner_audio->lock); + if (playback) { + snd_soc_write(codec, AUDIO_CTRL_IC_TXFIFO_OP, AUDIO_FIFO_RESET); + snd_soc_write(codec, AUDIO_CTRL_IC_TXFIFO_INT_MSK, 0); + snd_soc_write(codec, AUDIO_CTRL_IC_TXFIFO_OP, 0x0); + snd_soc_write(codec, AUDIO_CTRL_IC_TXFIFO_OP, + AUDIO_FIFO_START); + snd_soc_write(codec, AUDIO_CTRL_IC_CODEC_TX_CTRL, + IC_TX_ENABLE); + val = snd_soc_read(codec, AUDIO_IC_CODEC_CTRL0); + val |= (IC_HSLEN | IC_HSREN); + snd_soc_write(codec, AUDIO_IC_CODEC_CTRL0, val); + } else { + snd_soc_write(codec, AUDIO_CTRL_IC_RXFIFO_OP, AUDIO_FIFO_RESET); + /* unmask rx fifo interrupt */ + snd_soc_write(codec, AUDIO_CTRL_IC_RXFIFO_INT_MSK, 0); + + snd_soc_write(codec, AUDIO_CTRL_IC_RXFIFO_OP, 0x0); + /* First start the FIFO, then enable the tx/rx */ + snd_soc_write(codec, AUDIO_CTRL_IC_RXFIFO_OP, + AUDIO_FIFO_START); + /* mono capture from dacr*/ + if (substream->runtime->channels == 1) + snd_soc_write(codec, + AUDIO_CTRL_IC_CODEC_RX_CTRL, 0x1); + else + snd_soc_write(codec, + AUDIO_CTRL_IC_CODEC_RX_CTRL, + IC_RX_ENABLE); + } + spin_unlock(&sinner_audio->lock); + break; + default: + return -EINVAL; + } + return 0; +} + +struct snd_soc_dai_ops sirf_inner_codec_dai_ops = { + .trigger = sirf_inner_codec_trigger, +}; + +struct snd_soc_dai_driver sirf_inner_codec_dai = { + .name = "sirf-soc-inner", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sirf_inner_codec_dai_ops, +}; +EXPORT_SYMBOL_GPL(sirf_inner_codec_dai); + +static int sirf_inner_codec_probe(struct snd_soc_codec *codec) +{ + pm_runtime_enable(codec->dev); + if (of_device_is_compatible(codec->dev->of_node, "sirf,prima2-audio")) + return snd_soc_add_codec_controls(codec, + volume_controls_prima2, + ARRAY_SIZE(volume_controls_prima2)); + if (of_device_is_compatible(codec->dev->of_node, "sirf,atlas6-audio")) + return snd_soc_add_codec_controls(codec, + volume_controls_atlas6, + ARRAY_SIZE(volume_controls_atlas6)); + + return -EINVAL; +} + +static int sirf_inner_codec_remove(struct snd_soc_codec *codec) +{ + pm_runtime_disable(codec->dev); + return 0; +} + +static unsigned int sirf_inner_codec_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + return readl(sinner_audio->base + reg); +} + +static int sirf_inner_codec_reg_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + writel(val, sinner_audio->base + reg); + return 0; +} + + +static struct snd_soc_codec_driver soc_codec_device_sirf_inner_codec = { + .probe = sirf_inner_codec_probe, + .remove = sirf_inner_codec_remove, + .read = sirf_inner_codec_reg_read, + .write = sirf_inner_codec_reg_write, + .dapm_widgets = sirf_inner_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sirf_inner_dapm_widgets), + .dapm_routes = sirf_inner_audio_map, + .num_dapm_routes = ARRAY_SIZE(sirf_inner_audio_map), + .idle_bias_off = true, +}; + +static struct snd_dmaengine_dai_dma_data dma_data[2]; + +static int sirf_soc_inner_dai_probe(struct snd_soc_dai *dai) +{ + dai->playback_dma_data = &dma_data[0]; + dai->capture_dma_data = &dma_data[1]; + return 0; +} + +static struct snd_soc_dai_driver sirf_soc_inner_dai = { + .probe = sirf_soc_inner_dai_probe, + .name = "sirf-soc-inner", + .id = 0, + .playback = { + .stream_name = "inner Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "inner Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static const struct snd_soc_component_driver sirf_soc_inner_component = { + .name = "sirf-soc-inner", +}; + +static const struct of_device_id sirf_soc_inner_of_match[] = { + { .compatible = "sirf,prima2-audio", .data = &sirf_soc_inner_audio_reg_bits_prima2 }, + { .compatible = "sirf,atlas6-audio", .data = &sirf_soc_inner_audio_reg_bits_atlas6 }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_soc_inner_of_match); + +static int sirf_soc_inner_probe(struct platform_device *pdev) +{ + int ret; + struct sirf_soc_inner_audio *sinner_audio; + struct resource *mem_res; + struct device_node *dn = NULL; + const struct of_device_id *match; + u32 val; + + match = of_match_node(sirf_soc_inner_of_match, pdev->dev.of_node); + + sinner_audio = devm_kzalloc(&pdev->dev, + sizeof(struct sirf_soc_inner_audio), GFP_KERNEL); + if (!sinner_audio) + return -ENOMEM; + + sinner_audio->sirf_pcm_pdev = platform_device_register_simple("sirf-pcm-audio", + 1, NULL, 0); + if (IS_ERR(sinner_audio->sirf_pcm_pdev)) + return PTR_ERR(sinner_audio->sirf_pcm_pdev); + + platform_set_drvdata(pdev, sinner_audio); + + dma_data[0].chan_name = "tx"; + dma_data[1].chan_name = "rx"; + + dn = of_find_compatible_node(dn, NULL, "sirf,prima2-pwrc"); + if (!dn) { + dev_err(&pdev->dev, "Failed to get sirf,prima2-pwrc node!\n"); + return -ENODEV; + } + + ret = of_property_read_u32(dn, "reg", &sinner_audio->sys_pwrc_reg_base); + if (ret < 0) { + dev_err(&pdev->dev, "Failed tp get pwrc register base address\n"); + return -EINVAL; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sinner_audio->base = devm_ioremap_resource(&pdev->dev, mem_res); + if (sinner_audio->base == NULL) + return -ENOMEM; + + sinner_audio->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sinner_audio->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + return PTR_ERR(sinner_audio->clk); + } + + ret = clk_prepare_enable(sinner_audio->clk); + if (ret) { + dev_err(&pdev->dev, "Enable clock failed.\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &sirf_soc_inner_component, + &sirf_soc_inner_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio SoC dai failed.\n"); + goto err_clk_put; + } + + ret = snd_soc_register_codec(&(pdev->dev), + &soc_codec_device_sirf_inner_codec, + &sirf_inner_codec_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio Codec dai failed.\n"); + goto err_com_unreg; + } + + sinner_audio->reg_bits = (struct sirf_soc_inner_audio_reg_bits *)match->data; + /* + * Always open charge pump, if not, when the charge pump closed the + * adc will not stable + */ + val = readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + val |= IC_CPFREQ; + writel(val, sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + if (of_device_is_compatible(pdev->dev.of_node, "sirf,atlas6-audio")) { + val |= IC_CPEN; + writel(val, sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + } + spin_lock_init(&sinner_audio->lock); + return 0; + +err_com_unreg: + snd_soc_unregister_component(&pdev->dev); +err_clk_put: + clk_disable_unprepare(sinner_audio->clk); + return ret; +} + +static int sirf_soc_inner_remove(struct platform_device *pdev) +{ + struct sirf_soc_inner_audio *sinner_audio = platform_get_drvdata(pdev); + + clk_disable_unprepare(sinner_audio->clk); + snd_soc_unregister_codec(&(pdev->dev)); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int sirf_inner_runtime_suspend(struct device *dev) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(dev); + u32 val; + val = readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + val &= ~(1 << sinner_audio->reg_bits->codec_clk_en_bits); + writel(val, sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + return 0; +} + +static int sirf_inner_runtime_resume(struct device *dev) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(dev); + u32 val; + val = readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + val |= (1 << sinner_audio->reg_bits->codec_clk_en_bits); + val &= ~(1 << sinner_audio->reg_bits->por_bits); + writel(val, sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + msleep(20); + val |= (1 << sinner_audio->reg_bits->por_bits); + writel(val, sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int sirf_soc_inner_suspend(struct device *dev) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(dev); + + sinner_audio->reg_ctrl0 = readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + sinner_audio->reg_ctrl1 = readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + sirf_inner_runtime_suspend(dev); + clk_disable_unprepare(sinner_audio->clk); + + return 0; +} + +static int sirf_soc_inner_resume(struct device *dev) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(sinner_audio->clk); + if (ret) + return ret; + + writel(sinner_audio->reg_ctrl0, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + writel(sinner_audio->reg_ctrl1, sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + if (!pm_runtime_status_suspended(dev)) + sirf_inner_runtime_resume(dev); + + return 0; +} +#endif + +static const struct dev_pm_ops sirf_inner_pm_ops = { + SET_RUNTIME_PM_OPS(sirf_inner_runtime_suspend, sirf_inner_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(sirf_soc_inner_suspend, sirf_soc_inner_resume) +}; + +static struct platform_driver sirf_soc_inner_driver = { + .driver = { + .name = "sirf-soc-inner", + .owner = THIS_MODULE, + .of_match_table = sirf_soc_inner_of_match, + .pm = &sirf_inner_pm_ops, + }, + .probe = sirf_soc_inner_probe, + .remove = sirf_soc_inner_remove, +}; + +module_platform_driver(sirf_soc_inner_driver); + +MODULE_DESCRIPTION("SiRF SoC inner bus and codec driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2");
On Fri, Jan 03, 2014 at 02:05:03PM +0800, RongJun Ying wrote:
@@ -6,5 +6,8 @@ config SND_SIRF_SOC config SND_SOC_SIRF_I2S tristate
+config SND_SIRF_SOC_INNER
- tristate
config SND_SOC_SIRF_USP
Everything else is SND_SOC_SIRF_, please keep this consistent.
+static struct sirf_soc_inner_audio_reg_bits sirf_soc_inner_audio_reg_bits_prima2 = {
- 20, 21, 22, 23, 24, 25, 26, 27,
+};
+static struct sirf_soc_inner_audio_reg_bits sirf_soc_inner_audio_reg_bits_atlas6 = {
- 22, 23, 24, 25, 26, 27, 28, 29,
+};
Please used named initialisers to set these values rather than just a list of numbers, this isn't very easy to read.
+static const struct snd_kcontrol_new sirf_inner_input_mode_control =
- SOC_DAPM_ENUM("Route", sirf_inner_enum[0]);
Either use constants to reference the array or (better) use named variables for each enum. This is more readable and less error prone.
+static struct snd_kcontrol_new volume_controls_atlas6[] = {
- SOC_DOUBLE("Playback Volume", AUDIO_IC_CODEC_CTRL0, 21, 14,
0x7F, 0),
- SOC_DOUBLE("Capture Volume", AUDIO_IC_CODEC_CTRL1, 16, 10,
0x3F, 0),
Please provide TLV information.
+static struct snd_kcontrol_new left_input_path_controls[] = {
- SOC_DAPM_SINGLE("Line left Switch", AUDIO_IC_CODEC_CTRL1, 6, 1, 0),
Line Left Switch.
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
/* Enable capture power of codec*/
val = sirfsoc_rtc_iobrg_readl(sinner_audio->sys_pwrc_reg_base +
PWRC_PDN_CTRL_OFFSET);
val |= (1 << AUDIO_POWER_EN_BIT);
sirfsoc_rtc_iobrg_writel(val,
sinner_audio->sys_pwrc_reg_base + PWRC_PDN_CTRL_OFFSET);
break;
- case SND_SOC_DAPM_POST_PMU:
/*After enable adc, Delay 200ms to avoid pop noise*/
msleep(200);
No need to split these operations, just have one callback. Why is there no power down callback?
+static int hp_amp_left_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
- struct snd_soc_codec *codec = w->codec;
- struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev);
- u32 val;
- val = snd_soc_read(codec, AUDIO_IC_CODEC_CTRL1);
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
val |= (1 << sinner_audio->reg_bits->firdac_hsl_en_bits);
break;
- case SND_SOC_DAPM_POST_PMD:
val &= ~(1 << sinner_audio->reg_bits->firdac_hsl_en_bits);
- default:
break;
- }
- snd_soc_write(codec, AUDIO_IC_CODEC_CTRL1, val);
- return 0;
snd_soc_update_bits(). Can this not be represented as multiple widgets - remember that DAPM will coalesce writes as much as it can.
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock(&sinner_audio->lock);
if (playback) {
The lock only gets used in the trigger function, is it needed?
+static struct snd_soc_codec_driver soc_codec_device_sirf_inner_codec = {
- .probe = sirf_inner_codec_probe,
- .remove = sirf_inner_codec_remove,
- .read = sirf_inner_codec_reg_read,
- .write = sirf_inner_codec_reg_write,
Don't use custom read and write callbacks, use a MMIO regmap.
From: Rongjun Ying Rongjun.Ying@csr.com
This connects DMA, CPU DAI and Codec DAI together and works as a mach driver.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com --- -v3: Use devm_gpio_request_one instead of gpio_request. Remove the extcon stuff code. Add binding document Use the devm_snd_soc_register_card instead of soc-audio device register
.../bindings/sound/sirf,inner-audio-codec.txt | 41 +++++ sound/soc/sirf/Kconfig | 5 + sound/soc/sirf/Makefile | 2 + sound/soc/sirf/sirf-inner.c | 155 ++++++++++++++++++++ 4 files changed, 203 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/sirf,inner-audio-codec.txt create mode 100644 sound/soc/sirf/sirf-inner.c
diff --git a/Documentation/devicetree/bindings/sound/sirf,inner-audio-codec.txt b/Documentation/devicetree/bindings/sound/sirf,inner-audio-codec.txt new file mode 100644 index 0000000..67393e3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sirf,inner-audio-codec.txt @@ -0,0 +1,41 @@ +* SiRF atlas6 and prima2 inner audio codec based audio setups + +Required properties: +- compatible: "sirf,sirf-inner" +- sirf,inner-platform: phandle for the platform node +- sirf,inner-codec: phandle for the SiRF inner codec node + +Optional properties: +- hp-pa-gpios: Need to be present if the board need control external + headphone amplifier. +- spk-pa-gpios: Need to be present if the board need control external + speaker amplifier. +- hp-switch-gpios: Need to be present if the board capable to detect jack + insertion, removal. + +Available audio endpoints for the audio-routing table: + +Board connectors: + * Headset Stereophone + * Ext Spk + * Line In + * Mic + +SiRF inner codec pins: + * HPOUTL + * HPOUTR + * SPKOUT + * Ext Mic + * Mic Bias + +Example: + +sound { + compatible = "sirf,sirf-inner"; + sirf,inner-codec = <&audio>; + sirf,inner-platform = <&audio>; + hp-pa-gpios = <&gpio 44 0>; + spk-pa-gpios = <&gpio 46 0>; + hp-switch-gpios = <&gpio 45 0>; +}; + diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index afa3952..564b0ec 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -11,3 +11,8 @@ config SND_SIRF_SOC_INNER
config SND_SOC_SIRF_USP tristate + +config SND_SIRF_INNER + tristate "SoC Audio support for SiRF inner codec of SiRF EVB" + depends on SND_SIRF_SOC + select SND_SIRF_SOC_INNER diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index 8517c67..80abdf6 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,9 +1,11 @@ snd-soc-sirf-objs := sirf-pcm.o +snd-soc-sirf-inner-objs := sirf-inner.o snd-soc-sirf-soc-inner-objs := sirf-soc-inner.o snd-soc-sirf-i2s-objs := sirf-i2s.o snd-soc-sirf-usp-objs := sirf-usp.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o +obj-$(CONFIG_SND_SIRF_INNER) += snd-soc-sirf-inner.o obj-$(CONFIG_SND_SIRF_SOC_INNER) += snd-soc-sirf-soc-inner.o obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-inner.c b/sound/soc/sirf/sirf-inner.c new file mode 100644 index 0000000..1a10c55 --- /dev/null +++ b/sound/soc/sirf/sirf-inner.c @@ -0,0 +1,155 @@ +/* + * SiRF inner audio device driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +struct sirf_inner_card { + unsigned int gpio_hp_pa; + unsigned int gpio_spk_pa; +}; + +static int sirf_inner_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *ctrl, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + int on = !SND_SOC_DAPM_EVENT_OFF(event); + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + gpio_set_value(sinner_card->gpio_hp_pa, on); + return 0; +} + +static int sirf_inner_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *ctrl, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + int on = !SND_SOC_DAPM_EVENT_OFF(event); + + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + gpio_set_value(sinner_card->gpio_spk_pa, on); + + return 0; +} +static const struct snd_soc_dapm_widget sirf_inner_dapm_widgets[] = { + SND_SOC_DAPM_HP("Hp", sirf_inner_hp_event), + SND_SOC_DAPM_SPK("Ext Spk", sirf_inner_spk_event), + SND_SOC_DAPM_MIC("Ext Mic", NULL), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"Hp", NULL, "HPOUTL"}, + {"Hp", NULL, "HPOUTR"}, + {"Ext Spk", NULL, "SPKOUT"}, + {"MICIN1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Ext Mic"}, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link sirf_inner_dai_links[] = { + { + .name = "SiRF inner", + .stream_name = "SiRF inner", + .codec_dai_name = "sirf-soc-inner", + .platform_name = "sirf-pcm-audio.1", + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_sirf_inner_card = { + .name = "SiRF inner", + .owner = THIS_MODULE, + .dai_link = sirf_inner_dai_links, + .num_links = ARRAY_SIZE(sirf_inner_dai_links), + .dapm_widgets = sirf_inner_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sirf_inner_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), +}; + +static int sirf_inner_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_sirf_inner_card; + struct sirf_inner_card *sinner_card; + int ret; + + sinner_card = devm_kzalloc(&pdev->dev, sizeof(struct sirf_inner_card), + GFP_KERNEL); + if (sinner_card == NULL) + return -ENOMEM; + + sirf_inner_dai_links[0].cpu_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,inner-platform", 0); + sirf_inner_dai_links[0].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,inner-codec", 0); + sinner_card->gpio_spk_pa = of_get_named_gpio(pdev->dev.of_node, + "spk-pa-gpios", 0); + sinner_card->gpio_hp_pa = of_get_named_gpio(pdev->dev.of_node, + "hp-pa-gpios", 0); + if (gpio_is_valid(sinner_card->gpio_spk_pa)) { + ret = devm_gpio_request_one(&pdev->dev, + sinner_card->gpio_spk_pa, + GPIOF_OUT_INIT_LOW, "SPA_PA_SD"); + if (ret) { + dev_err(&pdev->dev, + "Failed to request GPIO_%d for reset: %d\n", + sinner_card->gpio_spk_pa, ret); + return ret; + } + } + if (gpio_is_valid(sinner_card->gpio_hp_pa)) { + ret = devm_gpio_request_one(&pdev->dev, + sinner_card->gpio_hp_pa, + GPIOF_OUT_INIT_LOW, "HP_PA_SD"); + if (ret) { + dev_err(&pdev->dev, + "Failed to request GPIO_%d for reset: %d\n", + sinner_card->gpio_hp_pa, ret); + return ret; + } + } + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, sinner_card); + + 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 const struct of_device_id sirf_inner_of_match[] = { + {.compatible = "sirf,sirf-inner", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sirf_inner_of_match); + +static struct platform_driver sirf_inner_driver = { + .driver = { + .name = "sirf-inner", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = sirf_inner_of_match, + }, + .probe = sirf_inner_probe, +}; +module_platform_driver(sirf_inner_driver); + +MODULE_AUTHOR("RongJun Ying RongJun.Ying@csr.com"); +MODULE_DESCRIPTION("ALSA SoC SIRF inner AUDIO driver"); +MODULE_LICENSE("GPL v2");
On Fri, Jan 03, 2014 at 02:05:04PM +0800, RongJun Ying wrote:
- struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card);
I see where it comes from but "sinner" is a really odd abbrevation to use in a mostly English context.
Otherwise this looks fine.
participants (4)
-
Lars-Peter Clausen
-
Mark Brown
-
RongJun Ying
-
Rongjun Ying