1/* 2 * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> 3 * 4 * This program is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU General Public License as published by the 6 * Free Software Foundation; either version 2 of the License, or (at your 7 * option) any later version. 8 * 9 * You should have received a copy of the GNU General Public License along 10 * with this program; if not, write to the Free Software Foundation, Inc., 11 * 675 Mass Ave, Cambridge, MA 02139, USA. 12 * 13 */ 14 15#include <linux/init.h> 16#include <linux/interrupt.h> 17#include <linux/kernel.h> 18#include <linux/module.h> 19#include <linux/platform_device.h> 20#include <linux/slab.h> 21 22#include <linux/dma-mapping.h> 23 24#include <sound/core.h> 25#include <sound/pcm.h> 26#include <sound/pcm_params.h> 27#include <sound/soc.h> 28 29#include <asm/mach-jz4740/dma.h> 30#include "jz4740-pcm.h" 31 32struct jz4740_runtime_data { 33 unsigned long dma_period; 34 dma_addr_t dma_start; 35 dma_addr_t dma_pos; 36 dma_addr_t dma_end; 37 38 struct jz4740_dma_chan *dma; 39 40 dma_addr_t fifo_addr; 41}; 42 43/* identify hardware playback capabilities */ 44static const struct snd_pcm_hardware jz4740_pcm_hardware = { 45 .info = SNDRV_PCM_INFO_MMAP | 46 SNDRV_PCM_INFO_MMAP_VALID | 47 SNDRV_PCM_INFO_INTERLEAVED | 48 SNDRV_PCM_INFO_BLOCK_TRANSFER, 49 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, 50 51 .rates = SNDRV_PCM_RATE_8000_48000, 52 .channels_min = 1, 53 .channels_max = 2, 54 .period_bytes_min = 16, 55 .period_bytes_max = 2 * PAGE_SIZE, 56 .periods_min = 2, 57 .periods_max = 128, 58 .buffer_bytes_max = 128 * 2 * PAGE_SIZE, 59 .fifo_size = 32, 60}; 61 62static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd, 63 struct snd_pcm_substream *substream) 64{ 65 unsigned long count; 66 67 if (prtd->dma_pos == prtd->dma_end) 68 prtd->dma_pos = prtd->dma_start; 69 70 if (prtd->dma_pos + prtd->dma_period > prtd->dma_end) 71 count = prtd->dma_end - prtd->dma_pos; 72 else 73 count = prtd->dma_period; 74 75 jz4740_dma_disable(prtd->dma); 76 77 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 78 jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos); 79 jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr); 80 } else { 81 jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr); 82 jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos); 83 } 84 85 jz4740_dma_set_transfer_count(prtd->dma, count); 86 87 prtd->dma_pos += count; 88 89 jz4740_dma_enable(prtd->dma); 90} 91 92static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err, 93 void *dev_id) 94{ 95 struct snd_pcm_substream *substream = dev_id; 96 struct snd_pcm_runtime *runtime = substream->runtime; 97 struct jz4740_runtime_data *prtd = runtime->private_data; 98 99 snd_pcm_period_elapsed(substream); 100 101 jz4740_pcm_start_transfer(prtd, substream); 102} 103 104static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream, 105 struct snd_pcm_hw_params *params) 106{ 107 struct snd_pcm_runtime *runtime = substream->runtime; 108 struct jz4740_runtime_data *prtd = runtime->private_data; 109 struct snd_soc_pcm_runtime *rtd = substream->private_data; 110 struct jz4740_pcm_config *config; 111 112 config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); 113 114 if (!config) 115 return 0; 116 117 if (!prtd->dma) { 118 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 119 prtd->dma = jz4740_dma_request(substream, "PCM Capture"); 120 else 121 prtd->dma = jz4740_dma_request(substream, "PCM Playback"); 122 } 123 124 if (!prtd->dma) 125 return -EBUSY; 126 127 jz4740_dma_configure(prtd->dma, &config->dma_config); 128 prtd->fifo_addr = config->fifo_addr; 129 130 jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done); 131 132 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 133 runtime->dma_bytes = params_buffer_bytes(params); 134 135 prtd->dma_period = params_period_bytes(params); 136 prtd->dma_start = runtime->dma_addr; 137 prtd->dma_pos = prtd->dma_start; 138 prtd->dma_end = prtd->dma_start + runtime->dma_bytes; 139 140 return 0; 141} 142 143static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream) 144{ 145 struct jz4740_runtime_data *prtd = substream->runtime->private_data; 146 147 snd_pcm_set_runtime_buffer(substream, NULL); 148 if (prtd->dma) { 149 jz4740_dma_free(prtd->dma); 150 prtd->dma = NULL; 151 } 152 153 return 0; 154} 155 156static int jz4740_pcm_prepare(struct snd_pcm_substream *substream) 157{ 158 struct jz4740_runtime_data *prtd = substream->runtime->private_data; 159 160 if (!prtd->dma) 161 return -EBUSY; 162 163 prtd->dma_pos = prtd->dma_start; 164 165 return 0; 166} 167 168static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 169{ 170 struct snd_pcm_runtime *runtime = substream->runtime; 171 struct jz4740_runtime_data *prtd = runtime->private_data; 172 173 switch (cmd) { 174 case SNDRV_PCM_TRIGGER_START: 175 case SNDRV_PCM_TRIGGER_RESUME: 176 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 177 jz4740_pcm_start_transfer(prtd, substream); 178 break; 179 case SNDRV_PCM_TRIGGER_STOP: 180 case SNDRV_PCM_TRIGGER_SUSPEND: 181 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 182 jz4740_dma_disable(prtd->dma); 183 break; 184 default: 185 break; 186 } 187 188 return 0; 189} 190 191static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream) 192{ 193 struct snd_pcm_runtime *runtime = substream->runtime; 194 struct jz4740_runtime_data *prtd = runtime->private_data; 195 unsigned long byte_offset; 196 snd_pcm_uframes_t offset; 197 struct jz4740_dma_chan *dma = prtd->dma; 198 199 /* prtd->dma_pos points to the end of the current transfer. So by 200 * subtracting prdt->dma_start we get the offset to the end of the 201 * current period in bytes. By subtracting the residue of the transfer 202 * we get the current offset in bytes. */ 203 byte_offset = prtd->dma_pos - prtd->dma_start; 204 byte_offset -= jz4740_dma_get_residue(dma); 205 206 offset = bytes_to_frames(runtime, byte_offset); 207 if (offset >= runtime->buffer_size) 208 offset = 0; 209 210 return offset; 211} 212 213static int jz4740_pcm_open(struct snd_pcm_substream *substream) 214{ 215 struct snd_pcm_runtime *runtime = substream->runtime; 216 struct jz4740_runtime_data *prtd; 217 218 prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); 219 if (prtd == NULL) 220 return -ENOMEM; 221 222 snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware); 223 224 runtime->private_data = prtd; 225 226 return 0; 227} 228 229static int jz4740_pcm_close(struct snd_pcm_substream *substream) 230{ 231 struct snd_pcm_runtime *runtime = substream->runtime; 232 struct jz4740_runtime_data *prtd = runtime->private_data; 233 234 kfree(prtd); 235 236 return 0; 237} 238 239static int jz4740_pcm_mmap(struct snd_pcm_substream *substream, 240 struct vm_area_struct *vma) 241{ 242 return remap_pfn_range(vma, vma->vm_start, 243 substream->dma_buffer.addr >> PAGE_SHIFT, 244 vma->vm_end - vma->vm_start, vma->vm_page_prot); 245} 246 247static struct snd_pcm_ops jz4740_pcm_ops = { 248 .open = jz4740_pcm_open, 249 .close = jz4740_pcm_close, 250 .ioctl = snd_pcm_lib_ioctl, 251 .hw_params = jz4740_pcm_hw_params, 252 .hw_free = jz4740_pcm_hw_free, 253 .prepare = jz4740_pcm_prepare, 254 .trigger = jz4740_pcm_trigger, 255 .pointer = jz4740_pcm_pointer, 256 .mmap = jz4740_pcm_mmap, 257}; 258 259static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) 260{ 261 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 262 struct snd_dma_buffer *buf = &substream->dma_buffer; 263 size_t size = jz4740_pcm_hardware.buffer_bytes_max; 264 265 buf->dev.type = SNDRV_DMA_TYPE_DEV; 266 buf->dev.dev = pcm->card->dev; 267 buf->private_data = NULL; 268 269 buf->area = dma_alloc_noncoherent(pcm->card->dev, size, 270 &buf->addr, GFP_KERNEL); 271 if (!buf->area) 272 return -ENOMEM; 273 274 buf->bytes = size; 275 276 return 0; 277} 278 279static void jz4740_pcm_free(struct snd_pcm *pcm) 280{ 281 struct snd_pcm_substream *substream; 282 struct snd_dma_buffer *buf; 283 int stream; 284 285 for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) { 286 substream = pcm->streams[stream].substream; 287 if (!substream) 288 continue; 289 290 buf = &substream->dma_buffer; 291 if (!buf->area) 292 continue; 293 294 dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area, 295 buf->addr); 296 buf->area = NULL; 297 } 298} 299 300static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32); 301 302int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, 303 struct snd_pcm *pcm) 304{ 305 int ret = 0; 306 307 if (!card->dev->dma_mask) 308 card->dev->dma_mask = &jz4740_pcm_dmamask; 309 310 if (!card->dev->coherent_dma_mask) 311 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 312 313 if (dai->playback.channels_min) { 314 ret = jz4740_pcm_preallocate_dma_buffer(pcm, 315 SNDRV_PCM_STREAM_PLAYBACK); 316 if (ret) 317 goto err; 318 } 319 320 if (dai->capture.channels_min) { 321 ret = jz4740_pcm_preallocate_dma_buffer(pcm, 322 SNDRV_PCM_STREAM_CAPTURE); 323 if (ret) 324 goto err; 325 } 326 327err: 328 return ret; 329} 330 331struct snd_soc_platform jz4740_soc_platform = { 332 .name = "jz4740-pcm", 333 .pcm_ops = &jz4740_pcm_ops, 334 .pcm_new = jz4740_pcm_new, 335 .pcm_free = jz4740_pcm_free, 336}; 337EXPORT_SYMBOL_GPL(jz4740_soc_platform); 338 339static int __devinit jz4740_pcm_probe(struct platform_device *pdev) 340{ 341 return snd_soc_register_platform(&jz4740_soc_platform); 342} 343 344static int __devexit jz4740_pcm_remove(struct platform_device *pdev) 345{ 346 snd_soc_unregister_platform(&jz4740_soc_platform); 347 return 0; 348} 349 350static struct platform_driver jz4740_pcm_driver = { 351 .probe = jz4740_pcm_probe, 352 .remove = __devexit_p(jz4740_pcm_remove), 353 .driver = { 354 .name = "jz4740-pcm", 355 .owner = THIS_MODULE, 356 }, 357}; 358 359static int __init jz4740_soc_platform_init(void) 360{ 361 return platform_driver_register(&jz4740_pcm_driver); 362} 363module_init(jz4740_soc_platform_init); 364 365static void __exit jz4740_soc_platform_exit(void) 366{ 367 return platform_driver_unregister(&jz4740_pcm_driver); 368} 369module_exit(jz4740_soc_platform_exit); 370 371MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 372MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver"); 373MODULE_LICENSE("GPL"); 374