1 /* 2 * kirkwood-dma.c 3 * 4 * (c) 2010 Arnaud Patard <apatard@mandriva.com> 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License as published by the 8 * Free Software Foundation; either version 2 of the License, or (at your 9 * option) any later version. 10 */ 11 12 #include <linux/init.h> 13 #include <linux/module.h> 14 #include <linux/device.h> 15 #include <linux/io.h> 16 #include <linux/slab.h> 17 #include <linux/interrupt.h> 18 #include <linux/dma-mapping.h> 19 #include <linux/mbus.h> 20 #include <sound/soc.h> 21 #include "kirkwood-dma.h" 22 #include "kirkwood.h" 23 24 #define KIRKWOOD_RATES \ 25 (SNDRV_PCM_RATE_44100 | \ 26 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) 27 #define KIRKWOOD_FORMATS \ 28 (SNDRV_PCM_FMTBIT_S16_LE | \ 29 SNDRV_PCM_FMTBIT_S24_LE | \ 30 SNDRV_PCM_FMTBIT_S32_LE) 31 32 struct kirkwood_dma_priv { 33 struct snd_pcm_substream *play_stream; 34 struct snd_pcm_substream *rec_stream; 35 struct kirkwood_dma_data *data; 36 }; 37 38 static struct snd_pcm_hardware kirkwood_dma_snd_hw = { 39 .info = (SNDRV_PCM_INFO_INTERLEAVED | 40 SNDRV_PCM_INFO_MMAP | 41 SNDRV_PCM_INFO_MMAP_VALID | 42 SNDRV_PCM_INFO_BLOCK_TRANSFER | 43 SNDRV_PCM_INFO_PAUSE), 44 .formats = KIRKWOOD_FORMATS, 45 .rates = KIRKWOOD_RATES, 46 .rate_min = 44100, 47 .rate_max = 96000, 48 .channels_min = 1, 49 .channels_max = 2, 50 .buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS, 51 .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES, 52 .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES, 53 .periods_min = KIRKWOOD_SND_MIN_PERIODS, 54 .periods_max = KIRKWOOD_SND_MAX_PERIODS, 55 .fifo_size = 0, 56 }; 57 58 static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL; 59 60 static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id) 61 { 62 struct kirkwood_dma_priv *prdata = dev_id; 63 struct kirkwood_dma_data *priv = prdata->data; 64 unsigned long mask, status, cause; 65 66 mask = readl(priv->io + KIRKWOOD_INT_MASK); 67 status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask; 68 69 cause = readl(priv->io + KIRKWOOD_ERR_CAUSE); 70 if (unlikely(cause)) { 71 printk(KERN_WARNING "%s: got err interrupt 0x%lx\n", 72 __func__, cause); 73 writel(cause, priv->io + KIRKWOOD_ERR_CAUSE); 74 return IRQ_HANDLED; 75 } 76 77 /* we've enabled only bytes interrupts ... */ 78 if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \ 79 KIRKWOOD_INT_CAUSE_REC_BYTES)) { 80 printk(KERN_WARNING "%s: unexpected interrupt %lx\n", 81 __func__, status); 82 return IRQ_NONE; 83 } 84 85 /* ack int */ 86 writel(status, priv->io + KIRKWOOD_INT_CAUSE); 87 88 if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES) 89 snd_pcm_period_elapsed(prdata->play_stream); 90 91 if (status & KIRKWOOD_INT_CAUSE_REC_BYTES) 92 snd_pcm_period_elapsed(prdata->rec_stream); 93 94 return IRQ_HANDLED; 95 } 96 97 static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win, 98 unsigned long dma, 99 struct mbus_dram_target_info *dram) 100 { 101 int i; 102 103 /* First disable and clear windows */ 104 writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); 105 writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); 106 107 /* try to find matching cs for current dma address */ 108 for (i = 0; i < dram->num_cs; i++) { 109 struct mbus_dram_window *cs = dram->cs + i; 110 if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) { 111 writel(cs->base & 0xffff0000, 112 base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); 113 writel(((cs->size - 1) & 0xffff0000) | 114 (cs->mbus_attr << 8) | 115 (dram->mbus_dram_target_id << 4) | 1, 116 base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); 117 } 118 } 119 } 120 121 static int kirkwood_dma_open(struct snd_pcm_substream *substream) 122 { 123 int err; 124 struct snd_pcm_runtime *runtime = substream->runtime; 125 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; 126 struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; 127 struct kirkwood_dma_data *priv; 128 struct kirkwood_dma_priv *prdata = cpu_dai->private_data; 129 unsigned long addr; 130 131 priv = snd_soc_dai_get_dma_data(cpu_dai, substream); 132 snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw); 133 134 /* Ensure that all constraints linked to dma burst are fullfilled */ 135 err = snd_pcm_hw_constraint_minmax(runtime, 136 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 137 priv->burst * 2, 138 KIRKWOOD_AUDIO_BUF_MAX-1); 139 if (err < 0) 140 return err; 141 142 err = snd_pcm_hw_constraint_step(runtime, 0, 143 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 144 priv->burst); 145 if (err < 0) 146 return err; 147 148 err = snd_pcm_hw_constraint_step(substream->runtime, 0, 149 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 150 priv->burst); 151 if (err < 0) 152 return err; 153 154 if (soc_runtime->dai->cpu_dai->private_data == NULL) { 155 prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL); 156 if (prdata == NULL) 157 return -ENOMEM; 158 159 prdata->data = priv; 160 161 err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED, 162 "kirkwood-i2s", prdata); 163 if (err) { 164 kfree(prdata); 165 return -EBUSY; 166 } 167 168 soc_runtime->dai->cpu_dai->private_data = prdata; 169 170 /* 171 * Enable Error interrupts. We're only ack'ing them but 172 * it's usefull for diagnostics 173 */ 174 writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK); 175 } 176 177 addr = virt_to_phys(substream->dma_buffer.area); 178 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 179 prdata->play_stream = substream; 180 kirkwood_dma_conf_mbus_windows(priv->io, 181 KIRKWOOD_PLAYBACK_WIN, addr, priv->dram); 182 } else { 183 prdata->rec_stream = substream; 184 kirkwood_dma_conf_mbus_windows(priv->io, 185 KIRKWOOD_RECORD_WIN, addr, priv->dram); 186 } 187 188 return 0; 189 } 190 191 static int kirkwood_dma_close(struct snd_pcm_substream *substream) 192 { 193 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; 194 struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; 195 struct kirkwood_dma_priv *prdata = cpu_dai->private_data; 196 struct kirkwood_dma_data *priv; 197 198 priv = snd_soc_dai_get_dma_data(cpu_dai, substream); 199 200 if (!prdata || !priv) 201 return 0; 202 203 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 204 prdata->play_stream = NULL; 205 else 206 prdata->rec_stream = NULL; 207 208 if (!prdata->play_stream && !prdata->rec_stream) { 209 writel(0, priv->io + KIRKWOOD_ERR_MASK); 210 free_irq(priv->irq, prdata); 211 kfree(prdata); 212 soc_runtime->dai->cpu_dai->private_data = NULL; 213 } 214 215 return 0; 216 } 217 218 static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream, 219 struct snd_pcm_hw_params *params) 220 { 221 struct snd_pcm_runtime *runtime = substream->runtime; 222 223 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 224 runtime->dma_bytes = params_buffer_bytes(params); 225 226 return 0; 227 } 228 229 static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream) 230 { 231 snd_pcm_set_runtime_buffer(substream, NULL); 232 return 0; 233 } 234 235 static int kirkwood_dma_prepare(struct snd_pcm_substream *substream) 236 { 237 struct snd_pcm_runtime *runtime = substream->runtime; 238 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; 239 struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; 240 struct kirkwood_dma_data *priv; 241 unsigned long size, count; 242 243 priv = snd_soc_dai_get_dma_data(cpu_dai, substream); 244 245 /* compute buffer size in term of "words" as requested in specs */ 246 size = frames_to_bytes(runtime, runtime->buffer_size); 247 size = (size>>2)-1; 248 count = snd_pcm_lib_period_bytes(substream); 249 250 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 251 writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT); 252 writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR); 253 writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE); 254 } else { 255 writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT); 256 writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR); 257 writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE); 258 } 259 260 261 return 0; 262 } 263 264 static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream 265 *substream) 266 { 267 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; 268 struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; 269 struct kirkwood_dma_data *priv; 270 snd_pcm_uframes_t count; 271 272 priv = snd_soc_dai_get_dma_data(cpu_dai, substream); 273 274 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 275 count = bytes_to_frames(substream->runtime, 276 readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT)); 277 else 278 count = bytes_to_frames(substream->runtime, 279 readl(priv->io + KIRKWOOD_REC_BYTE_COUNT)); 280 281 return count; 282 } 283 284 struct snd_pcm_ops kirkwood_dma_ops = { 285 .open = kirkwood_dma_open, 286 .close = kirkwood_dma_close, 287 .ioctl = snd_pcm_lib_ioctl, 288 .hw_params = kirkwood_dma_hw_params, 289 .hw_free = kirkwood_dma_hw_free, 290 .prepare = kirkwood_dma_prepare, 291 .pointer = kirkwood_dma_pointer, 292 }; 293 294 static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm, 295 int stream) 296 { 297 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 298 struct snd_dma_buffer *buf = &substream->dma_buffer; 299 size_t size = kirkwood_dma_snd_hw.buffer_bytes_max; 300 301 buf->dev.type = SNDRV_DMA_TYPE_DEV; 302 buf->dev.dev = pcm->card->dev; 303 buf->area = dma_alloc_coherent(pcm->card->dev, size, 304 &buf->addr, GFP_KERNEL); 305 if (!buf->area) 306 return -ENOMEM; 307 buf->bytes = size; 308 buf->private_data = NULL; 309 310 return 0; 311 } 312 313 static int kirkwood_dma_new(struct snd_card *card, 314 struct snd_soc_dai *dai, struct snd_pcm *pcm) 315 { 316 int ret; 317 318 if (!card->dev->dma_mask) 319 card->dev->dma_mask = &kirkwood_dma_dmamask; 320 if (!card->dev->coherent_dma_mask) 321 card->dev->coherent_dma_mask = 0xffffffff; 322 323 if (dai->playback.channels_min) { 324 ret = kirkwood_dma_preallocate_dma_buffer(pcm, 325 SNDRV_PCM_STREAM_PLAYBACK); 326 if (ret) 327 return ret; 328 } 329 330 if (dai->capture.channels_min) { 331 ret = kirkwood_dma_preallocate_dma_buffer(pcm, 332 SNDRV_PCM_STREAM_CAPTURE); 333 if (ret) 334 return ret; 335 } 336 337 return 0; 338 } 339 340 static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm) 341 { 342 struct snd_pcm_substream *substream; 343 struct snd_dma_buffer *buf; 344 int stream; 345 346 for (stream = 0; stream < 2; stream++) { 347 substream = pcm->streams[stream].substream; 348 if (!substream) 349 continue; 350 buf = &substream->dma_buffer; 351 if (!buf->area) 352 continue; 353 354 dma_free_coherent(pcm->card->dev, buf->bytes, 355 buf->area, buf->addr); 356 buf->area = NULL; 357 } 358 } 359 360 struct snd_soc_platform kirkwood_soc_platform = { 361 .name = "kirkwood-dma", 362 .pcm_ops = &kirkwood_dma_ops, 363 .pcm_new = kirkwood_dma_new, 364 .pcm_free = kirkwood_dma_free_dma_buffers, 365 }; 366 EXPORT_SYMBOL_GPL(kirkwood_soc_platform); 367 368 static int __init kirkwood_soc_platform_init(void) 369 { 370 return snd_soc_register_platform(&kirkwood_soc_platform); 371 } 372 module_init(kirkwood_soc_platform_init); 373 374 static void __exit kirkwood_soc_platform_exit(void) 375 { 376 snd_soc_unregister_platform(&kirkwood_soc_platform); 377 } 378 module_exit(kirkwood_soc_platform_exit); 379 380 MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); 381 MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module"); 382 MODULE_LICENSE("GPL"); 383 384