1/* 2 * ALSA PCM device for the 3 * ALSA interface to cx18 PCM capture streams 4 * 5 * Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net> 6 * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> 7 * 8 * Portions of this work were sponsored by ONELAN Limited. 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 23 * 02111-1307 USA 24 */ 25 26#include <linux/init.h> 27#include <linux/kernel.h> 28#include <linux/vmalloc.h> 29 30#include <media/v4l2-device.h> 31 32#include <sound/core.h> 33#include <sound/pcm.h> 34 35#include "cx18-driver.h" 36#include "cx18-queue.h" 37#include "cx18-streams.h" 38#include "cx18-fileops.h" 39#include "cx18-alsa.h" 40#include "cx18-alsa-pcm.h" 41 42static unsigned int pcm_debug; 43module_param(pcm_debug, int, 0644); 44MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); 45 46#define dprintk(fmt, arg...) do { \ 47 if (pcm_debug) \ 48 printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \ 49 __func__, ##arg); \ 50 } while (0) 51 52static struct snd_pcm_hardware snd_cx18_hw_capture = { 53 .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 54 SNDRV_PCM_INFO_MMAP | 55 SNDRV_PCM_INFO_INTERLEAVED | 56 SNDRV_PCM_INFO_MMAP_VALID, 57 58 .formats = SNDRV_PCM_FMTBIT_S16_LE, 59 60 .rates = SNDRV_PCM_RATE_48000, 61 62 .rate_min = 48000, 63 .rate_max = 48000, 64 .channels_min = 2, 65 .channels_max = 2, 66 .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ 67 .period_bytes_min = 64, /* 12544/2, */ 68 .period_bytes_max = 12544, 69 .periods_min = 2, 70 .periods_max = 98, /* 12544, */ 71}; 72 73void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data, 74 size_t num_bytes) 75{ 76 struct snd_pcm_substream *substream; 77 struct snd_pcm_runtime *runtime; 78 unsigned int oldptr; 79 unsigned int stride; 80 int period_elapsed = 0; 81 int length; 82 83 dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zu\n", cxsc, 84 pcm_data, num_bytes); 85 86 substream = cxsc->capture_pcm_substream; 87 if (substream == NULL) { 88 dprintk("substream was NULL\n"); 89 return; 90 } 91 92 runtime = substream->runtime; 93 if (runtime == NULL) { 94 dprintk("runtime was NULL\n"); 95 return; 96 } 97 98 stride = runtime->frame_bits >> 3; 99 if (stride == 0) { 100 dprintk("stride is zero\n"); 101 return; 102 } 103 104 length = num_bytes / stride; 105 if (length == 0) { 106 dprintk("%s: length was zero\n", __func__); 107 return; 108 } 109 110 if (runtime->dma_area == NULL) { 111 dprintk("dma area was NULL - ignoring\n"); 112 return; 113 } 114 115 oldptr = cxsc->hwptr_done_capture; 116 if (oldptr + length >= runtime->buffer_size) { 117 unsigned int cnt = 118 runtime->buffer_size - oldptr; 119 memcpy(runtime->dma_area + oldptr * stride, pcm_data, 120 cnt * stride); 121 memcpy(runtime->dma_area, pcm_data + cnt * stride, 122 length * stride - cnt * stride); 123 } else { 124 memcpy(runtime->dma_area + oldptr * stride, pcm_data, 125 length * stride); 126 } 127 snd_pcm_stream_lock(substream); 128 129 cxsc->hwptr_done_capture += length; 130 if (cxsc->hwptr_done_capture >= 131 runtime->buffer_size) 132 cxsc->hwptr_done_capture -= 133 runtime->buffer_size; 134 135 cxsc->capture_transfer_done += length; 136 if (cxsc->capture_transfer_done >= 137 runtime->period_size) { 138 cxsc->capture_transfer_done -= 139 runtime->period_size; 140 period_elapsed = 1; 141 } 142 143 snd_pcm_stream_unlock(substream); 144 145 if (period_elapsed) 146 snd_pcm_period_elapsed(substream); 147} 148 149static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream) 150{ 151 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 152 struct snd_pcm_runtime *runtime = substream->runtime; 153 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 154 struct cx18 *cx = to_cx18(v4l2_dev); 155 struct cx18_stream *s; 156 struct cx18_open_id item; 157 int ret; 158 159 /* Instruct the cx18 to start sending packets */ 160 snd_cx18_lock(cxsc); 161 s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; 162 163 item.cx = cx; 164 item.type = s->type; 165 item.open_id = cx->open_id++; 166 167 /* See if the stream is available */ 168 if (cx18_claim_stream(&item, item.type)) { 169 /* No, it's already in use */ 170 snd_cx18_unlock(cxsc); 171 return -EBUSY; 172 } 173 174 if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) || 175 test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) { 176 /* We're already streaming. No additional action required */ 177 snd_cx18_unlock(cxsc); 178 return 0; 179 } 180 181 182 runtime->hw = snd_cx18_hw_capture; 183 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 184 cxsc->capture_pcm_substream = substream; 185 runtime->private_data = cx; 186 187 cx->pcm_announce_callback = cx18_alsa_announce_pcm_data; 188 189 /* Not currently streaming, so start it up */ 190 set_bit(CX18_F_S_STREAMING, &s->s_flags); 191 ret = cx18_start_v4l2_encode_stream(s); 192 snd_cx18_unlock(cxsc); 193 194 return ret; 195} 196 197static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream) 198{ 199 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 200 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 201 struct cx18 *cx = to_cx18(v4l2_dev); 202 struct cx18_stream *s; 203 204 /* Instruct the cx18 to stop sending packets */ 205 snd_cx18_lock(cxsc); 206 s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; 207 cx18_stop_v4l2_encode_stream(s, 0); 208 clear_bit(CX18_F_S_STREAMING, &s->s_flags); 209 210 cx18_release_stream(s); 211 212 cx->pcm_announce_callback = NULL; 213 snd_cx18_unlock(cxsc); 214 215 return 0; 216} 217 218static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream, 219 unsigned int cmd, void *arg) 220{ 221 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 222 int ret; 223 224 snd_cx18_lock(cxsc); 225 ret = snd_pcm_lib_ioctl(substream, cmd, arg); 226 snd_cx18_unlock(cxsc); 227 return ret; 228} 229 230 231static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, 232 size_t size) 233{ 234 struct snd_pcm_runtime *runtime = subs->runtime; 235 236 dprintk("Allocating vbuffer\n"); 237 if (runtime->dma_area) { 238 if (runtime->dma_bytes > size) 239 return 0; 240 241 vfree(runtime->dma_area); 242 } 243 runtime->dma_area = vmalloc(size); 244 if (!runtime->dma_area) 245 return -ENOMEM; 246 247 runtime->dma_bytes = size; 248 249 return 0; 250} 251 252static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream, 253 struct snd_pcm_hw_params *params) 254{ 255 dprintk("%s called\n", __func__); 256 257 return snd_pcm_alloc_vmalloc_buffer(substream, 258 params_buffer_bytes(params)); 259} 260 261static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) 262{ 263 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 264 unsigned long flags; 265 266 spin_lock_irqsave(&cxsc->slock, flags); 267 if (substream->runtime->dma_area) { 268 dprintk("freeing pcm capture region\n"); 269 vfree(substream->runtime->dma_area); 270 substream->runtime->dma_area = NULL; 271 } 272 spin_unlock_irqrestore(&cxsc->slock, flags); 273 274 return 0; 275} 276 277static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) 278{ 279 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 280 281 cxsc->hwptr_done_capture = 0; 282 cxsc->capture_transfer_done = 0; 283 284 return 0; 285} 286 287static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 288{ 289 return 0; 290} 291 292static 293snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) 294{ 295 unsigned long flags; 296 snd_pcm_uframes_t hwptr_done; 297 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 298 299 spin_lock_irqsave(&cxsc->slock, flags); 300 hwptr_done = cxsc->hwptr_done_capture; 301 spin_unlock_irqrestore(&cxsc->slock, flags); 302 303 return hwptr_done; 304} 305 306static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, 307 unsigned long offset) 308{ 309 void *pageptr = subs->runtime->dma_area + offset; 310 311 return vmalloc_to_page(pageptr); 312} 313 314static struct snd_pcm_ops snd_cx18_pcm_capture_ops = { 315 .open = snd_cx18_pcm_capture_open, 316 .close = snd_cx18_pcm_capture_close, 317 .ioctl = snd_cx18_pcm_ioctl, 318 .hw_params = snd_cx18_pcm_hw_params, 319 .hw_free = snd_cx18_pcm_hw_free, 320 .prepare = snd_cx18_pcm_prepare, 321 .trigger = snd_cx18_pcm_trigger, 322 .pointer = snd_cx18_pcm_pointer, 323 .page = snd_pcm_get_vmalloc_page, 324}; 325 326int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) 327{ 328 struct snd_pcm *sp; 329 struct snd_card *sc = cxsc->sc; 330 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 331 struct cx18 *cx = to_cx18(v4l2_dev); 332 int ret; 333 334 ret = snd_pcm_new(sc, "CX23418 PCM", 335 0, /* PCM device 0, the only one for this card */ 336 0, /* 0 playback substreams */ 337 1, /* 1 capture substream */ 338 &sp); 339 if (ret) { 340 CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", 341 __func__, ret); 342 goto err_exit; 343 } 344 345 spin_lock_init(&cxsc->slock); 346 347 snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, 348 &snd_cx18_pcm_capture_ops); 349 sp->info_flags = 0; 350 sp->private_data = cxsc; 351 strlcpy(sp->name, cx->card_name, sizeof(sp->name)); 352 353 return 0; 354 355err_exit: 356 return ret; 357} 358