1/* 2 * File: sound/soc/blackfin/bf5xx-i2s-pcm.c 3 * Author: Cliff Cai <Cliff.Cai@analog.com> 4 * 5 * Created: Tue June 06 2008 6 * Description: DMA driver for i2s codec 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-sport.h" 43#include "bf5xx-i2s-pcm.h" 44 45static void bf5xx_dma_irq(void *data) 46{ 47 struct snd_pcm_substream *pcm = data; 48 snd_pcm_period_elapsed(pcm); 49} 50 51static const struct snd_pcm_hardware bf5xx_pcm_hardware = { 52 .info = SNDRV_PCM_INFO_INTERLEAVED | 53 SNDRV_PCM_INFO_MMAP_VALID | 54 SNDRV_PCM_INFO_BLOCK_TRANSFER, 55 .period_bytes_min = 32, 56 .period_bytes_max = 0x10000, 57 .periods_min = 1, 58 .periods_max = PAGE_SIZE/32, 59 .buffer_bytes_max = 0x20000, /* 128 kbytes */ 60 .fifo_size = 16, 61}; 62 63static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, 64 struct snd_pcm_hw_params *params) 65{ 66 struct snd_soc_pcm_runtime *rtd = substream->private_data; 67 unsigned int buffer_size = params_buffer_bytes(params); 68 struct bf5xx_i2s_pcm_data *dma_data; 69 70 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 71 72 if (dma_data->tdm_mode) 73 buffer_size = buffer_size / params_channels(params) * 8; 74 75 return snd_pcm_lib_malloc_pages(substream, buffer_size); 76} 77 78static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) 79{ 80 snd_pcm_lib_free_pages(substream); 81 82 return 0; 83} 84 85static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) 86{ 87 struct snd_soc_pcm_runtime *rtd = substream->private_data; 88 struct snd_pcm_runtime *runtime = substream->runtime; 89 struct sport_device *sport = runtime->private_data; 90 int period_bytes = frames_to_bytes(runtime, runtime->period_size); 91 struct bf5xx_i2s_pcm_data *dma_data; 92 93 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 94 95 if (dma_data->tdm_mode) 96 period_bytes = period_bytes / runtime->channels * 8; 97 98 pr_debug("%s enter\n", __func__); 99 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 100 sport_set_tx_callback(sport, bf5xx_dma_irq, substream); 101 sport_config_tx_dma(sport, runtime->dma_area, 102 runtime->periods, period_bytes); 103 } else { 104 sport_set_rx_callback(sport, bf5xx_dma_irq, substream); 105 sport_config_rx_dma(sport, runtime->dma_area, 106 runtime->periods, period_bytes); 107 } 108 109 return 0; 110} 111 112static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 113{ 114 struct snd_pcm_runtime *runtime = substream->runtime; 115 struct sport_device *sport = runtime->private_data; 116 int ret = 0; 117 118 pr_debug("%s enter\n", __func__); 119 switch (cmd) { 120 case SNDRV_PCM_TRIGGER_START: 121 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 122 sport_tx_start(sport); 123 else 124 sport_rx_start(sport); 125 break; 126 case SNDRV_PCM_TRIGGER_STOP: 127 case SNDRV_PCM_TRIGGER_SUSPEND: 128 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 129 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 130 sport_tx_stop(sport); 131 else 132 sport_rx_stop(sport); 133 break; 134 default: 135 ret = -EINVAL; 136 } 137 138 return ret; 139} 140 141static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) 142{ 143 struct snd_soc_pcm_runtime *rtd = substream->private_data; 144 struct snd_pcm_runtime *runtime = substream->runtime; 145 struct sport_device *sport = runtime->private_data; 146 unsigned int diff; 147 snd_pcm_uframes_t frames; 148 struct bf5xx_i2s_pcm_data *dma_data; 149 150 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 151 152 pr_debug("%s enter\n", __func__); 153 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 154 diff = sport_curr_offset_tx(sport); 155 } else { 156 diff = sport_curr_offset_rx(sport); 157 } 158 159 /* 160 * TX at least can report one frame beyond the end of the 161 * buffer if we hit the wraparound case - clamp to within the 162 * buffer as the ALSA APIs require. 163 */ 164 if (diff == snd_pcm_lib_buffer_bytes(substream)) 165 diff = 0; 166 167 frames = bytes_to_frames(substream->runtime, diff); 168 if (dma_data->tdm_mode) 169 frames = frames * runtime->channels / 8; 170 171 return frames; 172} 173 174static int bf5xx_pcm_open(struct snd_pcm_substream *substream) 175{ 176 struct snd_soc_pcm_runtime *rtd = substream->private_data; 177 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 178 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai); 179 struct snd_pcm_runtime *runtime = substream->runtime; 180 struct snd_dma_buffer *buf = &substream->dma_buffer; 181 struct bf5xx_i2s_pcm_data *dma_data; 182 int ret; 183 184 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 185 186 pr_debug("%s enter\n", __func__); 187 188 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); 189 if (dma_data->tdm_mode) 190 runtime->hw.buffer_bytes_max /= 4; 191 else 192 runtime->hw.info |= SNDRV_PCM_INFO_MMAP; 193 194 ret = snd_pcm_hw_constraint_integer(runtime, 195 SNDRV_PCM_HW_PARAM_PERIODS); 196 if (ret < 0) 197 goto out; 198 199 if (sport_handle != NULL) { 200 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 201 sport_handle->tx_buf = buf->area; 202 else 203 sport_handle->rx_buf = buf->area; 204 205 runtime->private_data = sport_handle; 206 } else { 207 pr_err("sport_handle is NULL\n"); 208 return -1; 209 } 210 return 0; 211 212 out: 213 return ret; 214} 215 216static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, 217 struct vm_area_struct *vma) 218{ 219 struct snd_pcm_runtime *runtime = substream->runtime; 220 size_t size = vma->vm_end - vma->vm_start; 221 vma->vm_start = (unsigned long)runtime->dma_area; 222 vma->vm_end = vma->vm_start + size; 223 vma->vm_flags |= VM_SHARED; 224 225 return 0 ; 226} 227 228static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, 229 snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) 230{ 231 struct snd_soc_pcm_runtime *rtd = substream->private_data; 232 struct snd_pcm_runtime *runtime = substream->runtime; 233 unsigned int sample_size = runtime->sample_bits / 8; 234 struct bf5xx_i2s_pcm_data *dma_data; 235 unsigned int i; 236 void *src, *dst; 237 238 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 239 240 if (dma_data->tdm_mode) { 241 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 242 src = buf; 243 dst = runtime->dma_area; 244 dst += pos * sample_size * 8; 245 246 while (count--) { 247 for (i = 0; i < runtime->channels; i++) { 248 memcpy(dst + dma_data->map[i] * 249 sample_size, src, sample_size); 250 src += sample_size; 251 } 252 dst += 8 * sample_size; 253 } 254 } else { 255 src = runtime->dma_area; 256 src += pos * sample_size * 8; 257 dst = buf; 258 259 while (count--) { 260 for (i = 0; i < runtime->channels; i++) { 261 memcpy(dst, src + dma_data->map[i] * 262 sample_size, sample_size); 263 dst += sample_size; 264 } 265 src += 8 * sample_size; 266 } 267 } 268 } else { 269 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 270 src = buf; 271 dst = runtime->dma_area; 272 dst += frames_to_bytes(runtime, pos); 273 } else { 274 src = runtime->dma_area; 275 src += frames_to_bytes(runtime, pos); 276 dst = buf; 277 } 278 279 memcpy(dst, src, frames_to_bytes(runtime, count)); 280 } 281 282 return 0; 283} 284 285static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, 286 int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) 287{ 288 struct snd_soc_pcm_runtime *rtd = substream->private_data; 289 struct snd_pcm_runtime *runtime = substream->runtime; 290 unsigned int sample_size = runtime->sample_bits / 8; 291 void *buf = runtime->dma_area; 292 struct bf5xx_i2s_pcm_data *dma_data; 293 unsigned int offset, samples; 294 295 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); 296 297 if (dma_data->tdm_mode) { 298 offset = pos * 8 * sample_size; 299 samples = count * 8; 300 } else { 301 offset = frames_to_bytes(runtime, pos); 302 samples = count * runtime->channels; 303 } 304 305 snd_pcm_format_set_silence(runtime->format, buf + offset, samples); 306 307 return 0; 308} 309 310static struct snd_pcm_ops bf5xx_pcm_i2s_ops = { 311 .open = bf5xx_pcm_open, 312 .ioctl = snd_pcm_lib_ioctl, 313 .hw_params = bf5xx_pcm_hw_params, 314 .hw_free = bf5xx_pcm_hw_free, 315 .prepare = bf5xx_pcm_prepare, 316 .trigger = bf5xx_pcm_trigger, 317 .pointer = bf5xx_pcm_pointer, 318 .mmap = bf5xx_pcm_mmap, 319 .copy = bf5xx_pcm_copy, 320 .silence = bf5xx_pcm_silence, 321}; 322 323static int bf5xx_pcm_i2s_new(struct snd_soc_pcm_runtime *rtd) 324{ 325 struct snd_card *card = rtd->card->snd_card; 326 size_t size = bf5xx_pcm_hardware.buffer_bytes_max; 327 int ret; 328 329 pr_debug("%s enter\n", __func__); 330 ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); 331 if (ret) 332 return ret; 333 334 return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, 335 SNDRV_DMA_TYPE_DEV, card->dev, size, size); 336} 337 338static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = { 339 .ops = &bf5xx_pcm_i2s_ops, 340 .pcm_new = bf5xx_pcm_i2s_new, 341}; 342 343static int bfin_i2s_soc_platform_probe(struct platform_device *pdev) 344{ 345 return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform); 346} 347 348static int bfin_i2s_soc_platform_remove(struct platform_device *pdev) 349{ 350 snd_soc_unregister_platform(&pdev->dev); 351 return 0; 352} 353 354static struct platform_driver bfin_i2s_pcm_driver = { 355 .driver = { 356 .name = "bfin-i2s-pcm-audio", 357 }, 358 359 .probe = bfin_i2s_soc_platform_probe, 360 .remove = bfin_i2s_soc_platform_remove, 361}; 362 363module_platform_driver(bfin_i2s_pcm_driver); 364 365MODULE_AUTHOR("Cliff Cai"); 366MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module"); 367MODULE_LICENSE("GPL"); 368