[alsa-devel] [PATCH 0/6] ASoC: add CSR SiRFSoC sound drivers
This patchset adds CSR SiRFSoC sound drivers including: 1. the platform DMA driver which will be shard by all DAI 2. the I2S CPU DAI driver 3. the USP-based PCM CPU DAI driver 4. CPU DAI and Codec driver for internal chip codec 5. the mach driver for EVB board using internal codec
Barry Song (1): arm: prima2: defconfig: enable sound components
Rongjun Ying (5): ASoC: sirf: add sirf platform driver which provides DMA ASoC: sirf: add I2S CPU DAI driver ASoC: usp-pcm: add CPU DAI driver for PCM simulated from USP ASoC: sirf-soc-inner: add drivers for both CPU and Codec DAIs ASoC: sirf-inner: add mach driver for SiRFSoC internal codec
arch/arm/configs/prima2_defconfig | 10 +- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sirf/Kconfig | 18 ++ sound/soc/sirf/Makefile | 11 + sound/soc/sirf/sirf-audio.h | 266 ++++++++++++++++ sound/soc/sirf/sirf-i2s.c | 411 ++++++++++++++++++++++++ sound/soc/sirf/sirf-inner.c | 267 ++++++++++++++++ sound/soc/sirf/sirf-pcm.c | 221 +++++++++++++ sound/soc/sirf/sirf-pcm.h | 17 + sound/soc/sirf/sirf-soc-inner.c | 653 ++++++++++++++++++++++++++++++++++++++ sound/soc/sirf/sirf-usp.c | 481 ++++++++++++++++++++++++++++ sound/soc/sirf/sirf-usp.h | 276 ++++++++++++++++ 13 files changed, 2630 insertions(+), 3 deletions(-) create mode 100644 sound/soc/sirf/Kconfig create mode 100644 sound/soc/sirf/Makefile create mode 100644 sound/soc/sirf/sirf-audio.h create mode 100644 sound/soc/sirf/sirf-i2s.c create mode 100644 sound/soc/sirf/sirf-inner.c create mode 100644 sound/soc/sirf/sirf-pcm.c create mode 100644 sound/soc/sirf/sirf-pcm.h create mode 100644 sound/soc/sirf/sirf-soc-inner.c create mode 100644 sound/soc/sirf/sirf-usp.c create mode 100644 sound/soc/sirf/sirf-usp.h
From: Rongjun Ying Rongjun.Ying@csr.com
this driver uses dmaengine APIs and provides DMA to the CPU DAIs of I2S, USP and SiRF-soc-inner. SiRFSoC has 3 audio DAIs: I2S, USP(Universal Serial Ports) and DAI connected to soc-inner-codec, all of them will use the same DMA driver here.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com Signed-off-by: Barry Song Baohua.Song@csr.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/sirf/Kconfig | 4 + sound/soc/sirf/Makefile | 3 + sound/soc/sirf/sirf-pcm.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/sirf/sirf-pcm.h | 17 ++++ 6 files changed, 247 insertions(+) create mode 100644 sound/soc/sirf/Kconfig create mode 100644 sound/soc/sirf/Makefile create mode 100644 sound/soc/sirf/sirf-pcm.c create mode 100644 sound/soc/sirf/sirf-pcm.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 45eeaa9..2581e55 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -51,6 +51,7 @@ source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index bc02614..e15df84 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig new file mode 100644 index 0000000..3678aed --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC + tristate "Platform DMA driver for the SiRF SoC chips" + depends on ARCH_SIRF && SND_SOC + select SND_SOC_DMAENGINE_PCM diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile new file mode 100644 index 0000000..f268b83 --- /dev/null +++ b/sound/soc/sirf/Makefile @@ -0,0 +1,3 @@ +snd-soc-sirf-objs := sirf-pcm.o + +obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o diff --git a/sound/soc/sirf/sirf-pcm.c b/sound/soc/sirf/sirf-pcm.c new file mode 100644 index 0000000..d4cb897 --- /dev/null +++ b/sound/soc/sirf/sirf-pcm.c @@ -0,0 +1,221 @@ +/* + * ALSA PCM interface for the SiRF SoC + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/sirfsoc_dma.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "sirf-pcm.h" + +static struct snd_pcm_hardware sirf_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_RESUME + | SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_48000), + .rate_min = 512, + .rate_max = 115200, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 128, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 2, +}; + +static int sirf_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sirf_pcm_dma_data *dma_data; + substream->runtime->hw = sirf_pcm_hardware; + snd_soc_set_runtime_hwparams(substream, &sirf_pcm_hardware); + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + return snd_dmaengine_pcm_open_request_chan(substream, + (dma_filter_fn)sirfsoc_dma_filter_id, + (void *)(dma_data->dma_req)); +} + +static int sirf_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sirf_pcm_dma_data *dma_data; + struct dma_slave_config config; + struct dma_chan *chan; + int err = 0; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + chan = snd_dmaengine_pcm_get_chan(substream); + if (!chan) + return -EINVAL; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_8_BYTES; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_8_BYTES; + + config.src_addr = runtime->dma_addr; + config.dst_addr = runtime->dma_addr; + config.src_maxburst = DMA_SLAVE_BUSWIDTH_8_BYTES; + config.dst_maxburst = DMA_SLAVE_BUSWIDTH_8_BYTES; + + return dmaengine_slave_config(chan, &config); +} + +static int sirf_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int sirf_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops sirf_pcm_ops = { + .open = sirf_pcm_open, + .close = snd_dmaengine_pcm_close_release_chan, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sirf_pcm_hw_params, + .hw_free = sirf_pcm_hw_free, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = snd_dmaengine_pcm_pointer, + .mmap = sirf_pcm_mmap, +}; + +static void sirf_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; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int sirf_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 = sirf_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static int sirf_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + int ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = sirf_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = sirf_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + + return 0; + +out: + sirf_pcm_free_dma_buffers(pcm); + return ret; +} + +static struct snd_soc_platform_driver sirf_soc_platform = { + .ops = &sirf_pcm_ops, + .pcm_new = sirf_pcm_new, + .pcm_free = sirf_pcm_free_dma_buffers, +}; + +static int sirf_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &sirf_soc_platform); +} + +static int sirf_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static const struct of_device_id sirf_pcm_of_match[] = { + { .compatible = "sirf,pcm-audio", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_pcm_of_match); + +static struct platform_driver sirf_pcm_driver = { + .driver = { + .name = "sirf-pcm-audio", + .owner = THIS_MODULE, + .of_match_table = sirf_pcm_of_match, + }, + .probe = sirf_pcm_probe, + .remove = sirf_pcm_remove, +}; +module_platform_driver(sirf_pcm_driver); + +MODULE_DESCRIPTION("SiRF PCM audio interface driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sirf/sirf-pcm.h b/sound/soc/sirf/sirf-pcm.h new file mode 100644 index 0000000..c178016 --- /dev/null +++ b/sound/soc/sirf/sirf-pcm.h @@ -0,0 +1,17 @@ +/* + * SiRF pcm dma data struct + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#ifndef __SIRF_PCM_H__ +#define __SIRF_PCM_H__ + +struct sirf_pcm_dma_data { + char *name; /* Stream name */ + int dma_req; /* DMA request line */ +}; + +#endif
On 07/19/2013 01:07 PM, Barry Song wrote:
From: Rongjun Ying Rongjun.Ying@csr.com
this driver uses dmaengine APIs and provides DMA to the CPU DAIs of I2S, USP and SiRF-soc-inner. SiRFSoC has 3 audio DAIs: I2S, USP(Universal Serial Ports) and DAI connected to soc-inner-codec, all of them will use the same DMA driver here.
I think the bulk of the code here can be replaced by using the generic dmaengine PCM driver. Have a look at e.g. mxs to see how that is done.
[...]
+static int sirf_pcm_open(struct snd_pcm_substream *substream) +{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct sirf_pcm_dma_data *dma_data;
- substream->runtime->hw = sirf_pcm_hardware;
- snd_soc_set_runtime_hwparams(substream, &sirf_pcm_hardware);
- dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
- return snd_dmaengine_pcm_open_request_chan(substream,
(dma_filter_fn)sirfsoc_dma_filter_id,
(void *)(dma_data->dma_req));
Since you are using devicetree on this platform use the devicetree to get a handle to the DMA channel.
+}
+static int sirf_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct sirf_pcm_dma_data *dma_data;
- struct dma_slave_config config;
- struct dma_chan *chan;
- int err = 0;
- dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
- runtime->dma_bytes = params_buffer_bytes(params);
- chan = snd_dmaengine_pcm_get_chan(substream);
- if (!chan)
return -EINVAL;
- /* fills in addr_width and direction */
- err = snd_hwparams_to_dma_slave_config(substream, params, &config);
- if (err)
return err;
- config.dst_addr_width = DMA_SLAVE_BUSWIDTH_8_BYTES;
- config.src_addr_width = DMA_SLAVE_BUSWIDTH_8_BYTES;
- config.src_addr = runtime->dma_addr;
- config.dst_addr = runtime->dma_addr;
- config.src_maxburst = DMA_SLAVE_BUSWIDTH_8_BYTES;
- config.dst_maxburst = DMA_SLAVE_BUSWIDTH_8_BYTES;
- return dmaengine_slave_config(chan, &config);
Nothing special in here, just use the generic snd_dmaengine_pcm_prepare_slave_config().
+}
[...]
static const struct of_device_id sirf_pcm_of_match[] = {
- { .compatible = "sirf,pcm-audio", },
- {}
+};
Since this is not a separate piece of hardware, but just the glue logic between the DMA controller and the audio DAI it should not have it's own devicetree node. Usually the the PCM device is registered by the DAI driver. Again take at other platforms using the generic dmaengine PCM driver for examples.
+MODULE_DEVICE_TABLE(of, sirf_pcm_of_match);
+static struct platform_driver sirf_pcm_driver = {
- .driver = {
.name = "sirf-pcm-audio",
.owner = THIS_MODULE,
.of_match_table = sirf_pcm_of_match,
- },
- .probe = sirf_pcm_probe,
- .remove = sirf_pcm_remove,
+}; +module_platform_driver(sirf_pcm_driver);
[...]
On Fri, Jul 19, 2013 at 07:07:17PM +0800, Barry Song wrote:
index 0000000..3678aed --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC
- tristate "Platform DMA driver for the SiRF SoC chips"
- depends on ARCH_SIRF && SND_SOC
- select SND_SOC_DMAENGINE_PCM
Does this have any actual platform build dependencies or can this be ARCH_SIRF || COMPILE_TEST?
2013/7/20 Mark Brown broonie@kernel.org:
On Fri, Jul 19, 2013 at 07:07:17PM +0800, Barry Song wrote:
index 0000000..3678aed --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,4 @@ +config SND_SIRF_SOC
tristate "Platform DMA driver for the SiRF SoC chips"
depends on ARCH_SIRF && SND_SOC
select SND_SOC_DMAENGINE_PCM
Does this have any actual platform build dependencies or can this be ARCH_SIRF || COMPILE_TEST?
it depends on SIRF as it is specific to SiRFSoC, but i think it would be good to have " || COMPILE_TEST"
-barry
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");
On Fri, Jul 19, 2013 at 07:07:18PM +0800, Barry Song wrote:
Pretty good overall - there's a few things below but should all be fairly small.
+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;
+}
device_reset() sounds like it's not going to work for simultaneous playback and capture.
+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);
Do we really need these 1ms delays? They're very long for the context...
+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;
A switch statement for the number of channels would make me happier here.
- 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;
Do we need locking on all these register updates?
- case SND_SOC_DAIFMT_CBS_CFS:
si2s->master_mode = 1;
return -EINVAL;
Should this be undoing the work done for _CBM_CFM?
+#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;
+}
Should these be runtime PM calls as well? Seems like the bus could be unclocked whenever audio is not playing. If you need the clock to write conifguraiton perhaps regmap-mmio could help?
- spin_lock_init(&si2s->lock);
What is this lock actually protecting?
2013/7/20 Mark Brown broonie@kernel.org:
On Fri, Jul 19, 2013 at 07:07:18PM +0800, Barry Song wrote:
Pretty good overall - there's a few things below but should all be fairly small.
+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;
+}
device_reset() sounds like it's not going to work for simultaneous playback and capture.
right. i'll have rongjun to check whether this reset is necessary or it can be moved to other place. as here playback can break capture if we start a playback when we are capturing here.
+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);
Do we really need these 1ms delays? They're very long for the context...
the hardware does need a delay to be stable as said by hardware guys
+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;
A switch statement for the number of channels would make me happier here.
here there is some hardware set to explain.
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;
Do we need locking on all these register updates?
yes if considering here it is happening read-modify-write.
case SND_SOC_DAIFMT_CBS_CFS:
si2s->master_mode = 1;
return -EINVAL;
Should this be undoing the work done for _CBM_CFM?
it seems it need to rollback some registers. i'll have rongjun to check that.
+#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;
+}
Should these be runtime PM calls as well? Seems like the bus could be unclocked whenever audio is not playing. If you need the clock to write conifguraiton perhaps regmap-mmio could help?
i will make the runtime PM happen for this and other drivers on sirf platform.
spin_lock_init(&si2s->lock);
What is this lock actually protecting?
sirf_i2s_trigger().
-barry
From: Rongjun Ying Rongjun.Ying@csr.com
Universal Serial Ports (USP) can be used as PCM. this patch adds support for this functionality.
The USP provides 128-byte data FIFO which supports DMA and I/O modes. Here we use the common DMA driver.
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-usp.c | 481 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/sirf/sirf-usp.h | 276 ++++++++++++++++++++++++++ 4 files changed, 762 insertions(+) create mode 100644 sound/soc/sirf/sirf-usp.c create mode 100644 sound/soc/sirf/sirf-usp.h
diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index d98acd4..3606614 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -5,3 +5,6 @@ config SND_SIRF_SOC
config SND_SOC_SIRF_I2S tristate + +config SND_SOC_SIRF_USP + tristate diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index 9f754fe..630c9be 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,5 +1,7 @@ snd-soc-sirf-objs := sirf-pcm.o snd-soc-sirf-i2s-objs := sirf-i2s.o +snd-soc-sirf-usp-objs := sirf-usp.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o +obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-usp.c b/sound/soc/sirf/sirf-usp.c new file mode 100644 index 0000000..107aa4b --- /dev/null +++ b/sound/soc/sirf/sirf-usp.c @@ -0,0 +1,481 @@ +/* + * SiRF USP audio transfer interface like I2S + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <sound/soc.h> + +#include "sirf-usp.h" +#include "sirf-pcm.h" + +#define FIFO_RESET 0 +#define FIFO_START 1 +#define FIFO_STOP 2 + +#define AUDIO_WORD_SIZE 16 + +struct sirf_usp { + void __iomem *base; + struct clk *clk; + u32 mode1_reg; + u32 mode2_reg; +}; + +static struct sirf_pcm_dma_data sirf_usp_pcm_dai_dma_data[2] = { + { + .name = "Audio Playback", + }, { + .name = "Audio Capture", + } +}; +static void sirf_usp_tx_fifo_op(struct sirf_usp *susp, int cmd) +{ + switch (cmd) { + case FIFO_RESET: + writel(USP_TX_FIFO_RESET, susp->base + USP_TX_FIFO_OP); + writel(0, susp->base + USP_TX_FIFO_OP); + break; + case FIFO_START: + writel(USP_TX_FIFO_START, susp->base + USP_TX_FIFO_OP); + break; + case FIFO_STOP: + writel(0, susp->base + USP_TX_FIFO_OP); + break; + } +} + +static void sirf_usp_rx_fifo_op(struct sirf_usp *susp, int cmd) +{ + switch (cmd) { + case FIFO_RESET: + writel(USP_RX_FIFO_RESET, susp->base + USP_RX_FIFO_OP); + writel(0, susp->base + USP_RX_FIFO_OP); + break; + case FIFO_START: + writel(USP_RX_FIFO_START, susp->base + USP_RX_FIFO_OP); + break; + case FIFO_STOP: + writel(0, susp->base + USP_RX_FIFO_OP); + break; + } +} + +static inline void sirf_usp_tx_enable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) | USP_TX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static inline void sirf_usp_tx_disable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) & ~USP_TX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static inline void sirf_usp_rx_enable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) | USP_RX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static inline void sirf_usp_rx_disable(struct sirf_usp *susp) +{ + writel(readl(susp->base + USP_TX_RX_ENABLE) & ~USP_RX_ENA, + susp->base + USP_TX_RX_ENABLE); +} + +static int sirf_usp_pcm_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + if (playback) + sirf_usp_tx_fifo_op(susp, FIFO_RESET); + else + sirf_usp_rx_fifo_op(susp, FIFO_RESET); + + snd_soc_dai_set_dma_data(dai, substream, + &sirf_usp_pcm_dai_dma_data[substream->stream]); + return 0; +} + +static int sirf_usp_pcm_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + u32 val = readl(susp->base + USP_MODE2); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dev_info(dai->dev, "USP master mode is not supported.\n"); + return -EINVAL; + case SND_SOC_DAIFMT_CBM_CFM: + writel(readl(susp->base + USP_MODE1) + | USP_CLOCK_MODE_SLAVE, susp->base + USP_MODE1); + val |= (USP_TFS_CLK_SLAVE_MODE); + val |= (USP_RFS_CLK_SLAVE_MODE); + break; + default: + return -EINVAL; + } + writel(val, susp->base + USP_MODE2); + + return 0; +} + +static int sirf_usp_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (playback) { + sirf_usp_tx_fifo_op(susp, FIFO_RESET); + sirf_usp_tx_fifo_op(susp, FIFO_START); + sirf_usp_tx_enable(susp); + } else { + sirf_usp_rx_fifo_op(susp, FIFO_RESET); + sirf_usp_rx_fifo_op(susp, FIFO_START); + sirf_usp_rx_enable(susp); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (playback) { + sirf_usp_tx_disable(susp); + sirf_usp_tx_fifo_op(susp, FIFO_STOP); + } else { + sirf_usp_rx_disable(susp); + sirf_usp_rx_fifo_op(susp, FIFO_STOP); + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (playback) { + sirf_usp_tx_fifo_op(susp, FIFO_RESET); + sirf_usp_tx_fifo_op(susp, FIFO_START); + sirf_usp_tx_enable(susp); + } else { + sirf_usp_rx_fifo_op(susp, FIFO_RESET); + sirf_usp_rx_fifo_op(susp, FIFO_START); + sirf_usp_rx_enable(susp); + } + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (playback) { + sirf_usp_tx_disable(susp); + sirf_usp_tx_fifo_op(susp, FIFO_STOP); + } else { + sirf_usp_rx_disable(susp); + sirf_usp_rx_fifo_op(susp, FIFO_STOP); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (playback) { + sirf_usp_tx_disable(susp); + sirf_usp_tx_fifo_op(susp, FIFO_STOP); + } else { + sirf_usp_rx_disable(susp); + sirf_usp_rx_fifo_op(susp, FIFO_STOP); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (playback) { + sirf_usp_tx_fifo_op(susp, FIFO_START); + sirf_usp_tx_enable(susp); + } else { + sirf_usp_rx_fifo_op(susp, FIFO_START); + sirf_usp_rx_enable(susp); + } + break; + } + + return 0; +} + +static int sirf_usp_pcm_divider(struct snd_soc_dai *dai, int div_id, int rate) +{ + struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai); + + u32 clk_rate = clk_get_rate(susp->clk); + u32 clk_div = (clk_rate/(2*rate)) - 1; + u32 clk_div_hi = (clk_div & 0xC00)>>10; + u32 clk_div_lo = (clk_div & 0x3FF); + + writel((clk_div_lo<<21) | readl(susp->base + USP_MODE2), + susp->base + USP_MODE2); + writel((clk_div_hi<<30) | readl(susp->base + USP_TX_FRAME_CTRL), + susp->base + USP_TX_FRAME_CTRL); + + return 0; +} + +static const struct snd_soc_dai_ops sirf_usp_pcm_dai_ops = { + .startup = sirf_usp_pcm_dai_startup, + .trigger = sirf_usp_pcm_trigger, + .set_fmt = sirf_usp_pcm_set_dai_fmt, + .set_clkdiv = sirf_usp_pcm_divider, +}; + +static struct snd_soc_dai_driver sirf_usp_pcm_dai = { + .name = "sirf-usp-pcm", + .id = 0, + .playback = { + .stream_name = "SiRF USP PCM Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_48000 + | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_32000 + | SNDRV_PCM_RATE_22050 + | SNDRV_PCM_RATE_16000 + | SNDRV_PCM_RATE_11025 + | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "SiRF USP PCM Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_48000 + | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_32000 + | SNDRV_PCM_RATE_22050 + | SNDRV_PCM_RATE_16000 + | SNDRV_PCM_RATE_11025 + | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sirf_usp_pcm_dai_ops, +}; + +static void sirf_usp_controller_init(struct sirf_usp *susp) +{ + u32 val; + + /* Configure RISC mode */ + writel(readl(susp->base + USP_RISC_DSP_MODE) & ~USP_RISC_DSP_SEL, + susp->base + USP_RISC_DSP_MODE); + + /* Disable all interrupts status */ + writel(readl(susp->base + USP_INT_STATUS), susp->base + USP_INT_STATUS); + + /* Configure DMA IO Length register */ + writel(0, susp->base + USP_TX_DMA_IO_LEN); + writel(0, susp->base + USP_RX_DMA_IO_LEN); + + /* Configure RX Frame Control */ + val = (AUDIO_WORD_SIZE*2 - 1)<<USP_RXC_DATA_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE*2 - 1)<<USP_RXC_FRAME_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE*2 - 1)<<USP_RXC_SHIFTER_LEN_OFFSET; + val |= USP_SINGLE_SYNC_MODE; + writel(val, susp->base + USP_RX_FRAME_CTRL); + + /* Configure TX Frame Control */ + val = (AUDIO_WORD_SIZE*2 - 1)<<USP_TXC_DATA_LEN_OFFSET; + val |= 0<<USP_TXC_SYNC_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE*2 - 1)<<USP_TXC_FRAME_LEN_OFFSET; + val |= (AUDIO_WORD_SIZE*2 - 1)<<USP_TXC_SHIFTER_LEN_OFFSET; + val |= USP_TXC_SLAVE_CLK_SAMPLE; + writel(val, susp->base + USP_TX_FRAME_CTRL); + + /* Configure Mode2 register */ + val = (1<<USP_RXD_DELAY_LEN_OFFSET) | (0<<USP_TXD_DELAY_LEN_OFFSET); + val &= ~USP_ENA_CTRL_MODE; + val &= ~USP_FRAME_CTRL_MODE; + val &= ~USP_TFS_SOURCE_MODE; + writel(val, susp->base + USP_MODE2); + + /* Configure Mode1 register */ + val = 0; + val |= USP_SYNC_MODE; + val |= USP_ENDIAN_CTRL_LSBF; + val |= USP_EN; + val |= USP_RXD_ACT_EDGE_FALLING; + val &= ~USP_TXD_ACT_EDGE_FALLING; + val |= USP_RFS_ACT_LEVEL_LOGIC1; + val |= USP_TFS_ACT_LEVEL_LOGIC1; + val |= USP_SCLK_IDLE_MODE_TOGGLE; + val |= USP_SCLK_IDLE_LEVEL_LOGIC1; + val &= ~USP_SCLK_PIN_MODE_IO; + val &= ~USP_RFS_PIN_MODE_IO; + val &= ~USP_TFS_PIN_MODE_IO; + val &= ~USP_RXD_PIN_MODE_IO; + val &= ~USP_TXD_PIN_MODE_IO; + val |= USP_TX_UFLOW_REPEAT_ZERO; + writel(val, susp->base + USP_MODE1); + + /* Configure RX DMA IO Control register */ + writel(0x0, susp->base + USP_RX_DMA_IO_CTRL); + + /* Congiure RX FIFO Control register */ + writel((USP_RX_FIFO_THRESHOLD << USP_RX_FIFO_THD_OFFSET) | + (USP_TX_RX_FIFO_WIDTH_DWORD << USP_RX_FIFO_WIDTH_OFFSET), + susp->base + USP_RX_FIFO_CTRL); + + /* Congiure RX FIFO Level Check register */ + writel(RX_FIFO_SC(0x04)|RX_FIFO_LC(0x0E)|RX_FIFO_HC(0x1B), + susp->base + USP_RX_FIFO_LEVEL_CHK); + + /* Configure TX DMA IO Control register*/ + writel(0x0, susp->base + USP_TX_DMA_IO_CTRL); + + /* Configure TX FIFO Control register */ + writel((USP_TX_FIFO_THRESHOLD << USP_TX_FIFO_THD_OFFSET) | + (USP_TX_RX_FIFO_WIDTH_DWORD << USP_TX_FIFO_WIDTH_OFFSET), + susp->base + USP_TX_FIFO_CTRL); + + /* Congiure TX FIFO Level Check register */ + writel(TX_FIFO_SC(0x1B)|TX_FIFO_LC(0x0E)|TX_FIFO_HC(0x04), + susp->base + USP_TX_FIFO_LEVEL_CHK); + + /* Configure RX FIFO */ + writel(USP_RX_FIFO_RESET, susp->base + USP_RX_FIFO_OP); + writel(0, susp->base + USP_RX_FIFO_OP); + + /* Configure TX FIFO */ + writel(USP_TX_FIFO_RESET, susp->base + USP_TX_FIFO_OP); + writel(0, susp->base + USP_TX_FIFO_OP); +} + +static void sirf_usp_controller_uninit(struct sirf_usp *susp) +{ + /* Disable RX/TX */ + writel(0, susp->base+USP_INT_ENABLE); + writel(0, susp->base + USP_TX_RX_ENABLE); +} + +#ifdef CONFIG_PM +static int sirf_usp_pcm_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct sirf_usp *susp = platform_get_drvdata(pdev); + + susp->mode1_reg = readl(susp->base + USP_MODE1); + susp->mode2_reg = readl(susp->base + USP_MODE2); + sirf_usp_controller_uninit(susp); + clk_disable_unprepare(susp->clk); + + return 0; +} + +static int sirf_usp_pcm_resume(struct platform_device *pdev) +{ + struct sirf_usp *susp = platform_get_drvdata(pdev); + + clk_prepare_enable(susp->clk); + sirf_usp_controller_init(susp); + + writel(susp->mode1_reg, susp->base + USP_MODE1); + writel(susp->mode2_reg, susp->base + USP_MODE2); + + return 0; +} +#else +#define sirf_usp_pcm_suspend NULL +#define sirf_usp_pcm_resume NULL +#endif + +static const struct snd_soc_component_driver sirf_usp_component = { + .name = "sirf-usp", +}; + +static int sirf_usp_pcm_probe(struct platform_device *pdev) +{ + struct sirf_usp *susp; + u32 rx_dma_ch, tx_dma_ch; + int ret; + struct resource *mem_res; + + susp = devm_kzalloc(&pdev->dev, sizeof(struct sirf_usp), + GFP_KERNEL); + if (!susp) + return -ENOMEM; + + platform_set_drvdata(pdev, susp); + + ret = of_property_read_u32(pdev->dev.of_node, + "sirf,usp-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,usp-dma-tx-channel", &tx_dma_ch); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to USP0 tx dma channel\n"); + return ret; + } + sirf_usp_pcm_dai_dma_data[0].dma_req = tx_dma_ch; + sirf_usp_pcm_dai_dma_data[1].dma_req = rx_dma_ch; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + susp->base = devm_ioremap_resource(&pdev->dev, mem_res); + if (susp->base == NULL) + return -ENOMEM; + + susp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(susp->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + return PTR_ERR(susp->clk); + } + clk_prepare_enable(susp->clk); + + sirf_usp_controller_init(susp); + + ret = snd_soc_register_component(&pdev->dev, &sirf_usp_component, + &sirf_usp_pcm_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(susp->clk); + clk_put(susp->clk); + return ret; +} + +static int sirf_usp_pcm_remove(struct platform_device *pdev) +{ + struct sirf_usp *susp = platform_get_drvdata(pdev); + snd_soc_unregister_component(&pdev->dev); + sirf_usp_controller_uninit(susp); + clk_disable_unprepare(susp->clk); + clk_put(susp->clk); + + return 0; +} + +static const struct of_device_id sirf_usp_pcm_of_match[] = { + { .compatible = "sirf,prima2-usp-pcm", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_usp_pcm_of_match); + +static struct platform_driver sirf_usp_pcm_driver = { + .driver = { + .name = "sirf-usp-pcm", + .owner = THIS_MODULE, + .of_match_table = sirf_usp_pcm_of_match, + }, + .probe = sirf_usp_pcm_probe, + .remove = sirf_usp_pcm_remove, + .suspend = sirf_usp_pcm_suspend, + .resume = sirf_usp_pcm_resume, +}; + +module_platform_driver(sirf_usp_pcm_driver); + +MODULE_DESCRIPTION("SiRF SoC USP PCM bus driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sirf/sirf-usp.h b/sound/soc/sirf/sirf-usp.h new file mode 100644 index 0000000..cb9085f --- /dev/null +++ b/sound/soc/sirf/sirf-usp.h @@ -0,0 +1,276 @@ +/* + * arch/arm/mach-prima2/include/mach/sirfsoc_usp.h + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#ifndef __SIRFSOC_USP__ +#define __SIRFSOC_USP__ + +/* USP Registers */ +#define USP_MODE1 0x00 +#define USP_MODE2 0x04 +#define USP_TX_FRAME_CTRL 0x08 +#define USP_RX_FRAME_CTRL 0x0C +#define USP_TX_RX_ENABLE 0x10 +#define USP_INT_ENABLE 0x14 +#define USP_INT_STATUS 0x18 +#define USP_PIN_IO_DATA 0x1C +#define USP_RISC_DSP_MODE 0x20 +#define USP_AYSNC_PARAM_REG 0x24 +#define USP_IRDA_X_MODE_DIV 0x28 +#define USP_SM_CFG 0x2C +#define USP_TX_DMA_IO_CTRL 0x100 +#define USP_TX_DMA_IO_LEN 0x104 +#define USP_TX_FIFO_CTRL 0x108 +#define USP_TX_FIFO_LEVEL_CHK 0x10C +#define USP_TX_FIFO_OP 0x110 +#define USP_TX_FIFO_STATUS 0x114 +#define USP_TX_FIFO_DATA 0x118 +#define USP_RX_DMA_IO_CTRL 0x120 +#define USP_RX_DMA_IO_LEN 0x124 +#define USP_RX_FIFO_CTRL 0x128 +#define USP_RX_FIFO_LEVEL_CHK 0x12C +#define USP_RX_FIFO_OP 0x130 +#define USP_RX_FIFO_STATUS 0x134 +#define USP_RX_FIFO_DATA 0x138 + +/* USP MODE register-1 */ +#define USP_SYNC_MODE 0x00000001 +#define USP_CLOCK_MODE_SLAVE 0x00000002 +#define USP_LOOP_BACK_EN 0x00000004 +#define USP_HPSIR_EN 0x00000008 +#define USP_ENDIAN_CTRL_LSBF 0x00000010 +#define USP_EN 0x00000020 +#define USP_RXD_ACT_EDGE_FALLING 0x00000040 +#define USP_TXD_ACT_EDGE_FALLING 0x00000080 +#define USP_RFS_ACT_LEVEL_LOGIC1 0x00000100 +#define USP_TFS_ACT_LEVEL_LOGIC1 0x00000200 +#define USP_SCLK_IDLE_MODE_TOGGLE 0x00000400 +#define USP_SCLK_IDLE_LEVEL_LOGIC1 0x00000800 +#define USP_SCLK_PIN_MODE_IO 0x00001000 +#define USP_RFS_PIN_MODE_IO 0x00002000 +#define USP_TFS_PIN_MODE_IO 0x00004000 +#define USP_RXD_PIN_MODE_IO 0x00008000 +#define USP_TXD_PIN_MODE_IO 0x00010000 +#define USP_SCLK_IO_MODE_INPUT 0x00020000 +#define USP_RFS_IO_MODE_INPUT 0x00040000 +#define USP_TFS_IO_MODE_INPUT 0x00080000 +#define USP_RXD_IO_MODE_INPUT 0x00100000 +#define USP_TXD_IO_MODE_INPUT 0x00200000 +#define USP_IRDA_WIDTH_DIV_MASK 0x3FC00000 +#define USP_IRDA_WIDTH_DIV_OFFSET 0 +#define USP_IRDA_IDLE_LEVEL_HIGH 0x40000000 +#define USP_TX_UFLOW_REPEAT_ZERO 0x80000000 +#define USP_TX_ENDIAN_MODE 0x00000020 +#define USP_RX_ENDIAN_MODE 0x00000020 + +/* USP Mode Register-2 */ +#define USP_RXD_DELAY_LEN_MASK 0x000000FF +#define USP_RXD_DELAY_LEN_OFFSET 0 + +#define USP_TXD_DELAY_LEN_MASK 0x0000FF00 +#define USP_TXD_DELAY_LEN_OFFSET 8 + +#define USP_ENA_CTRL_MODE 0x00010000 +#define USP_FRAME_CTRL_MODE 0x00020000 +#define USP_TFS_SOURCE_MODE 0x00040000 +#define USP_TFS_MS_MODE 0x00080000 +#define USP_CLK_DIVISOR_MASK 0x7FE00000 +#define USP_CLK_DIVISOR_OFFSET 21 + +#define USP_TFS_CLK_SLAVE_MODE (1<<20) +#define USP_RFS_CLK_SLAVE_MODE (1<<19) + +#define USP_IRDA_DATA_WIDTH 0x80000000 + +/* USP Transmit Frame Control Register */ + +#define USP_TXC_DATA_LEN_MASK 0x000000FF +#define USP_TXC_DATA_LEN_OFFSET 0 + +#define USP_TXC_SYNC_LEN_MASK 0x0000FF00 +#define USP_TXC_SYNC_LEN_OFFSET 8 + +#define USP_TXC_FRAME_LEN_MASK 0x00FF0000 +#define USP_TXC_FRAME_LEN_OFFSET 16 + +#define USP_TXC_SHIFTER_LEN_MASK 0x1F000000 +#define USP_TXC_SHIFTER_LEN_OFFSET 24 + +#define USP_TXC_SLAVE_CLK_SAMPLE 0x20000000 + +#define USP_TXC_CLK_DIVISOR_MASK 0xC0000000 +#define USP_TXC_CLK_DIVISOR_OFFSET 30 + +/* USP Receive Frame Control Register */ + +#define USP_RXC_DATA_LEN_MASK 0x000000FF +#define USP_RXC_DATA_LEN_OFFSET 0 + +#define USP_RXC_FRAME_LEN_MASK 0x0000FF00 +#define USP_RXC_FRAME_LEN_OFFSET 8 + +#define USP_RXC_SHIFTER_LEN_MASK 0x001F0000 +#define USP_RXC_SHIFTER_LEN_OFFSET 16 + +#define USP_I2S_SYNC_CHG 0x00200000 + +#define USP_RXC_CLK_DIVISOR_MASK 0x0F000000 +#define USP_RXC_CLK_DIVISOR_OFFSET 24 +#define USP_SINGLE_SYNC_MODE 0x00400000 + +/* Tx - RX Enable Register */ + +#define USP_RX_ENA 0x00000001 +#define USP_TX_ENA 0x00000002 + +/* USP Interrupt Enable and status Register */ +#define USP_RX_DONE_INT 0x00000001 +#define USP_TX_DONE_INT 0x00000002 +#define USP_RX_OFLOW_INT 0x00000004 +#define USP_TX_UFLOW_INT 0x00000008 +#define USP_RX_IO_DMA_INT 0x00000010 +#define USP_TX_IO_DMA_INT 0x00000020 +#define USP_RXFIFO_FULL_INT 0x00000040 +#define USP_TXFIFO_EMPTY_INT 0x00000080 +#define USP_RXFIFO_THD_INT 0x00000100 +#define USP_TXFIFO_THD_INT 0x00000200 +#define USP_UART_FRM_ERR_INT 0x00000400 +#define USP_RX_TIMEOUT_INT 0x00000800 +#define USP_TX_ALLOUT_INT 0x00001000 +#define USP_RXD_BREAK_INT 0x00008000 + +/* All possible TX interruots */ +#define USP_TX_INTERRUPT (USP_TX_DONE_INT|USP_TX_UFLOW_INT|USP_TX_IO_DMA_INT|\ + USP_TXFIFO_EMPTY_INT|USP_TXFIFO_THD_INT) +/* All possible RX interruots */ +#define USP_RX_INTERRUPT (USP_RX_DONE_INT|USP_RX_OFLOW_INT|USP_RX_IO_DMA_INT|\ + USP_RXFIFO_FULL_INT|USP_RXFIFO_THD_INT|USP_RXFIFO_THD_INT|USP_RX_TIMEOUT_INT) + +#define USP_INT_ALL 0x1FFF + +/* USP Pin I/O Data Register */ + +#define USP_RFS_PIN_VALUE_MASK 0x00000001 +#define USP_TFS_PIN_VALUE_MASK 0x00000002 +#define USP_RXD_PIN_VALUE_MASK 0x00000004 +#define USP_TXD_PIN_VALUE_MASK 0x00000008 +#define USP_SCLK_PIN_VALUE_MASK 0x00000010 + +/* USP RISC/DSP Mode Register */ +#define USP_RISC_DSP_SEL 0x00000001 + +/* USP ASYNC PARAMETER Register*/ + +#define USP_ASYNC_TIMEOUT_MASK 0x0000FFFF +#define USP_ASYNC_TIMEOUT_OFFSET 0 +#define USP_ASYNC_TIMEOUT(x) (((x)&USP_ASYNC_TIMEOUT_MASK)<<USP_ASYNC_TIMEOUT_OFFSET) + +#define USP_ASYNC_DIV2_MASK 0x003F0000 +#define USP_ASYNC_DIV2_OFFSET 16 + +/* USP TX DMA I/O MODE Register */ +#define USP_TX_MODE_IO 0x00000001 + +/* USP TX DMA I/O Length Register */ +#define USP_TX_DATA_LEN_MASK 0xFFFFFFFF +#define USP_TX_DATA_LEN_OFFSET 0 + +/* USP TX FIFO Control Register */ +#define USP_TX_FIFO_WIDTH_MASK 0x00000003 +#define USP_TX_FIFO_WIDTH_OFFSET 0 + +#define USP_TX_FIFO_THD_MASK 0x000001FC +#define USP_TX_FIFO_THD_OFFSET 2 + +/* USP TX FIFO Level Check Register */ +#define USP_TX_FIFO_LEVEL_CHECK_MASK 0x1F +#define USP_TX_FIFO_SC_OFFSET 0 +#define USP_TX_FIFO_LC_OFFSET 10 +#define USP_TX_FIFO_HC_OFFSET 20 + +#define TX_FIFO_SC(x) (((x) & USP_TX_FIFO_LEVEL_CHECK_MASK) << USP_TX_FIFO_SC_OFFSET) +#define TX_FIFO_LC(x) (((x) & USP_TX_FIFO_LEVEL_CHECK_MASK) << USP_TX_FIFO_LC_OFFSET) +#define TX_FIFO_HC(x) (((x) & USP_TX_FIFO_LEVEL_CHECK_MASK) << USP_TX_FIFO_HC_OFFSET) + +/* USP TX FIFO Operation Register */ +#define USP_TX_FIFO_RESET 0x00000001 +#define USP_TX_FIFO_START 0x00000002 + +/* USP TX FIFO Status Register */ +#define USP_TX_FIFO_LEVEL_MASK 0x0000007F +#define USP_TX_FIFO_LEVEL_OFFSET 0 + +#define USP_TX_FIFO_FULL 0x00000080 +#define USP_TX_FIFO_EMPTY 0x00000100 + +/* USP TX FIFO Data Register */ +#define USP_TX_FIFO_DATA_MASK 0xFFFFFFFF +#define USP_TX_FIFO_DATA_OFFSET 0 + +/* USP RX DMA I/O MODE Register */ +#define USP_RX_MODE_IO 0x00000001 +#define USP_RX_DMA_FLUSH 0x00000004 + +/* USP RX DMA I/O Length Register */ +#define USP_RX_DATA_LEN_MASK 0xFFFFFFFF +#define USP_RX_DATA_LEN_OFFSET 0 + +/* USP RX FIFO Control Register */ +#define USP_RX_FIFO_WIDTH_MASK 0x00000003 +#define USP_RX_FIFO_WIDTH_OFFSET 0 + +#define USP_RX_FIFO_THD_MASK 0x000001FC +#define USP_RX_FIFO_THD_OFFSET 2 + +/* USP RX FIFO Level Check Register */ + +#define USP_RX_FIFO_LEVEL_CHECK_MASK 0x1F +#define USP_RX_FIFO_SC_OFFSET 0 +#define USP_RX_FIFO_LC_OFFSET 10 +#define USP_RX_FIFO_HC_OFFSET 20 + +#define RX_FIFO_SC(x) (((x) & USP_RX_FIFO_LEVEL_CHECK_MASK) << USP_RX_FIFO_SC_OFFSET) +#define RX_FIFO_LC(x) (((x) & USP_RX_FIFO_LEVEL_CHECK_MASK) << USP_RX_FIFO_LC_OFFSET) +#define RX_FIFO_HC(x) (((x) & USP_RX_FIFO_LEVEL_CHECK_MASK) << USP_RX_FIFO_HC_OFFSET) + +/* USP RX FIFO Operation Register */ +#define USP_RX_FIFO_RESET 0x00000001 +#define USP_RX_FIFO_START 0x00000002 + +/* USP RX FIFO Status Register */ + +#define USP_RX_FIFO_LEVEL_MASK 0x0000007F +#define USP_RX_FIFO_LEVEL_OFFSET 0 + +#define USP_RX_FIFO_FULL 0x00000080 +#define USP_RX_FIFO_EMPTY 0x00000100 + +/* USP RX FIFO Data Register */ + +#define USP_RX_FIFO_DATA_MASK 0xFFFFFFFF +#define USP_RX_FIFO_DATA_OFFSET 0 + +/* + * When rx thd irq occur, sender just disable tx empty irq, + * Remaining data in tx fifo wil also be sent out. + */ +#define USP_FIFO_SIZE 128 +#define USP_TX_FIFO_THRESHOLD (USP_FIFO_SIZE/2) +#define USP_RX_FIFO_THRESHOLD (USP_FIFO_SIZE/2) + +/* FIFO_WIDTH for the USP_TX_FIFO_CTRL and USP_RX_FIFO_CTRL registers */ +#define USP_FIFO_WIDTH_BYTE 0x00 +#define USP_FIFO_WIDTH_WORD 0x01 +#define USP_FIFO_WIDTH_DWORD 0x02 + +#define USP_ASYNC_DIV2 16 + +#define USP_PLUGOUT_RETRY_CNT 2 + +#define USP_TX_RX_FIFO_WIDTH_DWORD 2 + +#endif
On Fri, Jul 19, 2013 at 07:07:19PM +0800, Barry Song wrote:
+static int sirf_usp_pcm_divider(struct snd_soc_dai *dai, int div_id, int rate) +{
- struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai);
- u32 clk_rate = clk_get_rate(susp->clk);
- u32 clk_div = (clk_rate/(2*rate)) - 1;
- u32 clk_div_hi = (clk_div & 0xC00)>>10;
- u32 clk_div_lo = (clk_div & 0x3FF);
- writel((clk_div_lo<<21) | readl(susp->base + USP_MODE2),
susp->base + USP_MODE2);
- writel((clk_div_hi<<30) | readl(susp->base + USP_TX_FRAME_CTRL),
susp->base + USP_TX_FRAME_CTRL);
- return 0;
+}
I'd be happier if this did some validation on the supplied rate (for example, checking that it's less than the master rate) and had arguments specifying which clock was being affected (in case there's more in future revisions).
Also what exactly is the rate being set - can the driver just figure this out automatically from the sample rate?
+#ifdef CONFIG_PM +static int sirf_usp_pcm_suspend(struct platform_device *pdev,
- pm_message_t state)
+{
- struct sirf_usp *susp = platform_get_drvdata(pdev);
- susp->mode1_reg = readl(susp->base + USP_MODE1);
- susp->mode2_reg = readl(susp->base + USP_MODE2);
- sirf_usp_controller_uninit(susp);
- clk_disable_unprepare(susp->clk);
- return 0;
+}
Can this be done as runtime PM as well? Seems like it'd save power.
- susp->clk = clk_get(&pdev->dev, NULL);
- if (IS_ERR(susp->clk)) {
dev_err(&pdev->dev, "Get clock failed.\n");
return PTR_ERR(susp->clk);
- }
devm_clk_get().
2013/7/20 Mark Brown broonie@kernel.org:
On Fri, Jul 19, 2013 at 07:07:19PM +0800, Barry Song wrote:
+static int sirf_usp_pcm_divider(struct snd_soc_dai *dai, int div_id, int rate) +{
struct sirf_usp *susp = snd_soc_dai_get_drvdata(dai);
u32 clk_rate = clk_get_rate(susp->clk);
u32 clk_div = (clk_rate/(2*rate)) - 1;
u32 clk_div_hi = (clk_div & 0xC00)>>10;
u32 clk_div_lo = (clk_div & 0x3FF);
writel((clk_div_lo<<21) | readl(susp->base + USP_MODE2),
susp->base + USP_MODE2);
writel((clk_div_hi<<30) | readl(susp->base + USP_TX_FRAME_CTRL),
susp->base + USP_TX_FRAME_CTRL);
return 0;
+}
I'd be happier if this did some validation on the supplied rate (for example, checking that it's less than the master rate) and had arguments specifying which clock was being affected (in case there's more in future revisions).
sounds good.
Also what exactly is the rate being set - can the driver just figure this out automatically from the sample rate?
here it is generating the right pcm clock by an internal DIVISOR in USP module.
For USP_MODE2: 30:21 (R/W) USP_CLK_DIVISOR 10’h0 USP serial clock divider
For USP_TX_FRAME_CTRL: 31:30 (R/W) USP_CLK_DIVISOR 2’h0 This is the two bit [11:10] of USP_CLK_DIVISOR in USP_MODE2
for figuring this out automatically from the sample rate, do you mean a lookup table for common sample rate?
+#ifdef CONFIG_PM +static int sirf_usp_pcm_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct sirf_usp *susp = platform_get_drvdata(pdev);
susp->mode1_reg = readl(susp->base + USP_MODE1);
susp->mode2_reg = readl(susp->base + USP_MODE2);
sirf_usp_controller_uninit(susp);
clk_disable_unprepare(susp->clk);
return 0;
+}
Can this be done as runtime PM as well? Seems like it'd save power.
agree.
susp->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(susp->clk)) {
dev_err(&pdev->dev, "Get clock failed.\n");
return PTR_ERR(susp->clk);
}
devm_clk_get().
agree. missed this one.
-barry
On Thu, Jul 25, 2013 at 05:32:12PM +0800, Barry Song wrote:
2013/7/20 Mark Brown broonie@kernel.org:
Also what exactly is the rate being set - can the driver just figure this out automatically from the sample rate?
here it is generating the right pcm clock by an internal DIVISOR in USP module.
For USP_MODE2: 30:21 (R/W) USP_CLK_DIVISOR 10’h0 USP serial clock divider
For USP_TX_FRAME_CTRL: 31:30 (R/W) USP_CLK_DIVISOR 2’h0 This is the two bit [11:10] of USP_CLK_DIVISOR in USP_MODE2
for figuring this out automatically from the sample rate, do you mean a lookup table for common sample rate?
That or just calculating it - if you know the input rate and know the output rate you need then for a simple divisor it should be possible to calculate the required value.
From: Rongjun Ying Rongjun.Ying@csr.com
there is an internal codec embedded in the SiRF SoC. this is not a typical user scenerios of ASoC. but we can still get benefit by sharing platform DMA codes instead of implementing a pure ALSA driver. This driver adds DAI drivers for this internal codec.
The features of Internal Codec Controller include: Support two channel 16-bit resolution playback with fix 48KHz sample rate Support two channel 16-bit resolution record with fix 48KHz sample rate Use dedicated Internal Codec TXFIFO and Internal Codec RXFIFO Supports two DMA channels for Internal Codec TXFIFO and Internal Codec RXFIFO
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-soc-inner.c | 653 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 658 insertions(+) create mode 100644 sound/soc/sirf/sirf-soc-inner.c
diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index 3606614..60b8857 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -6,5 +6,8 @@ config SND_SIRF_SOC config SND_SOC_SIRF_I2S tristate
+config SND_SIRF_SOC_INNER + tristate + config SND_SOC_SIRF_USP tristate diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index 630c9be..8517c67 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,7 +1,9 @@ snd-soc-sirf-objs := sirf-pcm.o +snd-soc-sirf-soc-inner-objs := sirf-soc-inner.o snd-soc-sirf-i2s-objs := sirf-i2s.o snd-soc-sirf-usp-objs := sirf-usp.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o +obj-$(CONFIG_SND_SIRF_SOC_INNER) += snd-soc-sirf-soc-inner.o obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-soc-inner.c b/sound/soc/sirf/sirf-soc-inner.c new file mode 100644 index 0000000..96bf59b --- /dev/null +++ b/sound/soc/sirf/sirf-soc-inner.c @@ -0,0 +1,653 @@ +/* + * SiRF inner audio codec driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later.* + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc/sirfsoc_rtciobrg.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "sirf-audio.h" +#include "sirf-pcm.h" + +struct sirf_soc_inner_audio_reg_bits { + u32 dig_mic_en_bits; + u32 dig_mic_freq_bits; + u32 adc14b_12_bits; + u32 firdac_hsl_en_bits; + u32 firdac_hsr_en_bits; + u32 firdac_lout_en_bits; + u32 por_bits; + u32 codec_clk_en_bits; + u32 hp_3db_boost_bits; + u32 adc_left_gain_shift; + u32 adc_right_gain_shift; + u32 adc_gain_mask; + u32 mic_max_gain; +}; + +struct sirf_soc_inner_audio { + void __iomem *base; + unsigned int playing; + struct clk *clk; + spinlock_t lock; + u32 sys_pwrc_reg_base; + struct sirf_soc_inner_audio_reg_bits *reg_bits; +}; + +static struct sirf_soc_inner_audio_reg_bits sirf_soc_inner_audio_reg_bits_prima2 = { + 20, 21, 22, 23, 24, 25, 26, 27, 28, 15, 10, 0x1f, 0x19, +}; + +static struct sirf_soc_inner_audio_reg_bits sirf_soc_inner_audio_reg_bits_atlas6 = { + 22, 23, 24, 25, 26, 27, 28, 29, 30, 16, 10, 0x3f, 0x39, +}; + +static int sirf_inner_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + int get, char *name) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_card *card = codec->card; + int i; + for (i = 0; i < card->num_controls; i++) { + if (!strcmp(card->controls[i].name, name)) { + if (card->controls[i].get && get) + return card->controls[i].get(kcontrol, ucontrol); + else if (card->controls[i].put && !get) + return card->controls[i].put(kcontrol, ucontrol); + } + } + return 0; +} + +static int sirf_inner_snd_speaker_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + sirf_inner_control(kcontrol, ucontrol, 1, "Speaker Out"); + return 0; +} + +static int sirf_inner_snd_speaker_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + + spin_lock(&sinner_audio->lock); + sirf_inner_control(kcontrol, ucontrol, 0, "Speaker Out"); + + if (ucontrol->value.integer.value[0]) { + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + | IC_RDACEN | IC_SPSELR, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) | + (1 << sinner_audio->reg_bits->firdac_lout_en_bits), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | + IC_SPEN), sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + } else { + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + & ~(IC_SPEN | IC_SPSELR)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + & ~(1 << sinner_audio->reg_bits->firdac_lout_en_bits), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + } + + spin_unlock(&sinner_audio->lock); + + return 0; +} + +static int sirf_inner_snd_headphone_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + sirf_inner_control(kcontrol, ucontrol, 1, "Headphone Out"); + return 0; +} + +static int sirf_inner_snd_headphone_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + + spin_lock(&sinner_audio->lock); + sirf_inner_control(kcontrol, ucontrol, 0, "Headphone Out"); + if (ucontrol->value.integer.value[0]) + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + | IC_HSLEN | IC_HSREN | IC_HPRSELR + | IC_HPLSELL, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + else + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + & ~(IC_HSLEN | IC_HSREN | IC_HPRSELR + | IC_HPLSELL), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + spin_unlock(&sinner_audio->lock); + return 0; +} + +static struct snd_kcontrol_new snd_sirf_inner_volume_controls_atlas6[] = { + SOC_DOUBLE("Speaker Volume", AUDIO_IC_CODEC_CTRL0, 21, 14, + 0x7F, 0), + SOC_DOUBLE("Capture Volume", AUDIO_IC_CODEC_CTRL1, 16, 10, + 0x3F, 0), + SOC_DOUBLE("Capture Switch", AUDIO_IC_CODEC_CTRL1, 0, 1, + 1, 0), + SOC_SINGLE_BOOL_EXT("Speaker Switch", 0, sirf_inner_snd_speaker_get, + sirf_inner_snd_speaker_set), + SOC_SINGLE_BOOL_EXT("Headphone Switch", 0, sirf_inner_snd_headphone_get, + sirf_inner_snd_headphone_set), +}; + +static struct snd_kcontrol_new snd_sirf_inner_volume_controls_prima2[] = { + SOC_DOUBLE("Speaker Volume", AUDIO_IC_CODEC_CTRL0, 21, 14, + 0x7F, 0), + SOC_DOUBLE("Capture Volume", AUDIO_IC_CODEC_CTRL1, 15, 10, + 0x1F, 0), + SOC_DOUBLE("Capture Switch", AUDIO_IC_CODEC_CTRL1, 0, 1, + 1, 0), + SOC_SINGLE_BOOL_EXT("Speaker Switch", 0, sirf_inner_snd_speaker_get, + sirf_inner_snd_speaker_set), + SOC_SINGLE_BOOL_EXT("Headphone Switch", 0, sirf_inner_snd_headphone_get, + sirf_inner_snd_headphone_set), +}; + +static int sirf_inner_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sirf_soc_inner_audio *sinner_audio = snd_soc_dai_get_drvdata(dai); + u32 adc_gain_mask = sinner_audio->reg_bits->adc_gain_mask; + u32 adc_left_gain_shift = sinner_audio->reg_bits->adc_left_gain_shift; + u32 adc_right_gain_shift = sinner_audio->reg_bits->adc_right_gain_shift; + u32 mic_max_gain = sinner_audio->reg_bits->mic_max_gain; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | (1 << sinner_audio->reg_bits->codec_clk_en_bits) | + (1 << sinner_audio->reg_bits->por_bits)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_HSINVEN) + & ~IC_MONOR, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + msleep(50); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_RDACEN | + IC_LDACEN | IC_HPRSELR | IC_HPLSELL, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) | + (1 << sinner_audio->reg_bits->firdac_hsl_en_bits) | + (1 << sinner_audio->reg_bits->firdac_hsr_en_bits), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + usleep_range(300, 1000); + + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + | IC_HSREN | IC_HSLEN), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + /* avoid break noise when sound loud, set RX gain -1dB */ + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) & + ~((IC_RXPGAR_MASK << IC_RXPGAR_SHIFT) | + (IC_RXPGAL_MASK << IC_RXPGAL_SHIFT)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | + ((IC_RXPGAR << IC_RXPGAR_SHIFT) | + (IC_RXPGAL << IC_RXPGAL_SHIFT)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { +#define PWRC_PDN_CTRL_OFFSET 0 +#define AUDIO_POWER_EN_BIT 14 + sirfsoc_rtc_iobrg_writel(sirfsoc_rtc_iobrg_readl( + (sinner_audio->sys_pwrc_reg_base + + PWRC_PDN_CTRL_OFFSET)) + | (1 << AUDIO_POWER_EN_BIT), + sinner_audio->sys_pwrc_reg_base + + PWRC_PDN_CTRL_OFFSET); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) | + (1 << sinner_audio->reg_bits->codec_clk_en_bits) | + (1 << sinner_audio->reg_bits->por_bits), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + msleep(50); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_PWR) | + MICBIASEN, sinner_audio->base + AUDIO_IC_CODEC_PWR); + usleep_range(300, 1000); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | IC_MICINREN | IC_MICINLEN, + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + usleep_range(100, 200); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) | IC_RADCEN | + IC_LADCEN, sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + usleep_range(100, 200); + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) | IC_MICIN1SEL + | IC_MICDIFSEL) & (~IC_MICIN2SEL), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) & + ~(adc_gain_mask << adc_left_gain_shift) & + ~(adc_gain_mask << adc_right_gain_shift)) | + (mic_max_gain << adc_left_gain_shift) | + (mic_max_gain << adc_right_gain_shift), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + } + return 0; +} + +static void sirf_inner_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ +} + +static int sirf_inner_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int sirf_inner_codec_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct sirf_soc_inner_audio *sinner_audio = snd_soc_dai_get_drvdata(dai); + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock(&sinner_audio->lock); + if (playback) { + writel(readl(sinner_audio->base + AUDIO_CTRL_IC_CODEC_TX_CTRL) + & ~IC_TX_ENABLE, + sinner_audio->base + AUDIO_CTRL_IC_CODEC_TX_CTRL); + sinner_audio->playing = false; + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + & ~(IC_SPEN | IC_SPSELR | IC_HSLEN)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + & ~(1 << sinner_audio->reg_bits->firdac_lout_en_bits), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + & ~(IC_HSLEN | IC_HSREN | IC_HPRSELR | IC_HPLSELL), + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + } else { + writel(readl(sinner_audio->base + AUDIO_CTRL_IC_CODEC_RX_CTRL) + & ~IC_RX_ENABLE, + sinner_audio->base + AUDIO_CTRL_IC_CODEC_RX_CTRL); + } + spin_unlock(&sinner_audio->lock); + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock(&sinner_audio->lock); + if (playback) { + writel(0, sinner_audio->base + AUDIO_CTRL_IC_TXFIFO_INT_MSK); + writel(AUDIO_FIFO_START, + sinner_audio->base + AUDIO_CTRL_IC_TXFIFO_OP); + writel(IC_TX_ENABLE, + sinner_audio->base + AUDIO_CTRL_IC_CODEC_TX_CTRL); + sinner_audio->playing = true; + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + | IC_RDACEN | IC_SPSELR, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | (1 << sinner_audio->reg_bits->firdac_lout_en_bits), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + | IC_SPEN, sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) + | IC_HSLEN | IC_HSREN + | IC_HPRSELR | IC_HPLSELL, + sinner_audio->base + + AUDIO_IC_CODEC_CTRL0); + } else { + /* unmask rx fifo interrupt */ + writel(0, sinner_audio->base + + AUDIO_CTRL_IC_RXFIFO_INT_MSK); + + /* First start the FIFO, then enable the tx/rx */ + writel(AUDIO_FIFO_START, sinner_audio->base + + AUDIO_CTRL_IC_RXFIFO_OP); + /* mono capture from dacr*/ + if (substream->runtime->channels == 1) + writel(0x01, sinner_audio->base + + AUDIO_CTRL_IC_CODEC_RX_CTRL); + else + writel(IC_RX_ENABLE, sinner_audio->base + + AUDIO_CTRL_IC_CODEC_RX_CTRL); + } + spin_unlock(&sinner_audio->lock); + break; + default: + return -EINVAL; + } + return 0; +} + +struct snd_soc_dai_ops sirf_inner_codec_dai_ops = { + .startup = sirf_inner_codec_startup, + .hw_params = sirf_inner_codec_hw_params, + .shutdown = sirf_inner_codec_shutdown, + .trigger = sirf_inner_codec_trigger, +}; + +struct snd_soc_dai_driver sirf_inner_codec_dai = { + .name = "sirf-soc-inner", + .playback = { + .stream_name = "Audio Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Audio Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sirf_inner_codec_dai_ops, +}; +EXPORT_SYMBOL_GPL(sirf_inner_codec_dai); + +static int sirf_inner_codec_probe(struct snd_soc_codec *codec) +{ + if (of_device_is_compatible(codec->dev->of_node, "sirf,prima2-audio")) + return snd_soc_add_codec_controls(codec, + snd_sirf_inner_volume_controls_prima2, + ARRAY_SIZE(snd_sirf_inner_volume_controls_prima2)); + if (of_device_is_compatible(codec->dev->of_node, "sirf,atlas6-audio")) + return snd_soc_add_codec_controls(codec, + snd_sirf_inner_volume_controls_atlas6, + ARRAY_SIZE(snd_sirf_inner_volume_controls_atlas6)); + + return -EINVAL; +} + +static int sirf_inner_codec_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static unsigned int sirf_inner_codec_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + return readl(sinner_audio->base + reg); +} + +static int sirf_inner_codec_reg_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val) +{ + struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev); + writel(val, sinner_audio->base + reg); + return 0; +} + + +static struct snd_soc_codec_driver soc_codec_device_sirf_inner_codec = { + .probe = sirf_inner_codec_probe, + .remove = sirf_inner_codec_remove, + .read = sirf_inner_codec_reg_read, + .write = sirf_inner_codec_reg_write, +}; + +static struct sirf_pcm_dma_data sirf_soc_inner_dai_dma_data[2] = { + { + .name = "Audio Playback", + }, { + .name = "Audio Capture", + }, +}; + +static int sirf_soc_inner_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, + &sirf_soc_inner_dai_dma_data[substream->stream]); + return 0; +} + +static int sirf_soc_inner_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct sirf_soc_inner_audio *sinner_audio = + snd_soc_dai_get_drvdata(dai); + if (playback) { + writel(AUDIO_FIFO_RESET, + sinner_audio->base + AUDIO_CTRL_IC_TXFIFO_OP); + + writel(0x00, + sinner_audio->base + AUDIO_CTRL_IC_TXFIFO_OP); + } else { + writel(AUDIO_FIFO_RESET, + sinner_audio->base + AUDIO_CTRL_IC_RXFIFO_OP); + + writel(0x00, + sinner_audio->base + AUDIO_CTRL_IC_RXFIFO_OP); + } + return 0; +} + +static const struct snd_soc_dai_ops sirf_soc_inner_dai_ops = { + .startup = sirf_soc_inner_dai_startup, + .hw_params = sirf_soc_inner_dai_hw_params, +}; + +static struct snd_soc_dai_driver sirf_soc_inner_dai = { + .name = "sirf-soc-inner", + .id = 0, + .playback = { + .stream_name = "inner Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "inner Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sirf_soc_inner_dai_ops, +}; + +static const struct snd_soc_component_driver sirf_soc_inner_component = { + .name = "sirf-soc-inner", +}; + +static const struct of_device_id sirf_soc_inner_of_match[] = { + { .compatible = "sirf,prima2-audio", .data = &sirf_soc_inner_audio_reg_bits_prima2 }, + { .compatible = "sirf,atlas6-audio", .data = &sirf_soc_inner_audio_reg_bits_atlas6 }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_soc_inner_of_match); + +static int sirf_soc_inner_probe(struct platform_device *pdev) +{ + int ret; + u32 rx_dma_ch, tx_dma_ch; + struct sirf_soc_inner_audio *sinner_audio; + struct resource *mem_res; + struct device_node *dn = NULL; + const struct of_device_id *match; + + match = of_match_node(sirf_soc_inner_of_match, pdev->dev.of_node); + + sinner_audio = devm_kzalloc(&pdev->dev, + sizeof(struct sirf_soc_inner_audio), GFP_KERNEL); + if (!sinner_audio) + return -ENOMEM; + + platform_set_drvdata(pdev, sinner_audio); + + ret = of_property_read_u32(pdev->dev.of_node, + "sirf,inner-audio-dma-rx-channel", &rx_dma_ch); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to audio capture dma channel\n"); + return ret; + } + ret = of_property_read_u32(pdev->dev.of_node, + "sirf,inner-audio-dma-tx-channel", &tx_dma_ch); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to audio playback dma channel\n"); + return ret; + } + sirf_soc_inner_dai_dma_data[0].dma_req = tx_dma_ch; + sirf_soc_inner_dai_dma_data[1].dma_req = rx_dma_ch; + + dn = of_find_compatible_node(dn, NULL, "sirf,prima2-pwrc"); + if (!dn) { + dev_err(&pdev->dev, "Failed to get sirf,prima2-pwrc node!\n"); + return -ENODEV; + } + + ret = of_property_read_u32(dn, "reg", &sinner_audio->sys_pwrc_reg_base); + if (ret < 0) { + dev_err(&pdev->dev, "Failed tp get pwrc register base address\n"); + return -EINVAL; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sinner_audio->base = devm_ioremap_resource(&pdev->dev, mem_res); + if (sinner_audio->base == NULL) + return -ENOMEM; + + sinner_audio->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sinner_audio->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + return PTR_ERR(sinner_audio->clk); + } + clk_prepare_enable(sinner_audio->clk); + + ret = snd_soc_register_component(&pdev->dev, &sirf_soc_inner_component, + &sirf_soc_inner_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio SoC dai failed.\n"); + goto err_clk_put; + } + + ret = snd_soc_register_codec(&(pdev->dev), + &soc_codec_device_sirf_inner_codec, + &sirf_inner_codec_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio Codec dai failed.\n"); + goto err_com_unreg; + } + + sinner_audio->reg_bits = (struct sirf_soc_inner_audio_reg_bits *)match->data; + + spin_lock_init(&sinner_audio->lock); + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | (1 << sinner_audio->reg_bits->codec_clk_en_bits)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | (1 << sinner_audio->reg_bits->adc14b_12_bits)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPFREQ, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPEN, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + return 0; + +err_com_unreg: + snd_soc_unregister_component(&pdev->dev); +err_clk_put: + clk_disable_unprepare(sinner_audio->clk); + return ret; +} + +static int sirf_soc_inner_remove(struct platform_device *pdev) +{ + struct sirf_soc_inner_audio *sinner_audio = platform_get_drvdata(pdev); + + clk_disable_unprepare(sinner_audio->clk); + snd_soc_unregister_codec(&(pdev->dev)); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int sirf_soc_inner_suspend(struct platform_device *pdev, + pm_message_t msg) +{ + struct sirf_soc_inner_audio *sinner_audio = platform_get_drvdata(pdev); + + clk_disable_unprepare(sinner_audio->clk); + return 0; +} + +static int sirf_soc_inner_resume(struct platform_device *pdev) +{ + struct sirf_soc_inner_audio *sinner_audio = platform_get_drvdata(pdev); + + clk_prepare_enable(sinner_audio->clk); + + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | (1 << sinner_audio->reg_bits->codec_clk_en_bits)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) + | (1 << sinner_audio->reg_bits->adc14b_12_bits)), + sinner_audio->base + AUDIO_IC_CODEC_CTRL1); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPFREQ, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPEN, + sinner_audio->base + AUDIO_IC_CODEC_CTRL0); + return 0; +} +#else +#define sirf_soc_inner_suspend NULL +#define sirf_soc_inner_resume NULL +#endif + +static struct platform_driver sirf_soc_inner_driver = { + .driver = { + .name = "sirf-soc-inner", + .owner = THIS_MODULE, + .of_match_table = sirf_soc_inner_of_match, + }, + .probe = sirf_soc_inner_probe, + .remove = sirf_soc_inner_remove, + .suspend = sirf_soc_inner_suspend, + .resume = sirf_soc_inner_resume, +}; + +module_platform_driver(sirf_soc_inner_driver); + +MODULE_DESCRIPTION("SiRF SoC inner bus and codec driver"); +MODULE_AUTHOR("RongJun Ying Rongjun.Ying@csr.com"); +MODULE_LICENSE("GPL v2");
On Fri, Jul 19, 2013 at 07:07:20PM +0800, Barry Song wrote:
there is an internal codec embedded in the SiRF SoC. this is not a typical user scenerios of ASoC. but we can still get benefit by sharing platform DMA codes instead of implementing a pure ALSA driver. This driver adds DAI drivers for this internal codec.
To be honest this shouldn't be too exciting from an ASoC point of view - just a normal CODEC driver with some stub DAIs. It looks like it might benefit from regmap-mmio and DAPM.
+static int sirf_inner_control(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
int get, char *name)
+{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct snd_soc_card *card = codec->card;
- int i;
- for (i = 0; i < card->num_controls; i++) {
if (!strcmp(card->controls[i].name, name)) {
if (card->controls[i].get && get)
return card->controls[i].get(kcontrol, ucontrol);
else if (card->controls[i].put && !get)
return card->controls[i].put(kcontrol, ucontrol);
}
- }
- return 0;
+}
What is all this about? I don't quite understand what the goal is and there's no comments.
+static int sirf_inner_snd_speaker_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev);
- spin_lock(&sinner_audio->lock);
- sirf_inner_control(kcontrol, ucontrol, 0, "Speaker Out");
- if (ucontrol->value.integer.value[0]) {
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0)
| IC_RDACEN | IC_SPSELR,
sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) |
(1 << sinner_audio->reg_bits->firdac_lout_en_bits),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) |
IC_SPEN), sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
Is this possibly some supplies plus a power bit?
+static int sirf_inner_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct sirf_soc_inner_audio *sinner_audio = snd_soc_dai_get_drvdata(dai);
- u32 adc_gain_mask = sinner_audio->reg_bits->adc_gain_mask;
- u32 adc_left_gain_shift = sinner_audio->reg_bits->adc_left_gain_shift;
- u32 adc_right_gain_shift = sinner_audio->reg_bits->adc_right_gain_shift;
- u32 mic_max_gain = sinner_audio->reg_bits->mic_max_gain;
Would regmap_field help here?
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) |
(1 << sinner_audio->reg_bits->codec_clk_en_bits) |
(1 << sinner_audio->reg_bits->por_bits),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
msleep(50);
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_PWR) |
MICBIASEN, sinner_audio->base + AUDIO_IC_CODEC_PWR);
usleep_range(300, 1000);
This all looks a lot like a normal CODEC power up sequence that's been open coded but could use DAPM?
+static void sirf_inner_codec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{ +}
+static int sirf_inner_codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- return 0;
+}
Remove empty functions.
+static int sirf_soc_inner_resume(struct platform_device *pdev) +{
- struct sirf_soc_inner_audio *sinner_audio = platform_get_drvdata(pdev);
- clk_prepare_enable(sinner_audio->clk);
- writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1)
| (1 << sinner_audio->reg_bits->codec_clk_en_bits)),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
- writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1)
| (1 << sinner_audio->reg_bits->adc14b_12_bits)),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
- writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPFREQ,
sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
- writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPEN,
sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
- return 0;
+} +#else +#define sirf_soc_inner_suspend NULL +#define sirf_soc_inner_resume NULL +#endif
Standard runtime PM question and this definitely does look like regmap-mmio will help - you've essentially open coded some of it.
2013/7/20 Mark Brown broonie@kernel.org:
On Fri, Jul 19, 2013 at 07:07:20PM +0800, Barry Song wrote:
there is an internal codec embedded in the SiRF SoC. this is not a typical user scenerios of ASoC. but we can still get benefit by sharing platform DMA codes instead of implementing a pure ALSA driver. This driver adds DAI drivers for this internal codec.
To be honest this shouldn't be too exciting from an ASoC point of view - just a normal CODEC driver with some stub DAIs. It looks like it might benefit from regmap-mmio and DAPM.
the driver was a legacy pure alsa driver as it is not soc+external codec+mach framework. so it has many legacy codes. i agree we can move to regmap-mmio and asoc dapm.
+static int sirf_inner_control(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
int get, char *name)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct snd_soc_card *card = codec->card;
int i;
for (i = 0; i < card->num_controls; i++) {
if (!strcmp(card->controls[i].name, name)) {
if (card->controls[i].get && get)
return card->controls[i].get(kcontrol, ucontrol);
else if (card->controls[i].put && !get)
return card->controls[i].put(kcontrol, ucontrol);
}
}
return 0;
+}
What is all this about? I don't quite understand what the goal is and there's no comments.
it is a common kcontrol get/set function which will be called by all of sirf_inner_snd_speaker_set, sirf_inner_snd_headphone_get, sirf_inner_snd_headphone_set, sirf_inner_snd_speaker_get. after we move to DAPM, it should be useless.
+static int sirf_inner_snd_speaker_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct sirf_soc_inner_audio *sinner_audio = dev_get_drvdata(codec->dev);
spin_lock(&sinner_audio->lock);
sirf_inner_control(kcontrol, ucontrol, 0, "Speaker Out");
if (ucontrol->value.integer.value[0]) {
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0)
| IC_RDACEN | IC_SPSELR,
sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) |
(1 << sinner_audio->reg_bits->firdac_lout_en_bits),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) |
IC_SPEN), sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
Is this possibly some supplies plus a power bit?
+static int sirf_inner_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
struct sirf_soc_inner_audio *sinner_audio = snd_soc_dai_get_drvdata(dai);
u32 adc_gain_mask = sinner_audio->reg_bits->adc_gain_mask;
u32 adc_left_gain_shift = sinner_audio->reg_bits->adc_left_gain_shift;
u32 adc_right_gain_shift = sinner_audio->reg_bits->adc_right_gain_shift;
u32 mic_max_gain = sinner_audio->reg_bits->mic_max_gain;
Would regmap_field help here?
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1) |
(1 << sinner_audio->reg_bits->codec_clk_en_bits) |
(1 << sinner_audio->reg_bits->por_bits),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
msleep(50);
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_PWR) |
MICBIASEN, sinner_audio->base + AUDIO_IC_CODEC_PWR);
usleep_range(300, 1000);
This all looks a lot like a normal CODEC power up sequence that's been open coded but could use DAPM?
+static void sirf_inner_codec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{ +}
+static int sirf_inner_codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
return 0;
+}
Remove empty functions.
ok.
+static int sirf_soc_inner_resume(struct platform_device *pdev) +{
struct sirf_soc_inner_audio *sinner_audio = platform_get_drvdata(pdev);
clk_prepare_enable(sinner_audio->clk);
writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1)
| (1 << sinner_audio->reg_bits->codec_clk_en_bits)),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
writel((readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL1)
| (1 << sinner_audio->reg_bits->adc14b_12_bits)),
sinner_audio->base + AUDIO_IC_CODEC_CTRL1);
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPFREQ,
sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
writel(readl(sinner_audio->base + AUDIO_IC_CODEC_CTRL0) | IC_CPEN,
sinner_audio->base + AUDIO_IC_CODEC_CTRL0);
return 0;
+} +#else +#define sirf_soc_inner_suspend NULL +#define sirf_soc_inner_resume NULL +#endif
Standard runtime PM question and this definitely does look like regmap-mmio will help - you've essentially open coded some of it.
we can use runtime PM to handle clock disable/enable here. audio path widgets, codec and speaker widgets will be handled by DAPM.
-barry
From: Rongjun Ying Rongjun.Ying@csr.com
This connects DMA, CPU DAI and Codec DAI together and works as a mach driver.
Signed-off-by: Rongjun Ying Rongjun.Ying@csr.com Signed-off-by: Barry Song Baohua.Song@csr.com --- sound/soc/sirf/Kconfig | 7 +- sound/soc/sirf/Makefile | 2 + sound/soc/sirf/sirf-inner.c | 267 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 sound/soc/sirf/sirf-inner.c
diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig index 60b8857..ce3c025 100644 --- a/sound/soc/sirf/Kconfig +++ b/sound/soc/sirf/Kconfig @@ -2,7 +2,7 @@ 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
@@ -11,3 +11,8 @@ config SND_SIRF_SOC_INNER
config SND_SOC_SIRF_USP tristate + +config SND_SIRF_INNER + tristate "SoC Audio support for SiRF inner codec of SiRF EVB" + depends on SND_SIRF_SOC + select SND_SIRF_SOC_INNER diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile index 8517c67..80abdf6 100644 --- a/sound/soc/sirf/Makefile +++ b/sound/soc/sirf/Makefile @@ -1,9 +1,11 @@ snd-soc-sirf-objs := sirf-pcm.o +snd-soc-sirf-inner-objs := sirf-inner.o snd-soc-sirf-soc-inner-objs := sirf-soc-inner.o snd-soc-sirf-i2s-objs := sirf-i2s.o snd-soc-sirf-usp-objs := sirf-usp.o
obj-$(CONFIG_SND_SIRF_SOC) += snd-soc-sirf.o +obj-$(CONFIG_SND_SIRF_INNER) += snd-soc-sirf-inner.o obj-$(CONFIG_SND_SIRF_SOC_INNER) += snd-soc-sirf-soc-inner.o obj-$(CONFIG_SND_SOC_SIRF_I2S) += snd-soc-sirf-i2s.o obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-inner.c b/sound/soc/sirf/sirf-inner.c new file mode 100644 index 0000000..a8f2765 --- /dev/null +++ b/sound/soc/sirf/sirf-inner.c @@ -0,0 +1,267 @@ +/* + * SiRF inner audio device driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/jack.h> + +struct sirf_inner_card { + unsigned int gpio_hp_pa; + unsigned int gpio_spk_pa; + /* + * Android platform uses switch gpio instead of jack. + */ +#ifndef CONFIG_ANDROID + unsigned int gpio_hp_detect; + struct snd_soc_jack hp_jack; +#endif +}; + +#ifndef CONFIG_ANDROID +static int sirf_inner_jack_status_check(void); + +static struct snd_soc_jack_gpio hp_jack_gpios[] = { + { + .name = "hpdet-gpio", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + .jack_status_check = sirf_inner_jack_status_check, + }, +}; + +static int sirf_inner_jack_status_check(void) +{ + int spk_out = 0; + struct snd_soc_codec *codec = hp_jack_gpios[0].jack->codec; + struct snd_soc_card *card = codec->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + int hp_report = 0; + + if (gpio_is_valid(sinner_card->gpio_hp_detect)) + spk_out = gpio_get_value(sinner_card->gpio_hp_detect); + + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + gpio_direction_output(sinner_card->gpio_hp_pa, !spk_out); + + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + gpio_direction_output(sinner_card->gpio_spk_pa, spk_out); + + if (!spk_out) + hp_report |= SND_JACK_HEADPHONE; + + return hp_report; +} + +static int sirf_inner_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + int ret; + hp_jack_gpios[0].gpio = sinner_card->gpio_hp_detect; + ret = snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, + &sinner_card->hp_jack); + if (ret) + return ret; + return snd_soc_jack_add_gpios(&sinner_card->hp_jack, + ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); +} +#endif + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link sirf_inner_dai_links[] = { + { + .name = "SiRF inner", + .stream_name = "SiRF inner", + .codec_dai_name = "sirf-soc-inner", +#ifndef CONFIG_ANDROID + .init = sirf_inner_init, +#endif + }, +}; + +static int sirf_inner_headphone_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int hp_out = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_card *card = codec->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + hp_out = gpio_get_value(sinner_card->gpio_hp_pa); + + *ucontrol->value.integer.value = hp_out; + return 0; +} + +static int sirf_inner_headphone_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int is_hp_out = ucontrol->value.integer.value[0]; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_card *card = codec->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + gpio_direction_output(sinner_card->gpio_hp_pa, is_hp_out); + + return 0; +} + +static int sirf_inner_speaker_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int spk_out = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_card *card = codec->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + spk_out = gpio_get_value(sinner_card->gpio_spk_pa); + + *ucontrol->value.integer.value = spk_out; + return 0; +} + +static int sirf_inner_speaker_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int is_spk_out = ucontrol->value.integer.value[0]; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_card *card = codec->card; + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + gpio_direction_output(sinner_card->gpio_spk_pa, is_spk_out); + return 0; +} + +static int sirf_inner_speaker_out_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return 0; +} + +static int sirf_inner_headphone_out_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return 0; +} + +static struct snd_kcontrol_new snd_sirf_inner_out_route_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Out", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = sirf_inner_speaker_out_info, + .get = sirf_inner_speaker_out_get, + .put = sirf_inner_speaker_out_put, + }, { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Out", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = sirf_inner_headphone_out_info, + .get = sirf_inner_headphone_out_get, + .put = sirf_inner_headphone_out_put, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_sirf_inner_card = { + .name = "SiRF inner", + .owner = THIS_MODULE, + .dai_link = sirf_inner_dai_links, + .num_links = ARRAY_SIZE(sirf_inner_dai_links), + .controls = snd_sirf_inner_out_route_controls, + .num_controls = ARRAY_SIZE(snd_sirf_inner_out_route_controls), +}; + +static int sirf_inner_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_sirf_inner_card; + struct sirf_inner_card *sinner_card; + sinner_card = devm_kzalloc(&pdev->dev, sizeof(struct sirf_inner_card), + GFP_KERNEL); + if (sinner_card == NULL) + return -ENOMEM; + + sirf_inner_dai_links[0].platform_of_node = + of_find_compatible_node(NULL, NULL, "sirf,pcm-audio"); + sirf_inner_dai_links[0].cpu_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,inner-platform", 0); + sirf_inner_dai_links[0].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,inner-codec", 0); + sinner_card->gpio_spk_pa = of_get_named_gpio(pdev->dev.of_node, + "spk-pa-gpios", 0); + sinner_card->gpio_hp_pa = of_get_named_gpio(pdev->dev.of_node, + "hp-pa-gpios", 0); +#ifndef CONFIG_ANDROID + sinner_card->gpio_hp_detect = of_get_named_gpio(pdev->dev.of_node, + "hp-switch-gpios", 0); +#endif + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + gpio_request(sinner_card->gpio_spk_pa, "SPA_PA_SD"); + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + gpio_request(sinner_card->gpio_hp_pa, "HP_PA_SD"); + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, sinner_card); + platform_set_drvdata(pdev, card); + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + gpio_direction_output(sinner_card->gpio_hp_pa, 0); + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + gpio_direction_output(sinner_card->gpio_spk_pa, 0); + + return snd_soc_register_card(card); +} + +static int sirf_inner_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card); + + if (gpio_is_valid(sinner_card->gpio_hp_pa)) + gpio_free(sinner_card->gpio_hp_pa); + if (gpio_is_valid(sinner_card->gpio_spk_pa)) + gpio_free(sinner_card->gpio_spk_pa); + + snd_soc_unregister_card(card); + return 0; +} + +static const struct of_device_id sirf_inner_of_match[] = { + {.compatible = "sirf,sirf-inner-audio", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sirf_inner_of_match); + +static struct platform_driver sirf_inner_driver = { + .driver = { + .name = "sirf-inner-audio", + .owner = THIS_MODULE, + .of_match_table = sirf_inner_of_match, + }, + .probe = sirf_inner_probe, + .remove = sirf_inner_remove, +}; +module_platform_driver(sirf_inner_driver); + +MODULE_AUTHOR("RongJun Ying RongJun.Ying@csr.com"); +MODULE_DESCRIPTION("ALSA SoC SIRF inner AUDIO driver"); +MODULE_LICENSE("GPL v2");
On Fri, Jul 19, 2013 at 07:07:21PM +0800, Barry Song wrote:
+#ifndef CONFIG_ANDROID
- unsigned int gpio_hp_detect;
- struct snd_soc_jack hp_jack;
+#endif
Not for mainline. I'd suggest setting it up for extcon anyway especially if it's just a simple accessory, that should work for both Android and mainline.
+static int sirf_inner_speaker_out_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- int is_spk_out = ucontrol->value.integer.value[0];
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct snd_soc_card *card = codec->card;
- struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card);
- if (gpio_is_valid(sinner_card->gpio_spk_pa))
gpio_direction_output(sinner_card->gpio_spk_pa, is_spk_out);
- return 0;
+}
Oh, right. If this is doing what I think this is doing you want to use DAPM within the CODEC then this can just become a pin switch or you can hook in directly if you continue to use soc-jack - it has DAPM integration.
2013/7/20 Mark Brown broonie@kernel.org:
On Fri, Jul 19, 2013 at 07:07:21PM +0800, Barry Song wrote:
+#ifndef CONFIG_ANDROID
unsigned int gpio_hp_detect;
struct snd_soc_jack hp_jack;
+#endif
Not for mainline. I'd suggest setting it up for extcon anyway especially if it's just a simple accessory, that should work for both Android and mainline.
sounds good as if CONFIG_ANDROID is enabled without CONFIG_ANDROID_SWITCH, /sys/class/switch/* will be symbolically linked to /sys/class/extcon/, so that they are still compatible with legacy userspace processes.
+static int sirf_inner_speaker_out_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
int is_spk_out = ucontrol->value.integer.value[0];
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct snd_soc_card *card = codec->card;
struct sirf_inner_card *sinner_card = snd_soc_card_get_drvdata(card);
if (gpio_is_valid(sinner_card->gpio_spk_pa))
gpio_direction_output(sinner_card->gpio_spk_pa, is_spk_out);
return 0;
+}
Oh, right. If this is doing what I think this is doing you want to use DAPM within the CODEC then this can just become a pin switch or you can hook in directly if you continue to use soc-jack - it has DAPM integration.
ok. here it is more of machine stuff than codec stuff, so i think we move to snd_soc_jack_gpio.
-barry
Signed-off-by: Barry Song Baohua.Song@csr.com --- arch/arm/configs/prima2_defconfig | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/arch/arm/configs/prima2_defconfig b/arch/arm/configs/prima2_defconfig index 002a1ce..07a87c8 100644 --- a/arch/arm/configs/prima2_defconfig +++ b/arch/arm/configs/prima2_defconfig @@ -1,4 +1,3 @@ -CONFIG_EXPERIMENTAL=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_RELAY=y @@ -39,6 +38,11 @@ CONFIG_SPI=y CONFIG_SPI_SIRF=y CONFIG_SPI_SPIDEV=y # CONFIG_HWMON is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_SIRF_SOC=y +CONFIG_SND_SIRF_INNER=y CONFIG_USB_GADGET=y CONFIG_USB_MASS_STORAGE=m CONFIG_MMC=y @@ -59,12 +63,12 @@ CONFIG_ROMFS_FS=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ASCII=y CONFIG_NLS_ISO8859_1=y -CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_INFO=y CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_MAGIC_SYSRQ=y CONFIG_DEBUG_KERNEL=y # CONFIG_DEBUG_PREEMPT is not set CONFIG_DEBUG_RT_MUTEXES=y CONFIG_DEBUG_SPINLOCK=y CONFIG_DEBUG_MUTEXES=y -CONFIG_DEBUG_INFO=y CONFIG_CRC_CCITT=y
participants (3)
-
Barry Song
-
Lars-Peter Clausen
-
Mark Brown