/* * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer * * Copyright 2009 Sascha Hauer * * This code is based on code copyrighted by Freescale, * Liam Girdwood, Javier Martin and probably others. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ #include #include #include #include #include #include #include #include #include "imx-pcm.h" static struct snd_pcm_hardware imx_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, .period_bytes_min = 128, .period_bytes_max = 65532, /* Limited by SDMA engine */ .periods_min = 2, .periods_max = 255, .fifo_size = 0, }; static bool imx_dma_filter_fn(struct dma_chan *chan, void *param) { if (!imx_dma_is_general_purpose(chan)) return false; chan->private = param; return true; } /* this may get called several times by oss emulation */ static int imx_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 snd_dmaengine_dai_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); /* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */ if (!dma_data) return 0; 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; snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data, &config); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) config.dst_fifo_num = dma_data->fifo_num; else config.src_fifo_num = dma_data->fifo_num; return dmaengine_slave_config(chan, &config); } static int imx_pcm_hw_free(struct snd_pcm_substream *substream) { snd_pcm_set_runtime_buffer(substream, NULL); return 0; } static snd_pcm_uframes_t imx_pcm_pointer(struct snd_pcm_substream *substream) { return snd_dmaengine_pcm_pointer(substream); } static int imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, struct device *dev) { size_t size = imx_pcm_hardware.buffer_bytes_max; int ret; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_IRAM, dev, size, &substream->dma_buffer); if (ret) return ret; return 0; } static void imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream) { if (substream) { snd_dma_free_pages(&substream->dma_buffer); substream->dma_buffer.area = NULL; substream->dma_buffer.addr = 0; } } static int imx_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_dmaengine_dai_dma_data *dma_data; struct dma_slave_caps dma_caps; struct dma_chan *chan; u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); int ret; int i; dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); /* DT boot: filter_data is the DMA name */ if (rtd->cpu_dai->dev->of_node) { struct dma_chan *chan; chan = dma_request_slave_channel(rtd->cpu_dai->dev, dma_data->chan_name); ret = snd_dmaengine_pcm_open(substream, chan); if (ret) return ret; } else { ret = snd_dmaengine_pcm_open_request_chan(substream, imx_dma_filter_fn, dma_data->filter_data); if (ret) return ret; } chan = snd_dmaengine_pcm_get_chan(substream); ret = dma_get_slave_caps(chan, &dma_caps); if (ret == 0) { if (dma_caps.cmd_pause) imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) addr_widths = dma_caps.dst_addr_widths; else addr_widths = dma_caps.src_addr_widths; } /* * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep * hw.formats set to 0, meaning no restrictions are in place. * In this case it's the responsibility of the DAI driver to * provide the supported format information. */ if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) /* * Prepare formats mask for valid/allowed sample types. If the * dma does not have support for the given physical word size, * it needs to be masked out so user space can not use the * format which produces corrupted audio. * In case the dma driver does not implement the slave_caps the * default assumption is that it supports 1, 2 and 4 bytes * widths. */ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { int bits = snd_pcm_format_physical_width(i); /* * Enable only samples with DMA supported physical * widths */ switch (bits) { case 8: case 16: case 24: case 32: case 64: if (addr_widths & (1 << (bits / 8))) imx_pcm_hardware.formats |= (1LL << i); break; default: /* Unsupported types */ break; } } snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware); ret = imx_pcm_preallocate_dma_buffer(substream, chan->device->dev); if (ret) return ret; ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) return ret; return 0; } static int imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = substream->runtime; return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); } static int imx_pcm_close(struct snd_pcm_substream *substream) { imx_pcm_free_dma_buffers(substream); return snd_dmaengine_pcm_close_release_chan(substream); } static struct snd_pcm_ops imx_pcm_ops = { .open = imx_pcm_open, .close = imx_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = imx_pcm_hw_params, .hw_free = imx_pcm_hw_free, .trigger = snd_dmaengine_pcm_trigger, .pointer = imx_pcm_pointer, .mmap = imx_pcm_mmap, }; static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; int ret = 0; ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); if (ret) return ret; return ret; } static struct snd_soc_component_driver imx_soc_platform = { .name = "imx-pcm-dma-v2", .ops = &imx_pcm_ops, .pcm_new = imx_pcm_new, }; int imx_pcm_platform_register(struct device *dev) { return devm_snd_soc_register_component(dev, &imx_soc_platform, NULL, 0); } EXPORT_SYMBOL_GPL(imx_pcm_platform_register); MODULE_LICENSE("GPL");