summaryrefslogtreecommitdiff
path: root/sound/soc/tegra/tegra_i2s.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/tegra/tegra_i2s.c')
-rw-r--r--sound/soc/tegra/tegra_i2s.c317
1 files changed, 260 insertions, 57 deletions
diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c
index 25587d8420df..97034b19e318 100644
--- a/sound/soc/tegra/tegra_i2s.c
+++ b/sound/soc/tegra/tegra_i2s.c
@@ -19,7 +19,89 @@
#include "tegra_soc.h"
-struct snd_soc_dai tegra_i2s_dai;
+/* i2s controller */
+struct tegra_i2s_info {
+ struct platform_device *pdev;
+ struct tegra_audio_platform_data *pdata;
+ struct clk *i2s_clk;
+ struct clk *dap_mclk;
+ phys_addr_t i2s_phys;
+ void __iomem *i2s_base;
+
+ unsigned long dma_req_sel;
+
+ int irq;
+ /* Control for whole I2S (Data format, etc.) */
+ unsigned int bit_format;
+ struct i2s_runtime_data i2s_regs;
+};
+
+
+int setup_dma_request(struct snd_pcm_substream *substream,
+ struct tegra_dma_req *req,
+ void (*dma_callback)(struct tegra_dma_req *req),
+ void *dma_data)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct tegra_i2s_info *info = cpu_dai->private_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ req->to_memory = false;
+ req->dest_addr =
+ i2s_get_fifo_phy_base(cpu_dai->id, I2S_FIFO_TX);
+ req->dest_wrap = 4;
+ req->source_wrap = 0;
+ } else {
+ req->to_memory = true;
+ req->source_addr =
+ i2s_get_fifo_phy_base(cpu_dai->id, I2S_FIFO_RX);
+ req->dest_wrap = 0;
+ req->source_wrap = 4;
+ }
+
+ req->complete = dma_callback;
+ req->dev = dma_data;
+ req->source_bus_width = 32;
+ req->dest_bus_width = 32;
+ req->req_sel = info->dma_req_sel;
+
+ return 0;
+}
+
+
+/* playback */
+static inline void start_i2s_playback(struct snd_soc_dai *cpu_dai)
+{
+ i2s_fifo_set_attention_level(cpu_dai->id, I2S_FIFO_TX,
+ I2S_FIFO_ATN_LVL_FOUR_SLOTS);
+ i2s_fifo_enable(cpu_dai->id, I2S_FIFO_TX, 1);
+}
+
+static inline void stop_i2s_playback(struct snd_soc_dai *cpu_dai)
+{
+ i2s_set_fifo_irq_on_err(cpu_dai->id, I2S_FIFO_TX, 0);
+ i2s_set_fifo_irq_on_qe(cpu_dai->id, I2S_FIFO_TX, 0);
+ i2s_fifo_enable(cpu_dai->id, I2S_FIFO_TX, 0);
+ while (i2s_get_status(cpu_dai->id) & I2S_I2S_FIFO_TX_BUSY);
+}
+
+/* recording */
+static inline void start_i2s_capture(struct snd_soc_dai *cpu_dai)
+{
+ i2s_fifo_set_attention_level(cpu_dai->id, I2S_FIFO_RX,
+ I2S_FIFO_ATN_LVL_FOUR_SLOTS);
+ i2s_fifo_enable(cpu_dai->id, I2S_FIFO_RX, 1);
+}
+
+static inline void stop_i2s_capture(struct snd_soc_dai *cpu_dai)
+{
+ i2s_set_fifo_irq_on_err(cpu_dai->id, I2S_FIFO_RX, 0);
+ i2s_set_fifo_irq_on_qe(cpu_dai->id, I2S_FIFO_RX, 0);
+ i2s_fifo_enable(cpu_dai->id, I2S_FIFO_RX, 0);
+ while (i2s_get_status(cpu_dai->id) & I2S_I2S_FIFO_RX_BUSY);
+}
+
static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
@@ -27,8 +109,9 @@ static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct tegra_runtime_data *prtd = runtime->private_data;
- int ret=0;
+ int ret = 0;
int val;
+ unsigned int i2s_id = dai->id;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
@@ -45,7 +128,7 @@ static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
goto err;
}
- i2s_set_bit_size(I2S_IFC, val);
+ i2s_set_bit_size(i2s_id, val);
switch (params_rate(params)) {
case 8000:
@@ -61,8 +144,8 @@ static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
goto err;
}
- i2s_set_channel_bit_count(I2S_IFC, val, clk_get_rate(prtd->i2s_clk));
- tegra_i2s_dai.private_data = (void *)prtd;
+ i2s_set_channel_bit_count(i2s_id, val, clk_get_rate(prtd->i2s_clk));
+
return 0;
err:
@@ -75,6 +158,7 @@ static int tegra_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
{
int val1;
int val2;
+ unsigned int i2s_id = cpu_dai->id;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
@@ -91,7 +175,7 @@ static int tegra_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
- i2s_set_master(I2S_IFC, val1);
+ i2s_set_master(i2s_id, val1);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
@@ -118,8 +202,8 @@ static int tegra_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
- i2s_set_bit_format(I2S_IFC,val1);
- i2s_set_left_right_control_polarity(I2S_IFC,val2);
+ i2s_set_bit_format(i2s_id,val1);
+ i2s_set_left_right_control_polarity(i2s_id,val2);
/* Clock inversion */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
@@ -176,11 +260,20 @@ static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ start_i2s_playback(dai);
+ else
+ start_i2s_capture(dai);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stop_i2s_playback(dai);
+ else
+ stop_i2s_capture(dai);
break;
default:
ret = -EINVAL;
@@ -190,17 +283,21 @@ static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
}
#ifdef CONFIG_PM
-int tegra_i2s_suspend(struct snd_soc_dai *i2s_dai)
+int tegra_i2s_suspend(struct snd_soc_dai *cpu_dai)
{
- struct tegra_runtime_data *prtd = tegra_i2s_dai.private_data;
- i2s_get_all_regs(I2S_IFC, &prtd->i2s_regs);
+ struct tegra_i2s_info *info = cpu_dai->private_data;
+
+ i2s_get_all_regs(cpu_dai->id, &info->i2s_regs);
+
return 0;
}
-int tegra_i2s_resume(struct snd_soc_dai *i2s_dai)
+int tegra_i2s_resume(struct snd_soc_dai *cpu_dai)
{
- struct tegra_runtime_data *prtd = tegra_i2s_dai.private_data;
- i2s_set_all_regs(I2S_IFC, &prtd->i2s_regs);
+ struct tegra_i2s_info *info = cpu_dai->private_data;
+
+ i2s_set_all_regs(cpu_dai->id, &info->i2s_regs);
+
return 0;
}
@@ -218,37 +315,23 @@ static int tegra_i2s_startup(struct snd_pcm_substream *substream,
static void tegra_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
+ return;
}
static int tegra_i2s_probe(struct platform_device *pdev,
struct snd_soc_dai *dai)
{
- /* DAC1 -> DAP1, DAC1 master, DAP2 bypass */
- i2s_enable_fifos(I2S_IFC, 0);
- i2s_set_left_right_control_polarity(I2S_IFC, 0); /* default */
- i2s_set_master(I2S_IFC, 1); /* set as master */
- i2s_set_fifo_mode(I2S_IFC, FIFO1, 1); /* FIFO1 is TX */
- i2s_set_fifo_mode(I2S_IFC, FIFO2, 0); /* FIFO2 is RX */
- i2s_set_bit_format(I2S_IFC, I2S_BIT_FORMAT_I2S);
- i2s_set_bit_size(I2S_IFC, I2S_BIT_SIZE_16);
- i2s_set_fifo_format(I2S_IFC, I2S_FIFO_PACKED);
- return 0;
-}
-
-static int tegra_i2s_driver_probe(struct platform_device *dev)
-{
- int ret;
-
- tegra_i2s_dai.dev = &dev->dev;
- tegra_i2s_dai.private_data = NULL;
- ret = snd_soc_register_dai(&tegra_i2s_dai);
- return ret;
-}
+ unsigned int i2s_id = dai->id;
+ i2s_enable_fifos(i2s_id, 0);
+ i2s_set_left_right_control_polarity(i2s_id, 0); /* default */
+ i2s_set_master(i2s_id, 1); /* set as master */
+ i2s_set_fifo_mode(i2s_id, FIFO1, 1); /* FIFO1 is TX */
+ i2s_set_fifo_mode(i2s_id, FIFO2, 0); /* FIFO2 is RX */
+ i2s_set_bit_format(i2s_id, I2S_BIT_FORMAT_I2S);
+ i2s_set_bit_size(i2s_id, I2S_BIT_SIZE_16);
+ i2s_set_fifo_format(i2s_id, I2S_FIFO_PACKED);
-static int __devexit tegra_i2s_driver_remove(struct platform_device *dev)
-{
- snd_soc_unregister_dai(&tegra_i2s_dai);
return 0;
}
@@ -256,33 +339,153 @@ static struct snd_soc_dai_ops tegra_i2s_dai_ops = {
.startup = tegra_i2s_startup,
.shutdown = tegra_i2s_shutdown,
.trigger = tegra_i2s_trigger,
- .hw_params = tegra_i2s_hw_params,
+ .hw_params = tegra_i2s_hw_params,
.set_fmt = tegra_i2s_set_dai_fmt,
.set_sysclk = tegra_i2s_set_dai_sysclk,
};
-struct snd_soc_dai tegra_i2s_dai = {
- .name = "tegra-i2s",
- .id = 0,
- .probe = tegra_i2s_probe,
- .suspend = tegra_i2s_suspend,
- .resume = tegra_i2s_resume,
- .playback = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = TEGRA_SAMPLE_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
+struct snd_soc_dai tegra_i2s_dai[] = {
+ {
+ .name = "tegra-i2s-1",
+ .id = 0,
+ .probe = tegra_i2s_probe,
+ .suspend = tegra_i2s_suspend,
+ .resume = tegra_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = TEGRA_SAMPLE_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = TEGRA_SAMPLE_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra_i2s_dai_ops,
},
- .capture = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = TEGRA_SAMPLE_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ {
+ .name = "tegra-i2s-2",
+ .id = 1,
+ .probe = tegra_i2s_probe,
+ .suspend = tegra_i2s_suspend,
+ .resume = tegra_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = TEGRA_SAMPLE_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = TEGRA_SAMPLE_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra_i2s_dai_ops,
},
- .ops = &tegra_i2s_dai_ops,
};
EXPORT_SYMBOL_GPL(tegra_i2s_dai);
+static int tegra_i2s_driver_probe(struct platform_device *pdev)
+{
+ int err = 0;
+ struct resource *res, *mem;
+ struct tegra_i2s_info *info;
+ int i = 0;
+
+ pr_info("%s\n", __func__);
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->pdev = pdev;
+ info->pdata = pdev->dev.platform_data;
+ info->pdata->driver_data = info;
+ BUG_ON(!info->pdata);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no mem resource!\n");
+ err = -ENODEV;
+ goto fail;
+ }
+
+ mem = request_mem_region(res->start, resource_size(res), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "memory region already claimed!\n");
+ err = -EBUSY;
+ goto fail;
+ }
+
+ info->i2s_phys = res->start;
+ info->i2s_base = ioremap(res->start, res->end - res->start + 1);
+ if (!info->i2s_base) {
+ dev_err(&pdev->dev, "cannot remap iomem!\n");
+ err = -ENOMEM;
+ goto fail_release_mem;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no dma resource!\n");
+ err = -ENODEV;
+ goto fail_unmap_mem;
+ }
+ info->dma_req_sel = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no irq resource!\n");
+ err = -ENODEV;
+ goto fail_unmap_mem;
+ }
+ info->irq = res->start;
+
+ info->i2s_clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(info->i2s_clk)) {
+ err = PTR_ERR(info->i2s_clk);
+ goto fail_unmap_mem;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tegra_i2s_dai); i++) {
+ if (tegra_i2s_dai[i].id == pdev->id) {
+ tegra_i2s_dai[i].dev = &pdev->dev;
+ tegra_i2s_dai[i].private_data = info;
+ err = snd_soc_register_dai(&tegra_i2s_dai[i]);
+ if (err)
+ goto fail_unmap_mem;
+ }
+ }
+
+ return 0;
+
+fail_unmap_mem:
+ iounmap(info->i2s_base);
+fail_release_mem:
+ release_mem_region(mem->start, resource_size(mem));
+fail:
+ kfree(info);
+ return err;
+}
+
+
+static int __devexit tegra_i2s_driver_remove(struct platform_device *pdev)
+{
+ struct tegra_i2s_info *info = tegra_i2s_dai[pdev->id].private_data;
+
+ if (info->i2s_base)
+ iounmap(info->i2s_base);
+
+ if (info)
+ kfree(info);
+
+ snd_soc_unregister_dai(&tegra_i2s_dai[pdev->id]);
+ return 0;
+}
+
static struct platform_driver tegra_i2s_driver = {
.probe = tegra_i2s_driver_probe,
.remove = __devexit_p(tegra_i2s_driver_remove),