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 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 int ret; 256 257 dprintk("%s called\n", __func__); 258 259 ret = snd_pcm_alloc_vmalloc_buffer(substream, 260 params_buffer_bytes(params)); 261 return 0; 262} 263 264static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) 265{ 266 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 267 unsigned long flags; 268 269 spin_lock_irqsave(&cxsc->slock, flags); 270 if (substream->runtime->dma_area) { 271 dprintk("freeing pcm capture region\n"); 272 vfree(substream->runtime->dma_area); 273 substream->runtime->dma_area = NULL; 274 } 275 spin_unlock_irqrestore(&cxsc->slock, flags); 276 277 return 0; 278} 279 280static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream) 281{ 282 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 283 284 cxsc->hwptr_done_capture = 0; 285 cxsc->capture_transfer_done = 0; 286 287 return 0; 288} 289 290static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 291{ 292 return 0; 293} 294 295static 296snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) 297{ 298 unsigned long flags; 299 snd_pcm_uframes_t hwptr_done; 300 struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); 301 302 spin_lock_irqsave(&cxsc->slock, flags); 303 hwptr_done = cxsc->hwptr_done_capture; 304 spin_unlock_irqrestore(&cxsc->slock, flags); 305 306 return hwptr_done; 307} 308 309static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, 310 unsigned long offset) 311{ 312 void *pageptr = subs->runtime->dma_area + offset; 313 314 return vmalloc_to_page(pageptr); 315} 316 317static struct snd_pcm_ops snd_cx18_pcm_capture_ops = { 318 .open = snd_cx18_pcm_capture_open, 319 .close = snd_cx18_pcm_capture_close, 320 .ioctl = snd_cx18_pcm_ioctl, 321 .hw_params = snd_cx18_pcm_hw_params, 322 .hw_free = snd_cx18_pcm_hw_free, 323 .prepare = snd_cx18_pcm_prepare, 324 .trigger = snd_cx18_pcm_trigger, 325 .pointer = snd_cx18_pcm_pointer, 326 .page = snd_pcm_get_vmalloc_page, 327}; 328 329int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) 330{ 331 struct snd_pcm *sp; 332 struct snd_card *sc = cxsc->sc; 333 struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; 334 struct cx18 *cx = to_cx18(v4l2_dev); 335 int ret; 336 337 ret = snd_pcm_new(sc, "CX23418 PCM", 338 0, /* PCM device 0, the only one for this card */ 339 0, /* 0 playback substreams */ 340 1, /* 1 capture substream */ 341 &sp); 342 if (ret) { 343 CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", 344 __func__, ret); 345 goto err_exit; 346 } 347 348 spin_lock_init(&cxsc->slock); 349 350 snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, 351 &snd_cx18_pcm_capture_ops); 352 sp->info_flags = 0; 353 sp->private_data = cxsc; 354 strlcpy(sp->name, cx->card_name, sizeof(sp->name)); 355 356 return 0; 357 358err_exit: 359 return ret; 360} 361