diff options
Diffstat (limited to 'sound/soc/sprd/dai/i2s')
| -rw-r--r-- | sound/soc/sprd/dai/i2s/Kconfig | 2 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/i2s/Makefile | 6 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/i2s/i2s.c | 1441 |
3 files changed, 1449 insertions, 0 deletions
diff --git a/sound/soc/sprd/dai/i2s/Kconfig b/sound/soc/sprd/dai/i2s/Kconfig new file mode 100644 index 00000000..76da44c9 --- /dev/null +++ b/sound/soc/sprd/dai/i2s/Kconfig @@ -0,0 +1,2 @@ +config SND_SOC_SPRD_I2S + tristate #"I2S" diff --git a/sound/soc/sprd/dai/i2s/Makefile b/sound/soc/sprd/dai/i2s/Makefile new file mode 100644 index 00000000..c6d03d85 --- /dev/null +++ b/sound/soc/sprd/dai/i2s/Makefile @@ -0,0 +1,6 @@ +# SPRD I2S Support +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai + +snd-soc-sprd-i2s-objs := i2s.o +obj-$(CONFIG_SND_SOC_SPRD_I2S) += snd-soc-sprd-i2s.o diff --git a/sound/soc/sprd/dai/i2s/i2s.c b/sound/soc/sprd/dai/i2s/i2s.c new file mode 100644 index 00000000..a29819a9 --- /dev/null +++ b/sound/soc/sprd/dai/i2s/i2s.c @@ -0,0 +1,1441 @@ +/* + * sound/soc/sprd/dai/i2s/i2s.c + * + * SPRD SoC CPU-DAI -- SpreadTrum SOC DAI i2s. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" I2S ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include <soc/sprd/hardware.h> + +#include "sprd-asoc-common.h" +#ifndef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE +#include "sprd-pcm.h" +#else +#include "sprd-dmaengine-pcm.h" +#endif +#include <soc/sprd/i2s.h> + +/* register offset */ +#define IIS_TXD (0x0000) +#define IIS_CLKD (0x0004) +#define IIS_CTRL0 (0x0008) +#define IIS_CTRL1 (0x000C) +#define IIS_CTRL2 (0x0010) +#define IIS_CTRL3 (0x0014) +#define IIS_INT_IEN (0x0018) +#define IIS_INT_CLR (0x001C) +#define IIS_INT_RAW (0x0020) +#define IIS_INT_STS (0x0024) +#define IIS_STS1 (0x0028) +#define IIS_STS2 (0x002C) +#define IIS_STS3 (0x0030) +#define IIS_DSPWAIT (0x0034) +#define IIS_CTRL4 (0x0038) +#define IIS_STS4 (0x003C) + +#define I2S_REG(i2s, offset) ((unsigned long)((i2s)->membase + (offset))) +#define I2S_PHY_REG(i2s, offset) (((unsigned int)(i2s)->memphys + (offset))) + +static unsigned int membase = 0; +static struct i2s_config *dup_config = NULL; + +#define CMD_BUFFER_LENGTH 64 + + +struct i2s_rtx { + int dma_no; +}; +#define DAI_NAME_SIZE 20 +struct i2s_priv { + int id; + int hw_port; + struct device *dev; + struct list_head list; + struct i2s_rtx rx; + struct i2s_rtx tx; + atomic_t open_cnt; + char dai_name[DAI_NAME_SIZE]; + int irq_no; + void __iomem *membase; + unsigned int *memphys; + unsigned int reg_size; + struct i2s_config config; + struct clk *i2s_clk; + struct snd_soc_dai_driver i2s_dai_driver[2]; + int i2s_type; +}; + +static struct sprd_pcm_dma_params i2s_pcm_stereo_out = { + .name = "I2S PCM Stereo out", + .irq_type = BLK_DONE, + .desc = { + .datawidth = WORD_WIDTH, + .src_step = 4, + .des_step = 0, + }, +}; + +static struct sprd_pcm_dma_params i2s_pcm_stereo_in = { + .name = "I2S PCM Stereo in", + .irq_type = BLK_DONE, + .desc = { + .datawidth = WORD_WIDTH, + .src_step = 0, + .des_step = 4, + }, +}; + +/* default pcm config */ +const static struct i2s_config def_pcm_config = { + .hw_port = 0, + .fs = 8000, + .slave_timeout = 0xF11, + .bus_type = PCM_BUS, + .byte_per_chan = I2S_BPCH_16, + .mode = I2S_MASTER, + .lsb = I2S_MSB, + .rtx_mode = I2S_RTX_MODE, + .sync_mode = I2S_SYNC,// I2S_SYNC better! + .lrck_inv = I2S_L_LEFT, + .clk_inv = I2S_CLK_N, + .pcm_bus_mode = I2S_SHORT_FRAME, + .pcm_slot = 0x1, + .pcm_cycle = 1, + .tx_watermark = 12, + .rx_watermark = 20, +}; + +/* default i2s config */ +const static struct i2s_config def_i2s_config = { + .hw_port = 0, + .fs = 32000, + .slave_timeout = 0xF11, + .bus_type = I2S_BUS, + .byte_per_chan = I2S_BPCH_16, + .mode = I2S_SLAVE, + .lsb = I2S_MSB, + .rtx_mode = I2S_RX_MODE, + .sync_mode = I2S_LRCK, + .lrck_inv = I2S_L_LEFT, + .clk_inv = I2S_CLK_N, + .i2s_bus_mode = I2S_MSBJUSTFIED, + .tx_watermark = 12, + .rx_watermark = 20, +}; + +static DEFINE_SPINLOCK(i2s_lock); + +static inline int i2s_reg_read(unsigned long reg) +{ + return __raw_readl((void *__iomem)reg); +} + +static inline void i2s_reg_raw_write(unsigned long reg, int val) +{ + __raw_writel(val, (void *__iomem)reg); +} + +#if 0 +static int i2s_reg_write(unsigned int reg, int val) +{ + spin_lock(&i2s_lock); + i2s_reg_raw_write(reg, val); + spin_unlock(&i2s_lock); + sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val, + i2s_reg_read(reg)); + return 0; +} +#endif + + +static inline struct i2s_dai *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +/* + * Returns 1 for change, 0 for no change, or negative error code. + */ +static int i2s_reg_update(unsigned long reg, int val, int mask) +{ + int new, old; + spin_lock(&i2s_lock); + old = i2s_reg_read(reg); + new = (old & ~mask) | (val & mask); + i2s_reg_raw_write(reg, new); + spin_unlock(&i2s_lock); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new, + i2s_reg_read(reg)); + return old != new; +} + +static int i2s_global_disable(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s\n", __func__); + return arch_audio_i2s_disable(i2s->hw_port); +} + +static int i2s_global_enable(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s\n", __func__); + arch_audio_i2s_enable(i2s->hw_port); + return arch_audio_i2s_switch(i2s->hw_port, AUDIO_TO_ARM_CTRL); +} + +static int i2s_soft_reset(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s\n", __func__); + return arch_audio_i2s_reset(i2s->hw_port); +} + +static int i2s_calc_clk(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int bit_clk; + int cycle; + int source_clk; + int bit; + int val; + struct clk *clk_parent; + switch (config->fs) { + case 8000: + case 16000: + case 32000: + clk_parent = clk_get(NULL, "clk_128m"); + break; + case 9600: + case 12000: + case 24000: + case 48000: + clk_parent = clk_get(NULL, "clk_76m8"); + break; + default: + pr_err("ERR:I2S Can't Support %d Clock\n", config->fs); + return -ENOTSUPP; + } + + if (IS_ERR(clk_parent)) { + int ret = PTR_ERR(clk_parent); + pr_err("ERR:I2S Get Clock Source Error %d!\n", ret); + return ret; + } + clk_set_parent(i2s->i2s_clk, clk_parent); + source_clk = clk_get_rate(clk_parent); + clk_set_rate(i2s->i2s_clk, source_clk); + clk_put(clk_parent); + + sp_asoc_pr_dbg("I2S Source Clock is %d HZ\n", source_clk); + cycle = (PCM_BUS == config->bus_type) ? (config->pcm_cycle + 1) : 2; + bit = 8 << config->byte_per_chan; + bit_clk = config->fs * cycle * bit; + sp_asoc_pr_dbg("I2S BIT Clock is %d HZ\n", bit_clk); + if (source_clk % (bit_clk << 1)) + return -ENOTSUPP; + val = (source_clk / bit_clk) >> 1; + --val; + return val; +} + +static int i2s_set_clkd(struct i2s_priv *i2s) +{ + int shift = 0; + int mask = 0xFFFF << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CLKD); + sp_asoc_pr_dbg("%s\n", __func__); + val = i2s_calc_clk(i2s); + if (val < 0) + return val; + i2s_reg_update(reg, val, mask); + return 0; +} + +static int i2s_set_clk_m_n(struct i2s_priv *i2s) +{ + /* This will support futher */ + return -ENOTSUPP; +} + +static void i2s_set_bus_type(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(15); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (PCM_BUS == config->bus_type) ? mask : 0, mask); +} + +static void i2s_set_byte_per_channal(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 4; + int mask = 0x3 << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->byte_per_chan; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(3); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s mode %d\n", __func__, config->mode); + i2s_reg_update(reg, (I2S_SLAVE == config->mode) ? mask : 0, mask); +} + +static void i2s_set_lsb(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(2); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_LSB == config->lsb) ? mask : 0, mask); +} + +static void i2s_set_rtx_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 6; + int mask = 0x3 << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->rtx_mode; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_sync_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(9); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_SYNC == config->sync_mode) ? mask : 0, mask); +} + +static void i2s_set_lrck_invert(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(10); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_L_RIGTH == config->lrck_inv) ? mask : 0, mask); +} + +static void i2s_set_clk_invert(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(11); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_CLK_R == config->clk_inv) ? mask : 0, mask); +} + +static void i2s_set_i2s_bus_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(8); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_COMPATIBLE == config->i2s_bus_mode) ? mask : 0, + mask); +} + +static void i2s_set_pcm_bus_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(8); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, + (I2S_SHORT_FRAME == config->pcm_bus_mode) ? mask : 0, + mask); +} + +static void i2s_set_pcm_slot(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0x7 << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL2); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->pcm_slot; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_pcm_cycle(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 3; + int mask = 0x7F << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL2); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->pcm_cycle; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_rx_watermark(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0x1F1F << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL3); + sp_asoc_pr_dbg("%s\n", __func__); + /* full watermark */ + val = config->rx_watermark; + /* empty watermark */ + val |= (I2S_FIFO_DEPTH - config->rx_watermark) << 8; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_tx_watermark(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0x1F1F << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL4); + sp_asoc_pr_dbg("%s\n", __func__); + /* empty watermark */ + val = config->tx_watermark << 8; + /* full watermark */ + val |= I2S_FIFO_DEPTH - config->tx_watermark; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_slave_timeout(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0xFFF << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL1); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->slave_timeout; + i2s_reg_update(reg, val << shift, mask); +} + +static int i2s_get_dma_data_width(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + if (PCM_BUS == config->bus_type) { + if (config->byte_per_chan == I2S_BPCH_16) { + return SHORT_WIDTH; + } + } + return WORD_WIDTH; +} + +static int i2s_get_dma_step(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + if (PCM_BUS == config->bus_type) { + if (config->byte_per_chan == I2S_BPCH_16) { + return 2; + } + } + return 4; +} + +static int i2s_get_data_position(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + if (PCM_BUS == config->bus_type) { + if (config->byte_per_chan == I2S_BPCH_16) { + if (I2S_SLAVE == config->mode) { + return 2; + } + } + } + return 0; +} + +static int i2s_config_rate(struct i2s_priv *i2s) +{ + int ret = 0; + struct i2s_config *config = &i2s->config; + if (I2S_MASTER == config->mode) { + ret = i2s_set_clkd(i2s); + } + if (ret) { + ret = i2s_set_clk_m_n(i2s); + } + return ret; +} + +static int i2s_config_apply(struct i2s_priv *i2s) +{ + int ret = 0; + struct i2s_config *config = &i2s->config; + sp_asoc_pr_dbg("%s\n", __func__); + + if (I2S_BPCH_8 == config->byte_per_chan) { + pr_err("ERR:The I2S can't Support 8byte Mode in this code\n"); + ret = -ENOTSUPP; + } + + i2s_set_bus_type(i2s); + i2s_set_mode(i2s); + i2s_set_byte_per_channal(i2s); + i2s_set_rtx_mode(i2s); + i2s_set_sync_mode(i2s); + i2s_set_lsb(i2s); + i2s_set_lrck_invert(i2s); + i2s_set_clk_invert(i2s); + i2s_set_rx_watermark(i2s); + i2s_set_tx_watermark(i2s); + if (I2S_SLAVE == config->mode) { + i2s_set_slave_timeout(i2s); + } + if (I2S_BUS == config->bus_type) { + i2s_set_i2s_bus_mode(i2s); + } + if (PCM_BUS == config->bus_type) { + i2s_set_pcm_bus_mode(i2s); + i2s_set_pcm_slot(i2s); + i2s_set_pcm_cycle(i2s); + } + + ret = i2s_config_rate(i2s); + + return ret; +} + +static void i2s_dma_ctrl(struct i2s_priv *i2s, int enable) +{ + int mask = BIT(14); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s Enable = %d\n", __func__, enable); + if (!enable) { + if (atomic_read(&i2s->open_cnt) <= 0) { + i2s_reg_update(reg, 0, mask); + } + } else { + i2s_reg_update(reg, mask, mask); + } +} + +static int i2s_close(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, atomic_read(&i2s->open_cnt)); + if (atomic_dec_and_test(&i2s->open_cnt)) { + i2s_soft_reset(i2s); + i2s_global_disable(i2s); + if (!IS_ERR(i2s->i2s_clk)) { + clk_disable_unprepare(i2s->i2s_clk); + clk_put(i2s->i2s_clk); + } + } + return 0; +} + +static int i2s_open(struct i2s_priv *i2s) +{ + int ret = 0; + + sp_asoc_pr_dbg("%s %d\n", __func__, atomic_read(&i2s->open_cnt)); + + atomic_inc(&i2s->open_cnt); + if (atomic_read(&i2s->open_cnt) == 1) { + i2s->i2s_clk = + clk_get(NULL, arch_audio_i2s_clk_name(i2s->hw_port)); + if (IS_ERR(i2s->i2s_clk)) { + ret = PTR_ERR(i2s->i2s_clk); + pr_err("ERR:I2S Get clk Error %d!\n", ret); + return ret; + } + i2s_global_enable(i2s); + i2s_soft_reset(i2s); + i2s_dma_ctrl(i2s, 0); + ret = i2s_config_apply(i2s); + clk_prepare_enable(i2s->i2s_clk); + } + + return ret; +} + +static int i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct i2s_priv *i2s = to_info(dai); + printk("i2s->id %d \n",i2s->id); + dai->ac97_pdata = &i2s->config; + ret = i2s_open(i2s); + if (ret < 0) { + pr_err("ERR:I2S Open Error!\n"); + return ret; + } + + return 0; +} + +static void i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_config *config = dai->ac97_pdata; + struct i2s_priv *i2s = container_of(config, struct i2s_priv, config); + + sp_asoc_pr_dbg("%s\n", __func__); + + i2s_close(i2s); +} + +static int i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct sprd_pcm_dma_params *dma_data; + struct i2s_config *config = dai->ac97_pdata; + struct i2s_priv *i2s = container_of(config, struct i2s_priv, config); + + sp_asoc_pr_dbg("%s Port %d\n", __func__, i2s->hw_port); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data = &i2s_pcm_stereo_out; + dma_data->channels[0] = i2s->tx.dma_no; + } else { + dma_data = &i2s_pcm_stereo_in; + dma_data->channels[0] = i2s->rx.dma_no; + } + dma_data->desc.datawidth = i2s_get_dma_data_width(i2s); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data->desc.src_step = i2s_get_dma_step(i2s); + else + dma_data->desc.des_step = i2s_get_dma_step(i2s); + + dma_data->dev_paddr[0] = + I2S_PHY_REG(i2s, IIS_TXD) + i2s_get_data_position(i2s); + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + i2s->config.fs = params_rate(params); + ret = i2s_config_rate(i2s); + if (ret) { + return ret; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + ret = -ENOTSUPP; + pr_err("ERR:I2S Only Supports format S16_LE now!\n"); + break; + } + + if (params_channels(params) > 2) { + ret = -ENOTSUPP; + pr_err("ERR:I2S Can not Supports Grate 2 Channels\n"); + } + + return ret; +} + +static int i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct i2s_config *config = dai->ac97_pdata; + struct i2s_priv *i2s = container_of(config, struct i2s_priv, config); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + i2s_dma_ctrl(i2s, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + i2s_dma_ctrl(i2s, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct snd_soc_dai_ops sprd_i2s_dai_ops = { + .startup = i2s_startup, + .shutdown = i2s_shutdown, + .hw_params = i2s_hw_params, + .trigger = i2s_trigger, +}; +static void i2s_config_setting(int index,int value,struct i2s_config *config) +{ + pr_debug("i2s_config_setting i2s index %d,value %d \n",index,value); + if (config == NULL) { + pr_err("i2s_config_setting config is NULL,error! \n"); + return; + } + switch (index) { + case FS: + if (value >= SAMPLATE_MIN && value <= SAMPLATE_MAX) + config->fs = value; + break; + case HW_PROT: + if (value >= 0 && value <= 3) + config->hw_port = value; + break; + case SLAVE_TIMEOUT: + config->slave_timeout = value; + break; + case BUS_TYPE: + if (value == I2S_BUS || value == PCM_BUS) + config->bus_type = value; + break; + case BYTE_PER_CHAN: + if (value == I2S_BPCH_8 || value == I2S_BPCH_16 || value == I2S_BPCH_32) + config->byte_per_chan = value; + break; + case MODE: + if (value == I2S_MASTER || value == I2S_SLAVE) + config->mode = value; + break; + case LSB: + if (value == I2S_MSB || value == I2S_LSB) + config->lsb = value; + break; + case TRX_MODE: + if (value >= I2S_RTX_DIS && value <= I2S_RTX_MODE) + config->rtx_mode = value; + break; + case LRCK_INV: + if (value == I2S_L_LEFT || value == I2S_L_RIGTH) + config->lrck_inv = value; + break; + case SYNC_MODE: + if (value == I2S_SYNC || value == I2S_LRCK) + config->sync_mode = value; + break; + case CLK_INV: + if (value == I2S_CLK_N || value == I2S_CLK_R) + config->clk_inv = value; + break; + case I2S_BUS_MODE: + if (value == I2S_MSBJUSTFIED || value == I2S_COMPATIBLE) + config->i2s_bus_mode = value; + break; + case PCM_BUS_MODE: + if (value == I2S_SHORT_FRAME || value == I2S_LONG_FRAME) + config->pcm_bus_mode = value; + break; + case PCM_SLOT: + if (value >= 1 && value <= 4) + config->pcm_slot = value; + break; + case PCM_CYCLE: + if (value >= 0 && value <= 127) + config->pcm_cycle = value; + break; + case TX_WATERMARK: + if (value >= 0 && value <= I2S_FIFO_DEPTH) + config->tx_watermark = value; + break; + case RX_WATERMARK: + if (value >= 0 && value <= I2S_FIFO_DEPTH) + config->rx_watermark = value; + break; + default: + pr_err("i2s echo cmd is invalide \n"); + break; + } +} + +static int i2s_config_getting(int index,struct i2s_config *config) +{ + pr_debug("i2s_config_getting i2s index %d\n",index); + if (config == NULL) { + pr_err("i2s_config_getting config is NULL,error\n"); + return -1; + } + switch (index) { + case FS: + return config->fs; + case HW_PROT: + return config->hw_port; + case SLAVE_TIMEOUT: + return config->slave_timeout; + case BUS_TYPE: + return config->bus_type; + case BYTE_PER_CHAN: + return config->byte_per_chan; + case MODE: + return config->mode; + case LSB: + return config->lsb; + case TRX_MODE: + return config->rtx_mode; + case LRCK_INV: + return config->lrck_inv; + case SYNC_MODE: + return config->sync_mode; + case CLK_INV: + return config->clk_inv; + case I2S_BUS_MODE: + return config->i2s_bus_mode; + case PCM_BUS_MODE: + return config->pcm_bus_mode; + case PCM_SLOT: + return config->pcm_slot; + case PCM_CYCLE: + return config->pcm_cycle; + case TX_WATERMARK: + return config->tx_watermark; + case RX_WATERMARK: + return config->rx_watermark; + default: + pr_err("i2s config index is invalide \n"); + return -1; + } +} + +static int get_index(char *line_first,char *line_end) +{ + char *line_index = line_first; + char *line_first_dummy = line_first; + if (line_first == NULL || line_end == NULL) + return -1; + for (line_first;line_first < line_end;line_first++) { + if (*line_first >= '0' && *line_first <= '9') { + *line_index = *line_first; + line_index++; + } + } + *line_index = '\0'; + return simple_strtoul(line_first_dummy, NULL, 10); +} + +static int get_index_value(char *line_first) +{ + char *line_index_value = line_first; + char *line_first_dummy = line_first; + if (line_first == NULL) + return -1; + pr_debug("i2s get_index_value %s\n",line_first); + for (line_first;;line_first++) { + if (*line_first == '\0') + break; + if (*line_first >= '0' && *line_first <= '9') { + *line_index_value = *line_first; + line_index_value ++; + } + } + *line_index_value = '\0'; + return simple_strtoul(line_first_dummy, NULL, 10); +} + +void i2s_debug_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int index; + int index_value; + char *line = NULL; + char *sym_eq = NULL; + if (dup_config == NULL) { + pr_err("i2s_debug_write failed! \n"); + return; + } + line = kzalloc(CMD_BUFFER_LENGTH,GFP_KERNEL); + if (!line) { + pr_err("malloc line buffer failed! \n"); + return; + } + if (!snd_info_get_line(buffer, line, CMD_BUFFER_LENGTH)) { + pr_debug("i2s: input line %s\n",line); + sym_eq = strchr(line,'='); + if (sym_eq != NULL) { + index = get_index(line,sym_eq); + pr_debug("i2s: index %d\n",index); + index_value = get_index_value(++sym_eq); + pr_debug("i2s: index_value %d \n",index_value); + if (index >= 0 && index_value >= 0) + i2s_config_setting(index,index_value,dup_config); + } else if (strstr(line,"pcm")) { + memcpy(dup_config,&def_pcm_config,sizeof(def_pcm_config)); + } else if (strstr(line,"i2s")) { + memcpy(dup_config,&def_i2s_config,sizeof(def_i2s_config)); + } else { + pr_err("i2s:echo str fmt not right \n"); + } + } else { + pr_err("i2s echo error \n"); + } + kfree(line); + return; +} + +void i2s_debug_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + if (dup_config == NULL) { + snd_iprintf(buffer, "\n\n dup_config is NUll,error!\n"); + return; + } + snd_iprintf(buffer, "\n\n using examples :\n"); + snd_iprintf(buffer, " 1)echo 0=33 > i2s-debug \n"); + snd_iprintf(buffer, " 2)echo i2s > i2s-debug \n"); + snd_iprintf(buffer, " 3)echo pcm >i2s-debug \n\n"); + snd_iprintf(buffer, " 0 fs 0d:%d\n 1 hw_port 0d:%d\n 2 slave_timeout 0x%x\n", + dup_config->fs,dup_config->hw_port,dup_config->slave_timeout); + snd_iprintf(buffer, " 3 bus_type(****tip:bus_type 0 is iis,1 is pcm****) 0x%x\n 4 byte_per_chan 0x%x\n", + dup_config->bus_type,dup_config->byte_per_chan); + snd_iprintf(buffer, " 5 mode(****tip:mode 0 is master,1 is slave****) 0x%x\n 6 lsb 0x%x\n", + dup_config->mode,dup_config->lsb); + snd_iprintf(buffer, " 7 rtx_mode 0x%x\n 8 lrck_inv 0x%x\n", + dup_config->rtx_mode,dup_config->lrck_inv); + snd_iprintf(buffer, " 9 sync_mode 0x%x\n 10 clk_inv 0x%x\n", + dup_config->sync_mode,dup_config->clk_inv); + snd_iprintf(buffer, " 11 i2s_bus_mode 0x%x\n 12 pcm_bus_mode 0x%x\n", + dup_config->i2s_bus_mode,dup_config->pcm_bus_mode); + snd_iprintf(buffer, " 13 pcm_slot 0x%x\n 14 pcm_cycle 0x%x\n", + dup_config->pcm_slot,dup_config->pcm_cycle); + snd_iprintf(buffer, " 15 tx_watermark 0d:%d\n 16 rx_watermark 0d:%d\n\n", + dup_config->tx_watermark,dup_config->rx_watermark); +} + +static inline int iis_reg_read(unsigned int reg) +{ + return __raw_readl((void *__iomem)reg); +} + +void i2s_register_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int reg; + if (membase == 0) { + snd_iprintf(buffer,"\n\n i2s membase is NULL \n"); + return; + } + pr_debug("i2s membase 0x%x \n",membase); + snd_iprintf(buffer, "i2s register dump\n"); + for (reg = IIS_TXD + membase; reg <= IIS_STS4 + membase; reg += 0x10) { + snd_iprintf(buffer, "0x%08x | 0x%08x 0x%08x 0x%08x 0x%08x\n", + (reg - IIS_TXD -membase) + , iis_reg_read(reg + 0x00) + , iis_reg_read(reg + 0x04) + , iis_reg_read(reg + 0x08) + , iis_reg_read(reg + 0x0C) + ); + } +} + +int i2s_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + if (dup_config == NULL) { + pr_err("i2s_config_get return \n"); + return 0; + } + ucontrol->value.integer.value[0] = i2s_config_getting(id,dup_config); + pr_debug("i2s_config_get return value %d,id %d\n",ucontrol->value.integer.value[0],id); + return 0; +} + +int i2s_config_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + int id = FUN_REG(mc->reg); + if (dup_config == NULL) { + pr_err("i2s_config_set return \n"); + return 0; + } + i2s_config_setting(id, ucontrol->value.integer.value[0],dup_config); + return 0; +} + +static const struct snd_soc_component_driver sprd_i2s_component = { + .name = "i2s", +}; + +static int i2s_config_from_node(struct device_node *node, struct i2s_priv *i2s) +{ + if (!of_find_property(node, "sprd,config", NULL)) { + i2s->config = def_i2s_config; + sp_asoc_pr_dbg("Use I2S default config!\n"); + } else { + u32 val; + struct device_node *config_node; + config_node = of_parse_phandle(node, "sprd,config", 0); + if (of_find_property(config_node, "sprd,def_pcm_config", NULL)) { + i2s->config = def_pcm_config; + sp_asoc_pr_dbg("Use PCM default config!\n"); + } else { + i2s->config = def_i2s_config; + sp_asoc_pr_dbg("Use I2S default config!\n"); + } + + if (!of_property_read_u32 + (config_node, "sprd,_hw_port", &val)) { + i2s->config.hw_port = val; + sp_asoc_pr_dbg("Change _hw_port to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,fs", &val)) { + i2s->config.fs = val; + sp_asoc_pr_dbg("Change fs to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,bus_type", &val)) { + if (val){ + i2s->config.bus_type = PCM_BUS; + } else { + i2s->config.bus_type = I2S_BUS; + } + sp_asoc_pr_dbg("Change bus_type to %d!\n", val); + } + + if (!of_property_read_u32 + (config_node, "sprd,rtx_mode", &val)) { + i2s->config.rtx_mode = val; + sp_asoc_pr_dbg("Change rtx_mode to %d!\n", val); + } + + if (!of_property_read_u32 + (config_node, "sprd,slave_timeout", &val)) { + i2s->config.slave_timeout = val; + sp_asoc_pr_dbg("Change slave_timeout to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,byte_per_chan", &val)) { + i2s->config.byte_per_chan = val; + sp_asoc_pr_dbg("Change byte per channal to %d!\n", val); + } + if (!of_property_read_u32(config_node, "sprd,slave_mode", &val)) { + if (val) { + i2s->config.mode = I2S_SLAVE; + } else { + i2s->config.mode = I2S_MASTER; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Slave" : "Master"); + } + if (!of_property_read_u32(config_node, "sprd,lsb", &val)) { + if (val) { + i2s->config.lsb = I2S_LSB; + } else { + i2s->config.lsb = I2S_MSB; + } + sp_asoc_pr_dbg("Change to %s!\n", val ? "LSB" : "MSB"); + } + if (!of_property_read_u32(config_node, "sprd,lrck", &val)) { + if (val) { + i2s->config.sync_mode = I2S_LRCK; + } else { + i2s->config.sync_mode = I2S_SYNC; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "LRCK" : "SYNC"); + } + if (!of_property_read_u32 + (config_node, "sprd,low_for_left", &val)) { + if (val) { + i2s->config.lrck_inv = I2S_L_LEFT; + } else { + i2s->config.lrck_inv = I2S_L_RIGTH; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Low for Left" : "Low for Right"); + } + if (!of_property_read_u32(config_node, "sprd,clk_inv", &val)) { + if (val) { + i2s->config.clk_inv = I2S_CLK_R; + } else { + i2s->config.clk_inv = I2S_CLK_N; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "CLK INV" : "CLK Normal"); + } + if (!of_property_read_u32 + (config_node, "sprd,i2s_compatible", &val)) { + if (val) { + i2s->config.i2s_bus_mode = I2S_COMPATIBLE; + } else { + i2s->config.i2s_bus_mode = I2S_MSBJUSTFIED; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Compatible" : "MSBJustfied"); + } + if (!of_property_read_u32 + (config_node, "sprd,pcm_short_frame", &val)) { + if (val) { + i2s->config.pcm_bus_mode = I2S_SHORT_FRAME; + } else { + i2s->config.pcm_bus_mode = I2S_LONG_FRAME; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Short Frame" : "Long Frame"); + } + if (!of_property_read_u32(config_node, "sprd,pcm_slot", &val)) { + i2s->config.pcm_slot = val; + sp_asoc_pr_dbg("Change PCM Slot to 0x%x!\n", val); + } + if (!of_property_read_u32(config_node, "sprd,pcm_cycle", &val)) { + i2s->config.pcm_cycle = val; + sp_asoc_pr_dbg("Change PCM Cycle to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,tx_watermark", &val)) { + i2s->config.tx_watermark = val; + sp_asoc_pr_dbg("Change TX Watermark to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,rx_watermark", &val)) { + i2s->config.rx_watermark = val; + sp_asoc_pr_dbg("Change RX Watermark to %d!\n", val); + } + of_node_put(config_node); + } + return 0; +} + +static int i2s_drv_probe(struct platform_device *pdev) +{ + int ret; + struct i2s_priv *i2s; + struct resource *res; + struct device_node *node = pdev->dev.of_node; + + sp_asoc_pr_dbg("%s\n", __func__); + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct i2s_priv), GFP_KERNEL); + if (!i2s) { + pr_err("ERR:No memery!\n"); + return -ENOMEM; + } + i2s->dev = &pdev->dev; + if (node) { + u32 val[2]; + if (of_property_read_u32(node, "sprd,id", &val[0])) { + pr_err("ERR:Must give me the id!\n"); + goto out; + } + i2s->id = val[0]; + if (of_property_read_u32(node, "sprd,hw_port", &val[0])) { + pr_err("ERR:Must give me the hw_port!\n"); + goto out; + } + i2s->hw_port = val[0]; + if (of_property_read_u32_array(node, "sprd,base", &val[0], 2)) { + pr_err("ERR:Must give me the base address!\n"); + goto out; + } + i2s->memphys = (unsigned int *)val[0]; + i2s->reg_size= (unsigned int)val[1]; + i2s->membase = (void __iomem *)ioremap_nocache(i2s->memphys, i2s->reg_size); + if (!i2s->membase) { + pr_err("ERR:i2s reg address ioremap_nocache error !\n"); + return -EINVAL; + } + if (of_property_read_u32(node, "sprd,dma_rx_no", &val[0])) { + pr_err("ERR:Must give me the dma rx number!\n"); + goto out; + } + i2s->rx.dma_no = val[0]; + if (of_property_read_u32(node, "sprd,dma_tx_no", &val[0])) { + pr_err("ERR:Must give me the dma tx number!\n"); + goto out; + } + i2s->tx.dma_no = val[0]; + + const char *dai_name = NULL; + if (of_property_read_string(node, "sprd,dai_name", &dai_name)) { + pr_err("ERR:Must give me the dai_name!\n"); + //goto out; + } + if (dai_name != NULL) + strcpy(i2s->dai_name,dai_name); + printk("dt version i2s->dai_name %s \n",i2s->dai_name); + ret = i2s_config_from_node(node, i2s); + + const char * config_type = NULL; + if (of_property_read_string(node, "sprd,config_type", &config_type)) { + i2s->config = def_pcm_config; + i2s->i2s_type = 0; + printk("warning:Must give me the config_type! default is pcm\n"); + } + printk("i2s config_type %s \n",config_type); + + if (config_type != NULL){ + if (strcmp(config_type,"i2s") == 0){ + i2s->config = def_i2s_config; + i2s->i2s_type = 1; + } else if (strcmp(config_type,"pcm") == 0) { + i2s->config = def_pcm_config; + i2s->i2s_type = 0; + } else { + pr_err("ERR:config_type must be pcm or i2s !\n"); + } + } + + if (!of_property_read_u32 + (node, "sprd,_hw_port", &val[0])) { + i2s->config.hw_port = val[0]; + sp_asoc_pr_dbg("Change _hw_port to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,fs", &val[0])) { + i2s->config.fs = val[0]; + sp_asoc_pr_dbg("Change fs to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,bus_type", &val[0])) { + if (val[0]){ + i2s->config.bus_type = PCM_BUS; + } else { + i2s->config.bus_type = I2S_BUS; + } + sp_asoc_pr_dbg("Change bus_type to %d!\n", val[0]); + } + + if (!of_property_read_u32 + (node, "sprd,rtx_mode", &val[0])) { + i2s->config.rtx_mode = val[0]; + sp_asoc_pr_dbg("Change rtx_mode to %d!\n", val[0]); + } + + if (!of_property_read_u32 + (node, "sprd,slave_timeout", &val[0])) { + i2s->config.slave_timeout = val[0]; + sp_asoc_pr_dbg("Change slave_timeout to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,byte_per_chan", &val[0])) { + i2s->config.byte_per_chan = val[0]; + sp_asoc_pr_dbg("Change byte per channal to %d!\n", val[0]); + } + if (!of_property_read_u32(node, "sprd,slave_mode", &val[0])) { + if (val[0]) { + i2s->config.mode = I2S_SLAVE; + } else { + i2s->config.mode = I2S_MASTER; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Slave" : "Master"); + } + if (!of_property_read_u32(node, "sprd,lsb", &val[0])) { + if (val[0]) { + i2s->config.lsb = I2S_LSB; + } else { + i2s->config.lsb = I2S_MSB; + } + sp_asoc_pr_dbg("Change to %s!\n", val[0] ? "LSB" : "MSB"); + } + if (!of_property_read_u32(node, "sprd,lrck", &val[0])) { + if (val[0]) { + i2s->config.sync_mode = I2S_LRCK; + } else { + i2s->config.sync_mode = I2S_SYNC; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "LRCK" : "SYNC"); + } + if (!of_property_read_u32 + (node, "sprd,low_for_left", &val[0])) { + if (val[0]) { + i2s->config.lrck_inv = I2S_L_LEFT; + } else { + i2s->config.lrck_inv = I2S_L_RIGTH; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Low for Left" : "Low for Right"); + } + if (!of_property_read_u32(node, "sprd,clk_inv", &val[0])) { + if (val[0]) { + i2s->config.clk_inv = I2S_CLK_R; + } else { + i2s->config.clk_inv = I2S_CLK_N; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "CLK INV" : "CLK Normal"); + } + if (i2s->i2s_type){ + if (!of_property_read_u32 + (node, "sprd,i2s_compatible", &val[0])) { + if (val[0]) { + i2s->config.i2s_bus_mode = I2S_COMPATIBLE;//I2S_SHORT_FRAME + } else { + i2s->config.i2s_bus_mode = I2S_MSBJUSTFIED;//I2S_LONG_FRAME + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Compatible" : "MSBJustfied"); + } + } + if (!of_property_read_u32 + (node, "sprd,pcm_short_frame", &val[0])) { + if (val[0]) { + i2s->config.pcm_bus_mode = I2S_SHORT_FRAME; + } else { + i2s->config.pcm_bus_mode = I2S_LONG_FRAME; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Short Frame" : "Long Frame"); + } + if (!i2s->i2s_type) { + if (!of_property_read_u32(node, "sprd,pcm_slot", &val[0])) { + i2s->config.pcm_slot = val[0]; + sp_asoc_pr_dbg("Change PCM Slot to 0x%x!\n", val[0]); + } + if (!of_property_read_u32(node, "sprd,pcm_cycle", &val[0])) { + i2s->config.pcm_cycle = val[0]; + sp_asoc_pr_dbg("Change PCM Cycle to %d!\n", val[0]); + } + } + if (!of_property_read_u32 + (node, "sprd,tx_watermark", &val[0])) { + i2s->config.tx_watermark = val[0]; + sp_asoc_pr_dbg("Change TX Watermark to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,rx_watermark", &val[0])) { + i2s->config.rx_watermark = val[0]; + sp_asoc_pr_dbg("Change RX Watermark to %d!\n", val[0]); + } + } else { + i2s->config = + *((struct i2s_config *)(dev_get_platdata(&pdev->dev))); + i2s->id = pdev->id; + snprintf(i2s->dai_name,DAI_NAME_SIZE,"%s%d","i2s_bt_sco",i2s->id); + printk("not bt version i2s->dai_name %s \n",i2s->dai_name); + i2s->hw_port = i2s->config.hw_port; + sp_asoc_pr_dbg("i2s.%d(%d) default fs is (%d)\n", i2s->id, + i2s->hw_port, i2s->config.fs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->membase = (void __iomem *)res->start; + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + i2s->memphys = (unsigned int *)res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + i2s->tx.dma_no = res->start; + i2s->rx.dma_no = res->end; + } + + i2s->i2s_dai_driver[0].id = I2S_MAGIC_ID; + i2s->i2s_dai_driver[0].name = i2s->dai_name; + printk("i2s->i2s_dai_driver[0].name %s \n",i2s->i2s_dai_driver[0].name); + i2s->i2s_dai_driver[0].playback.channels_min = 1; + i2s->i2s_dai_driver[0].playback.channels_max = 2; + i2s->i2s_dai_driver[0].playback.rates = SNDRV_PCM_RATE_CONTINUOUS; + i2s->i2s_dai_driver[0].playback.rate_max = 96000; + i2s->i2s_dai_driver[0].playback.formats = SNDRV_PCM_FMTBIT_S16_LE; + + i2s->i2s_dai_driver[0].capture.channels_min = 1; + i2s->i2s_dai_driver[0].capture.channels_max = 2; + i2s->i2s_dai_driver[0].capture.rates = SNDRV_PCM_RATE_CONTINUOUS; + i2s->i2s_dai_driver[0].capture.rate_max = 96000; + i2s->i2s_dai_driver[0].capture.formats = SNDRV_PCM_FMTBIT_S16_LE; + i2s->i2s_dai_driver[0].ops = &sprd_i2s_dai_ops; + + i2s->i2s_dai_driver[1].id = I2S_MAGIC_ID + 1; + i2s->i2s_dai_driver[1].name = "i2s_bt_sco_dummy"; + printk("i2s->i2s_dai_driver[1].name %s \n",i2s->i2s_dai_driver[1].name); + + arch_audio_i2s_switch(i2s->hw_port, AUDIO_TO_ARM_CTRL); + sp_asoc_pr_dbg("membase = 0x%x memphys = 0x%x\n", (int)i2s->membase, + (int)i2s->memphys); + sp_asoc_pr_dbg("DMA Number tx = %d rx = %d\n", i2s->tx.dma_no, + i2s->rx.dma_no); + + platform_set_drvdata(pdev, i2s); + + atomic_set(&i2s->open_cnt, 0); + + ret = + snd_soc_register_component(&pdev->dev, &sprd_i2s_component, + i2s->i2s_dai_driver, ARRAY_SIZE(i2s->i2s_dai_driver)); + sp_asoc_pr_dbg("return %i\n", ret); + + if (strcmp(i2s->dai_name,"i2s_bt_sco0") == 0) { + dup_config = &(i2s->config); + membase = i2s->membase; + } + return ret; +out: + devm_kfree(&pdev->dev,i2s); + return -EINVAL; +} + +static int i2s_drv_remove(struct platform_device *pdev) +{ + struct i2s_priv *i2s; + i2s = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id i2s_of_match[] = { + {.compatible = "sprd,i2s",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, i2s_of_match); +#endif + +static struct platform_driver i2s_driver = { + .driver = { + .name = "i2s", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(i2s_of_match), + }, + + .probe = i2s_drv_probe, + .remove = i2s_drv_remove, +}; + +module_platform_driver(i2s_driver); + +MODULE_DESCRIPTION("SPRD ASoC I2S CUP-DAI driver"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cpu-dai:i2s"); |
