1/* 2 * Copyright (c) 2010 Nuvoton technology corporation. 3 * 4 * Wan ZongShun <mcuos.com@gmail.com> 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;version 2 of the License. 9 * 10 */ 11 12#include <linux/module.h> 13#include <linux/init.h> 14#include <linux/io.h> 15#include <linux/platform_device.h> 16#include <linux/slab.h> 17#include <linux/dma-mapping.h> 18 19#include <sound/core.h> 20#include <sound/pcm.h> 21#include <sound/pcm_params.h> 22#include <sound/soc.h> 23 24#include <mach/hardware.h> 25 26#include "nuc900-audio.h" 27 28static const struct snd_pcm_hardware nuc900_pcm_hardware = { 29 .info = SNDRV_PCM_INFO_INTERLEAVED | 30 SNDRV_PCM_INFO_BLOCK_TRANSFER | 31 SNDRV_PCM_INFO_MMAP | 32 SNDRV_PCM_INFO_MMAP_VALID | 33 SNDRV_PCM_INFO_PAUSE | 34 SNDRV_PCM_INFO_RESUME, 35 .formats = SNDRV_PCM_FMTBIT_S16_LE, 36 .channels_min = 1, 37 .channels_max = 2, 38 .buffer_bytes_max = 4*1024, 39 .period_bytes_min = 1*1024, 40 .period_bytes_max = 4*1024, 41 .periods_min = 1, 42 .periods_max = 1024, 43}; 44 45static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, 46 struct snd_pcm_hw_params *params) 47{ 48 struct snd_pcm_runtime *runtime = substream->runtime; 49 struct nuc900_audio *nuc900_audio = runtime->private_data; 50 unsigned long flags; 51 int ret = 0; 52 53 ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); 54 if (ret < 0) 55 return ret; 56 57 spin_lock_irqsave(&nuc900_audio->lock, flags); 58 59 nuc900_audio->substream = substream; 60 nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr; 61 nuc900_audio->buffersize[substream->stream] = 62 params_buffer_bytes(params); 63 64 spin_unlock_irqrestore(&nuc900_audio->lock, flags); 65 66 return ret; 67} 68 69static void nuc900_update_dma_register(struct snd_pcm_substream *substream, 70 dma_addr_t dma_addr, size_t count) 71{ 72 struct snd_pcm_runtime *runtime = substream->runtime; 73 struct nuc900_audio *nuc900_audio = runtime->private_data; 74 void __iomem *mmio_addr, *mmio_len; 75 76 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 77 mmio_addr = nuc900_audio->mmio + ACTL_PDSTB; 78 mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH; 79 } else { 80 mmio_addr = nuc900_audio->mmio + ACTL_RDSTB; 81 mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; 82 } 83 84 AUDIO_WRITE(mmio_addr, dma_addr); 85 AUDIO_WRITE(mmio_len, count); 86} 87 88static void nuc900_dma_start(struct snd_pcm_substream *substream) 89{ 90 struct snd_pcm_runtime *runtime = substream->runtime; 91 struct nuc900_audio *nuc900_audio = runtime->private_data; 92 unsigned long val; 93 94 val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); 95 val |= (T_DMA_IRQ | R_DMA_IRQ); 96 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); 97} 98 99static void nuc900_dma_stop(struct snd_pcm_substream *substream) 100{ 101 struct snd_pcm_runtime *runtime = substream->runtime; 102 struct nuc900_audio *nuc900_audio = runtime->private_data; 103 unsigned long val; 104 105 val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); 106 val &= ~(T_DMA_IRQ | R_DMA_IRQ); 107 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); 108} 109 110static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id) 111{ 112 struct snd_pcm_substream *substream = dev_id; 113 struct nuc900_audio *nuc900_audio = substream->runtime->private_data; 114 unsigned long val; 115 116 spin_lock(&nuc900_audio->lock); 117 118 val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); 119 120 if (val & R_DMA_IRQ) { 121 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ); 122 123 val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); 124 125 if (val & R_DMA_MIDDLE_IRQ) { 126 val |= R_DMA_MIDDLE_IRQ; 127 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); 128 } 129 130 if (val & R_DMA_END_IRQ) { 131 val |= R_DMA_END_IRQ; 132 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); 133 } 134 } else if (val & T_DMA_IRQ) { 135 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ); 136 137 val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); 138 139 if (val & P_DMA_MIDDLE_IRQ) { 140 val |= P_DMA_MIDDLE_IRQ; 141 AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); 142 } 143 144 if (val & P_DMA_END_IRQ) { 145 val |= P_DMA_END_IRQ; 146 AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); 147 } 148 } else { 149 dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n"); 150 spin_unlock(&nuc900_audio->lock); 151 return IRQ_HANDLED; 152 } 153 154 spin_unlock(&nuc900_audio->lock); 155 156 snd_pcm_period_elapsed(substream); 157 158 return IRQ_HANDLED; 159} 160 161static int nuc900_dma_hw_free(struct snd_pcm_substream *substream) 162{ 163 snd_pcm_lib_free_pages(substream); 164 return 0; 165} 166 167static int nuc900_dma_prepare(struct snd_pcm_substream *substream) 168{ 169 struct snd_pcm_runtime *runtime = substream->runtime; 170 struct nuc900_audio *nuc900_audio = runtime->private_data; 171 unsigned long flags, val; 172 int ret = 0; 173 174 spin_lock_irqsave(&nuc900_audio->lock, flags); 175 176 nuc900_update_dma_register(substream, 177 nuc900_audio->dma_addr[substream->stream], 178 nuc900_audio->buffersize[substream->stream]); 179 180 val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); 181 182 switch (runtime->channels) { 183 case 1: 184 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 185 val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); 186 val |= PLAY_RIGHT_CHNNEL; 187 } else { 188 val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); 189 val |= RECORD_RIGHT_CHNNEL; 190 } 191 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); 192 break; 193 case 2: 194 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 195 val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); 196 else 197 val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); 198 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); 199 break; 200 default: 201 ret = -EINVAL; 202 } 203 spin_unlock_irqrestore(&nuc900_audio->lock, flags); 204 return ret; 205} 206 207static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) 208{ 209 int ret = 0; 210 211 switch (cmd) { 212 case SNDRV_PCM_TRIGGER_START: 213 case SNDRV_PCM_TRIGGER_RESUME: 214 nuc900_dma_start(substream); 215 break; 216 217 case SNDRV_PCM_TRIGGER_STOP: 218 case SNDRV_PCM_TRIGGER_SUSPEND: 219 nuc900_dma_stop(substream); 220 break; 221 222 default: 223 ret = -EINVAL; 224 break; 225 } 226 227 return ret; 228} 229 230int nuc900_dma_getposition(struct snd_pcm_substream *substream, 231 dma_addr_t *src, dma_addr_t *dst) 232{ 233 struct snd_pcm_runtime *runtime = substream->runtime; 234 struct nuc900_audio *nuc900_audio = runtime->private_data; 235 236 if (src != NULL) 237 *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); 238 239 if (dst != NULL) 240 *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); 241 242 return 0; 243} 244 245static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) 246{ 247 struct snd_pcm_runtime *runtime = substream->runtime; 248 dma_addr_t src, dst; 249 unsigned long res; 250 251 nuc900_dma_getposition(substream, &src, &dst); 252 253 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 254 res = dst - runtime->dma_addr; 255 else 256 res = src - runtime->dma_addr; 257 258 return bytes_to_frames(substream->runtime, res); 259} 260 261static int nuc900_dma_open(struct snd_pcm_substream *substream) 262{ 263 struct snd_pcm_runtime *runtime = substream->runtime; 264 struct nuc900_audio *nuc900_audio; 265 266 snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); 267 268 nuc900_audio = nuc900_ac97_data; 269 270 if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, 271 IRQF_DISABLED, "nuc900-dma", substream)) 272 return -EBUSY; 273 274 runtime->private_data = nuc900_audio; 275 276 return 0; 277} 278 279static int nuc900_dma_close(struct snd_pcm_substream *substream) 280{ 281 struct snd_pcm_runtime *runtime = substream->runtime; 282 struct nuc900_audio *nuc900_audio = runtime->private_data; 283 284 free_irq(nuc900_audio->irq_num, substream); 285 286 return 0; 287} 288 289static int nuc900_dma_mmap(struct snd_pcm_substream *substream, 290 struct vm_area_struct *vma) 291{ 292 struct snd_pcm_runtime *runtime = substream->runtime; 293 294 return dma_mmap_writecombine(substream->pcm->card->dev, vma, 295 runtime->dma_area, 296 runtime->dma_addr, 297 runtime->dma_bytes); 298} 299 300static struct snd_pcm_ops nuc900_dma_ops = { 301 .open = nuc900_dma_open, 302 .close = nuc900_dma_close, 303 .ioctl = snd_pcm_lib_ioctl, 304 .hw_params = nuc900_dma_hw_params, 305 .hw_free = nuc900_dma_hw_free, 306 .prepare = nuc900_dma_prepare, 307 .trigger = nuc900_dma_trigger, 308 .pointer = nuc900_dma_pointer, 309 .mmap = nuc900_dma_mmap, 310}; 311 312static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm) 313{ 314 snd_pcm_lib_preallocate_free_for_all(pcm); 315} 316 317static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32); 318static int nuc900_dma_new(struct snd_card *card, 319 struct snd_soc_dai *dai, struct snd_pcm *pcm) 320{ 321 if (!card->dev->dma_mask) 322 card->dev->dma_mask = &nuc900_pcm_dmamask; 323 if (!card->dev->coherent_dma_mask) 324 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 325 326 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, 327 card->dev, 4 * 1024, (4 * 1024) - 1); 328 329 return 0; 330} 331 332static struct snd_soc_platform_driver nuc900_soc_platform = { 333 .ops = &nuc900_dma_ops, 334 .pcm_new = nuc900_dma_new, 335 .pcm_free = nuc900_dma_free_dma_buffers, 336}; 337 338static int __devinit nuc900_soc_platform_probe(struct platform_device *pdev) 339{ 340 return snd_soc_register_platform(&pdev->dev, &nuc900_soc_platform); 341} 342 343static int __devexit nuc900_soc_platform_remove(struct platform_device *pdev) 344{ 345 snd_soc_unregister_platform(&pdev->dev); 346 return 0; 347} 348 349static struct platform_driver nuc900_pcm_driver = { 350 .driver = { 351 .name = "nuc900-pcm-audio", 352 .owner = THIS_MODULE, 353 }, 354 355 .probe = nuc900_soc_platform_probe, 356 .remove = __devexit_p(nuc900_soc_platform_remove), 357}; 358 359static int __init nuc900_pcm_init(void) 360{ 361 return platform_driver_register(&nuc900_pcm_driver); 362} 363module_init(nuc900_pcm_init); 364 365static void __exit nuc900_pcm_exit(void) 366{ 367 platform_driver_unregister(&nuc900_pcm_driver); 368} 369module_exit(nuc900_pcm_exit); 370 371MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>"); 372MODULE_DESCRIPTION("nuc900 Audio DMA module"); 373MODULE_LICENSE("GPL"); 374