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 Signed-off-by: Barry Song Baohua.Song@csr.com --- sound/soc/sirf/Kconfig | 3 + sound/soc/sirf/Makefile | 2 + sound/soc/sirf/sirf-audio.h | 266 ++++++++++++++++++++++++++++ sound/soc/sirf/sirf-i2s.c | 411 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 682 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 3678aed..d98acd4 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_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..514c815 --- /dev/null +++ b/sound/soc/sirf/sirf-audio.h @@ -0,0 +1,266 @@ +/* + * 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 + +#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..d0f81f9 --- /dev/null +++ b/sound/soc/sirf/sirf-i2s.c @@ -0,0 +1,411 @@ +/* + * 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/clk.h> +#include <linux/pwm.h> +#include <linux/delay.h> +#include <linux/reset.h> + +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "sirf-pcm.h" +#include "sirf-audio.h" + +struct sirf_i2s { + void __iomem *base; + struct clk *clk; + struct pwm_device *mclk_pwm; + u32 i2s_ctrl; + spinlock_t lock; + int master_mode; +}; + +static struct sirf_pcm_dma_data sirf_i2s_dai_dma_data[2] = { + { + .name = "Audio Playback", + }, { + .name = "Audio Capture", + } +}; + +static int sirf_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + + if (si2s->master_mode) + pwm_enable(si2s->mclk_pwm); + clk_prepare_enable(si2s->clk); + + device_reset(dai->dev); + snd_soc_dai_set_dma_data(dai, substream, + &sirf_i2s_dai_dma_data[substream->stream]); + return 0; +} + +static void sirf_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + if (si2s->master_mode) + pwm_disable(si2s->mclk_pwm); + clk_disable_unprepare(si2s->clk); +} + +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); + mdelay(1); + writel(AUDIO_FIFO_START, + si2s->base + AUDIO_CTRL_EXT_TXFIFO1_OP); + mdelay(1); + + writel(readl(si2s->base+AUDIO_CTRL_I2S_TX_RX_EN) + | I2S_TX_ENABLE | I2S_DOUT_OE | + (si2s->master_mode == 1 ? I2S_MCLK_EN : 0), + 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); + mdelay(1); + writel(AUDIO_FIFO_START, + si2s->base + AUDIO_CTRL_RXFIFO_OP); + mdelay(1); + + writel(readl(si2s->base+AUDIO_CTRL_I2S_TX_RX_EN) + | I2S_RX_ENABLE | + (si2s->master_mode == 1 ? I2S_MCLK_EN : 0), + 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_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sirf_i2s *si2s = snd_soc_dai_get_drvdata(dai); + u32 ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL); + + if (runtime->channels == 2) + ctrl &= ~I2S_SIX_CHANNELS; + else + ctrl |= I2S_SIX_CHANNELS; + + writel(ctrl, si2s->base+AUDIO_CTRL_I2S_CTRL); + + 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; + + 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 ctrl; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl = readl(si2s->base + AUDIO_CTRL_I2S_CTRL); + ctrl |= I2S_SLAVE_MODE; + writel(ctrl, si2s->base + AUDIO_CTRL_I2S_CTRL); + si2s->master_mode = 0; + break; + case SND_SOC_DAIFMT_CBS_CFS: + si2s->master_mode = 1; + return -EINVAL; + default: + return -EINVAL; + } + + /* 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; +} + +struct snd_soc_dai_ops sirfsoc_i2s_dai_ops = { + .startup = sirf_i2s_startup, + .shutdown = sirf_i2s_shutdown, + .trigger = sirf_i2s_trigger, + .prepare = sirf_i2s_prepare, + .hw_params = sirf_i2s_hw_params, + .set_fmt = sirf_i2s_set_dai_fmt, +}; + +static struct snd_soc_dai_driver sirf_i2s_dai = { + .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 +static int sirf_i2s_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct sirf_i2s *si2s = platform_get_drvdata(pdev); + + si2s->i2s_ctrl = readl(si2s->base+AUDIO_CTRL_I2S_CTRL); + + clk_disable_unprepare(si2s->clk); + + if (si2s->master_mode) + pwm_disable(si2s->mclk_pwm); + return 0; +} + +static int sirf_i2s_resume(struct platform_device *pdev) +{ + struct sirf_i2s *si2s = platform_get_drvdata(pdev); + if (si2s->master_mode) + pwm_enable(si2s->mclk_pwm); + clk_prepare_enable(si2s->clk); + + device_reset(&pdev->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); + writel(0, si2s->base + AUDIO_CTRL_EXT_TXFIFO1_INT_MSK); + writel(0, si2s->base + AUDIO_CTRL_RXFIFO_INT_MSK); + + return 0; +} +#else +#define sirf_usp_pcm_suspend NULL +#define sirf_usp_pcm_resume NULL +#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; + + 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; + } + sirf_i2s_dai_dma_data[0].dma_req = tx_dma_ch; + sirf_i2s_dai_dma_data[1].dma_req = 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; + } + + /* i2s bus uses an internal PWM to generate MCLK */ + si2s->mclk_pwm = devm_pwm_get(&pdev->dev, NULL); + if (IS_ERR(si2s->mclk_pwm)) { + dev_err(&pdev->dev, "unable to request PWM\n"); + ret = PTR_ERR(si2s->mclk_pwm); + goto err_clk_put; + } + + 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_clk_put; + } + + return 0; + +err_clk_put: + clk_disable_unprepare(si2s->clk); +err: + return ret; +} + +static int sirf_i2s_remove(struct platform_device *pdev) +{ + struct sirf_i2s *si2s = platform_get_drvdata(pdev); + + pwm_disable(si2s->mclk_pwm); + snd_soc_unregister_component(&pdev->dev); + clk_disable_unprepare(si2s->clk); + + 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 struct platform_driver sirf_i2s_driver = { + .driver = { + .name = "sirf-i2s", + .owner = THIS_MODULE, + .of_match_table = sirf_i2s_of_match, + }, + .probe = sirf_i2s_probe, + .remove = sirf_i2s_remove, + .suspend = sirf_i2s_suspend, + .resume = sirf_i2s_resume, +}; + +module_platform_driver(sirf_i2s_driver); + +MODULE_DESCRIPTION("SiRF SoC I2S driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2");