1/* 2 * linux/sound/soc/pxa/mmp-pcm.c 3 * 4 * Copyright (C) 2011 Marvell International Ltd. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 */ 12#include <linux/module.h> 13#include <linux/init.h> 14#include <linux/platform_device.h> 15#include <linux/slab.h> 16#include <linux/dma-mapping.h> 17#include <linux/dmaengine.h> 18#include <linux/platform_data/dma-mmp_tdma.h> 19#include <linux/platform_data/mmp_audio.h> 20 21#include <sound/pxa2xx-lib.h> 22#include <sound/core.h> 23#include <sound/pcm.h> 24#include <sound/pcm_params.h> 25#include <sound/soc.h> 26#include <sound/dmaengine_pcm.h> 27 28struct mmp_dma_data { 29 int ssp_id; 30 struct resource *dma_res; 31}; 32 33#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \ 34 SNDRV_PCM_INFO_MMAP_VALID | \ 35 SNDRV_PCM_INFO_INTERLEAVED | \ 36 SNDRV_PCM_INFO_PAUSE | \ 37 SNDRV_PCM_INFO_RESUME | \ 38 SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) 39 40static struct snd_pcm_hardware mmp_pcm_hardware[] = { 41 { 42 .info = MMP_PCM_INFO, 43 .period_bytes_min = 1024, 44 .period_bytes_max = 2048, 45 .periods_min = 2, 46 .periods_max = 32, 47 .buffer_bytes_max = 4096, 48 .fifo_size = 32, 49 }, 50 { 51 .info = MMP_PCM_INFO, 52 .period_bytes_min = 1024, 53 .period_bytes_max = 2048, 54 .periods_min = 2, 55 .periods_max = 32, 56 .buffer_bytes_max = 4096, 57 .fifo_size = 32, 58 }, 59}; 60 61static int mmp_pcm_hw_params(struct snd_pcm_substream *substream, 62 struct snd_pcm_hw_params *params) 63{ 64 struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); 65 struct dma_slave_config slave_config; 66 int ret; 67 68 ret = 69 snd_dmaengine_pcm_prepare_slave_config(substream, params, 70 &slave_config); 71 if (ret) 72 return ret; 73 74 ret = dmaengine_slave_config(chan, &slave_config); 75 if (ret) 76 return ret; 77 78 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 79 80 return 0; 81} 82 83static bool filter(struct dma_chan *chan, void *param) 84{ 85 struct mmp_dma_data *dma_data = param; 86 bool found = false; 87 char *devname; 88 89 devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name, 90 dma_data->ssp_id); 91 if ((strcmp(dev_name(chan->device->dev), devname) == 0) && 92 (chan->chan_id == dma_data->dma_res->start)) { 93 found = true; 94 } 95 96 kfree(devname); 97 return found; 98} 99 100static int mmp_pcm_open(struct snd_pcm_substream *substream) 101{ 102 struct snd_soc_pcm_runtime *rtd = substream->private_data; 103 struct platform_device *pdev = to_platform_device(rtd->platform->dev); 104 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 105 struct mmp_dma_data dma_data; 106 struct resource *r; 107 108 r = platform_get_resource(pdev, IORESOURCE_DMA, substream->stream); 109 if (!r) 110 return -EBUSY; 111 112 snd_soc_set_runtime_hwparams(substream, 113 &mmp_pcm_hardware[substream->stream]); 114 115 dma_data.dma_res = r; 116 dma_data.ssp_id = cpu_dai->id; 117 118 return snd_dmaengine_pcm_open_request_chan(substream, filter, 119 &dma_data); 120} 121 122static int mmp_pcm_mmap(struct snd_pcm_substream *substream, 123 struct vm_area_struct *vma) 124{ 125 struct snd_pcm_runtime *runtime = substream->runtime; 126 unsigned long off = vma->vm_pgoff; 127 128 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 129 return remap_pfn_range(vma, vma->vm_start, 130 __phys_to_pfn(runtime->dma_addr) + off, 131 vma->vm_end - vma->vm_start, vma->vm_page_prot); 132} 133 134static struct snd_pcm_ops mmp_pcm_ops = { 135 .open = mmp_pcm_open, 136 .close = snd_dmaengine_pcm_close_release_chan, 137 .ioctl = snd_pcm_lib_ioctl, 138 .hw_params = mmp_pcm_hw_params, 139 .trigger = snd_dmaengine_pcm_trigger, 140 .pointer = snd_dmaengine_pcm_pointer, 141 .mmap = mmp_pcm_mmap, 142}; 143 144static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm) 145{ 146 struct snd_pcm_substream *substream; 147 struct snd_dma_buffer *buf; 148 int stream; 149 struct gen_pool *gpool; 150 151 gpool = sram_get_gpool("asram"); 152 if (!gpool) 153 return; 154 155 for (stream = 0; stream < 2; stream++) { 156 size_t size = mmp_pcm_hardware[stream].buffer_bytes_max; 157 158 substream = pcm->streams[stream].substream; 159 if (!substream) 160 continue; 161 162 buf = &substream->dma_buffer; 163 if (!buf->area) 164 continue; 165 gen_pool_free(gpool, (unsigned long)buf->area, size); 166 buf->area = NULL; 167 } 168 169 return; 170} 171 172static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, 173 int stream) 174{ 175 struct snd_dma_buffer *buf = &substream->dma_buffer; 176 size_t size = mmp_pcm_hardware[stream].buffer_bytes_max; 177 struct gen_pool *gpool; 178 179 buf->dev.type = SNDRV_DMA_TYPE_DEV; 180 buf->dev.dev = substream->pcm->card->dev; 181 buf->private_data = NULL; 182 183 gpool = sram_get_gpool("asram"); 184 if (!gpool) 185 return -ENOMEM; 186 187 buf->area = gen_pool_dma_alloc(gpool, size, &buf->addr); 188 if (!buf->area) 189 return -ENOMEM; 190 buf->bytes = size; 191 return 0; 192} 193 194static int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd) 195{ 196 struct snd_pcm_substream *substream; 197 struct snd_pcm *pcm = rtd->pcm; 198 int ret = 0, stream; 199 200 for (stream = 0; stream < 2; stream++) { 201 substream = pcm->streams[stream].substream; 202 203 ret = mmp_pcm_preallocate_dma_buffer(substream, stream); 204 if (ret) 205 goto err; 206 } 207 208 return 0; 209 210err: 211 mmp_pcm_free_dma_buffers(pcm); 212 return ret; 213} 214 215static struct snd_soc_platform_driver mmp_soc_platform = { 216 .ops = &mmp_pcm_ops, 217 .pcm_new = mmp_pcm_new, 218 .pcm_free = mmp_pcm_free_dma_buffers, 219}; 220 221static int mmp_pcm_probe(struct platform_device *pdev) 222{ 223 struct mmp_audio_platdata *pdata = pdev->dev.platform_data; 224 225 if (pdata) { 226 mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].buffer_bytes_max = 227 pdata->buffer_max_playback; 228 mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].period_bytes_max = 229 pdata->period_max_playback; 230 mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].buffer_bytes_max = 231 pdata->buffer_max_capture; 232 mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].period_bytes_max = 233 pdata->period_max_capture; 234 } 235 return snd_soc_register_platform(&pdev->dev, &mmp_soc_platform); 236} 237 238static int mmp_pcm_remove(struct platform_device *pdev) 239{ 240 snd_soc_unregister_platform(&pdev->dev); 241 return 0; 242} 243 244static struct platform_driver mmp_pcm_driver = { 245 .driver = { 246 .name = "mmp-pcm-audio", 247 }, 248 249 .probe = mmp_pcm_probe, 250 .remove = mmp_pcm_remove, 251}; 252 253module_platform_driver(mmp_pcm_driver); 254 255MODULE_AUTHOR("Leo Yan <leoy@marvell.com>"); 256MODULE_DESCRIPTION("MMP Soc Audio DMA module"); 257MODULE_LICENSE("GPL"); 258