diff options
author | Bharat Nihalani <bnihalani@nvidia.com> | 2010-11-09 15:14:03 +0530 |
---|---|---|
committer | Bharat Nihalani <bnihalani@nvidia.com> | 2010-11-09 02:41:26 -0800 |
commit | 9f6eec401700ddb5739be4b2b763f4b12da5675d (patch) | |
tree | ad9b66b656f5e7b1f741ae63365847f429745e37 /sound | |
parent | 952f2430bb2e56c8fec787248bfd1096b571ce96 (diff) |
[tegra ALSA] ALSA SoC driver with native I2S
ALSA SoC platform driver with native I2S driver implementation.
tegra_dma is used for buffer transfer between ALSA and I2S
Playback path is verified; recording is not yet supported.
Using coherent dma buffers to eliminate noise during playback!
Removed streaming dma mappings!
Change-Id: Id82f9bee3340c914cbf20a0e3f11ec76db59feab
Reviewed-on: http://git-master/r/10066
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Tested-by: Bharat Nihalani <bnihalani@nvidia.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/codecs/wm8903.c | 1 | ||||
-rw-r--r-- | sound/soc/tegra/Kconfig | 18 | ||||
-rw-r--r-- | sound/soc/tegra/Makefile | 6 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_i2s.c | 302 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_pcm.c | 499 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_soc.c | 295 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_soc.h | 100 |
9 files changed, 1223 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3e598e756e54..ba426cd21390 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -40,6 +40,7 @@ source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/tegra/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index eb183443eee4..d4d25076ea14 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += tegra/ diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index bf08282d5ee5..dbe9b52456ef 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -945,6 +945,7 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, reg &= ~(WM8903_VMID_RES_MASK); reg |= WM8903_VMID_RES_50K; snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_write(codec, WM8903_BIAS_CONTROL_0, 0xB); break; case SND_SOC_BIAS_STANDBY: diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 000000000000..88b54d890f98 --- /dev/null +++ b/sound/soc/tegra/Kconfig @@ -0,0 +1,18 @@ +config TEGRA_ALSA
+ tristate "Tegra ALSA SoC support"
+ select TEGRA_PCM
+ select TEGRA_I2S
+ select TEGRA_IEC
+ select SND_SOC_WM8903
+ help
+ Say Y if you for ALSA SoC support
+
+config TEGRA_PCM
+ tristate "Tegra ALSA pcm callbacks"
+
+config TEGRA_I2S
+ tristate "Tegra I2S"
+
+config TEGRA_IEC
+ tristate "Tegra IEC"
+
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 000000000000..23652208b893 --- /dev/null +++ b/sound/soc/tegra/Makefile @@ -0,0 +1,6 @@ +ccflags-y += -DNV_DEBUG=0
+obj-$(CONFIG_TEGRA_PCM) += tegra_pcm.o
+obj-$(CONFIG_TEGRA_I2S) += tegra_i2s.o
+obj-$(CONFIG_TEGRA_ALSA) += tegra_soc.o
+
+
diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c new file mode 100644 index 000000000000..d824544d6188 --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.c @@ -0,0 +1,302 @@ +/*
+ * tegra_i2s.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2010 Nvidia Graphics Pvt. Ltd.
+ * http://www.nvidia.com
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * (c) 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include "tegra_soc.h"
+
+static void *das_base = IO_ADDRESS(TEGRA_APB_MISC_BASE);
+
+static inline unsigned long das_readl(unsigned long offset)
+{
+ return readl(das_base + offset);
+}
+
+static inline void das_writel(unsigned long value, unsigned long offset)
+{
+ writel(value, das_base + offset);
+}
+
+static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+ int ret=0;
+ int val;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = I2S_BIT_SIZE_16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val = I2S_BIT_SIZE_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ val = I2S_BIT_SIZE_32;
+ break;
+ default:
+ ret =-EINVAL;
+ goto err;
+ }
+
+ i2s_set_bit_size(I2S_IFC, val);
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ val = params_rate(params);
+ break;
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ i2s_set_channel_bit_count(I2S_IFC, val, clk_get_rate(prtd->i2s_clk));
+
+ return 0;
+
+err:
+ return ret;
+}
+
+
+static int tegra_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ int val1;
+ int val2;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ val1 = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ val1= 0;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ /* Tegra does not support different combinations of
+ * master and slave for FSYNC and BCLK */
+ default:
+ return -EINVAL;
+ }
+
+ i2s_set_master(I2S_IFC, val1);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ val1 = I2S_BIT_FORMAT_DSP;
+ val2 = 0;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ val1 = I2S_BIT_FORMAT_DSP;
+ val2 = 1;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val1 = I2S_BIT_FORMAT_I2S;
+ val2 = 0;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val1 = I2S_BIT_FORMAT_RJM;
+ val2 = 0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val1 = I2S_BIT_FORMAT_LJM;
+ val2 = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ i2s_set_bit_format(I2S_IFC,val1);
+ i2s_set_left_right_control_polarity(I2S_IFC,val2);
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ /* aif1 |= WM8903_AIF_BCLK_INV; */
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ /* aif1 |= WM8903_AIF_BCLK_INV |
+ * WM8903_AIF_LRCLK_INV; */
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ /* aif1 |= WM8903_AIF_BCLK_INV; */
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ /* aif1 |= WM8903_AIF_LRCLK_INV; */
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ return 0;
+}
+
+static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int tegra_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ return 0;
+}
+
+static void tegra_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+}
+
+static int tegra_i2s_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ /* DAC1 -> DAP1, DAC1 master, DAP2 bypass */
+ das_writel(0, APB_MISC_DAS_DAP_CTRL_SEL_0);
+ das_writel(0, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_0);
+ 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 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,
+ .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,
+ .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,
+};
+EXPORT_SYMBOL_GPL(tegra_i2s_dai);
+
+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;
+}
+
+
+static int __devexit tegra_i2s_driver_remove(struct platform_device *dev)
+{
+ snd_soc_unregister_dai(&tegra_i2s_dai);
+ return 0;
+}
+
+static struct platform_driver tegra_i2s_driver = {
+ .probe = tegra_i2s_driver_probe,
+ .remove = __devexit_p(tegra_i2s_driver_remove),
+ .driver = {
+ .name = "i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init tegra_i2s_init(void)
+{
+ int ret = 0;
+
+ ret = platform_driver_register(&tegra_i2s_driver);
+ return ret;
+}
+module_init(tegra_i2s_init);
+
+static void __exit tegra_i2s_exit(void)
+{
+ platform_driver_unregister(&tegra_i2s_driver);
+}
+module_exit(tegra_i2s_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("Tegra I2S SoC interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 000000000000..3cdff93a4678 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c @@ -0,0 +1,499 @@ +/* + * tegra_pcm.c -- ALSA Soc Audio Layer + * + * (c) 2010 Nvidia Graphics Pvt. Ltd. + * http://www.nvidia.com + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include "tegra_soc.h" + +#define PLAYBACK_STARTED true +#define PLAYBACK_STOPPED false + +static void start_i2s_playback(void) +{ + i2s_fifo_set_attention_level(I2S_IFC, I2S_FIFO_TX, + I2S_FIFO_ATN_LVL_FOUR_SLOTS); + i2s_fifo_enable(I2S_IFC, I2S_FIFO_TX, 1); +} + +static void stop_i2s_playback(void) +{ + i2s_set_fifo_irq_on_err(I2S_IFC, I2S_FIFO_TX, 0); + i2s_set_fifo_irq_on_qe(I2S_IFC, I2S_FIFO_TX, 0); + i2s_fifo_enable(I2S_IFC, I2S_FIFO_TX, 0); + while (i2s_get_status(I2S_IFC) & I2S_I2S_FIFO_TX_BUSY); +} + +static void tegra_pcm_play(struct tegra_runtime_data *prtd) +{ + static int reqid = 0; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + if (runtime->dma_addr) { + prtd->size = frames_to_bytes(runtime, runtime->period_size); + if (reqid == 0) { + prtd->dma_req1.source_addr = buf->addr + + + frames_to_bytes(runtime,prtd->dma_pos); + prtd->dma_req1.size = prtd->size; + tegra_dma_enqueue_req(prtd->dma_chan, &prtd->dma_req1); + reqid = 1; + } else { + prtd->dma_req2.source_addr = buf->addr + + + frames_to_bytes(runtime,prtd->dma_pos); + prtd->dma_req2.size = prtd->size; + tegra_dma_enqueue_req(prtd->dma_chan, &prtd->dma_req2); + reqid = 0; + } + } + + prtd->dma_pos += runtime->period_size; + if (prtd->dma_pos >= runtime->buffer_size) { + prtd->dma_pos = 0; + } + +} + +static void dma_tx_complete_callback (struct tegra_dma_req *req) +{ + struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (++prtd->period_index >= runtime->periods) { + prtd->period_index = 0; + } + + snd_pcm_period_elapsed(substream); + tegra_pcm_play(prtd); +} + +static void setup_dma_tx_request(struct tegra_dma_req *req) +{ + memset(req, 0, sizeof(*req)); + req->complete = dma_tx_complete_callback; + req->to_memory = false; + req->dest_addr = i2s_get_fifo_phy_base(I2S_IFC, I2S_FIFO_TX); + req->dest_wrap = 4; + req->source_bus_width = 32; + req->source_wrap = 0; + req->dest_bus_width = 32; + req->req_sel = I2S_IFC ? 1 : 2; /* 1 = I2S2, 2 = I2S1 */ +} + +/* recording */ +static void start_i2s_capture(void) +{ + i2s_fifo_set_attention_level(I2S_IFC, I2S_FIFO_RX, + I2S_FIFO_ATN_LVL_FOUR_SLOTS); + i2s_fifo_enable(I2S_IFC, I2S_FIFO_RX, 1); +} + +static void stop_i2s_capture(void) +{ + i2s_set_fifo_irq_on_err(I2S_IFC, I2S_FIFO_RX, 0); + i2s_set_fifo_irq_on_qe(I2S_IFC, I2S_FIFO_RX, 0); + i2s_fifo_enable(I2S_IFC, I2S_FIFO_RX, 0); + while (i2s_get_status(I2S_IFC) & I2S_I2S_FIFO_RX_BUSY); +} + +static void tegra_pcm_capture(struct tegra_runtime_data *prtd) +{ + static int reqid = 0; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + if (runtime->dma_addr) { + prtd->size = frames_to_bytes(runtime, runtime->period_size); + if (reqid == 0) { + prtd->dma_req1.source_addr = buf->addr + + + frames_to_bytes(runtime,prtd->dma_pos); + prtd->dma_req1.size = prtd->size; + tegra_dma_enqueue_req(prtd->dma_chan, &prtd->dma_req1); + reqid = 1; + } else { + prtd->dma_req2.source_addr = buf->addr + + + frames_to_bytes(runtime,prtd->dma_pos); + prtd->dma_req2.size = prtd->size; + tegra_dma_enqueue_req(prtd->dma_chan, &prtd->dma_req2); + reqid = 0; + } + } + + prtd->dma_pos += runtime->period_size; + if (prtd->dma_pos >= runtime->buffer_size) { + prtd->dma_pos = 0; + } + +} + +static void dma_rx_complete_callback(struct tegra_dma_req *req) +{ + struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (++prtd->period_index >= runtime->periods) { + prtd->period_index = 0; + } + + snd_pcm_period_elapsed(substream); + tegra_pcm_capture(prtd); +} + +static void setup_dma_rx_request(struct tegra_dma_req *req) +{ + memset(req, 0, sizeof(*req)); + req->complete = dma_rx_complete_callback; + req->to_memory = true; + req->dest_addr = i2s_get_fifo_phy_base(I2S_IFC, I2S_FIFO_RX); + req->dest_wrap = 0; + req->source_bus_width = 32; + req->source_wrap = 4; + req->dest_bus_width = 32; + req->req_sel = I2S_IFC ? 1 : 2; /* 1 = I2S2, 2 = I2S1 */ +} + +static const struct snd_pcm_hardware tegra_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | \ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | \ + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID , + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (PAGE_SIZE * 8), + .period_bytes_min = 1024, + .period_bytes_max = (PAGE_SIZE), + .periods_min = 2, + .periods_max = 8, + .fifo_size = 4, +}; + +static int tegra_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int tegra_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int tegra_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct tegra_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->state = STATE_INIT; + tegra_pcm_play(prtd); /* dma enqueue req1 */ + tegra_pcm_play(prtd); /* dma enqueue req2 */ + start_i2s_playback(); + } else if (prtd->state != STATE_INIT) { + /* start recording */ + prtd->state = STATE_INIT; + tegra_pcm_capture(prtd); /* dma enqueue req1 */ + tegra_pcm_capture(prtd); /* dma enqueue req2 */ + start_i2s_capture(); + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_STOP: + prtd->state = STATE_ABORT; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + int size; + size = prtd->period_index * runtime->period_size; + return (size); +} + +static int tegra_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd; + int ret=0; + + prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + memset(prtd, 0, sizeof(*prtd)); + runtime->private_data = prtd; + prtd->substream = substream; + + /* Enable the DAP outputs */ + tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP1,TEGRA_TRI_NORMAL); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_CDEV1,TEGRA_TRI_NORMAL); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_CDEV2,TEGRA_TRI_NORMAL); + + /* Setup I2S clocks */ + prtd->i2s_clk = i2s_get_clock_by_name(I2S_NAME); + if (!prtd->i2s_clk) { + pr_err("%s: could not get i2s1 clock\n", __func__); + return -EIO; + } + + clk_set_rate(prtd->i2s_clk, I2S_CLK); + if (clk_enable(prtd->i2s_clk)) { + pr_err("%s: failed to enable i2s1 clock\n", __func__); + return -EIO; + } + + i2s_set_channel_bit_count(I2S_IFC, TEGRA_DEFAULT_SR, + clk_get_rate(prtd->i2s_clk)); + + prtd->dap_mclk = i2s_get_clock_by_name("clk_dev1"); + if (!prtd->dap_mclk) { + pr_err("%s: could not get DAP clock\n", __func__); + return -EIO; + } + clk_enable(prtd->dap_mclk); + + prtd->audio_sync_clk = i2s_get_clock_by_name("audio_2x"); + if (!prtd->audio_sync_clk) { + pr_err("%s: could not get audio_2x clock\n", __func__); + return -EIO; + } + clk_enable(prtd->audio_sync_clk); + + prtd->state = STATE_INVALID; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + setup_dma_tx_request(&prtd->dma_req1); + setup_dma_tx_request(&prtd->dma_req2); + } else { + setup_dma_rx_request(&prtd->dma_req1); + setup_dma_rx_request(&prtd->dma_req2); + } + + prtd->dma_req1.dev = prtd; + prtd->dma_req2.dev = prtd; + + prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); + if (IS_ERR(prtd->dma_chan)) { + pr_err("%s: could not allocate DMA channel for I2S: %ld\n", + __func__, PTR_ERR(prtd->dma_chan)); + ret = PTR_ERR(prtd->dma_chan); + goto fail; + } + + /* Set HW params now that initialization is complete */ + snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware); + + goto end; + +fail: + prtd->state = STATE_EXIT; + + if (prtd->dma_chan) { + tegra_dma_flush(prtd->dma_chan); + tegra_dma_free_channel(prtd->dma_chan); + } + /* Tristate the DAP pinmux */ + tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP1,TEGRA_TRI_TRISTATE); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_CDEV1,TEGRA_TRI_TRISTATE); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_CDEV2,TEGRA_TRI_TRISTATE); + + kfree(prtd); + +end: + return ret; +} + +static int tegra_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + if (!prtd) { + printk(KERN_ERR "tegra_pcm_close called with prtd == NULL\n"); + return 0; + } + + prtd->state = STATE_EXIT; + + if (prtd->dma_chan) { + tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req1); + tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req2); + stop_i2s_playback(); + tegra_dma_flush(prtd->dma_chan); + tegra_dma_free_channel(prtd->dma_chan); + prtd->dma_chan = NULL; + } + + kfree(prtd); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP1,TEGRA_TRI_TRISTATE); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_CDEV1,TEGRA_TRI_TRISTATE); + tegra_pinmux_set_tristate(TEGRA_PINGROUP_CDEV2,TEGRA_TRI_TRISTATE); + + return 0; +} + +static int tegra_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops tegra_pcm_ops = { + .open = tegra_pcm_open, + .close = tegra_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = tegra_pcm_hw_params, + .hw_free = tegra_pcm_hw_free, + .prepare = tegra_pcm_prepare, + .trigger = tegra_pcm_trigger, + .pointer = tegra_pcm_pointer, + .mmap = tegra_pcm_mmap, +}; + +static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = tegra_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + buf->bytes = size; + return 0; +} + +static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static void tegra_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + buf = &substream->dma_buffer; + if (!buf) { + printk(KERN_ERR "no buffer %d \n",stream); + continue; + } + tegra_pcm_deallocate_dma_buffer(pcm ,stream); + } + +} + +static u64 tegra_dma_mask = DMA_BIT_MASK(32); + +static int tegra_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &tegra_dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } +out: + return ret; +} + +struct snd_soc_platform tegra_soc_platform = { + .name = "tegra-audio", + .pcm_ops = &tegra_pcm_ops, + .pcm_new = tegra_pcm_new, + .pcm_free = tegra_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(tegra_soc_platform); + +static int __init tegra_soc_platform_init(void) +{ + return snd_soc_register_platform(&tegra_soc_platform); +} +module_init(tegra_soc_platform_init); + +static void __exit tegra_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&tegra_soc_platform); +} +module_exit(tegra_soc_platform_exit); + +MODULE_DESCRIPTION("Tegra PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_soc.c b/sound/soc/tegra/tegra_soc.c new file mode 100644 index 000000000000..4d8d24c40d6a --- /dev/null +++ b/sound/soc/tegra/tegra_soc.c @@ -0,0 +1,295 @@ +/*
+ * tegra_soc.c -- SoC audio for tegra
+ *
+ * (c) 2010 Nvidia Graphics Pvt. Ltd.
+ * http://www.nvidia.com
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include "../codecs/wm8903.h"
+#include "tegra_soc.h"
+#include <mach/audio.h>
+
+static struct platform_device *tegra_snd_device;
+static int tegra_jack_func;
+static int tegra_spk_func;
+
+#define TEGRA_HP 0
+#define TEGRA_MIC 1
+#define TEGRA_LINE 2
+#define TEGRA_HEADSET 3
+#define TEGRA_HP_OFF 4
+#define TEGRA_SPK_ON 0
+#define TEGRA_SPK_OFF 1
+
+static void tegra_ext_control(struct snd_soc_codec *codec)
+{
+ /* set up jack connection */
+ switch (tegra_jack_func) {
+ case TEGRA_HP:
+ /* set = unmute headphone */
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case TEGRA_MIC:
+ /* reset = mute headphone */
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case TEGRA_LINE:
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_enable_pin(codec, "Line Jack");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case TEGRA_HEADSET:
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Headset Jack");
+ break;
+ }
+
+ if (tegra_spk_func == TEGRA_SPK_ON) {
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ } else {
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+ }
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(codec);
+}
+
+static int tegra_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int err;
+
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ printk(KERN_ERR "codec_dai fmt not set \n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ printk(KERN_ERR "cpu_dai fmt not set \n");
+ return err;
+ }
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, I2S_CLK, SND_SOC_CLOCK_IN);
+
+ if (err<0) {
+ printk(KERN_ERR "codec_dai clock not set\n");
+ return err;
+ }
+ err = snd_soc_dai_set_sysclk(cpu_dai, 0, I2S_CLK, SND_SOC_CLOCK_IN);
+
+ if (err<0) {
+ printk(KERN_ERR "codec_dai clock not set\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops tegra_hifi_ops = {
+ .hw_params = tegra_hifi_hw_params,
+};
+
+
+static int tegra_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = tegra_jack_func;
+ return 0;
+}
+
+static int tegra_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (tegra_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ tegra_jack_func = ucontrol->value.integer.value[0];
+ tegra_ext_control(codec);
+ return 1;
+}
+
+static int tegra_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = tegra_spk_func;
+ return 0;
+}
+
+static int tegra_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+
+ if (tegra_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ tegra_spk_func = ucontrol->value.integer.value[0];
+ tegra_ext_control(codec);
+ return 1;
+}
+
+/*tegra machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_LINE("Line Jack", NULL),
+ SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Tegra machine audio map (connections to the codec pins) */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headset Jack - in = micin, out = LHPOUT*/
+ {"Headset Jack", NULL, "HPOUTL"},
+
+ /* headphone connected to LHPOUT1, RHPOUT1 */
+ {"Headphone Jack", NULL, "HPOUTR"}, {"Headphone Jack", NULL, "HPOUTL"},
+
+ /* speaker connected to LOUT, ROUT */
+ {"Ext Spk", NULL, "LINEOUTR"}, {"Ext Spk", NULL, "LINEOUTL"},
+
+ /* mic is connected to MICIN (via right channel of headphone jack) */
+ {"IN1L", NULL, "Mic Jack"},
+
+ /* Same as the above but no mic bias for line signals */
+ {"IN2L", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"
+ };
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum tegra_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8903_tegra_controls[] = {
+ SOC_ENUM_EXT("Jack Function", tegra_enum[0], tegra_get_jack,
+ tegra_set_jack),
+ SOC_ENUM_EXT("Speaker Function", tegra_enum[1], tegra_get_spk,
+ tegra_set_spk),
+};
+
+
+static int tegra_codec_init(struct snd_soc_codec *codec)
+{
+ int err;
+
+ /* Add tegra specific controls */
+ err = snd_soc_add_controls(codec, wm8903_tegra_controls,
+ ARRAY_SIZE(wm8903_tegra_controls));
+ if (err < 0)
+ return err;
+
+ /* Add tegra specific widgets */
+ snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets,
+ ARRAY_SIZE(wm8903_dapm_widgets));
+
+ /* Set up tegra specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ /* Default to HP output */
+ tegra_jack_func = TEGRA_HP;
+ tegra_spk_func = TEGRA_SPK_ON;
+ tegra_ext_control(codec);
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+extern struct snd_soc_dai tegra_i2s_dai;
+extern struct snd_soc_platform tegra_soc_platform;
+
+static struct snd_soc_dai_link tegra_soc_dai = {
+ .name = "WM8903",
+ .stream_name = "WM8903 HiFi",
+ .cpu_dai = &tegra_i2s_dai,
+ .codec_dai = &wm8903_dai,
+ .init = tegra_codec_init,
+ .ops = &tegra_hifi_ops,
+};
+
+static struct snd_soc_card tegra_snd_soc = {
+ .name = "tegra",
+ .platform = &tegra_soc_platform,
+ .dai_link = &tegra_soc_dai,
+ .num_links = 1,
+};
+
+struct tegra_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+static struct snd_soc_device tegra_snd_devdata = {
+ .card = &tegra_snd_soc,
+ .codec_dev = &soc_codec_dev_wm8903,
+};
+
+static int __init tegra_init(void)
+{
+ int ret;
+ struct tegra_setup_data tegra_setup;
+
+ tegra_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!tegra_snd_device)
+ return -ENOMEM;
+
+ memset(&tegra_setup,0,sizeof(struct tegra_setup_data));
+ platform_set_drvdata(tegra_snd_device, &tegra_snd_devdata);
+ tegra_snd_devdata.dev = &tegra_snd_device->dev;
+ ret = platform_device_add(tegra_snd_device);
+ if (ret) {
+ printk(KERN_ERR "audio device could not be added \n");
+ platform_device_put(tegra_snd_device);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void __exit tegra_exit(void)
+{
+ platform_device_unregister(tegra_snd_device);
+}
+
+module_init(tegra_init);
+module_exit(tegra_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("Tegra ALSA SoC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra_soc.h b/sound/soc/tegra/tegra_soc.h new file mode 100644 index 000000000000..36342d45b9a3 --- /dev/null +++ b/sound/soc/tegra/tegra_soc.h @@ -0,0 +1,100 @@ +/* + * tegra_soc.h -- SoC audio for tegra + * + * (c) 2010 Nvidia Graphics Pvt. Ltd. + * http://www.nvidia.com + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __TEGRA_AUDIO__ +#define __TEGRA_AUDIO__ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/jiffies.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/kthread.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <mach/iomap.h> +#include <mach/i2s_api.h> +#include <mach/irqs.h> +#include <mach/pinmux.h> +#include <mach/audio.h> +#include <mach/dma.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> +#include <sound/soc-dai.h> +#include <sound/tlv.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/hardware/scoop.h> + +#define STATE_INIT 0 +#define STATE_ABORT 1 +#define STATE_EXIT 2 +#define STATE_EXITED 3 +#define STATE_INVALID 4 + +#define APB_MISC_DAS_DAP_CTRL_SEL_0 0xc00 +#define APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_0 0xc40 + +#define FIFO1 0 +#define FIFO2 1 + +#define I2S_IFC 0 +#define I2S_INT INT_I2S1 +#define I2S_NAME "i2s1" +#define I2S_FIFO_TX FIFO1 +#define I2S_I2S_FIFO_TX_BUSY I2S_I2S_STATUS_FIFO1_BSY +#define I2S_I2S_FIFO_TX_QS I2S_I2S_STATUS_QS_FIFO1 +#define I2S_FIFO_RX FIFO2 +#define I2S_I2S_FIFO_RX_BUSY I2S_I2S_STATUS_FIFO2_BSY +#define I2S_I2S_FIFO_RX_QS I2S_I2S_STATUS_QS_FIFO2 + +#define I2S_CLK 11289600 +#define TEGRA_DEFAULT_SR 44100 + +#define TEGRA_SAMPLE_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +struct tegra_dma_channel; + +struct tegra_runtime_data { + struct snd_pcm_substream *substream; + int size; + int dma_pos; + struct tegra_dma_req dma_req1, dma_req2; + volatile int state; + int period_index; + struct tegra_dma_channel *dma_chan; + struct clk *i2s_clk; + struct clk *dap_mclk; + struct clk *audio_sync_clk; +}; + +#endif |