linux/drivers/media/pci/cx18/cx18-alsa-pcm.c
<<
>>
Prefs
   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
  21#include <linux/init.h>
  22#include <linux/kernel.h>
  23#include <linux/vmalloc.h>
  24
  25#include <media/v4l2-device.h>
  26
  27#include <sound/core.h>
  28#include <sound/pcm.h>
  29
  30#include "cx18-driver.h"
  31#include "cx18-queue.h"
  32#include "cx18-streams.h"
  33#include "cx18-fileops.h"
  34#include "cx18-alsa.h"
  35#include "cx18-alsa-pcm.h"
  36
  37static unsigned int pcm_debug;
  38module_param(pcm_debug, int, 0644);
  39MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm");
  40
  41#define dprintk(fmt, arg...) do {                                       \
  42            if (pcm_debug)                                              \
  43                printk(KERN_INFO "cx18-alsa-pcm %s: " fmt,              \
  44                                  __func__, ##arg);                     \
  45        } while (0)
  46
  47static struct snd_pcm_hardware snd_cx18_hw_capture = {
  48        .info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
  49                SNDRV_PCM_INFO_MMAP           |
  50                SNDRV_PCM_INFO_INTERLEAVED    |
  51                SNDRV_PCM_INFO_MMAP_VALID,
  52
  53        .formats = SNDRV_PCM_FMTBIT_S16_LE,
  54
  55        .rates = SNDRV_PCM_RATE_48000,
  56
  57        .rate_min = 48000,
  58        .rate_max = 48000,
  59        .channels_min = 2,
  60        .channels_max = 2,
  61        .buffer_bytes_max = 62720 * 8,  /* just about the value in usbaudio.c */
  62        .period_bytes_min = 64,         /* 12544/2, */
  63        .period_bytes_max = 12544,
  64        .periods_min = 2,
  65        .periods_max = 98,              /* 12544, */
  66};
  67
  68void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data,
  69                                 size_t num_bytes)
  70{
  71        struct snd_pcm_substream *substream;
  72        struct snd_pcm_runtime *runtime;
  73        unsigned int oldptr;
  74        unsigned int stride;
  75        int period_elapsed = 0;
  76        int length;
  77
  78        dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zu\n", cxsc,
  79                pcm_data, num_bytes);
  80
  81        substream = cxsc->capture_pcm_substream;
  82        if (substream == NULL) {
  83                dprintk("substream was NULL\n");
  84                return;
  85        }
  86
  87        runtime = substream->runtime;
  88        if (runtime == NULL) {
  89                dprintk("runtime was NULL\n");
  90                return;
  91        }
  92
  93        stride = runtime->frame_bits >> 3;
  94        if (stride == 0) {
  95                dprintk("stride is zero\n");
  96                return;
  97        }
  98
  99        length = num_bytes / stride;
 100        if (length == 0) {
 101                dprintk("%s: length was zero\n", __func__);
 102                return;
 103        }
 104
 105        if (runtime->dma_area == NULL) {
 106                dprintk("dma area was NULL - ignoring\n");
 107                return;
 108        }
 109
 110        oldptr = cxsc->hwptr_done_capture;
 111        if (oldptr + length >= runtime->buffer_size) {
 112                unsigned int cnt =
 113                        runtime->buffer_size - oldptr;
 114                memcpy(runtime->dma_area + oldptr * stride, pcm_data,
 115                       cnt * stride);
 116                memcpy(runtime->dma_area, pcm_data + cnt * stride,
 117                       length * stride - cnt * stride);
 118        } else {
 119                memcpy(runtime->dma_area + oldptr * stride, pcm_data,
 120                       length * stride);
 121        }
 122        snd_pcm_stream_lock(substream);
 123
 124        cxsc->hwptr_done_capture += length;
 125        if (cxsc->hwptr_done_capture >=
 126            runtime->buffer_size)
 127                cxsc->hwptr_done_capture -=
 128                        runtime->buffer_size;
 129
 130        cxsc->capture_transfer_done += length;
 131        if (cxsc->capture_transfer_done >=
 132            runtime->period_size) {
 133                cxsc->capture_transfer_done -=
 134                        runtime->period_size;
 135                period_elapsed = 1;
 136        }
 137
 138        snd_pcm_stream_unlock(substream);
 139
 140        if (period_elapsed)
 141                snd_pcm_period_elapsed(substream);
 142}
 143
 144static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream)
 145{
 146        struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
 147        struct snd_pcm_runtime *runtime = substream->runtime;
 148        struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
 149        struct cx18 *cx = to_cx18(v4l2_dev);
 150        struct cx18_stream *s;
 151        struct cx18_open_id item;
 152        int ret;
 153
 154        /* Instruct the cx18 to start sending packets */
 155        snd_cx18_lock(cxsc);
 156        s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];
 157
 158        item.cx = cx;
 159        item.type = s->type;
 160        item.open_id = cx->open_id++;
 161
 162        /* See if the stream is available */
 163        if (cx18_claim_stream(&item, item.type)) {
 164                /* No, it's already in use */
 165                snd_cx18_unlock(cxsc);
 166                return -EBUSY;
 167        }
 168
 169        if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||
 170            test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {
 171                /* We're already streaming.  No additional action required */
 172                snd_cx18_unlock(cxsc);
 173                return 0;
 174        }
 175
 176
 177        runtime->hw = snd_cx18_hw_capture;
 178        snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 179        cxsc->capture_pcm_substream = substream;
 180        runtime->private_data = cx;
 181
 182        cx->pcm_announce_callback = cx18_alsa_announce_pcm_data;
 183
 184        /* Not currently streaming, so start it up */
 185        set_bit(CX18_F_S_STREAMING, &s->s_flags);
 186        ret = cx18_start_v4l2_encode_stream(s);
 187        snd_cx18_unlock(cxsc);
 188
 189        return ret;
 190}
 191
 192static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream)
 193{
 194        struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
 195        struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
 196        struct cx18 *cx = to_cx18(v4l2_dev);
 197        struct cx18_stream *s;
 198
 199        /* Instruct the cx18 to stop sending packets */
 200        snd_cx18_lock(cxsc);
 201        s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];
 202        cx18_stop_v4l2_encode_stream(s, 0);
 203        clear_bit(CX18_F_S_STREAMING, &s->s_flags);
 204
 205        cx18_release_stream(s);
 206
 207        cx->pcm_announce_callback = NULL;
 208        snd_cx18_unlock(cxsc);
 209
 210        return 0;
 211}
 212
 213static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream,
 214                     unsigned int cmd, void *arg)
 215{
 216        struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
 217        int ret;
 218
 219        snd_cx18_lock(cxsc);
 220        ret = snd_pcm_lib_ioctl(substream, cmd, arg);
 221        snd_cx18_unlock(cxsc);
 222        return ret;
 223}
 224
 225
 226static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
 227                                        size_t size)
 228{
 229        struct snd_pcm_runtime *runtime = subs->runtime;
 230
 231        dprintk("Allocating vbuffer\n");
 232        if (runtime->dma_area) {
 233                if (runtime->dma_bytes > size)
 234                        return 0;
 235
 236                vfree(runtime->dma_area);
 237        }
 238        runtime->dma_area = vmalloc(size);
 239        if (!runtime->dma_area)
 240                return -ENOMEM;
 241
 242        runtime->dma_bytes = size;
 243
 244        return 0;
 245}
 246
 247static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream,
 248                         struct snd_pcm_hw_params *params)
 249{
 250        dprintk("%s called\n", __func__);
 251
 252        return snd_pcm_alloc_vmalloc_buffer(substream,
 253                                           params_buffer_bytes(params));
 254}
 255
 256static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream)
 257{
 258        struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
 259        unsigned long flags;
 260        unsigned char *dma_area = NULL;
 261
 262        spin_lock_irqsave(&cxsc->slock, flags);
 263        if (substream->runtime->dma_area) {
 264                dprintk("freeing pcm capture region\n");
 265                dma_area = substream->runtime->dma_area;
 266                substream->runtime->dma_area = NULL;
 267        }
 268        spin_unlock_irqrestore(&cxsc->slock, flags);
 269        vfree(dma_area);
 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 const 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