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 spin_lock_irqsave(&nuc900_audio->lock, flags); 54 55 ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); 56 if (ret < 0) 57 return ret; 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 173 spin_lock_irqsave(&nuc900_audio->lock, flags); 174 175 nuc900_update_dma_register(substream, 176 nuc900_audio->dma_addr[substream->stream], 177 nuc900_audio->buffersize[substream->stream]); 178 179 val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); 180 181 switch (runtime->channels) { 182 case 1: 183 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 184 val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); 185 val |= PLAY_RIGHT_CHNNEL; 186 } else { 187 val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); 188 val |= RECORD_RIGHT_CHNNEL; 189 } 190 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); 191 break; 192 case 2: 193 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 194 val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); 195 else 196 val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); 197 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); 198 break; 199 default: 200 return -EINVAL; 201 } 202 spin_unlock_irqrestore(&nuc900_audio->lock, flags); 203 return 0; 204} 205 206static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) 207{ 208 int ret = 0; 209 210 switch (cmd) { 211 case SNDRV_PCM_TRIGGER_START: 212 case SNDRV_PCM_TRIGGER_RESUME: 213 nuc900_dma_start(substream); 214 break; 215 216 case SNDRV_PCM_TRIGGER_STOP: 217 case SNDRV_PCM_TRIGGER_SUSPEND: 218 nuc900_dma_stop(substream); 219 break; 220 221 default: 222 ret = -EINVAL; 223 break; 224 } 225 226 return ret; 227} 228 229int nuc900_dma_getposition(struct snd_pcm_substream *substream, 230 dma_addr_t *src, dma_addr_t *dst) 231{ 232 struct snd_pcm_runtime *runtime = substream->runtime; 233 struct nuc900_audio *nuc900_audio = runtime->private_data; 234 235 if (src != NULL) 236 *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); 237 238 if (dst != NULL) 239 *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); 240 241 return 0; 242} 243 244static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) 245{ 246 struct snd_pcm_runtime *runtime = substream->runtime; 247 dma_addr_t src, dst; 248 unsigned long res; 249 250 nuc900_dma_getposition(substream, &src, &dst); 251 252 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 253 res = dst - runtime->dma_addr; 254 else 255 res = src - runtime->dma_addr; 256 257 return bytes_to_frames(substream->runtime, res); 258} 259 260static int nuc900_dma_open(struct snd_pcm_substream *substream) 261{ 262 struct snd_pcm_runtime *runtime = substream->runtime; 263 struct nuc900_audio *nuc900_audio; 264 265 snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); 266 267 nuc900_audio = nuc900_ac97_data; 268 269 if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, 270 IRQF_DISABLED, "nuc900-dma", substream)) 271 return -EBUSY; 272 273 runtime->private_data = nuc900_audio; 274 275 return 0; 276} 277 278static int nuc900_dma_close(struct snd_pcm_substream *substream) 279{ 280 struct snd_pcm_runtime *runtime = substream->runtime; 281 struct nuc900_audio *nuc900_audio = runtime->private_data; 282 283 free_irq(nuc900_audio->irq_num, substream); 284 285 return 0; 286} 287 288static int nuc900_dma_mmap(struct snd_pcm_substream *substream, 289 struct vm_area_struct *vma) 290{ 291 struct snd_pcm_runtime *runtime = substream->runtime; 292 293 return dma_mmap_writecombine(substream->pcm->card->dev, vma, 294 runtime->dma_area, 295 runtime->dma_addr, 296 runtime->dma_bytes); 297} 298 299static struct snd_pcm_ops nuc900_dma_ops = { 300 .open = nuc900_dma_open, 301 .close = nuc900_dma_close, 302 .ioctl = snd_pcm_lib_ioctl, 303 .hw_params = nuc900_dma_hw_params, 304 .hw_free = nuc900_dma_hw_free, 305 .prepare = nuc900_dma_prepare, 306 .trigger = nuc900_dma_trigger, 307 .pointer = nuc900_dma_pointer, 308 .mmap = nuc900_dma_mmap, 309}; 310 311static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm) 312{ 313 snd_pcm_lib_preallocate_free_for_all(pcm); 314} 315 316static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32); 317static int nuc900_dma_new(struct snd_card *card, 318 struct snd_soc_dai *dai, struct snd_pcm *pcm) 319{ 320 if (!card->dev->dma_mask) 321 card->dev->dma_mask = &nuc900_pcm_dmamask; 322 if (!card->dev->coherent_dma_mask) 323 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 324 325 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, 326 card->dev, 4 * 1024, (4 * 1024) - 1); 327 328 return 0; 329} 330 331struct snd_soc_platform nuc900_soc_platform = { 332 .name = "nuc900-dma", 333 .pcm_ops = &nuc900_dma_ops, 334 .pcm_new = nuc900_dma_new, 335 .pcm_free = nuc900_dma_free_dma_buffers, 336} 337EXPORT_SYMBOL_GPL(nuc900_soc_platform); 338 339static int __init nuc900_soc_platform_init(void) 340{ 341 return snd_soc_register_platform(&nuc900_soc_platform); 342} 343 344static void __exit nuc900_soc_platform_exit(void) 345{ 346 snd_soc_unregister_platform(&nuc900_soc_platform); 347} 348 349module_init(nuc900_soc_platform_init); 350module_exit(nuc900_soc_platform_exit); 351 352MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>"); 353MODULE_DESCRIPTION("nuc900 Audio DMA module"); 354MODULE_LICENSE("GPL"); 355