1/* 2 * omap-pcm.c -- ALSA PCM interface for the OMAP SoC 3 * 4 * Copyright (C) 2008 Nokia Corporation 5 * 6 * Contact: Jarkko Nikula <jhnikula@gmail.com> 7 * Peter Ujfalusi <peter.ujfalusi@nokia.com> 8 * 9 * This program is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU General Public License 11 * version 2 as published by the Free Software Foundation. 12 * 13 * This program is distributed in the hope that it will be useful, but 14 * WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 21 * 02110-1301 USA 22 * 23 */ 24 25#include <linux/dma-mapping.h> 26#include <linux/slab.h> 27#include <sound/core.h> 28#include <sound/pcm.h> 29#include <sound/pcm_params.h> 30#include <sound/soc.h> 31 32#include <plat/dma.h> 33#include "omap-pcm.h" 34 35static const struct snd_pcm_hardware omap_pcm_hardware = { 36 .info = SNDRV_PCM_INFO_MMAP | 37 SNDRV_PCM_INFO_MMAP_VALID | 38 SNDRV_PCM_INFO_INTERLEAVED | 39 SNDRV_PCM_INFO_PAUSE | 40 SNDRV_PCM_INFO_RESUME, 41 .formats = SNDRV_PCM_FMTBIT_S16_LE | 42 SNDRV_PCM_FMTBIT_S32_LE, 43 .period_bytes_min = 32, 44 .period_bytes_max = 64 * 1024, 45 .periods_min = 2, 46 .periods_max = 255, 47 .buffer_bytes_max = 128 * 1024, 48}; 49 50struct omap_runtime_data { 51 spinlock_t lock; 52 struct omap_pcm_dma_data *dma_data; 53 int dma_ch; 54 int period_index; 55}; 56 57static void omap_pcm_dma_irq(int ch, u16 stat, void *data) 58{ 59 struct snd_pcm_substream *substream = data; 60 struct snd_pcm_runtime *runtime = substream->runtime; 61 struct omap_runtime_data *prtd = runtime->private_data; 62 unsigned long flags; 63 64 if ((cpu_is_omap1510())) { 65 /* 66 * OMAP1510 doesn't fully support DMA progress counter 67 * and there is no software emulation implemented yet, 68 * so have to maintain our own progress counters 69 * that can be used by omap_pcm_pointer() instead. 70 */ 71 spin_lock_irqsave(&prtd->lock, flags); 72 if ((stat == OMAP_DMA_LAST_IRQ) && 73 (prtd->period_index == runtime->periods - 1)) { 74 /* we are in sync, do nothing */ 75 spin_unlock_irqrestore(&prtd->lock, flags); 76 return; 77 } 78 if (prtd->period_index >= 0) { 79 if (stat & OMAP_DMA_BLOCK_IRQ) { 80 /* end of buffer reached, loop back */ 81 prtd->period_index = 0; 82 } else if (stat & OMAP_DMA_LAST_IRQ) { 83 /* update the counter for the last period */ 84 prtd->period_index = runtime->periods - 1; 85 } else if (++prtd->period_index >= runtime->periods) { 86 /* end of buffer missed? loop back */ 87 prtd->period_index = 0; 88 } 89 } 90 spin_unlock_irqrestore(&prtd->lock, flags); 91 } 92 93 snd_pcm_period_elapsed(substream); 94} 95 96/* this may get called several times by oss emulation */ 97static int omap_pcm_hw_params(struct snd_pcm_substream *substream, 98 struct snd_pcm_hw_params *params) 99{ 100 struct snd_pcm_runtime *runtime = substream->runtime; 101 struct snd_soc_pcm_runtime *rtd = substream->private_data; 102 struct omap_runtime_data *prtd = runtime->private_data; 103 struct omap_pcm_dma_data *dma_data; 104 int err = 0; 105 106 dma_data = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); 107 108 if (!dma_data) 109 return 0; 110 111 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 112 runtime->dma_bytes = params_buffer_bytes(params); 113 114 if (prtd->dma_data) 115 return 0; 116 prtd->dma_data = dma_data; 117 err = omap_request_dma(dma_data->dma_req, dma_data->name, 118 omap_pcm_dma_irq, substream, &prtd->dma_ch); 119 if (!err) { 120 /* 121 * Link channel with itself so DMA doesn't need any 122 * reprogramming while looping the buffer 123 */ 124 omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch); 125 } 126 127 return err; 128} 129 130static int omap_pcm_hw_free(struct snd_pcm_substream *substream) 131{ 132 struct snd_pcm_runtime *runtime = substream->runtime; 133 struct omap_runtime_data *prtd = runtime->private_data; 134 135 if (prtd->dma_data == NULL) 136 return 0; 137 138 omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch); 139 omap_free_dma(prtd->dma_ch); 140 prtd->dma_data = NULL; 141 142 snd_pcm_set_runtime_buffer(substream, NULL); 143 144 return 0; 145} 146 147static int omap_pcm_prepare(struct snd_pcm_substream *substream) 148{ 149 struct snd_pcm_runtime *runtime = substream->runtime; 150 struct omap_runtime_data *prtd = runtime->private_data; 151 struct omap_pcm_dma_data *dma_data = prtd->dma_data; 152 struct omap_dma_channel_params dma_params; 153 int bytes; 154 155 if (!prtd->dma_data) 156 return 0; 157 158 memset(&dma_params, 0, sizeof(dma_params)); 159 dma_params.data_type = dma_data->data_type; 160 dma_params.trigger = dma_data->dma_req; 161 dma_params.sync_mode = dma_data->sync_mode; 162 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 163 dma_params.src_amode = OMAP_DMA_AMODE_POST_INC; 164 dma_params.dst_amode = OMAP_DMA_AMODE_CONSTANT; 165 dma_params.src_or_dst_synch = OMAP_DMA_DST_SYNC; 166 dma_params.src_start = runtime->dma_addr; 167 dma_params.dst_start = dma_data->port_addr; 168 dma_params.dst_port = OMAP_DMA_PORT_MPUI; 169 dma_params.dst_fi = dma_data->packet_size; 170 } else { 171 dma_params.src_amode = OMAP_DMA_AMODE_CONSTANT; 172 dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; 173 dma_params.src_or_dst_synch = OMAP_DMA_SRC_SYNC; 174 dma_params.src_start = dma_data->port_addr; 175 dma_params.dst_start = runtime->dma_addr; 176 dma_params.src_port = OMAP_DMA_PORT_MPUI; 177 dma_params.src_fi = dma_data->packet_size; 178 } 179 /* 180 * Set DMA transfer frame size equal to ALSA period size and frame 181 * count as no. of ALSA periods. Then with DMA frame interrupt enabled, 182 * we can transfer the whole ALSA buffer with single DMA transfer but 183 * still can get an interrupt at each period bounary 184 */ 185 bytes = snd_pcm_lib_period_bytes(substream); 186 dma_params.elem_count = bytes >> dma_data->data_type; 187 dma_params.frame_count = runtime->periods; 188 omap_set_dma_params(prtd->dma_ch, &dma_params); 189 190 if ((cpu_is_omap1510())) 191 omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ | 192 OMAP_DMA_LAST_IRQ | OMAP_DMA_BLOCK_IRQ); 193 else 194 omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ); 195 196 if (!(cpu_class_is_omap1())) { 197 omap_set_dma_src_burst_mode(prtd->dma_ch, 198 OMAP_DMA_DATA_BURST_16); 199 omap_set_dma_dest_burst_mode(prtd->dma_ch, 200 OMAP_DMA_DATA_BURST_16); 201 } 202 203 return 0; 204} 205 206static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 207{ 208 struct snd_pcm_runtime *runtime = substream->runtime; 209 struct omap_runtime_data *prtd = runtime->private_data; 210 struct omap_pcm_dma_data *dma_data = prtd->dma_data; 211 unsigned long flags; 212 int ret = 0; 213 214 spin_lock_irqsave(&prtd->lock, flags); 215 switch (cmd) { 216 case SNDRV_PCM_TRIGGER_START: 217 case SNDRV_PCM_TRIGGER_RESUME: 218 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 219 prtd->period_index = 0; 220 /* Configure McBSP internal buffer usage */ 221 if (dma_data->set_threshold) 222 dma_data->set_threshold(substream); 223 224 omap_start_dma(prtd->dma_ch); 225 break; 226 227 case SNDRV_PCM_TRIGGER_STOP: 228 case SNDRV_PCM_TRIGGER_SUSPEND: 229 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 230 prtd->period_index = -1; 231 omap_stop_dma(prtd->dma_ch); 232 break; 233 default: 234 ret = -EINVAL; 235 } 236 spin_unlock_irqrestore(&prtd->lock, flags); 237 238 return ret; 239} 240 241static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream) 242{ 243 struct snd_pcm_runtime *runtime = substream->runtime; 244 struct omap_runtime_data *prtd = runtime->private_data; 245 dma_addr_t ptr; 246 snd_pcm_uframes_t offset; 247 248 if (cpu_is_omap1510()) { 249 offset = prtd->period_index * runtime->period_size; 250 } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { 251 ptr = omap_get_dma_dst_pos(prtd->dma_ch); 252 offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); 253 } else { 254 ptr = omap_get_dma_src_pos(prtd->dma_ch); 255 offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); 256 } 257 258 if (offset >= runtime->buffer_size) 259 offset = 0; 260 261 return offset; 262} 263 264static int omap_pcm_open(struct snd_pcm_substream *substream) 265{ 266 struct snd_pcm_runtime *runtime = substream->runtime; 267 struct omap_runtime_data *prtd; 268 int ret; 269 270 snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware); 271 272 /* Ensure that buffer size is a multiple of period size */ 273 ret = snd_pcm_hw_constraint_integer(runtime, 274 SNDRV_PCM_HW_PARAM_PERIODS); 275 if (ret < 0) 276 goto out; 277 278 prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); 279 if (prtd == NULL) { 280 ret = -ENOMEM; 281 goto out; 282 } 283 spin_lock_init(&prtd->lock); 284 runtime->private_data = prtd; 285 286out: 287 return ret; 288} 289 290static int omap_pcm_close(struct snd_pcm_substream *substream) 291{ 292 struct snd_pcm_runtime *runtime = substream->runtime; 293 294 kfree(runtime->private_data); 295 return 0; 296} 297 298static int omap_pcm_mmap(struct snd_pcm_substream *substream, 299 struct vm_area_struct *vma) 300{ 301 struct snd_pcm_runtime *runtime = substream->runtime; 302 303 return dma_mmap_writecombine(substream->pcm->card->dev, vma, 304 runtime->dma_area, 305 runtime->dma_addr, 306 runtime->dma_bytes); 307} 308 309static struct snd_pcm_ops omap_pcm_ops = { 310 .open = omap_pcm_open, 311 .close = omap_pcm_close, 312 .ioctl = snd_pcm_lib_ioctl, 313 .hw_params = omap_pcm_hw_params, 314 .hw_free = omap_pcm_hw_free, 315 .prepare = omap_pcm_prepare, 316 .trigger = omap_pcm_trigger, 317 .pointer = omap_pcm_pointer, 318 .mmap = omap_pcm_mmap, 319}; 320 321static u64 omap_pcm_dmamask = DMA_BIT_MASK(64); 322 323static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, 324 int stream) 325{ 326 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 327 struct snd_dma_buffer *buf = &substream->dma_buffer; 328 size_t size = omap_pcm_hardware.buffer_bytes_max; 329 330 buf->dev.type = SNDRV_DMA_TYPE_DEV; 331 buf->dev.dev = pcm->card->dev; 332 buf->private_data = NULL; 333 buf->area = dma_alloc_writecombine(pcm->card->dev, size, 334 &buf->addr, GFP_KERNEL); 335 if (!buf->area) 336 return -ENOMEM; 337 338 buf->bytes = size; 339 return 0; 340} 341 342static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm) 343{ 344 struct snd_pcm_substream *substream; 345 struct snd_dma_buffer *buf; 346 int stream; 347 348 for (stream = 0; stream < 2; stream++) { 349 substream = pcm->streams[stream].substream; 350 if (!substream) 351 continue; 352 353 buf = &substream->dma_buffer; 354 if (!buf->area) 355 continue; 356 357 dma_free_writecombine(pcm->card->dev, buf->bytes, 358 buf->area, buf->addr); 359 buf->area = NULL; 360 } 361} 362 363static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, 364 struct snd_pcm *pcm) 365{ 366 int ret = 0; 367 368 if (!card->dev->dma_mask) 369 card->dev->dma_mask = &omap_pcm_dmamask; 370 if (!card->dev->coherent_dma_mask) 371 card->dev->coherent_dma_mask = DMA_BIT_MASK(64); 372 373 if (dai->playback.channels_min) { 374 ret = omap_pcm_preallocate_dma_buffer(pcm, 375 SNDRV_PCM_STREAM_PLAYBACK); 376 if (ret) 377 goto out; 378 } 379 380 if (dai->capture.channels_min) { 381 ret = omap_pcm_preallocate_dma_buffer(pcm, 382 SNDRV_PCM_STREAM_CAPTURE); 383 if (ret) 384 goto out; 385 } 386 387out: 388 return ret; 389} 390 391struct snd_soc_platform omap_soc_platform = { 392 .name = "omap-pcm-audio", 393 .pcm_ops = &omap_pcm_ops, 394 .pcm_new = omap_pcm_new, 395 .pcm_free = omap_pcm_free_dma_buffers, 396}; 397EXPORT_SYMBOL_GPL(omap_soc_platform); 398 399static int __init omap_soc_platform_init(void) 400{ 401 return snd_soc_register_platform(&omap_soc_platform); 402} 403module_init(omap_soc_platform_init); 404 405static void __exit omap_soc_platform_exit(void) 406{ 407 snd_soc_unregister_platform(&omap_soc_platform); 408} 409module_exit(omap_soc_platform_exit); 410 411MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>"); 412MODULE_DESCRIPTION("OMAP PCM DMA module"); 413MODULE_LICENSE("GPL"); 414