* [PATCH v3 2/5] ASoC: sirf: add I2S CPU DAI driver
2014-01-03 6:04 [PATCH v3 0/5] ASoC: add CSR SiRFSoC sound drivers RongJun Ying
2014-01-03 6:05 ` [PATCH v3 1/5] ASoC: sirf: add sirf platform driver which provides DMA RongJun Ying
@ 2014-01-03 6:05 ` RongJun Ying
2014-01-05 11:52 ` Lars-Peter Clausen
2014-01-06 17:12 ` Mark Brown
2014-01-03 6:05 ` [PATCH v3 3/5] ASoC: usp-pcm: add CPU DAI driver for PCM simulated from USP RongJun Ying
` (2 subsequent siblings)
4 siblings, 2 replies; 15+ messages in thread
From: RongJun Ying @ 2014-01-03 6:05 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown
Cc: Takashi Iwai, Rongjun Ying, alsa-devel, Workgroup.linux
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");
--
1.7.5.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v3 3/5] ASoC: usp-pcm: add CPU DAI driver for PCM simulated from USP
2014-01-03 6:04 [PATCH v3 0/5] ASoC: add CSR SiRFSoC sound drivers RongJun Ying
2014-01-03 6:05 ` [PATCH v3 1/5] ASoC: sirf: add sirf platform driver which provides DMA RongJun Ying
2014-01-03 6:05 ` [PATCH v3 2/5] ASoC: sirf: add I2S CPU DAI driver RongJun Ying
@ 2014-01-03 6:05 ` RongJun Ying
2014-01-06 17:20 ` Mark Brown
2014-01-03 6:05 ` [PATCH v3 4/5] ASoC: sirf-soc-inner: add drivers for both CPU and Codec DAIs RongJun Ying
2014-01-03 6:05 ` [PATCH v3 5/5] ASoC: sirf-inner: add mach driver for SiRFSoC internal codec RongJun Ying
4 siblings, 1 reply; 15+ messages in thread
From: RongJun Ying @ 2014-01-03 6:05 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown
Cc: Takashi Iwai, Rongjun Ying, alsa-devel, Workgroup.linux
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
--
1.7.5.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v3 4/5] ASoC: sirf-soc-inner: add drivers for both CPU and Codec DAIs
2014-01-03 6:04 [PATCH v3 0/5] ASoC: add CSR SiRFSoC sound drivers RongJun Ying
` (2 preceding siblings ...)
2014-01-03 6:05 ` [PATCH v3 3/5] ASoC: usp-pcm: add CPU DAI driver for PCM simulated from USP RongJun Ying
@ 2014-01-03 6:05 ` RongJun Ying
2014-01-06 17:29 ` Mark Brown
2014-01-03 6:05 ` [PATCH v3 5/5] ASoC: sirf-inner: add mach driver for SiRFSoC internal codec RongJun Ying
4 siblings, 1 reply; 15+ messages in thread
From: RongJun Ying @ 2014-01-03 6:05 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown
Cc: Takashi Iwai, Rongjun Ying, alsa-devel, Workgroup.linux
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");
--
1.7.5.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v3 5/5] ASoC: sirf-inner: add mach driver for SiRFSoC internal codec
2014-01-03 6:04 [PATCH v3 0/5] ASoC: add CSR SiRFSoC sound drivers RongJun Ying
` (3 preceding siblings ...)
2014-01-03 6:05 ` [PATCH v3 4/5] ASoC: sirf-soc-inner: add drivers for both CPU and Codec DAIs RongJun Ying
@ 2014-01-03 6:05 ` RongJun Ying
2014-01-06 17:35 ` Mark Brown
4 siblings, 1 reply; 15+ messages in thread
From: RongJun Ying @ 2014-01-03 6:05 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown
Cc: Takashi Iwai, Rongjun Ying, alsa-devel, Workgroup.linux
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");
--
1.7.5.4
^ permalink raw reply related [flat|nested] 15+ messages in thread