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 51static void bf5xx_dma_irq(void *data) 52{ 53 struct snd_pcm_substream *pcm = data; 54 snd_pcm_period_elapsed(pcm); 55} 56 57static 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 71static 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 80static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) 81{ 82 snd_pcm_lib_free_pages(substream); 83 84 return 0; 85} 86 87static 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 110static 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 138static 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 155static int bf5xx_pcm_open(struct snd_pcm_substream *substream) 156{ 157 struct snd_pcm_runtime *runtime = substream->runtime; 158 int ret = 0; 159 160 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); 161 162 ret = snd_pcm_hw_constraint_integer(runtime, 163 SNDRV_PCM_HW_PARAM_PERIODS); 164 if (ret < 0) 165 goto out; 166 167 if (sport_handle != NULL) 168 runtime->private_data = sport_handle; 169 else { 170 pr_err("sport_handle is NULL\n"); 171 ret = -ENODEV; 172 } 173out: 174 return ret; 175} 176 177static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, 178 snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) 179{ 180 struct snd_pcm_runtime *runtime = substream->runtime; 181 struct sport_device *sport = runtime->private_data; 182 struct bf5xx_tdm_port *tdm_port = sport->private_data; 183 unsigned int *src; 184 unsigned int *dst; 185 int i; 186 187 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 188 src = buf; 189 dst = (unsigned int *)substream->runtime->dma_area; 190 191 dst += pos * 8; 192 while (count--) { 193 for (i = 0; i < substream->runtime->channels; i++) 194 *(dst + tdm_port->tx_map[i]) = *src++; 195 dst += 8; 196 } 197 } else { 198 src = (unsigned int *)substream->runtime->dma_area; 199 dst = buf; 200 201 src += pos * 8; 202 while (count--) { 203 for (i = 0; i < substream->runtime->channels; i++) 204 *dst++ = *(src + tdm_port->rx_map[i]); 205 src += 8; 206 } 207 } 208 209 return 0; 210} 211 212static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, 213 int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) 214{ 215 unsigned char *buf = substream->runtime->dma_area; 216 buf += pos * 8 * 4; 217 memset(buf, '\0', count * 8 * 4); 218 219 return 0; 220} 221 222 223struct snd_pcm_ops bf5xx_pcm_tdm_ops = { 224 .open = bf5xx_pcm_open, 225 .ioctl = snd_pcm_lib_ioctl, 226 .hw_params = bf5xx_pcm_hw_params, 227 .hw_free = bf5xx_pcm_hw_free, 228 .prepare = bf5xx_pcm_prepare, 229 .trigger = bf5xx_pcm_trigger, 230 .pointer = bf5xx_pcm_pointer, 231 .copy = bf5xx_pcm_copy, 232 .silence = bf5xx_pcm_silence, 233}; 234 235static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) 236{ 237 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 238 struct snd_dma_buffer *buf = &substream->dma_buffer; 239 size_t size = bf5xx_pcm_hardware.buffer_bytes_max; 240 241 buf->dev.type = SNDRV_DMA_TYPE_DEV; 242 buf->dev.dev = pcm->card->dev; 243 buf->private_data = NULL; 244 buf->area = dma_alloc_coherent(pcm->card->dev, size * 4, 245 &buf->addr, GFP_KERNEL); 246 if (!buf->area) { 247 pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n"); 248 return -ENOMEM; 249 } 250 buf->bytes = size; 251 252 if (stream == SNDRV_PCM_STREAM_PLAYBACK) 253 sport_handle->tx_buf = buf->area; 254 else 255 sport_handle->rx_buf = buf->area; 256 257 return 0; 258} 259 260static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) 261{ 262 struct snd_pcm_substream *substream; 263 struct snd_dma_buffer *buf; 264 int stream; 265 266 for (stream = 0; stream < 2; stream++) { 267 substream = pcm->streams[stream].substream; 268 if (!substream) 269 continue; 270 271 buf = &substream->dma_buffer; 272 if (!buf->area) 273 continue; 274 dma_free_coherent(NULL, buf->bytes, buf->area, 0); 275 buf->area = NULL; 276 } 277 if (sport_handle) 278 sport_done(sport_handle); 279} 280 281static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); 282 283static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai, 284 struct snd_pcm *pcm) 285{ 286 int ret = 0; 287 288 if (!card->dev->dma_mask) 289 card->dev->dma_mask = &bf5xx_pcm_dmamask; 290 if (!card->dev->coherent_dma_mask) 291 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 292 293 if (dai->playback.channels_min) { 294 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 295 SNDRV_PCM_STREAM_PLAYBACK); 296 if (ret) 297 goto out; 298 } 299 300 if (dai->capture.channels_min) { 301 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 302 SNDRV_PCM_STREAM_CAPTURE); 303 if (ret) 304 goto out; 305 } 306out: 307 return ret; 308} 309 310struct snd_soc_platform bf5xx_tdm_soc_platform = { 311 .name = "bf5xx-audio", 312 .pcm_ops = &bf5xx_pcm_tdm_ops, 313 .pcm_new = bf5xx_pcm_tdm_new, 314 .pcm_free = bf5xx_pcm_free_dma_buffers, 315}; 316EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform); 317 318static int __init bfin_pcm_tdm_init(void) 319{ 320 return snd_soc_register_platform(&bf5xx_tdm_soc_platform); 321} 322module_init(bfin_pcm_tdm_init); 323 324static void __exit bfin_pcm_tdm_exit(void) 325{ 326 snd_soc_unregister_platform(&bf5xx_tdm_soc_platform); 327} 328module_exit(bfin_pcm_tdm_exit); 329 330MODULE_AUTHOR("Barry Song"); 331MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module"); 332MODULE_LICENSE("GPL"); 333