[PATCH v2 2/6] ASoC: sirf: add I2S CPU DAI driver

Barry Song 21cnbao at gmail.com
Sun Oct 27 18:37:04 EDT 2013


From: Rongjun Ying <Rongjun.Ying at 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 at csr.com>
Signed-off-by: Barry Song <Baohua.Song at csr.com>
---
 -v2:
 runtime pm enabled;
 fix wrong device_reset path in v1;
 other minor fixes according to Mark's comments

 sound/soc/sirf/Kconfig      |   3 +
 sound/soc/sirf/Makefile     |   2 +
 sound/soc/sirf/sirf-audio.h | 268 +++++++++++++++++++++++++
 sound/soc/sirf/sirf-i2s.c   | 464 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 737 insertions(+)
 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..ae4102f
--- /dev/null
+++ b/sound/soc/sirf/sirf-i2s.c
@@ -0,0 +1,464 @@
+/*
+ * 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 <linux/reset.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;
+};
+
+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_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	pm_runtime_get_sync(dai->dev);
+	return 0;
+}
+
+static void sirf_i2s_shutdown(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	pm_runtime_put(dai->dev);
+}
+
+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 | I2S_MCLK_EN),
+				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 | I2S_MCLK_EN),
+				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 left_len, frame_len;
+	int channels = params_channels(params);
+
+	/*
+	 * 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);
+	writel(i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL);
+	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);
+	u32 i2s_ctrl, i2s_tx_rx_ctrl;
+
+	i2s_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL);
+	i2s_tx_rx_ctrl = readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		i2s_ctrl |= I2S_SLAVE_MODE;
+		i2s_tx_rx_ctrl &= ~I2S_MCLK_EN;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		i2s_ctrl &= ~I2S_SLAVE_MODE;
+		i2s_tx_rx_ctrl |= I2S_MCLK_EN;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(i2s_ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL);
+	writel(i2s_tx_rx_ctrl, si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		writel(readl(si2s->base + AUDIO_CTRL_MODE_SEL)
+			| I2S_MODE,
+			si2s->base + AUDIO_CTRL_MODE_SEL);
+		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 div)
+{
+	struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai);
+	u32 val;
+	u32 bclk_div_coefficient;
+
+	if (div < 2 || div % 2) {
+		dev_err(dai->dev, "BITCLK divider must greater than 1,"
+			"And must is a multiple of 2\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Calculate the divider coefficient of I2S reference
+	 * clock frequency divider.
+	 */
+	bclk_div_coefficient = div / 2 - 1;
+
+	if (bclk_div_coefficient >= (1 << 9)) {
+		dev_err(dai->dev, "The BITCLK divider(%d) must less than "
+			"%d.\n", div, (1 << 9) * 2);
+		return -EINVAL;
+	}
+
+	val = readl(si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
+	switch (div_id) {
+	case SIRF_I2S_EXT_CLK:
+		val |= I2S_REF_CLK_SEL_EXT;
+		break;
+	case SIRF_I2S_PWM_CLK:
+		val &= ~I2S_REF_CLK_SEL_EXT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(val, si2s->base + AUDIO_CTRL_I2S_TX_RX_EN);
+
+	val = readl(si2s->base + AUDIO_CTRL_I2S_CTRL);
+	val |= (bclk_div_coefficient << 24);
+	/*
+	 * MCLK coefficient must set to 0, means
+	 * divide-by-two from reference clock.
+	 */
+	val &= ~(((1 << 10) - 1) << 15);
+	writel(val, si2s->base + AUDIO_CTRL_I2S_CTRL);
+
+	return 0;
+}
+
+struct snd_soc_dai_ops sirfsoc_i2s_dai_ops = {
+	.startup	= sirf_i2s_startup,
+	.shutdown	= sirf_i2s_shutdown,
+	.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);
+	clk_prepare_enable(si2s->clk);
+	device_reset(dev);
+	return 0;
+}
+#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);
+	if (!pm_runtime_status_suspended(dev)) {
+		sirf_i2s_runtime_resume(dev);
+		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;
+	u32 rx_dma_ch, tx_dma_ch;
+	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);
+
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"sirf,i2s-dma-rx-channel", &rx_dma_ch);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Unable to USP0 rx dma channel\n");
+		return ret;
+	}
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"sirf,i2s-dma-tx-channel", &tx_dma_ch);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Unable to USP0 tx dma channel\n");
+		return ret;
+	}
+
+	dma_data[0].filter_data = (void *)tx_dma_ch;
+	dma_data[1].filter_data = (void *)rx_dma_ch;
+
+	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;
+	}
+
+	ret = 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;
+	}
+
+	pm_runtime_enable(&pdev->dev);
+	return 0;
+
+err:
+	return ret;
+}
+
+static int sirf_i2s_remove(struct platform_device *pdev)
+{
+	struct sirf_i2s *si2s = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_component(&pdev->dev);
+	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 at csr.com>");
+MODULE_LICENSE("GPL v2");
-- 
1.8.2.3




More information about the linux-arm-kernel mailing list