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 41static unsigned int pcm_debug; 42module_param(pcm_debug, int, 0644); 43MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); 44 45#define dprintk(fmt, arg...) do { \ 46 if (pcm_debug) \ 47 printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \ 48 __func__, ##arg); \ 49 } while (0) 50 51static struct snd_pcm_hardware snd_cx18_hw_capture = { 52 .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 53 SNDRV_PCM_INFO_MMAP | 54 SNDRV_PCM_INFO_INTERLEAVED | 55 SNDRV_PCM_INFO_MMAP_VALID, 56 57 .formats = SNDRV_PCM_FMTBIT_S16_LE, 58 59 .rates = SNDRV_PCM_RATE_48000, 60 61 .rate_min = 48000, 62 .rate_max = 48000, 63 .channels_min = 2, 64 .channels_max = 2, 65 .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ 66 .period_bytes_min = 64, /* 12544/2, */ 67 .period_bytes_max = 12544, 68 .periods_min = 2, 69 .periods_max = 98, /* 12544, */ 70}; 71 72void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data, 73 size_t num_bytes) 74{ 75 struct snd_pcm_substream *substream; 76 struct snd_pcm_runtime *runtime; 77 unsigned int oldptr; 78 unsigned int stride; 79 int period_elapsed = 0; 80 int length; 81 82 dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zd\n", cxsc, 83 pcm_data, num_bytes); 84 85 substream = cxsc->capture_pcm_substream; 86 if (substream == NULL) { 87 dprintk("substream was NULL\n"); 88 return; 89 } 90 91 runtime = substream->runtime; 92 if (runtime == NULL) { 93 dprintk("runtime was NULL\n"); 94 return; 95 } 96 97 stride = runtime->frame_bits >> 3; 98 if (stride == 0) { 99 dprintk("stride is zero\n"); 100 return; 101 } 102 103 length = num_bytes / stride; 104 if (length == 0) { 105 dprintk("%s: length was zero\n", __func__); 106 return; 107 } 108 109 if (runtime->dma_area == NULL) { 110 dprintk("dma area was NULL - ignoring\n"); 111 return; 112 } 113 114 oldptr = cxsc->hwptr_done_capture; 115 if (oldptr + length >= runtime->buffer_size) { 116 unsigned int cnt = 117 runtime->buffer_size - oldptr; 118 memcpy(runtime->dma_area + oldptr * stride, pcm_data, 119 cnt * stride); 120 memcpy(runtime->dma_area, pcm_data + cnt * stride, 121 length * stride - cnt * stride); 122 } else { 123 memcpy(runtime->dma_area + oldptr * stride, pcm_data, 124 length * stride); 125 } 126 snd_pcm_stream_lock(substream); 127 128 cxsc->hwptr_done_capture += length; 129 if (cxsc->hwptr_done_capture >= 130 runtime->buffer_size) 131 cxsc->hwptr_done_capture -= 132 runtime->buffer_size; 133 134 cxsc->capture_transfer_done += length; 135 if (cxsc->capture_transfer_done >= 136 runtime->period_size) { 137 cxsc->capture_transfer_done -= 138 runtime->period_size; 139 period_elapsed = 1; 140 } 141 142 snd_pcm_stream_unlock(substream); 143 144 if (period_elapsed) 145 snd_pcm_period_elapsed(substream); 146} 147 148static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream) 149{ 150 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 151 struct snd_pcm_runtime *runtime = substream->runtime; 152 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 153 struct cx18 *cx = to_cx18(v4l2_dev); 154 struct cx18_stream *s; 155 struct cx18_open_id item; 156 int ret; 157 158 /* Instruct the cx18 to start sending packets */ 159 snd_cx18_lock(cxsc); 160 s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; 161 162 item.cx = cx; 163 item.type = s->type; 164 item.open_id = cx->open_id++; 165 166 /* See if the stream is available */ 167 if (cx18_claim_stream(&item, item.type)) { 168 /* No, it's already in use */ 169 snd_cx18_unlock(cxsc); 170 return -EBUSY; 171 } 172 173 if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) || 174 test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) { 175 /* We're already streaming. No additional action required */ 176 snd_cx18_unlock(cxsc); 177 return 0; 178 } 179 180 181 runtime->hw = snd_cx18_hw_capture; 182 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 183 cxsc->capture_pcm_substream = substream; 184 runtime->private_data = cx; 185 186 cx->pcm_announce_callback = cx18_alsa_announce_pcm_data; 187 188 /* Not currently streaming, so start it up */ 189 set_bit(CX18_F_S_STREAMING, &s->s_flags); 190 ret = cx18_start_v4l2_encode_stream(s); 191 snd_cx18_unlock(cxsc); 192 193 return 0; 194} 195 196static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream) 197{ 198 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 199 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 200 struct cx18 *cx = to_cx18(v4l2_dev); 201 struct cx18_stream *s; 202 int ret; 203 204 /* Instruct the cx18 to stop sending packets */ 205 snd_cx18_lock(cxsc); 206 s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; 207 ret = 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 return snd_pcm_lib_ioctl(substream, cmd, arg); 222} 223 224 225static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, 226 size_t size) 227{ 228 struct snd_pcm_runtime *runtime = subs->runtime; 229 230 dprintk("Allocating vbuffer\n"); 231 if (runtime->dma_area) { 232 if (runtime->dma_bytes > size) 233 return 0; 234 235 vfree(runtime->dma_area); 236 } 237 runtime->dma_area = vmalloc(size); 238 if (!runtime->dma_area) 239 return -ENOMEM; 240 241 runtime->dma_bytes = size; 242 243 return 0; 244} 245 246static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream, 247 struct snd_pcm_hw_params *params) 248{ 249 int ret; 250 251 dprintk("%s called\n", __func__); 252 253 ret = snd_pcm_alloc_vmalloc_buffer(substream, 254 params_buffer_bytes(params)); 255 return 0; 256} 257 258static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) 259{ 260 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 261 unsigned long flags; 262 263 spin_lock_irqsave(&cxsc->slock, flags); 264 if (substream->runtime->dma_area) { 265 dprintk("freeing pcm capture region\n"); 266 vfree(substream->runtime->dma_area); 267 substream->runtime->dma_area = NULL; 268 } 269 spin_unlock_irqrestore(&cxsc->slock, flags); 270 271 return 0; 272} 273 274static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) 275{ 276 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 277 278 cxsc->hwptr_done_capture = 0; 279 cxsc->capture_transfer_done = 0; 280 281 return 0; 282} 283 284static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 285{ 286 return 0; 287} 288 289static 290snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) 291{ 292 unsigned long flags; 293 snd_pcm_uframes_t hwptr_done; 294 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 295 296 spin_lock_irqsave(&cxsc->slock, flags); 297 hwptr_done = cxsc->hwptr_done_capture; 298 spin_unlock_irqrestore(&cxsc->slock, flags); 299 300 return hwptr_done; 301} 302 303static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, 304 unsigned long offset) 305{ 306 void *pageptr = subs->runtime->dma_area + offset; 307 308 return vmalloc_to_page(pageptr); 309} 310 311static struct snd_pcm_ops snd_cx18_pcm_capture_ops = { 312 .open = snd_cx18_pcm_capture_open, 313 .close = snd_cx18_pcm_capture_close, 314 .ioctl = snd_cx18_pcm_ioctl, 315 .hw_params = snd_cx18_pcm_hw_params, 316 .hw_free = snd_cx18_pcm_hw_free, 317 .prepare = snd_cx18_pcm_prepare, 318 .trigger = snd_cx18_pcm_trigger, 319 .pointer = snd_cx18_pcm_pointer, 320 .page = snd_pcm_get_vmalloc_page, 321}; 322 323int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) 324{ 325 struct snd_pcm *sp; 326 struct snd_card *sc = cxsc->sc; 327 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 328 struct cx18 *cx = to_cx18(v4l2_dev); 329 int ret; 330 331 ret = snd_pcm_new(sc, "CX23418 PCM", 332 0, /* PCM device 0, the only one for this card */ 333 0, /* 0 playback substreams */ 334 1, /* 1 capture substream */ 335 &sp); 336 if (ret) { 337 CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", 338 __func__, ret); 339 goto err_exit; 340 } 341 342 spin_lock_init(&cxsc->slock); 343 344 snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, 345 &snd_cx18_pcm_capture_ops); 346 sp->info_flags = 0; 347 sp->private_data = cxsc; 348 strlcpy(sp->name, cx->card_name, sizeof(sp->name)); 349 350 return 0; 351 352err_exit: 353 return ret; 354} 355