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-i2s-pcm.h" 43#include "bf5xx-i2s.h" 44#include "bf5xx-sport.h" 45 46static void bf5xx_dma_irq(void *data) 47{ 48 struct snd_pcm_substream *pcm = data; 49 snd_pcm_period_elapsed(pcm); 50} 51 52static const struct snd_pcm_hardware bf5xx_pcm_hardware = { 53 .info = SNDRV_PCM_INFO_INTERLEAVED | 54 SNDRV_PCM_INFO_MMAP | 55 SNDRV_PCM_INFO_MMAP_VALID | 56 SNDRV_PCM_INFO_BLOCK_TRANSFER, 57 .formats = SNDRV_PCM_FMTBIT_S16_LE | 58 SNDRV_PCM_FMTBIT_S24_LE | 59 SNDRV_PCM_FMTBIT_S32_LE, 60 .period_bytes_min = 32, 61 .period_bytes_max = 0x10000, 62 .periods_min = 1, 63 .periods_max = PAGE_SIZE/32, 64 .buffer_bytes_max = 0x20000, /* 128 kbytes */ 65 .fifo_size = 16, 66}; 67 68static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, 69 struct snd_pcm_hw_params *params) 70{ 71 size_t size = bf5xx_pcm_hardware.buffer_bytes_max; 72 snd_pcm_lib_malloc_pages(substream, size); 73 74 return 0; 75} 76 77static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) 78{ 79 snd_pcm_lib_free_pages(substream); 80 81 return 0; 82} 83 84static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) 85{ 86 struct snd_pcm_runtime *runtime = substream->runtime; 87 struct sport_device *sport = runtime->private_data; 88 int period_bytes = frames_to_bytes(runtime, runtime->period_size); 89 90 pr_debug("%s enter\n", __func__); 91 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 92 sport_set_tx_callback(sport, bf5xx_dma_irq, substream); 93 sport_config_tx_dma(sport, runtime->dma_area, 94 runtime->periods, period_bytes); 95 } else { 96 sport_set_rx_callback(sport, bf5xx_dma_irq, substream); 97 sport_config_rx_dma(sport, runtime->dma_area, 98 runtime->periods, period_bytes); 99 } 100 101 return 0; 102} 103 104static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 105{ 106 struct snd_pcm_runtime *runtime = substream->runtime; 107 struct sport_device *sport = runtime->private_data; 108 int ret = 0; 109 110 pr_debug("%s enter\n", __func__); 111 switch (cmd) { 112 case SNDRV_PCM_TRIGGER_START: 113 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 114 sport_tx_start(sport); 115 else 116 sport_rx_start(sport); 117 break; 118 case SNDRV_PCM_TRIGGER_STOP: 119 case SNDRV_PCM_TRIGGER_SUSPEND: 120 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 121 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 122 sport_tx_stop(sport); 123 else 124 sport_rx_stop(sport); 125 break; 126 default: 127 ret = -EINVAL; 128 } 129 130 return ret; 131} 132 133static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) 134{ 135 struct snd_pcm_runtime *runtime = substream->runtime; 136 struct sport_device *sport = runtime->private_data; 137 unsigned int diff; 138 snd_pcm_uframes_t frames; 139 pr_debug("%s enter\n", __func__); 140 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 141 diff = sport_curr_offset_tx(sport); 142 frames = bytes_to_frames(substream->runtime, diff); 143 } else { 144 diff = sport_curr_offset_rx(sport); 145 frames = bytes_to_frames(substream->runtime, diff); 146 } 147 return frames; 148} 149 150static int bf5xx_pcm_open(struct snd_pcm_substream *substream) 151{ 152 struct snd_pcm_runtime *runtime = substream->runtime; 153 int ret; 154 155 pr_debug("%s enter\n", __func__); 156 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); 157 158 ret = snd_pcm_hw_constraint_integer(runtime, \ 159 SNDRV_PCM_HW_PARAM_PERIODS); 160 if (ret < 0) 161 goto out; 162 163 if (sport_handle != NULL) 164 runtime->private_data = sport_handle; 165 else { 166 pr_err("sport_handle is NULL\n"); 167 return -1; 168 } 169 return 0; 170 171 out: 172 return ret; 173} 174 175static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, 176 struct vm_area_struct *vma) 177{ 178 struct snd_pcm_runtime *runtime = substream->runtime; 179 size_t size = vma->vm_end - vma->vm_start; 180 vma->vm_start = (unsigned long)runtime->dma_area; 181 vma->vm_end = vma->vm_start + size; 182 vma->vm_flags |= VM_SHARED; 183 184 return 0 ; 185} 186 187static struct snd_pcm_ops bf5xx_pcm_i2s_ops = { 188 .open = bf5xx_pcm_open, 189 .ioctl = snd_pcm_lib_ioctl, 190 .hw_params = bf5xx_pcm_hw_params, 191 .hw_free = bf5xx_pcm_hw_free, 192 .prepare = bf5xx_pcm_prepare, 193 .trigger = bf5xx_pcm_trigger, 194 .pointer = bf5xx_pcm_pointer, 195 .mmap = bf5xx_pcm_mmap, 196}; 197 198static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) 199{ 200 struct snd_pcm_substream *substream = pcm->streams[stream].substream; 201 struct snd_dma_buffer *buf = &substream->dma_buffer; 202 size_t size = bf5xx_pcm_hardware.buffer_bytes_max; 203 204 buf->dev.type = SNDRV_DMA_TYPE_DEV; 205 buf->dev.dev = pcm->card->dev; 206 buf->private_data = NULL; 207 buf->area = dma_alloc_coherent(pcm->card->dev, size, 208 &buf->addr, GFP_KERNEL); 209 if (!buf->area) { 210 pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n"); 211 return -ENOMEM; 212 } 213 buf->bytes = size; 214 215 pr_debug("%s, area:%p, size:0x%08lx\n", __func__, 216 buf->area, buf->bytes); 217 218 if (stream == SNDRV_PCM_STREAM_PLAYBACK) 219 sport_handle->tx_buf = buf->area; 220 else 221 sport_handle->rx_buf = buf->area; 222 223 return 0; 224} 225 226static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) 227{ 228 struct snd_pcm_substream *substream; 229 struct snd_dma_buffer *buf; 230 int stream; 231 232 for (stream = 0; stream < 2; stream++) { 233 substream = pcm->streams[stream].substream; 234 if (!substream) 235 continue; 236 237 buf = &substream->dma_buffer; 238 if (!buf->area) 239 continue; 240 dma_free_coherent(NULL, buf->bytes, buf->area, 0); 241 buf->area = NULL; 242 } 243 if (sport_handle) 244 sport_done(sport_handle); 245} 246 247static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); 248 249int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai, 250 struct snd_pcm *pcm) 251{ 252 int ret = 0; 253 254 pr_debug("%s enter\n", __func__); 255 if (!card->dev->dma_mask) 256 card->dev->dma_mask = &bf5xx_pcm_dmamask; 257 if (!card->dev->coherent_dma_mask) 258 card->dev->coherent_dma_mask = DMA_BIT_MASK(32); 259 260 if (dai->playback.channels_min) { 261 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 262 SNDRV_PCM_STREAM_PLAYBACK); 263 if (ret) 264 goto out; 265 } 266 267 if (dai->capture.channels_min) { 268 ret = bf5xx_pcm_preallocate_dma_buffer(pcm, 269 SNDRV_PCM_STREAM_CAPTURE); 270 if (ret) 271 goto out; 272 } 273 out: 274 return ret; 275} 276 277struct snd_soc_platform bf5xx_i2s_soc_platform = { 278 .name = "bf5xx-audio", 279 .pcm_ops = &bf5xx_pcm_i2s_ops, 280 .pcm_new = bf5xx_pcm_i2s_new, 281 .pcm_free = bf5xx_pcm_free_dma_buffers, 282}; 283EXPORT_SYMBOL_GPL(bf5xx_i2s_soc_platform); 284 285static int __init bfin_i2s_init(void) 286{ 287 return snd_soc_register_platform(&bf5xx_i2s_soc_platform); 288} 289module_init(bfin_i2s_init); 290 291static void __exit bfin_i2s_exit(void) 292{ 293 snd_soc_unregister_platform(&bf5xx_i2s_soc_platform); 294} 295module_exit(bfin_i2s_exit); 296 297MODULE_AUTHOR("Cliff Cai"); 298MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module"); 299MODULE_LICENSE("GPL"); 300