Skip to content

Commit

Permalink
V4L/DVB: cx18: overhaul ALSA PCM device handling so it works
Browse files Browse the repository at this point in the history
Add code so that the PCM ALSA device actually works, and update the
cx18-streams mechanism so that it passes the data off to the cx18-alsa module.

This work was sponsored by ONELAN Limited.

Signed-off-by: Devin Heitmueller <dheitmueller@kernellabs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
  • Loading branch information
Devin Heitmueller authored and Mauro Carvalho Chehab committed Feb 26, 2010
1 parent 8ef22f7 commit 9972de9
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 7 deletions.
243 changes: 241 additions & 2 deletions drivers/media/video/cx18/cx18-alsa-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@radix.net>
* Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com>
*
* Portions of this work were sponsored by ONELAN Limited.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -29,20 +32,121 @@
#include <sound/pcm.h>

#include "cx18-driver.h"
#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-fileops.h"
#include "cx18-alsa.h"

extern int cx18_alsa_debug;

#define dprintk(fmt, arg...) do { \
if (cx18_alsa_debug) \
printk(KERN_INFO "cx18-alsa %s: " fmt, \
__func__, ##arg); \
} while (0)

/* Forward Declaration */
void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data,
size_t num_bytes);

static struct snd_pcm_hardware snd_cx18_hw_capture = {
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID,

.formats = SNDRV_PCM_FMTBIT_S16_LE,

.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,

.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */
.period_bytes_min = 64, /* 12544/2, */
.period_bytes_max = 12544,
.periods_min = 2,
.periods_max = 98, /* 12544, */
};

static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
struct snd_card *sc = cxsc->sc;
struct cx18 *cx = to_cx18(v4l2_dev);
struct cx18_stream *s;
struct cx18_open_id *item;
int ret;

/* Instruct the cx18 to start sending packets */
s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];

/* Allocate memory */
item = kmalloc(sizeof(struct cx18_open_id), GFP_KERNEL);
if (NULL == item) {
CX18_DEBUG_WARN("nomem on v4l2 open\n");
return -ENOMEM;
}
item->cx = cx;
item->type = s->type;
item->open_id = cx->open_id++;

/* See if the stream is available */
if (cx18_claim_stream(item, item->type)) {
/* No, it's already in use */
return -EBUSY;
}

if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||
test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {
/* We're already streaming. No additional action required */
return 0;
}


runtime->hw = snd_cx18_hw_capture;
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
cxsc->capture_pcm_substream = substream;
runtime->private_data = cx;

cx->pcm_announce_callback = cx18_alsa_announce_pcm_data;

/* Not currently streaming, so start it up */
set_bit(CX18_F_S_STREAMING, &s->s_flags);
ret = cx18_start_v4l2_encode_stream(s);

return 0;
}

static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream)
{
unsigned long flags;
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
struct cx18 *cx = to_cx18(v4l2_dev);
struct cx18_stream *s;
int ret;

/* Instruct the cx18 to stop sending packets */
s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];
ret = cx18_stop_v4l2_encode_stream(s, 0);
clear_bit(CX18_F_S_STREAMING, &s->s_flags);

cx18_release_stream(s);

cx->pcm_announce_callback = NULL;

spin_lock_irqsave(&cxsc->slock, flags);
if (substream->runtime->dma_area) {
dprintk("freeing pcm capture region\n");
vfree(substream->runtime->dma_area);
substream->runtime->dma_area = NULL;
}
spin_unlock_irqrestore(&cxsc->slock, flags);

return 0;
}

Expand All @@ -52,9 +156,37 @@ static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream,
return snd_pcm_lib_ioctl(substream, cmd, arg);
}


static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
size_t size)
{
struct snd_pcm_runtime *runtime = subs->runtime;

dprintk("Allocating vbuffer\n");
if (runtime->dma_area) {
if (runtime->dma_bytes > size)
return 0;

vfree(runtime->dma_area);
}
runtime->dma_area = vmalloc(size);
if (!runtime->dma_area)
return -ENOMEM;

runtime->dma_bytes = size;

return 0;
}

static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
int ret;

dprintk("%s called\n", __func__);

ret = snd_pcm_alloc_vmalloc_buffer(substream,
params_buffer_bytes(params));
return 0;
}

Expand All @@ -65,6 +197,11 @@ static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream)

static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);

cxsc->hwptr_done_capture = 0;
cxsc->capture_transfer_done = 0;

return 0;
}

Expand All @@ -76,7 +213,24 @@ static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
static
snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream)
{
return 0;
unsigned long flags;
snd_pcm_uframes_t hwptr_done;
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);

spin_lock_irqsave(&cxsc->slock, flags);
hwptr_done = cxsc->hwptr_done_capture;
spin_unlock_irqrestore(&cxsc->slock, flags);

return hwptr_done;
}


static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
unsigned long offset)
{
void *pageptr = subs->runtime->dma_area + offset;

return vmalloc_to_page(pageptr);
}

static struct snd_pcm_ops snd_cx18_pcm_capture_ops = {
Expand All @@ -88,9 +242,86 @@ static struct snd_pcm_ops snd_cx18_pcm_capture_ops = {
.prepare = snd_cx18_pcm_prepare,
.trigger = snd_cx18_pcm_trigger,
.pointer = snd_cx18_pcm_pointer,
.page = snd_pcm_get_vmalloc_page,
};

int __init snd_cx18_pcm_create(struct snd_cx18_card *cxsc)
void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data,
size_t num_bytes)
{
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned int oldptr;
unsigned int stride;
int period_elapsed = 0;
int length;

dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%d\n", cxsc,
pcm_data, num_bytes);

substream = cxsc->capture_pcm_substream;
if (substream == NULL) {
dprintk("substream was NULL\n");
return;
}

runtime = substream->runtime;
if (runtime == NULL) {
dprintk("runtime was NULL\n");
return;
}

stride = runtime->frame_bits >> 3;
if (stride == 0) {
dprintk("stride is zero\n");
return;
}

length = num_bytes / stride;
if (length == 0) {
dprintk("%s: length was zero\n", __func__);
return;
}

if (runtime->dma_area == NULL) {
dprintk("dma area was NULL - ignoring\n");
return;
}

oldptr = cxsc->hwptr_done_capture;
if (oldptr + length >= runtime->buffer_size) {
unsigned int cnt =
runtime->buffer_size - oldptr;
memcpy(runtime->dma_area + oldptr * stride, pcm_data,
cnt * stride);
memcpy(runtime->dma_area, pcm_data + cnt * stride,
length * stride - cnt * stride);
} else {
memcpy(runtime->dma_area + oldptr * stride, pcm_data,
length * stride);
}
snd_pcm_stream_lock(substream);

cxsc->hwptr_done_capture += length;
if (cxsc->hwptr_done_capture >=
runtime->buffer_size)
cxsc->hwptr_done_capture -=
runtime->buffer_size;

cxsc->capture_transfer_done += length;
if (cxsc->capture_transfer_done >=
runtime->period_size) {
cxsc->capture_transfer_done -=
runtime->period_size;
period_elapsed = 1;
}

snd_pcm_stream_unlock(substream);

if (period_elapsed)
snd_pcm_period_elapsed(substream);
}

int snd_cx18_pcm_create(struct snd_cx18_card *cxsc)
{
struct snd_pcm *sp;
struct snd_card *sc = cxsc->sc;
Expand All @@ -108,6 +339,14 @@ int __init snd_cx18_pcm_create(struct snd_cx18_card *cxsc)
__func__, ret);
goto err_exit;
}

spin_lock_init(&cxsc->slock);

snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, &snd_cx18_pcm_capture_ops);
sp->info_flags = 0;
sp->private_data = cxsc;
strcpy(sp->name, "Conexant cx23418 Capture");

return 0;

err_exit:
Expand Down
4 changes: 4 additions & 0 deletions drivers/media/video/cx18/cx18-alsa-pcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@
*/

int __init snd_cx18_pcm_create(struct snd_cx18_card *cxsc);

/* Used by cx18-mailbox to announce the PCM data to the module */
void cx18_alsa_announce_pcm_data(struct snd_cx18_card *card, u8 *pcm_data,
size_t num_bytes);
4 changes: 4 additions & 0 deletions drivers/media/video/cx18/cx18-alsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ struct snd_card;
struct snd_cx18_card {
struct v4l2_device *v4l2_dev;
struct snd_card *sc;
unsigned int capture_transfer_done;
unsigned int hwptr_done_capture;
struct snd_pcm_substream *capture_pcm_substream;
spinlock_t slock;
};

extern int cx18_alsa_debug;
Expand Down
3 changes: 3 additions & 0 deletions drivers/media/video/cx18/cx18-driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,9 @@ struct cx18 {
int stream_buf_size[CX18_MAX_STREAMS]; /* Stream buffer size */
struct cx18_stream streams[CX18_MAX_STREAMS]; /* Stream data */
struct snd_cx18_card *alsa; /* ALSA interface for PCM capture stream */
void (*pcm_announce_callback)(struct snd_cx18_card *card, u8 *pcm_data,
size_t num_bytes);

unsigned long i_flags; /* global cx18 flags */
atomic_t ana_capturing; /* count number of active analog capture streams */
atomic_t tot_capturing; /* total count number of active capture streams */
Expand Down
Loading

0 comments on commit 9972de9

Please sign in to comment.