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 .buffer_bytes_max = 4*1024, 36 .period_bytes_min = 1*1024, 37 .period_bytes_max = 4*1024, 38 .periods_min = 1, 39 .periods_max = 1024, 40}; 41 42static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, 43 struct snd_pcm_hw_params *params) 44{ 45 return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); 46} 47 48static void nuc900_update_dma_register(struct snd_pcm_substream *substream) 49{ 50 struct snd_pcm_runtime *runtime = substream->runtime; 51 struct nuc900_audio *nuc900_audio = runtime->private_data; 52 void __iomem *mmio_addr, *mmio_len; 53 54 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 55 mmio_addr = nuc900_audio->mmio + ACTL_PDSTB; 56 mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH; 57 } else { 58 mmio_addr = nuc900_audio->mmio + ACTL_RDSTB; 59 mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; 60 } 61 62 AUDIO_WRITE(mmio_addr, runtime->dma_addr); 63 AUDIO_WRITE(mmio_len, runtime->dma_bytes); 64} 65 66static void nuc900_dma_start(struct snd_pcm_substream *substream) 67{ 68 struct snd_pcm_runtime *runtime = substream->runtime; 69 struct nuc900_audio *nuc900_audio = runtime->private_data; 70 unsigned long val; 71 72 val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); 73 val |= (T_DMA_IRQ | R_DMA_IRQ); 74 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); 75} 76 77static void nuc900_dma_stop(struct snd_pcm_substream *substream) 78{ 79 struct snd_pcm_runtime *runtime = substream->runtime; 80 struct nuc900_audio *nuc900_audio = runtime->private_data; 81 unsigned long val; 82 83 val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); 84 val &= ~(T_DMA_IRQ | R_DMA_IRQ); 85 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); 86} 87 88static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id) 89{ 90 struct snd_pcm_substream *substream = dev_id; 91 struct nuc900_audio *nuc900_audio = substream->runtime->private_data; 92 unsigned long val; 93 94 spin_lock(&nuc900_audio->lock); 95 96 val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); 97 98 if (val & R_DMA_IRQ) { 99 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ); 100 101 val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); 102 103 if (val & R_DMA_MIDDLE_IRQ) { 104 val |= R_DMA_MIDDLE_IRQ; 105 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); 106 } 107 108 if (val & R_DMA_END_IRQ) { 109 val |= R_DMA_END_IRQ; 110 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); 111 } 112 } else if (val & T_DMA_IRQ) { 113 AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ); 114 115 val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); 116 117 if (val & P_DMA_MIDDLE_IRQ) { 118 val |= P_DMA_MIDDLE_IRQ; 119 AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); 120 } 121 122 if (val & P_DMA_END_IRQ) { 123 val |= P_DMA_END_IRQ; 124 AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); 125 } 126 } else { 127 dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n"); 128 spin_unlock(&nuc900_audio->lock); 129 return IRQ_HANDLED; 130 } 131 132 spin_unlock(&nuc900_audio->lock); 133 134 snd_pcm_period_elapsed(substream); 135 136 return IRQ_HANDLED; 137} 138 139static int nuc900_dma_hw_free(struct snd_pcm_substream *substream) 140{ 141 snd_pcm_lib_free_pages(substream); 142 return 0; 143} 144 145static int nuc900_dma_prepare(struct snd_pcm_substream *substream) 146{ 147 struct snd_pcm_runtime *runtime = substream->runtime; 148 struct nuc900_audio *nuc900_audio = runtime->private_data; 149 unsigned long flags, val; 150 int ret = 0; 151 152 spin_lock_irqsave(&nuc900_audio->lock, flags); 153 154 nuc900_update_dma_register(substream); 155 156 val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); 157 158 switch (runtime->channels) { 159 case 1: 160 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 161 val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); 162 val |= PLAY_RIGHT_CHNNEL; 163 } else { 164 val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); 165 val |= RECORD_RIGHT_CHNNEL; 166 } 167 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); 168 break; 169 case 2: 170 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 171 val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); 172 else 173 val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); 174 AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); 175 break; 176 default: 177 ret = -EINVAL; 178 } 179 spin_unlock_irqrestore(&nuc900_audio->lock, flags); 180 return ret; 181} 182 183static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) 184{ 185 int ret = 0; 186 187 switch (cmd) { 188 case SNDRV_PCM_TRIGGER_START: 189 case SNDRV_PCM_TRIGGER_RESUME: 190 nuc900_dma_start(substream); 191 break; 192 193 case SNDRV_PCM_TRIGGER_STOP: 194 case SNDRV_PCM_TRIGGER_SUSPEND: 195 nuc900_dma_stop(substream); 196 break; 197 198 default: 199 ret = -EINVAL; 200 break; 201 } 202 203 return ret; 204} 205 206static int nuc900_dma_getposition(struct snd_pcm_substream *substream, 207 dma_addr_t *src, dma_addr_t *dst) 208{ 209 struct snd_pcm_runtime *runtime = substream->runtime; 210 struct nuc900_audio *nuc900_audio = runtime->private_data; 211 212 if (src != NULL) 213 *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); 214 215 if (dst != NULL) 216 *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); 217 218 return 0; 219} 220 221static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) 222{ 223 struct snd_pcm_runtime *runtime = substream->runtime; 224 dma_addr_t src, dst; 225 unsigned long res; 226 227 nuc900_dma_getposition(substream, &src, &dst); 228 229 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 230 res = dst - runtime->dma_addr; 231 else 232 res = src - runtime->dma_addr; 233 234 return bytes_to_frames(substream->runtime, res); 235} 236 237static int nuc900_dma_open(struct snd_pcm_substream *substream) 238{ 239 struct snd_pcm_runtime *runtime = substream->runtime; 240 struct nuc900_audio *nuc900_audio; 241 242 snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); 243 244 nuc900_audio = nuc900_ac97_data; 245 246 if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, 247 0, "nuc900-dma", substream)) 248 return -EBUSY; 249 250 runtime->private_data = nuc900_audio; 251 252 return 0; 253} 254 255static int nuc900_dma_close(struct snd_pcm_substream *substream) 256{ 257 struct snd_pcm_runtime *runtime = substream->runtime; 258 struct nuc900_audio *nuc900_audio = runtime->private_data; 259 260 free_irq(nuc900_audio->irq_num, substream); 261 262 return 0; 263} 264 265static int nuc900_dma_mmap(struct snd_pcm_substream *substream, 266 struct vm_area_struct *vma) 267{ 268 struct snd_pcm_runtime *runtime = substream->runtime; 269 270 return dma_mmap_writecombine(substream->pcm->card->dev, vma, 271 runtime->dma_area, 272 runtime->dma_addr, 273 runtime->dma_bytes); 274} 275 276static struct snd_pcm_ops nuc900_dma_ops = { 277 .open = nuc900_dma_open, 278 .close = nuc900_dma_close, 279 .ioctl = snd_pcm_lib_ioctl, 280 .hw_params = nuc900_dma_hw_params, 281 .hw_free = nuc900_dma_hw_free, 282 .prepare = nuc900_dma_prepare, 283 .trigger = nuc900_dma_trigger, 284 .pointer = nuc900_dma_pointer, 285 .mmap = nuc900_dma_mmap, 286}; 287 288static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd) 289{ 290 struct snd_card *card = rtd->card->snd_card; 291 struct snd_pcm *pcm = rtd->pcm; 292 int ret; 293 294 ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); 295 if (ret) 296 return ret; 297 298 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, 299 card->dev, 4 * 1024, (4 * 1024) - 1); 300 301 return 0; 302} 303 304static struct snd_soc_platform_driver nuc900_soc_platform = { 305 .ops = &nuc900_dma_ops, 306 .pcm_new = nuc900_dma_new, 307}; 308 309static int nuc900_soc_platform_probe(struct platform_device *pdev) 310{ 311 return snd_soc_register_platform(&pdev->dev, &nuc900_soc_platform); 312} 313 314static int nuc900_soc_platform_remove(struct platform_device *pdev) 315{ 316 snd_soc_unregister_platform(&pdev->dev); 317 return 0; 318} 319 320static struct platform_driver nuc900_pcm_driver = { 321 .driver = { 322 .name = "nuc900-pcm-audio", 323 }, 324 325 .probe = nuc900_soc_platform_probe, 326 .remove = nuc900_soc_platform_remove, 327}; 328 329module_platform_driver(nuc900_pcm_driver); 330 331MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>"); 332MODULE_DESCRIPTION("nuc900 Audio DMA module"); 333MODULE_LICENSE("GPL"); 334