Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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