1/* 2 * File: sound/soc/blackfin/bf5xx-ac97-pcm.c 3 * Author: Cliff Cai <Cliff.Cai@analog.com> 4 * 5 * Created: Tue June 06 2008 6 * Description: DMA Driver for AC97 sound chip 7 * 8 * Modified: 9 * Copyright 2008 Analog Devices Inc. 10 * 11 * Bugs: Enter bugs at http://blackfin.uclinux.org/ 12 * 13 * This program is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License as published by 15 * the Free Software Foundation; either version 2 of the License, or 16 * (at your option) any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License 24 * along with this program; if not, see the file COPYING, or write 25 * to the Free Software Foundation, Inc., 26 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 27 */ 28 29#include <linux/module.h> 30#include <linux/init.h> 31#include <linux/platform_device.h> 32#include <linux/dma-mapping.h> 33#include <linux/gfp.h> 34 35#include <sound/core.h> 36#include <sound/pcm.h> 37#include <sound/pcm_params.h> 38#include <sound/soc.h> 39 40#include <asm/dma.h> 41 42#include "bf5xx-ac97-pcm.h" 43#include "bf5xx-ac97.h" 44#include "bf5xx-sport.h" 45 46static unsigned int ac97_chan_mask[] = { 47 SP_FL, /* Mono */ 48 SP_STEREO, /* Stereo */ 49 SP_2DOT1, /* 2.1*/ 50 SP_QUAD,/*Quadraquic*/ 51 SP_FL | SP_FR | SP_FC | SP_SL | SP_SR,/*5 channels */ 52 SP_5DOT1, /* 5.1 */ 53}; 54 55#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 56static void bf5xx_mmap_copy(struct snd_pcm_substream *substream, 57 snd_pcm_uframes_t count) 58{ 59 struct snd_pcm_runtime *runtime = substream->runtime; 60 struct sport_device *sport = runtime->private_data; 61 unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1]; 62 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 63 bf5xx_pcm_to_ac97((struct ac97_frame *)sport->tx_dma_buf + 64 sport->tx_pos, (__u16 *)runtime->dma_area + sport->tx_pos * 65 runtime->channels, count, chan_mask); 66 sport->tx_pos += runtime->period_size; 67 if (sport->tx_pos >= runtime->buffer_size) 68 sport->tx_pos %= runtime->buffer_size; 69 sport->tx_delay_pos = sport->tx_pos; 70 } else { 71 bf5xx_ac97_to_pcm((struct ac97_frame *)sport->rx_dma_buf + 72 sport->rx_pos, (__u16 *)runtime->dma_area + sport->rx_pos * 73 runtime->channels, count); 74 sport->rx_pos += runtime->period_size; 75 if (sport->rx_pos >= runtime->buffer_size) 76 sport->rx_pos %= runtime->buffer_size; 77 } 78} 79#endif 80 81static void bf5xx_dma_irq(void *data) 82{ 83 struct snd_pcm_substream *pcm = data; 84#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 85 struct snd_pcm_runtime *runtime = pcm->runtime; 86 struct sport_device *sport = runtime->private_data; 87 bf5xx_mmap_copy(pcm, runtime->period_size); 88 if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) { 89 if (sport->once == 0) { 90 snd_pcm_period_elapsed(pcm); 91 bf5xx_mmap_copy(pcm, runtime->period_size); 92 sport->once = 1; 93 } 94 } 95#endif 96 snd_pcm_period_elapsed(pcm); 97} 98 99/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes. 100 * The total rx/tx buffer is for ac97 frame to hold all pcm data 101 * is 0x20000 * sizeof(struct ac97_frame) / 4. 102 */ 103static const struct snd_pcm_hardware bf5xx_pcm_hardware = { 104 .info = SNDRV_PCM_INFO_INTERLEAVED | 105#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 106 SNDRV_PCM_INFO_MMAP | 107 SNDRV_PCM_INFO_MMAP_VALID | 108#endif 109 SNDRV_PCM_INFO_BLOCK_TRANSFER, 110 111 .formats = SNDRV_PCM_FMTBIT_S16_LE, 112 .period_bytes_min = 32, 113 .period_bytes_max = 0x10000, 114 .periods_min = 1, 115 .periods_max = PAGE_SIZE/32, 116 .buffer_bytes_max = 0x20000, /* 128 kbytes */ 117 .fifo_size = 16, 118}; 119 120static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, 121 struct snd_pcm_hw_params *params) 122{ 123 size_t size = bf5xx_pcm_hardware.buffer_bytes_max 124 * sizeof(struct ac97_frame) / 4; 125 126 snd_pcm_lib_malloc_pages(substream, size); 127 128 return 0; 129} 130 131static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) 132{ 133#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 134 struct snd_pcm_runtime *runtime = substream->runtime; 135 struct sport_device *sport = runtime->private_data; 136 137 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 138 sport->once = 0; 139 if (runtime->dma_area) 140 memset(runtime->dma_area, 0, runtime->buffer_size); 141 memset(sport->tx_dma_buf, 0, runtime->buffer_size * 142 sizeof(struct ac97_frame)); 143 } else 144 memset(sport->rx_dma_buf, 0, runtime->buffer_size * 145 sizeof(struct ac97_frame)); 146#endif 147 snd_pcm_lib_free_pages(substream); 148 return 0; 149} 150 151static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) 152{ 153 struct snd_pcm_runtime *runtime = substream->runtime; 154 struct sport_device *sport = runtime->private_data; 155 156 /* An intermediate buffer is introduced for implementing mmap for 157 * SPORT working in TMD mode(include AC97). 158 */ 159#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 160 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 161 sport_set_tx_callback(sport, bf5xx_dma_irq, substream); 162 sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods, 163 runtime->period_size * sizeof(struct ac97_frame)); 164 } else { 165 sport_set_rx_callback(sport, bf5xx_dma_irq, substream); 166 sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods, 167 runtime->period_size * sizeof(struct ac97_frame)); 168 } 169#else 170 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 171 sport_set_tx_callback(sport, bf5xx_dma_irq, substream); 172 sport_config_tx_dma(sport, runtime->dma_area, runtime->periods, 173 runtime->period_size * sizeof(struct ac97_frame)); 174 } else { 175 sport_set_rx_callback(sport, bf5xx_dma_irq, substream); 176 sport_config_rx_dma(sport, runtime->dma_area, runtime->periods, 177 runtime->period_size * sizeof(struct ac97_frame)); 178 } 179#endif 180 return 0; 181} 182 183static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 184{ 185 struct snd_pcm_runtime *runtime = substream->runtime; 186 struct sport_device *sport = runtime->private_data; 187 int ret = 0; 188 189 pr_debug("%s enter\n", __func__); 190 switch (cmd) { 191 case SNDRV_PCM_TRIGGER_START: 192 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 193#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 194 bf5xx_mmap_copy(substream, runtime->period_size); 195 sport->tx_delay_pos = 0; 196#endif 197 sport_tx_start(sport); 198 } else 199 sport_rx_start(sport); 200 break; 201 case SNDRV_PCM_TRIGGER_STOP: 202 case SNDRV_PCM_TRIGGER_SUSPEND: 203 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 204 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 205#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 206 sport->tx_pos = 0; 207#endif 208 sport_tx_stop(sport); 209 } else { 210#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 211 sport->rx_pos = 0; 212#endif 213 sport_rx_stop(sport); 214 } 215 break; 216 default: 217 ret = -EINVAL; 218 } 219 return ret; 220} 221 222static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) 223{ 224 struct snd_pcm_runtime *runtime = substream->runtime; 225 struct sport_device *sport = runtime->private_data; 226 unsigned int curr; 227 228#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 229 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 230 curr = sport->tx_delay_pos; 231 else 232 curr = sport->rx_pos; 233#else 234 235 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 236 curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame); 237 else 238 curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame); 239 240#endif 241 return curr; 242} 243 244static int bf5xx_pcm_open(struct snd_pcm_substream *substream) 245{ 246 struct snd_pcm_runtime *runtime = substream->runtime; 247 int ret; 248 249 pr_debug("%s enter\n", __func__); 250 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); 251 252 ret = snd_pcm_hw_constraint_integer(runtime, 253 SNDRV_PCM_HW_PARAM_PERIODS); 254 if (ret < 0) 255 goto out; 256 257 if (sport_handle != NULL) 258 runtime->private_data = sport_handle; 259 else { 260 pr_err("sport_handle is NULL\n"); 261 return -1; 262 } 263 return 0; 264 265 out: 266 return ret; 267} 268 269#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 270static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, 271 struct vm_area_struct *vma) 272{ 273 struct snd_pcm_runtime *runtime = substream->runtime; 274 size_t size = vma->vm_end - vma->vm_start; 275 vma->vm_start = (unsigned long)runtime->dma_area; 276 vma->vm_end = vma->vm_start + size; 277 vma->vm_flags |= VM_SHARED; 278 return 0 ; 279} 280#else 281static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, 282 snd_pcm_uframes_t pos, 283 void __user *buf, snd_pcm_uframes_t count) 284{ 285 struct snd_pcm_runtime *runtime = substream->runtime; 286 unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1]; 287 pr_debug("%s copy pos:0x%lx count:0x%lx\n", 288 substream->stream ? "Capture" : "Playback", pos, count); 289 290 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 291 bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos, 292 (__u16 *)buf, count, chan_mask); 293 else 294 bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos, 295 (__u16 *)buf, count); 296 return 0; 297} 298#endif 299 300static struct snd_pcm_ops bf5xx_pcm_ac97_ops = { 301 .open = bf5xx_pcm_open, 302 .ioctl = snd_pcm_lib_ioctl, 303 .hw_params = bf5xx_pcm_hw_params, 304 .hw_free = bf5xx_pcm_hw_free, 305 .prepare = bf5xx_pcm_prepare, 306 .trigger = bf5xx_pcm_trigger, 307 .pointer = bf5xx_pcm_pointer, 308#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 309 .mmap = bf5xx_pcm_mmap, 310#else 311 .copy = bf5xx_pcm_copy, 312#endif 313}; 314 315static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) 316{ 317 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 318 struct snd_dma_buffer *buf = &substream->dma_buffer; 319 size_t size = bf5xx_pcm_hardware.buffer_bytes_max 320 * sizeof(struct ac97_frame) / 4; 321 322 buf->dev.type = SNDRV_DMA_TYPE_DEV; 323 buf->dev.dev = pcm->card->dev; 324 buf->private_data = NULL; 325 buf->area = dma_alloc_coherent(pcm->card->dev, size, 326 &buf->addr, GFP_KERNEL); 327 if (!buf->area) { 328 pr_err("Failed to allocate dma memory\n"); 329 pr_err("Please increase uncached DMA memory region\n"); 330 return -ENOMEM; 331 } 332 buf->bytes = size; 333 334 pr_debug("%s, area:%p, size:0x%08lx\n", __func__, 335 buf->area, buf->bytes); 336 337 if (stream == SNDRV_PCM_STREAM_PLAYBACK) 338 sport_handle->tx_buf = buf->area; 339 else 340 sport_handle->rx_buf = buf->area; 341 342/* 343 * Need to allocate local buffer when enable 344 * MMAP for SPORT working in TMD mode (include AC97). 345 */ 346#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 347 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 348 if (!sport_handle->tx_dma_buf) { 349 sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \ 350 size, &sport_handle->tx_dma_phy, GFP_KERNEL); 351 if (!sport_handle->tx_dma_buf) { 352 pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n"); 353 return -ENOMEM; 354 } else 355 memset(sport_handle->tx_dma_buf, 0, size); 356 } else 357 memset(sport_handle->tx_dma_buf, 0, size); 358 } else { 359 if (!sport_handle->rx_dma_buf) { 360 sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \ 361 size, &sport_handle->rx_dma_phy, GFP_KERNEL); 362 if (!sport_handle->rx_dma_buf) { 363 pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n"); 364 return -ENOMEM; 365 } else 366 memset(sport_handle->rx_dma_buf, 0, size); 367 } else 368 memset(sport_handle->rx_dma_buf, 0, size); 369 } 370#endif 371 return 0; 372} 373 374static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) 375{ 376 struct snd_pcm_substream *substream; 377 struct snd_dma_buffer *buf; 378 int stream; 379#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 380 size_t size = bf5xx_pcm_hardware.buffer_bytes_max * 381 sizeof(struct ac97_frame) / 4; 382#endif 383 for (stream = 0; stream < 2; stream++) { 384 substream = pcm->streams[stream].substream; 385 if (!substream) 386 continue; 387 388 buf = &substream->dma_buffer; 389 if (!buf->area) 390 continue; 391 dma_free_coherent(NULL, buf->bytes, buf->area, 0); 392 buf->area = NULL; 393#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT) 394 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 395 if (sport_handle->tx_dma_buf) 396 dma_free_coherent(NULL, size, \ 397 sport_handle->tx_dma_buf, 0); 398 sport_handle->tx_dma_buf = NULL; 399 } else { 400 401 if (sport_handle->rx_dma_buf) 402 dma_free_coherent(NULL, size, \ 403 sport_handle->rx_dma_buf, 0); 404 sport_handle->rx_dma_buf = NULL; 405 } 406#endif 407 } 408 if (sport_handle) 409 sport_done(sport_handle); 410} 411 412static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); 413 414int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai, 415 struct snd_pcm *pcm) 416{ 417 int ret = 0; 418 419 pr_debug("%s enter\n", __func__); 420 if (!card->dev->dma_mask) 421 card->dev->dma_mask = &bf5xx_pcm_dmamask; 422 if (!card->dev->coherent_dma_mask) 423 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 424 425 if (dai->playback.channels_min) { 426 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 427 SNDRV_PCM_STREAM_PLAYBACK); 428 if (ret) 429 goto out; 430 } 431 432 if (dai->capture.channels_min) { 433 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 434 SNDRV_PCM_STREAM_CAPTURE); 435 if (ret) 436 goto out; 437 } 438 out: 439 return ret; 440} 441 442struct snd_soc_platform bf5xx_ac97_soc_platform = { 443 .name = "bf5xx-audio", 444 .pcm_ops = &bf5xx_pcm_ac97_ops, 445 .pcm_new = bf5xx_pcm_ac97_new, 446 .pcm_free = bf5xx_pcm_free_dma_buffers, 447}; 448EXPORT_SYMBOL_GPL(bf5xx_ac97_soc_platform); 449 450static int __init bfin_ac97_init(void) 451{ 452 return snd_soc_register_platform(&bf5xx_ac97_soc_platform); 453} 454module_init(bfin_ac97_init); 455 456static void __exit bfin_ac97_exit(void) 457{ 458 snd_soc_unregister_platform(&bf5xx_ac97_soc_platform); 459} 460module_exit(bfin_ac97_exit); 461 462MODULE_AUTHOR("Cliff Cai"); 463MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module"); 464MODULE_LICENSE("GPL"); 465