1 /* 2 * File: sound/soc/blackfin/bf5xx-tdm-pcm.c 3 * Author: Barry Song <Barry.Song@analog.com> 4 * 5 * Created: Tue June 06 2009 6 * Description: DMA driver for tdm codec 7 * 8 * Modified: 9 * Copyright 2009 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-tdm-pcm.h" 43 #include "bf5xx-tdm.h" 44 #include "bf5xx-sport.h" 45 46 #define PCM_BUFFER_MAX 0x8000 47 #define FRAGMENT_SIZE_MIN (4*1024) 48 #define FRAGMENTS_MIN 2 49 #define FRAGMENTS_MAX 32 50 51 static void bf5xx_dma_irq(void *data) 52 { 53 struct snd_pcm_substream *pcm = data; 54 snd_pcm_period_elapsed(pcm); 55 } 56 57 static const struct snd_pcm_hardware bf5xx_pcm_hardware = { 58 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | 59 SNDRV_PCM_INFO_RESUME), 60 .formats = SNDRV_PCM_FMTBIT_S32_LE, 61 .rates = SNDRV_PCM_RATE_48000, 62 .channels_min = 2, 63 .channels_max = 8, 64 .buffer_bytes_max = PCM_BUFFER_MAX, 65 .period_bytes_min = FRAGMENT_SIZE_MIN, 66 .period_bytes_max = PCM_BUFFER_MAX/2, 67 .periods_min = FRAGMENTS_MIN, 68 .periods_max = FRAGMENTS_MAX, 69 }; 70 71 static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, 72 struct snd_pcm_hw_params *params) 73 { 74 size_t size = bf5xx_pcm_hardware.buffer_bytes_max; 75 snd_pcm_lib_malloc_pages(substream, size * 4); 76 77 return 0; 78 } 79 80 static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) 81 { 82 snd_pcm_lib_free_pages(substream); 83 84 return 0; 85 } 86 87 static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) 88 { 89 struct snd_pcm_runtime *runtime = substream->runtime; 90 struct sport_device *sport = runtime->private_data; 91 int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size); 92 93 fragsize_bytes /= runtime->channels; 94 /* inflate the fragsize to match the dma width of SPORT */ 95 fragsize_bytes *= 8; 96 97 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 98 sport_set_tx_callback(sport, bf5xx_dma_irq, substream); 99 sport_config_tx_dma(sport, runtime->dma_area, 100 runtime->periods, fragsize_bytes); 101 } else { 102 sport_set_rx_callback(sport, bf5xx_dma_irq, substream); 103 sport_config_rx_dma(sport, runtime->dma_area, 104 runtime->periods, fragsize_bytes); 105 } 106 107 return 0; 108 } 109 110 static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 111 { 112 struct snd_pcm_runtime *runtime = substream->runtime; 113 struct sport_device *sport = runtime->private_data; 114 int ret = 0; 115 116 switch (cmd) { 117 case SNDRV_PCM_TRIGGER_START: 118 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 119 sport_tx_start(sport); 120 else 121 sport_rx_start(sport); 122 break; 123 case SNDRV_PCM_TRIGGER_STOP: 124 case SNDRV_PCM_TRIGGER_SUSPEND: 125 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 126 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 127 sport_tx_stop(sport); 128 else 129 sport_rx_stop(sport); 130 break; 131 default: 132 ret = -EINVAL; 133 } 134 135 return ret; 136 } 137 138 static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) 139 { 140 struct snd_pcm_runtime *runtime = substream->runtime; 141 struct sport_device *sport = runtime->private_data; 142 unsigned int diff; 143 snd_pcm_uframes_t frames; 144 145 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 146 diff = sport_curr_offset_tx(sport); 147 frames = diff / (8*4); /* 32 bytes per frame */ 148 } else { 149 diff = sport_curr_offset_rx(sport); 150 frames = diff / (8*4); 151 } 152 return frames; 153 } 154 155 static int bf5xx_pcm_open(struct snd_pcm_substream *substream) 156 { 157 struct snd_soc_pcm_runtime *rtd = substream->private_data; 158 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 159 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai); 160 struct snd_pcm_runtime *runtime = substream->runtime; 161 struct snd_dma_buffer *buf = &substream->dma_buffer; 162 163 int ret = 0; 164 165 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); 166 167 ret = snd_pcm_hw_constraint_integer(runtime, 168 SNDRV_PCM_HW_PARAM_PERIODS); 169 if (ret < 0) 170 goto out; 171 172 if (sport_handle != NULL) { 173 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 174 sport_handle->tx_buf = buf->area; 175 else 176 sport_handle->rx_buf = buf->area; 177 178 runtime->private_data = sport_handle; 179 } else { 180 pr_err("sport_handle is NULL\n"); 181 ret = -ENODEV; 182 } 183 out: 184 return ret; 185 } 186 187 static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, 188 snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) 189 { 190 struct snd_pcm_runtime *runtime = substream->runtime; 191 struct sport_device *sport = runtime->private_data; 192 struct bf5xx_tdm_port *tdm_port = sport->private_data; 193 unsigned int *src; 194 unsigned int *dst; 195 int i; 196 197 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 198 src = buf; 199 dst = (unsigned int *)substream->runtime->dma_area; 200 201 dst += pos * 8; 202 while (count--) { 203 for (i = 0; i < substream->runtime->channels; i++) 204 *(dst + tdm_port->tx_map[i]) = *src++; 205 dst += 8; 206 } 207 } else { 208 src = (unsigned int *)substream->runtime->dma_area; 209 dst = buf; 210 211 src += pos * 8; 212 while (count--) { 213 for (i = 0; i < substream->runtime->channels; i++) 214 *dst++ = *(src + tdm_port->rx_map[i]); 215 src += 8; 216 } 217 } 218 219 return 0; 220 } 221 222 static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, 223 int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) 224 { 225 unsigned char *buf = substream->runtime->dma_area; 226 buf += pos * 8 * 4; 227 memset(buf, '\0', count * 8 * 4); 228 229 return 0; 230 } 231 232 233 struct snd_pcm_ops bf5xx_pcm_tdm_ops = { 234 .open = bf5xx_pcm_open, 235 .ioctl = snd_pcm_lib_ioctl, 236 .hw_params = bf5xx_pcm_hw_params, 237 .hw_free = bf5xx_pcm_hw_free, 238 .prepare = bf5xx_pcm_prepare, 239 .trigger = bf5xx_pcm_trigger, 240 .pointer = bf5xx_pcm_pointer, 241 .copy = bf5xx_pcm_copy, 242 .silence = bf5xx_pcm_silence, 243 }; 244 245 static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) 246 { 247 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 248 struct snd_dma_buffer *buf = &substream->dma_buffer; 249 size_t size = bf5xx_pcm_hardware.buffer_bytes_max; 250 251 buf->dev.type = SNDRV_DMA_TYPE_DEV; 252 buf->dev.dev = pcm->card->dev; 253 buf->private_data = NULL; 254 buf->area = dma_alloc_coherent(pcm->card->dev, size * 4, 255 &buf->addr, GFP_KERNEL); 256 if (!buf->area) { 257 pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n"); 258 return -ENOMEM; 259 } 260 buf->bytes = size; 261 262 return 0; 263 } 264 265 static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) 266 { 267 struct snd_pcm_substream *substream; 268 struct snd_dma_buffer *buf; 269 int stream; 270 271 for (stream = 0; stream < 2; stream++) { 272 substream = pcm->streams[stream].substream; 273 if (!substream) 274 continue; 275 276 buf = &substream->dma_buffer; 277 if (!buf->area) 278 continue; 279 dma_free_coherent(NULL, buf->bytes, buf->area, 0); 280 buf->area = NULL; 281 } 282 } 283 284 static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); 285 286 static int bf5xx_pcm_tdm_new(struct snd_soc_pcm_runtime *rtd) 287 { 288 struct snd_card *card = rtd->card->snd_card; 289 struct snd_pcm *pcm = rtd->pcm; 290 int ret = 0; 291 292 if (!card->dev->dma_mask) 293 card->dev->dma_mask = &bf5xx_pcm_dmamask; 294 if (!card->dev->coherent_dma_mask) 295 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 296 297 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { 298 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 299 SNDRV_PCM_STREAM_PLAYBACK); 300 if (ret) 301 goto out; 302 } 303 304 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { 305 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 306 SNDRV_PCM_STREAM_CAPTURE); 307 if (ret) 308 goto out; 309 } 310 out: 311 return ret; 312 } 313 314 static struct snd_soc_platform_driver bf5xx_tdm_soc_platform = { 315 .ops = &bf5xx_pcm_tdm_ops, 316 .pcm_new = bf5xx_pcm_tdm_new, 317 .pcm_free = bf5xx_pcm_free_dma_buffers, 318 }; 319 320 static int bf5xx_soc_platform_probe(struct platform_device *pdev) 321 { 322 return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform); 323 } 324 325 static int bf5xx_soc_platform_remove(struct platform_device *pdev) 326 { 327 snd_soc_unregister_platform(&pdev->dev); 328 return 0; 329 } 330 331 static struct platform_driver bfin_tdm_driver = { 332 .driver = { 333 .name = "bfin-tdm-pcm-audio", 334 .owner = THIS_MODULE, 335 }, 336 337 .probe = bf5xx_soc_platform_probe, 338 .remove = bf5xx_soc_platform_remove, 339 }; 340 341 module_platform_driver(bfin_tdm_driver); 342 343 MODULE_AUTHOR("Barry Song"); 344 MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module"); 345 MODULE_LICENSE("GPL"); 346
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.