diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-07-25 00:37:37 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-07-25 00:37:37 +0400 |
commit | dbf7b5915b39bfff548e4c6a3a753fc291a60e25 (patch) | |
tree | 55c457a22aa869d2ab558317877138369ae5f9bb /sound/soc/ux500/ux500_pcm.c | |
parent | d14b7a419a664cd7c1c585c9e7fffee9e9051d53 (diff) | |
parent | c1b623d9e4117d18d244e9b7fb30d2c27aeaf074 (diff) | |
download | linux-dbf7b5915b39bfff548e4c6a3a753fc291a60e25.tar.xz |
Merge tag 'sound-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound update from Takashi Iwai:
"This is a fairly quiet release in all sound area. Only a little bit
of changes in the core side while most of changes are seen in the
drivers.
HD-audio:
- A few new codec additions for Nvidia, Realtek and VIA
- Intel Haswell audio support
- Support for "phantom" jacks for consistent jack reporting
- Major clean-ups in HDMI/DP driver codes
- A workaround for inverted digital-mic pins with Realtek codecs
- Removal of beep_mode=2 option
ASoC:
- Added the ability to add and remove DAPM paths dynamically, mostly
for reparenting on clock changes
- New machine drivers for Marvell Brownstone, ST-Ericsson Ux500
reference platform and ttc-dkp
- New CPU drivers for Blackfin BF6xx SPORTs in I2S mode, Marvell MMP,
Synopsis Designware I2S controllers, and SPEAr DMA and S/PDIF
- New CODEC drivers for Dialog DA732x, ST STA529, ST-Ericsson AB8500,
TI Isabelle and Wolfson Microelectronics WM5102 and WM5110
- DAPM fixes for the recent locking changes
- Fix for _PRE and _POST widgets (which have been broken for a few
releases now)
- A couple of minor driver updates
Misc
- Conversion to new dev_pm_ops in platform and PCI drivers
- LTC support and some fixes in PCXHR driver
- A few fixes and PM support for ISA OPti9xx and WSS cards
- Some TLV code cleanup
- Move driver-specific headers from include/sound to local dirs"
* tag 'sound-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (212 commits)
ASoC: dapm: Fix _PRE and _POST events for DAPM performance improvements
ALSA: hda - add dock support for Thinkpad X230 Tablet
ALSA: hda - Turn on PIN_OUT from hdmi playback prepare.
ASoC imx-audmux: add MX31_AUDMUX_PORT7_SSI_PINS_7 define
ASoC: littlemill: Add userspace control of the WM1250 I/O
ASoC: wm8994: Update micdet for irqdomain conversion
ALSA: hda - make sure alc268 does not OOPS on codec parse
ALSA: hda - Add support for Realtek ALC282
ALSA: hda - Fix index number conflicts of phantom jacks
ALSA: opti9xx: Fix section mismatch by PM support
ALSA: snd-opti9xx: Implement suspend/resume
ALSA: hda - Add new GPU codec ID to snd-hda
ALSA: hda - Fix driver type of Haswell controller to AZX_DRIVER_SCH
ALSA: hda - add Haswell HDMI codec id
ALSA: hda - Add DeviceID for Haswell HDA
ALSA: wss_lib: Fix resume on Yamaha OPL3-SAx
ALSA: wss_lib: fix suspend/resume
ALSA: es1938: replace TLV_DB_RANGE_HEAD with DECLARE_TLV_DB_RANGE
ALSA: tlv: add DECLARE_TLV_DB_RANGE()
ALSA: tlv: add DECLARE_TLV_CONTAINER()
...
Diffstat (limited to 'sound/soc/ux500/ux500_pcm.c')
-rw-r--r-- | sound/soc/ux500/ux500_pcm.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c new file mode 100644 index 000000000000..1a04e248453c --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Roger Nilsson <roger.xr.nilsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <asm/page.h> + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/slab.h> + +#include <plat/ste_dma40.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "ux500_msp_i2s.h" +#include "ux500_pcm.h" + +static struct snd_pcm_hardware ux500_pcm_hw_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_BE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = UX500_PLATFORM_MIN_RATE_PLAYBACK, + .rate_max = UX500_PLATFORM_MAX_RATE_PLAYBACK, + .channels_min = UX500_PLATFORM_MIN_CHANNELS, + .channels_max = UX500_PLATFORM_MAX_CHANNELS, + .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN, + .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX, + .periods_min = UX500_PLATFORM_PERIODS_MIN, + .periods_max = UX500_PLATFORM_PERIODS_MAX, +}; + +static struct snd_pcm_hardware ux500_pcm_hw_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_BE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = UX500_PLATFORM_MIN_RATE_CAPTURE, + .rate_max = UX500_PLATFORM_MAX_RATE_CAPTURE, + .channels_min = UX500_PLATFORM_MIN_CHANNELS, + .channels_max = UX500_PLATFORM_MAX_CHANNELS, + .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN, + .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX, + .periods_min = UX500_PLATFORM_PERIODS_MIN, + .periods_max = UX500_PLATFORM_PERIODS_MAX, +}; + +static void ux500_pcm_dma_hw_free(struct device *dev, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + + if (runtime->dma_area == NULL) + return; + + if (buf != &substream->dma_buffer) { + dma_free_coherent(buf->dev.dev, buf->bytes, buf->area, + buf->addr); + kfree(runtime->dma_buffer_p); + } + + snd_pcm_set_runtime_buffer(substream, NULL); +} + +static int ux500_pcm_open(struct snd_pcm_substream *substream) +{ + int stream_id = substream->pstr->stream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct device *dev = dai->dev; + int ret; + struct ux500_msp_dma_params *dma_params; + u16 per_data_width, mem_data_width; + struct stedma40_chan_cfg *dma_cfg; + + dev_dbg(dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, + snd_pcm_stream_str(substream)); + + dev_dbg(dev, "%s: Set runtime hwparams.\n", __func__); + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_set_runtime_hwparams(substream, + &ux500_pcm_hw_playback); + else + snd_soc_set_runtime_hwparams(substream, + &ux500_pcm_hw_capture); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "%s: Error: snd_pcm_hw_constraints failed (%d)\n", + __func__, ret); + return ret; + } + + dev_dbg(dev, "%s: Set hw-struct for %s.\n", __func__, + snd_pcm_stream_str(substream)); + runtime->hw = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? + ux500_pcm_hw_playback : ux500_pcm_hw_capture; + + mem_data_width = STEDMA40_HALFWORD_WIDTH; + + dma_params = snd_soc_dai_get_dma_data(dai, substream); + switch (dma_params->data_size) { + case 32: + per_data_width = STEDMA40_WORD_WIDTH; + break; + case 16: + per_data_width = STEDMA40_HALFWORD_WIDTH; + break; + case 8: + per_data_width = STEDMA40_BYTE_WIDTH; + break; + default: + per_data_width = STEDMA40_WORD_WIDTH; + dev_warn(rtd->platform->dev, + "%s: Unknown data-size (%d)! Assuming 32 bits.\n", + __func__, dma_params->data_size); + } + + dma_cfg = dma_params->dma_cfg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_cfg->src_info.data_width = mem_data_width; + dma_cfg->dst_info.data_width = per_data_width; + } else { + dma_cfg->src_info.data_width = per_data_width; + dma_cfg->dst_info.data_width = mem_data_width; + } + + + ret = snd_dmaengine_pcm_open(substream, stedma40_filter, dma_cfg); + if (ret) { + dev_dbg(dai->dev, + "%s: ERROR: snd_dmaengine_pcm_open failed (%d)!\n", + __func__, ret); + return ret; + } + + snd_dmaengine_pcm_set_data(substream, dma_cfg); + + return 0; +} + +static int ux500_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + + dev_dbg(dai->dev, "%s: Enter\n", __func__); + + snd_dmaengine_pcm_close(substream); + + return 0; +} + +static int ux500_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0; + int size; + + dev_dbg(rtd->platform->dev, "%s: Enter\n", __func__); + + size = params_buffer_bytes(hw_params); + + if (buf) { + if (buf->bytes >= size) + goto out; + ux500_pcm_dma_hw_free(NULL, substream); + } + + if (substream->dma_buffer.area != NULL && + substream->dma_buffer.bytes >= size) { + buf = &substream->dma_buffer; + } else { + buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL); + if (!buf) + goto nomem; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = NULL; + buf->area = dma_alloc_coherent(NULL, size, &buf->addr, + GFP_KERNEL); + buf->bytes = size; + buf->private_data = NULL; + + if (!buf->area) + goto free; + } + snd_pcm_set_runtime_buffer(substream, buf); + ret = 1; + out: + runtime->dma_bytes = size; + return ret; + + free: + kfree(buf); + nomem: + return -ENOMEM; +} + +static int ux500_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->platform->dev, "%s: Enter\n", __func__); + + ux500_pcm_dma_hw_free(NULL, substream); + + return 0; +} + +static int ux500_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->platform->dev, "%s: Enter.\n", __func__); + + return dma_mmap_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +static struct snd_pcm_ops ux500_pcm_ops = { + .open = ux500_pcm_open, + .close = ux500_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = ux500_pcm_hw_params, + .hw_free = ux500_pcm_hw_free, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = snd_dmaengine_pcm_pointer_no_residue, + .mmap = ux500_pcm_mmap +}; + +int ux500_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + dev_dbg(rtd->platform->dev, "%s: Enter (id = '%s').\n", __func__, + pcm->id); + + pcm->info_flags = 0; + + return 0; +} + +static struct snd_soc_platform_driver ux500_pcm_soc_drv = { + .ops = &ux500_pcm_ops, + .pcm_new = ux500_pcm_new, +}; + +static int __devexit ux500_pcm_drv_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_platform(&pdev->dev, &ux500_pcm_soc_drv); + if (ret < 0) { + dev_err(&pdev->dev, + "%s: ERROR: Failed to register platform '%s' (%d)!\n", + __func__, pdev->name, ret); + return ret; + } + + return 0; +} + +static int __devinit ux500_pcm_drv_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver ux500_pcm_driver = { + .driver = { + .name = "ux500-pcm", + .owner = THIS_MODULE, + }, + + .probe = ux500_pcm_drv_probe, + .remove = __devexit_p(ux500_pcm_drv_remove), +}; +module_platform_driver(ux500_pcm_driver); + +MODULE_LICENSE("GPL v2"); |