diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-02-26 17:16:20 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-02-26 17:16:20 -0800 |
commit | 2b8c70b2174402ca3dec13310ce56597233392d7 (patch) | |
tree | 0aed464521a2a671cbb7b4302b55fe72abc95d3d /drivers/media/video/cx18 | |
parent | 29e1fa3565a7951cc415c634eb2b78dbdbee151d (diff) | |
parent | 3621263a4d9679726b7bc1e2546c1c03941a59b4 (diff) |
Merge branch 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-2.6
* 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-2.6: (362 commits)
V4L-DVB: cx88-dvb: remove extra attribution for core
V4L/DVB: v4l: soc_camera: fix bound checking of mbus_fmt[] index
V4L/DVB: Add support for SMT7020 to cx88
V4L/DVB: radio-si470x: Use UTF-8 encoding on a comment
V4L/DVB: MAINTAINERS: Telegent tlg2300 section fix
V4L/DVB: gspca_stv06xx: Add support for camera button
V4L/DVB: gspca_ov519: add support for the button on ov511 based cams
V4L/DVB: gspca_ov519: Add support for the button on ov518 based cams
V4L/DVB: gspca_ov519: add support for the button on ov519 based cams
V4L/DVB: gspca_main: Fix a compile error when CONFIG_INPUT is not set
V4L/DVB: gspca_main: some input error handling fixes
V4L/DVB: gspca_main: Allow use of input device creation code for non int. inputs
V4L/DVB: gspca_pac7302: much improved exposure control
V4L/DVB: gspca_sonixb: Make sonixb driver handle pas106 and pas202 cameras
V4L/DVB: gspca_sonixb: pas106: fixup bright ctrl and add gain and exposure ctrls
V4L/DVB: Documentation: gspca.txt: update known mr97310a cams
V4L/DVB: gspca_mr97310a: add support for the Sakar 1638x CyberPix
V4L/DVB: gscpa_sonixb: limit ov7630 max framerate at 640x480
V4L/DVB: gspca_sonixb: pas202: fixup brightness ctrl and add gain and exposure ctrls
V4L/DVB: gscpa_sonixb: Differentiate between sensors with a coarse and fine expo ctrl
...
Diffstat (limited to 'drivers/media/video/cx18')
21 files changed, 1449 insertions, 133 deletions
diff --git a/drivers/media/video/cx18/Kconfig b/drivers/media/video/cx18/Kconfig index e8a50a611eb..baf7e91ee0f 100644 --- a/drivers/media/video/cx18/Kconfig +++ b/drivers/media/video/cx18/Kconfig @@ -19,3 +19,14 @@ config VIDEO_CX18 To compile this driver as a module, choose M here: the module will be called cx18. + +config VIDEO_CX18_ALSA + tristate "Conexant 23418 DMA audio support" + depends on VIDEO_CX18 && SND && EXPERIMENTAL + select SND_PCM + ---help--- + This is a video4linux driver for direct (DMA) audio on + Conexant 23418 based TV cards using ALSA. + + To compile this driver as a module, choose M here: the + module will be called cx18-alsa. diff --git a/drivers/media/video/cx18/Makefile b/drivers/media/video/cx18/Makefile index f7bf0edf93f..2fadd9ded34 100644 --- a/drivers/media/video/cx18/Makefile +++ b/drivers/media/video/cx18/Makefile @@ -3,8 +3,10 @@ cx18-objs := cx18-driver.o cx18-cards.o cx18-i2c.o cx18-firmware.o cx18-gpio. cx18-mailbox.o cx18-vbi.o cx18-audio.o cx18-video.o cx18-irq.o \ cx18-av-core.o cx18-av-audio.o cx18-av-firmware.o cx18-av-vbi.o cx18-scb.o \ cx18-dvb.o cx18-io.o +cx18-alsa-objs := cx18-alsa-main.o cx18-alsa-pcm.o obj-$(CONFIG_VIDEO_CX18) += cx18.o +obj-$(CONFIG_VIDEO_CX18_ALSA) += cx18-alsa.o EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core EXTRA_CFLAGS += -Idrivers/media/dvb/frontends diff --git a/drivers/media/video/cx18/cx18-alsa-main.c b/drivers/media/video/cx18/cx18-alsa-main.c new file mode 100644 index 00000000000..eb41d7ec65b --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-main.c @@ -0,0 +1,293 @@ +/* + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/spinlock.h> + +#include <media/v4l2-device.h> + +#include <sound/core.h> +#include <sound/initval.h> + +#include "cx18-driver.h" +#include "cx18-version.h" +#include "cx18-alsa.h" +#include "cx18-alsa-mixer.h" +#include "cx18-alsa-pcm.h" + +int cx18_alsa_debug; + +#define CX18_DEBUG_ALSA_INFO(fmt, arg...) \ + do { \ + if (cx18_alsa_debug & 2) \ + printk(KERN_INFO "%s: " fmt, "cx18-alsa", ## arg); \ + } while (0); + +module_param_named(debug, cx18_alsa_debug, int, 0644); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n"); + +MODULE_AUTHOR("Andy Walls"); +MODULE_DESCRIPTION("CX23418 ALSA Interface"); +MODULE_SUPPORTED_DEVICE("CX23418 MPEG2 encoder"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(CX18_VERSION); + +static inline +struct snd_cx18_card *to_snd_cx18_card(struct v4l2_device *v4l2_dev) +{ + return to_cx18(v4l2_dev)->alsa; +} + +static inline +struct snd_cx18_card *p_to_snd_cx18_card(struct v4l2_device **v4l2_dev) +{ + return container_of(v4l2_dev, struct snd_cx18_card, v4l2_dev); +} + +static void snd_cx18_card_free(struct snd_cx18_card *cxsc) +{ + if (cxsc == NULL) + return; + + if (cxsc->v4l2_dev != NULL) + to_cx18(cxsc->v4l2_dev)->alsa = NULL; + + /* FIXME - take any other stopping actions needed */ + + kfree(cxsc); +} + +static void snd_cx18_card_private_free(struct snd_card *sc) +{ + if (sc == NULL) + return; + snd_cx18_card_free(sc->private_data); + sc->private_data = NULL; + sc->private_free = NULL; +} + +static int snd_cx18_card_create(struct v4l2_device *v4l2_dev, + struct snd_card *sc, + struct snd_cx18_card **cxsc) +{ + *cxsc = kzalloc(sizeof(struct snd_cx18_card), GFP_KERNEL); + if (*cxsc == NULL) + return -ENOMEM; + + (*cxsc)->v4l2_dev = v4l2_dev; + (*cxsc)->sc = sc; + + sc->private_data = *cxsc; + sc->private_free = snd_cx18_card_private_free; + + return 0; +} + +static int snd_cx18_card_set_names(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + struct snd_card *sc = cxsc->sc; + + /* sc->driver is used by alsa-lib's configurator: simple, unique */ + strlcpy(sc->driver, "CX23418", sizeof(sc->driver)); + + /* sc->shortname is a symlink in /proc/asound: CX18-M -> cardN */ + snprintf(sc->shortname, sizeof(sc->shortname), "CX18-%d", + cx->instance); + + /* sc->longname is read from /proc/asound/cards */ + snprintf(sc->longname, sizeof(sc->longname), + "CX23418 #%d %s TV/FM Radio/Line-In Capture", + cx->instance, cx->card_name); + + return 0; +} + +static int snd_cx18_init(struct v4l2_device *v4l2_dev) +{ + struct cx18 *cx = to_cx18(v4l2_dev); + struct snd_card *sc = NULL; + struct snd_cx18_card *cxsc; + int ret; + + /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ + + /* (1) Check and increment the device index */ + /* This is a no-op for us. We'll use the cx->instance */ + + /* (2) Create a card instance */ + ret = snd_card_create(SNDRV_DEFAULT_IDX1, /* use first available id */ + SNDRV_DEFAULT_STR1, /* xid from end of shortname*/ + THIS_MODULE, 0, &sc); + if (ret) { + CX18_ALSA_ERR("%s: snd_card_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + /* (3) Create a main component */ + ret = snd_cx18_card_create(v4l2_dev, sc, &cxsc); + if (ret) { + CX18_ALSA_ERR("%s: snd_cx18_card_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + /* (4) Set the driver ID and name strings */ + snd_cx18_card_set_names(cxsc); + + + ret = snd_cx18_pcm_create(cxsc); + if (ret) { + CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + /* FIXME - proc files */ + + /* (7) Set the driver data and return 0 */ + /* We do this out of normal order for PCI drivers to avoid races */ + cx->alsa = cxsc; + + /* (6) Register the card instance */ + ret = snd_card_register(sc); + if (ret) { + cx->alsa = NULL; + CX18_ALSA_ERR("%s: snd_card_register() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + return 0; + +err_exit_free: + if (sc != NULL) + snd_card_free(sc); +err_exit: + return ret; +} + +int cx18_alsa_load(struct cx18 *cx) +{ + struct v4l2_device *v4l2_dev = &cx->v4l2_dev; + struct cx18_stream *s; + + if (v4l2_dev == NULL) { + printk(KERN_ERR "cx18-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + cx = to_cx18(v4l2_dev); + if (cx == NULL) { + printk(KERN_ERR "cx18-alsa cx is NULL\n"); + return 0; + } + + s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; + if (s->video_dev == NULL) { + CX18_DEBUG_ALSA_INFO("%s: PCM stream for card is disabled - " + "skipping\n", __func__); + return 0; + } + + if (cx->alsa != NULL) { + CX18_ALSA_ERR("%s: struct snd_cx18_card * already exists\n", + __func__); + return 0; + } + + if (snd_cx18_init(v4l2_dev)) { + CX18_ALSA_ERR("%s: failed to create struct snd_cx18_card\n", + __func__); + } else { + CX18_DEBUG_ALSA_INFO("%s: created cx18 ALSA interface instance " + "\n", __func__); + } + return 0; +} + +static int __init cx18_alsa_init(void) +{ + printk(KERN_INFO "cx18-alsa: module loading...\n"); + cx18_ext_init = &cx18_alsa_load; + return 0; +} + +static void __exit snd_cx18_exit(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + + /* FIXME - pointer checks & shutdown cxsc */ + + snd_card_free(cxsc->sc); + cx->alsa = NULL; +} + +static int __exit cx18_alsa_exit_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct snd_cx18_card *cxsc; + + if (v4l2_dev == NULL) { + printk(KERN_ERR "cx18-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + cxsc = to_snd_cx18_card(v4l2_dev); + if (cxsc == NULL) { + CX18_ALSA_WARN("%s: struct snd_cx18_card * is NULL\n", + __func__); + return 0; + } + + snd_cx18_exit(cxsc); + return 0; +} + +static void __exit cx18_alsa_exit(void) +{ + struct device_driver *drv; + int ret; + + printk(KERN_INFO "cx18-alsa: module unloading...\n"); + + drv = driver_find("cx18", &pci_bus_type); + ret = driver_for_each_device(drv, NULL, NULL, cx18_alsa_exit_callback); + put_driver(drv); + + cx18_ext_init = NULL; + printk(KERN_INFO "cx18-alsa: module unload complete\n"); +} + +module_init(cx18_alsa_init); +module_exit(cx18_alsa_exit); diff --git a/drivers/media/video/cx18/cx18-alsa-mixer.c b/drivers/media/video/cx18/cx18-alsa-mixer.c new file mode 100644 index 00000000000..ef21114309f --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-mixer.c @@ -0,0 +1,175 @@ +/* + * ALSA mixer controls for the + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls <awalls@radix.net> + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/videodev2.h> + +#include <media/v4l2-device.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "cx18-alsa.h" +#include "cx18-driver.h" + +/* + * Note the cx18-av-core volume scale is funny, due to the alignment of the + * scale with another chip's range: + * + * v4l2_control value /512 indicated dB actual dB reg 0x8d4 + * 0x0000 - 0x01ff 0 -119 -96 228 + * 0x0200 - 0x02ff 1 -118 -96 228 + * ... + * 0x2c00 - 0x2dff 22 -97 -96 228 + * 0x2e00 - 0x2fff 23 -96 -96 228 + * 0x3000 - 0x31ff 24 -95 -95 226 + * ... + * 0xee00 - 0xefff 119 0 0 36 + * ... + * 0xfe00 - 0xffff 127 +8 +8 20 + */ +static inline int dB_to_cx18_av_vol(int dB) +{ + if (dB < -96) + dB = -96; + else if (dB > 8) + dB = 8; + return (dB + 119) << 9; +} + +static inline int cx18_av_vol_to_dB(int v) +{ + if (v < (23 << 9)) + v = (23 << 9); + else if (v > (127 << 9)) + v = (127 << 9); + return (v >> 9) - 119; +} + +static int snd_cx18_mixer_tv_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + /* We're already translating values, just keep this control in dB */ + uinfo->value.integer.min = -96; + uinfo->value.integer.max = 8; + uinfo->value.integer.step = 1; + return 0; +} + +static int snd_cx18_mixer_tv_vol_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_cx18_card *cxsc = snd_kcontrol_chip(kctl); + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + struct v4l2_control vctrl; + int ret; + + vctrl.id = V4L2_CID_AUDIO_VOLUME; + vctrl.value = dB_to_cx18_av_vol(uctl->value.integer.value[0]); + + snd_cx18_lock(cxsc); + ret = v4l2_subdev_call(cx->sd_av, core, g_ctrl, &vctrl); + snd_cx18_unlock(cxsc); + + if (!ret) + uctl->value.integer.value[0] = cx18_av_vol_to_dB(vctrl.value); + return ret; +} + +static int snd_cx18_mixer_tv_vol_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_cx18_card *cxsc = snd_kcontrol_chip(kctl); + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + struct v4l2_control vctrl; + int ret; + + vctrl.id = V4L2_CID_AUDIO_VOLUME; + vctrl.value = dB_to_cx18_av_vol(uctl->value.integer.value[0]); + + snd_cx18_lock(cxsc); + + /* Fetch current state */ + ret = v4l2_subdev_call(cx->sd_av, core, g_ctrl, &vctrl); + + if (ret || + (cx18_av_vol_to_dB(vctrl.value) != uctl->value.integer.value[0])) { + + /* Set, if needed */ + vctrl.value = dB_to_cx18_av_vol(uctl->value.integer.value[0]); + ret = v4l2_subdev_call(cx->sd_av, core, s_ctrl, &vctrl); + if (!ret) + ret = 1; /* Indicate control was changed w/o error */ + } + snd_cx18_unlock(cxsc); + + return ret; +} + + +/* This is a bit of overkill, the slider is already in dB internally */ +static DECLARE_TLV_DB_SCALE(snd_cx18_mixer_tv_vol_db_scale, -9600, 100, 0); + +static struct snd_kcontrol_new snd_cx18_mixer_tv_vol __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog TV Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_cx18_mixer_tv_volume_info, + .get = snd_cx18_mixer_tv_volume_get, + .put = snd_cx18_mixer_tv_volume_put, + .tlv.p = snd_cx18_mixer_tv_vol_db_scale +}; + +/* FIXME - add mute switch and balance, bass, treble sliders: + V4L2_CID_AUDIO_MUTE + + V4L2_CID_AUDIO_BALANCE + + V4L2_CID_AUDIO_BASS + V4L2_CID_AUDIO_TREBLE +*/ + +/* FIXME - add stereo, lang1, lang2, mono menu */ +/* FIXME - add CS5345 I2S volume for HVR-1600 */ + +int __init snd_cx18_mixer_create(struct snd_cx18_card *cxsc) +{ + struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; + struct snd_card *sc = cxsc->sc; + int ret; + + strlcpy(sc->mixername, "CX23418 Mixer", sizeof(sc->mixername)); + + ret = snd_ctl_add(sc, snd_ctl_new1(snd_cx18_mixer_tv_vol, cxsc)); + if (ret) { + CX18_ALSA_WARN("%s: failed to add %s control, err %d\n", + __func__, snd_cx18_mixer_tv_vol.name, ret); + } + return ret; +} diff --git a/drivers/media/video/cx18/cx18-alsa-mixer.h b/drivers/media/video/cx18/cx18-alsa-mixer.h new file mode 100644 index 00000000000..2d418db000f --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-mixer.h @@ -0,0 +1,23 @@ +/* + * ALSA mixer controls for the + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls <awalls@radix.net> + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +int __init snd_cx18_mixer_create(struct snd_cx18_card *cxsc); diff --git a/drivers/media/video/cx18/cx18-alsa-pcm.c b/drivers/media/video/cx18/cx18-alsa-pcm.c new file mode 100644 index 00000000000..2bd312daeb1 --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-pcm.c @@ -0,0 +1,354 @@ +/* + * ALSA PCM device for the + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/vmalloc.h> + +#include <media/v4l2-device.h> + +#include <sound/core.h> +#include <sound/pcm.h> + +#include "cx18-driver.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-fileops.h" +#include "cx18-alsa.h" + +static unsigned int pcm_debug; +module_param(pcm_debug, int, 0644); +MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); + +#define dprintk(fmt, arg...) do { \ + if (pcm_debug) \ + printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \ + __func__, ##arg); \ + } while (0) + +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_48000, + + .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, */ +}; + +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=%zd\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); +} + +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 cx18 *cx = to_cx18(v4l2_dev); + struct cx18_stream *s; + struct cx18_open_id item; + int ret; + + /* Instruct the cx18 to start sending packets */ + snd_cx18_lock(cxsc); + s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM]; + + 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 */ + snd_cx18_unlock(cxsc); + 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 */ + snd_cx18_unlock(cxsc); + 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); + snd_cx18_unlock(cxsc); + + return 0; +} + +static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream) +{ + 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 */ + snd_cx18_lock(cxsc); + 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; + snd_cx18_unlock(cxsc); + + return 0; +} + +static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + 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; +} + +static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); + unsigned long flags; + + 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; +} + +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; +} + +static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return 0; +} + +static +snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream) +{ + 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 = { + .open = snd_cx18_pcm_capture_open, + .close = snd_cx18_pcm_capture_close, + .ioctl = snd_cx18_pcm_ioctl, + .hw_params = snd_cx18_pcm_hw_params, + .hw_free = snd_cx18_pcm_hw_free, + .prepare = snd_cx18_pcm_prepare, + .trigger = snd_cx18_pcm_trigger, + .pointer = snd_cx18_pcm_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + +int snd_cx18_pcm_create(struct snd_cx18_card *cxsc) +{ + struct snd_pcm *sp; + struct snd_card *sc = cxsc->sc; + struct v4l2_device *v4l2_dev = cxsc->v4l2_dev; + struct cx18 *cx = to_cx18(v4l2_dev); + int ret; + + ret = snd_pcm_new(sc, "CX23418 PCM", + 0, /* PCM device 0, the only one for this card */ + 0, /* 0 playback substreams */ + 1, /* 1 capture substream */ + &sp); + if (ret) { + CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n", + __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; + strlcpy(sp->name, cx->card_name, sizeof(sp->name)); + + return 0; + +err_exit: + return ret; +} diff --git a/drivers/media/video/cx18/cx18-alsa-pcm.h b/drivers/media/video/cx18/cx18-alsa-pcm.h new file mode 100644 index 00000000000..325662c647a --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa-pcm.h @@ -0,0 +1,27 @@ +/* + * ALSA PCM device for the + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls <awalls@radix.net> + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +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); diff --git a/drivers/media/video/cx18/cx18-alsa.h b/drivers/media/video/cx18/cx18-alsa.h new file mode 100644 index 00000000000..88a1cde7540 --- /dev/null +++ b/drivers/media/video/cx18/cx18-alsa.h @@ -0,0 +1,75 @@ +/* + * ALSA interface to cx18 PCM capture streams + * + * Copyright (C) 2009 Andy Walls <awalls@radix.net> + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +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; + +/* + * File operations that manipulate the encoder or video or audio subdevices + * need to be serialized. Use the same lock we use for v4l2 file ops. + */ +static inline void snd_cx18_lock(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + mutex_lock(&cx->serialize_lock); +} + +static inline void snd_cx18_unlock(struct snd_cx18_card *cxsc) +{ + struct cx18 *cx = to_cx18(cxsc->v4l2_dev); + mutex_unlock(&cx->serialize_lock); +} + +#define CX18_ALSA_DBGFLG_WARN (1 << 0) +#define CX18_ALSA_DBGFLG_WARN (1 << 0) +#define CX18_ALSA_DBGFLG_INFO (1 << 1) + +#define CX18_ALSA_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & cx18_alsa_debug) \ + printk(KERN_INFO "%s-alsa: " type ": " fmt, \ + v4l2_dev->name , ## args); \ + } while (0) + +#define CX18_ALSA_DEBUG_WARN(fmt, args...) \ + CX18_ALSA_DEBUG(CX18_ALSA_DBGFLG_WARN, "warning", fmt , ## args) + +#define CX18_ALSA_DEBUG_INFO(fmt, args...) \ + CX18_ALSA_DEBUG(CX18_ALSA_DBGFLG_INFO, "info", fmt , ## args) + +#define CX18_ALSA_ERR(fmt, args...) \ + printk(KERN_ERR "%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define CX18_ALSA_WARN(fmt, args...) \ + printk(KERN_WARNING "%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define CX18_ALSA_INFO(fmt, args...) \ + printk(KERN_INFO "%s-alsa: " fmt, v4l2_dev->name , ## args) diff --git a/drivers/media/video/cx18/cx18-cards.c b/drivers/media/video/cx18/cx18-cards.c index f11e47a5828..f808fb6fc1c 100644 --- a/drivers/media/video/cx18/cx18-cards.c +++ b/drivers/media/video/cx18/cx18-cards.c @@ -393,7 +393,7 @@ static const struct cx18_card cx18_card_leadtek_pvr2100 = { .gpio_init.direction = 0x7, .gpio_audio_input = { .mask = 0x7, .tuner = 0x6, .linein = 0x2, .radio = 0x2 }, - .xceive_pin = 15, + .xceive_pin = 1, .pci_list = cx18_pci_leadtek_pvr2100, .i2c = &cx18_i2c_std, }; diff --git a/drivers/media/video/cx18/cx18-driver.c b/drivers/media/video/cx18/cx18-driver.c index 7f65a47f12e..c95a86ba33b 100644 --- a/drivers/media/video/cx18/cx18-driver.c +++ b/drivers/media/video/cx18/cx18-driver.c @@ -47,6 +47,10 @@ setting this to 1 you ensure that radio0 is now also radio1. */ int cx18_first_minor; +/* Callback for registering extensions */ +int (*cx18_ext_init)(struct cx18 *); +EXPORT_SYMBOL(cx18_ext_init); + /* add your revision and whatnot here */ static struct pci_device_id cx18_pci_tbl[] __devinitdata = { {PCI_VENDOR_ID_CX, PCI_DEVICE_ID_CX23418, @@ -91,7 +95,7 @@ static int enc_pcm_bufsize = CX18_DEFAULT_ENC_PCM_BUFSIZE; static int enc_ts_bufs = -1; static int enc_mpg_bufs = -1; -static int enc_idx_bufs = -1; +static int enc_idx_bufs = CX18_MAX_FW_MDLS_PER_STREAM; static int enc_yuv_bufs = -1; static int enc_vbi_bufs = -1; static int enc_pcm_bufs = -1; @@ -196,14 +200,17 @@ MODULE_PARM_DESC(enc_mpg_bufs, "Number of encoder MPG buffers\n" "\t\t\tDefault is computed from other enc_mpg_* parameters"); MODULE_PARM_DESC(enc_idx_buffers, - "Encoder IDX buffer memory (MB). (enc_idx_bufs can override)\n" - "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_IDX_BUFFERS)); + "(Deprecated) Encoder IDX buffer memory (MB)\n" + "\t\t\tIgnored, except 0 disables IDX buffer allocations\n" + "\t\t\tDefault: 1 [Enabled]"); MODULE_PARM_DESC(enc_idx_bufsize, "Size of an encoder IDX buffer (kB)\n" - "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_IDX_BUFSIZE)); + "\t\t\tAllowed values are multiples of 1.5 kB rounded up\n" + "\t\t\t(multiples of size required for 64 index entries)\n" + "\t\t\tDefault: 2"); MODULE_PARM_DESC(enc_idx_bufs, "Number of encoder IDX buffers\n" - "\t\t\tDefault is computed from other enc_idx_* parameters"); + "\t\t\tDefault: " __stringify(CX18_MAX_FW_MDLS_PER_STREAM)); MODULE_PARM_DESC(enc_yuv_buffers, "Encoder YUV buffer memory (MB). (enc_yuv_bufs can override)\n" "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_YUV_BUFFERS)); @@ -231,7 +238,8 @@ MODULE_PARM_DESC(enc_pcm_bufs, "Number of encoder PCM buffers\n" "\t\t\tDefault is computed from other enc_pcm_* parameters"); -MODULE_PARM_DESC(cx18_first_minor, "Set device node number assigned to first card"); +MODULE_PARM_DESC(cx18_first_minor, + "Set device node number assigned to first card"); MODULE_AUTHOR("Hans Verkuil"); MODULE_DESCRIPTION("CX23418 driver"); @@ -240,6 +248,28 @@ MODULE_LICENSE("GPL"); MODULE_VERSION(CX18_VERSION); +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct cx18 *dev = container_of(work, struct cx18, request_module_wk); + + /* Make sure cx18-alsa module is loaded */ + request_module("cx18-alsa"); + + /* Initialize cx18-alsa for this instance of the cx18 device */ + if (cx18_ext_init != NULL) + cx18_ext_init(dev); +} + +static void request_modules(struct cx18 *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#endif /* CONFIG_MODULES */ + /* Generic utility functions */ int cx18_msleep_timeout(unsigned int msecs, int intr) { @@ -501,7 +531,12 @@ static void cx18_process_options(struct cx18 *cx) /* * YUV is a special case where the stream_buf_size needs to be * an integral multiple of 33.75 kB (storage for 32 screens - * lines to maintain alignment in case of lost buffers + * lines to maintain alignment in case of lost buffers). + * + * IDX is a special case where the stream_buf_size should be + * an integral multiple of 1.5 kB (storage for 64 index entries + * to maintain alignment in case of lost buffers). + * */ if (i == CX18_ENC_STREAM_TYPE_YUV) { cx->stream_buf_size[i] *= 1024; @@ -511,15 +546,24 @@ static void cx18_process_options(struct cx18 *cx) if (cx->stream_buf_size[i] < CX18_UNIT_ENC_YUV_BUFSIZE) cx->stream_buf_size[i] = CX18_UNIT_ENC_YUV_BUFSIZE; + } else if (i == CX18_ENC_STREAM_TYPE_IDX) { + cx->stream_buf_size[i] *= 1024; + cx->stream_buf_size[i] -= + (cx->stream_buf_size[i] % CX18_UNIT_ENC_IDX_BUFSIZE); + + if (cx->stream_buf_size[i] < CX18_UNIT_ENC_IDX_BUFSIZE) + cx->stream_buf_size[i] = + CX18_UNIT_ENC_IDX_BUFSIZE; } /* - * YUV is a special case where the stream_buf_size is + * YUV and IDX are special cases where the stream_buf_size is * now in bytes. * VBI is a special case where the stream_buf_size is fixed * and already in bytes */ if (i == CX18_ENC_STREAM_TYPE_VBI || - i == CX18_ENC_STREAM_TYPE_YUV) { + i == CX18_ENC_STREAM_TYPE_YUV || + i == CX18_ENC_STREAM_TYPE_IDX) { if (cx->stream_buffers[i] < 0) { cx->stream_buffers[i] = cx->options.megabytes[i] * 1024 * 1024 @@ -1032,6 +1076,10 @@ static int __devinit cx18_probe(struct pci_dev *pci_dev, } CX18_INFO("Initialized card: %s\n", cx->card_name); + + /* Load cx18 submodules (cx18-alsa) */ + request_modules(cx); + return 0; free_streams: @@ -1220,6 +1268,7 @@ static void cx18_remove(struct pci_dev *pci_dev) kfree(cx); } + /* define a pci_driver for card detection */ static struct pci_driver cx18_pci_driver = { .name = "cx18", @@ -1230,7 +1279,8 @@ static struct pci_driver cx18_pci_driver = { static int __init module_start(void) { - printk(KERN_INFO "cx18: Start initialization, version %s\n", CX18_VERSION); + printk(KERN_INFO "cx18: Start initialization, version %s\n", + CX18_VERSION); /* Validate parameters */ if (cx18_first_minor < 0 || cx18_first_minor >= CX18_MAX_CARDS) { diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/video/cx18/cx18-driver.h index e3f7911a738..23ad6d548dc 100644 --- a/drivers/media/video/cx18/cx18-driver.h +++ b/drivers/media/video/cx18/cx18-driver.h @@ -126,10 +126,22 @@ #define CX18_625_LINE_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 576/32) #define CX18_525_LINE_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 480/32) +/* IDX buffer size should be a multiple of the index entry size from the chip */ +struct cx18_enc_idx_entry { + __le32 length; + __le32 offset_low; + __le32 offset_high; + __le32 flags; + __le32 pts_low; + __le32 pts_high; +} __attribute__ ((packed)); +#define CX18_UNIT_ENC_IDX_BUFSIZE \ + (sizeof(struct cx18_enc_idx_entry) * V4L2_ENC_IDX_ENTRIES) + /* DMA buffer, default size in kB allocated */ #define CX18_DEFAULT_ENC_TS_BUFSIZE 32 #define CX18_DEFAULT_ENC_MPG_BUFSIZE 32 -#define CX18_DEFAULT_ENC_IDX_BUFSIZE 32 +#define CX18_DEFAULT_ENC_IDX_BUFSIZE (CX18_UNIT_ENC_IDX_BUFSIZE * 1 / 1024 + 1) #define CX18_DEFAULT_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 3 / 1024 + 1) #define CX18_DEFAULT_ENC_PCM_BUFSIZE 4 @@ -234,16 +246,8 @@ #define CX18_WARN_DEV(dev, fmt, args...) v4l2_warn(dev, fmt , ## args) #define CX18_INFO_DEV(dev, fmt, args...) v4l2_info(dev, fmt , ## args) -/* Values for CX18_API_DEC_PLAYBACK_SPEED mpeg_frame_type_mask parameter: */ -#define MPEG_FRAME_TYPE_IFRAME 1 -#define MPEG_FRAME_TYPE_IFRAME_PFRAME 3 -#define MPEG_FRAME_TYPE_ALL 7 - -#define CX18_MAX_PGM_INDEX (400) - extern int cx18_debug; - struct cx18_options { int megabytes[CX18_MAX_STREAMS]; /* Size in megabytes of each stream */ int cardtype; /* force card type on load */ @@ -276,6 +280,18 @@ struct cx18_options { #define CX18_SLICED_TYPE_WSS_625 (5) #define CX18_SLICED_TYPE_VPS (7) +/** + * list_entry_is_past_end - check if a previous loop cursor is off list end + * @pos: the type * previously used as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Check if the entry's list_head is the head of the list, thus it's not a + * real entry but was the loop cursor that walked past the end + */ +#define list_entry_is_past_end(pos, head, member) \ + (&pos->member == (head)) + struct cx18_buffer { struct list_head list; dma_addr_t dma_handle; @@ -558,6 +574,10 @@ struct cx18 { int stream_buffers[CX18_MAX_STREAMS]; /* # of buffers for each stream */ 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 */ @@ -575,12 +595,6 @@ struct cx18 { struct vbi_info vbi; - u32 pgm_info_offset; - u32 pgm_info_num; - u32 pgm_info_write_idx; - u32 pgm_info_read_idx; - struct v4l2_enc_idx_entry pgm_info[CX18_MAX_PGM_INDEX]; - u64 mpg_data_received; u64 vbi_data_inserted; @@ -623,6 +637,9 @@ struct cx18 { u32 active_input; v4l2_std_id std; v4l2_std_id tuner_std; /* The norm of the tuner (fixed) */ + + /* Used for cx18-alsa module loading */ + struct work_struct request_module_wk; }; static inline struct cx18 *to_cx18(struct v4l2_device *v4l2_dev) @@ -630,6 +647,9 @@ static inline struct cx18 *to_cx18(struct v4l2_device *v4l2_dev) return container_of(v4l2_dev, struct cx18, v4l2_dev); } +/* cx18 extensions to be loaded */ +extern int (*cx18_ext_init)(struct cx18 *); + /* Globals */ extern int cx18_first_minor; diff --git a/drivers/media/video/cx18/cx18-dvb.c b/drivers/media/video/cx18/cx18-dvb.c index 71ad2d1b4c2..0ae2c2e1eab 100644 --- a/drivers/media/video/cx18/cx18-dvb.c +++ b/drivers/media/video/cx18/cx18-dvb.c @@ -213,10 +213,14 @@ static int cx18_dvb_start_feed(struct dvb_demux_feed *feed) { struct dvb_demux *demux = feed->demux; struct cx18_stream *stream = (struct cx18_stream *) demux->priv; - struct cx18 *cx = stream->cx; + struct cx18 *cx; int ret; u32 v; + if (!stream) + return -EINVAL; + + cx = stream->cx; CX18_DEBUG_INFO("Start feed: pid = 0x%x index = %d\n", feed->pid, feed->index); @@ -253,12 +257,10 @@ static int cx18_dvb_start_feed(struct dvb_demux_feed *feed) if (!demux->dmx.frontend) return -EINVAL; - if (!stream) - return -EINVAL; - mutex_lock(&stream->dvb.feedlock); if (stream->dvb.feeding++ == 0) { CX18_DEBUG_INFO("Starting Transport DMA\n"); + mutex_lock(&cx->serialize_lock); set_bit(CX18_F_S_STREAMING, &stream->s_flags); ret = cx18_start_v4l2_encode_stream(stream); if (ret < 0) { @@ -267,6 +269,7 @@ static int cx18_dvb_start_feed(struct dvb_demux_feed *feed) if (stream->dvb.feeding == 0) clear_bit(CX18_F_S_STREAMING, &stream->s_flags); } + mutex_unlock(&cx->serialize_lock); } else ret = 0; mutex_unlock(&stream->dvb.feedlock); @@ -279,17 +282,20 @@ static int cx18_dvb_stop_feed(struct dvb_demux_feed *feed) { struct dvb_demux *demux = feed->demux; struct cx18_stream *stream = (struct cx18_stream *)demux->priv; - struct cx18 *cx = stream->cx; + struct cx18 *cx; int ret = -EINVAL; - CX18_DEBUG_INFO("Stop feed: pid = 0x%x index = %d\n", - feed->pid, feed->index); - if (stream) { + cx = stream->cx; + CX18_DEBUG_INFO("Stop feed: pid = 0x%x index = %d\n", + feed->pid, feed->index); + mutex_lock(&stream->dvb.feedlock); if (--stream->dvb.feeding == 0) { CX18_DEBUG_INFO("Stopping Transport DMA\n"); + mutex_lock(&cx->serialize_lock); ret = cx18_stop_v4l2_encode_stream(stream, 0); + mutex_unlock(&cx->serialize_lock); } else ret = 0; mutex_unlock(&stream->dvb.feedlock); diff --git a/drivers/media/video/cx18/cx18-fileops.c b/drivers/media/video/cx18/cx18-fileops.c index c0885c69fd8..863ce775823 100644 --- a/drivers/media/video/cx18/cx18-fileops.c +++ b/drivers/media/video/cx18/cx18-fileops.c @@ -37,15 +37,21 @@ /* This function tries to claim the stream for a specific file descriptor. If no one else is using this stream then the stream is claimed and - associated VBI streams are also automatically claimed. + associated VBI and IDX streams are also automatically claimed. Possible error returns: -EBUSY if someone else has claimed the stream or 0 on success. */ -static int cx18_claim_stream(struct cx18_open_id *id, int type) +int cx18_claim_stream(struct cx18_open_id *id, int type) { struct cx18 *cx = id->cx; struct cx18_stream *s = &cx->streams[type]; - struct cx18_stream *s_vbi; - int vbi_type; + struct cx18_stream *s_assoc; + + /* Nothing should ever try to directly claim the IDX stream */ + if (type == CX18_ENC_STREAM_TYPE_IDX) { + CX18_WARN("MPEG Index stream cannot be claimed " + "directly, but something tried.\n"); + return -EINVAL; + } if (test_and_set_bit(CX18_F_S_CLAIMED, &s->s_flags)) { /* someone already claimed this stream */ @@ -67,32 +73,47 @@ static int cx18_claim_stream(struct cx18_open_id *id, int type) } s->id = id->open_id; - /* CX18_ENC_STREAM_TYPE_MPG needs to claim CX18_ENC_STREAM_TYPE_VBI - (provided VBI insertion is on and sliced VBI is selected), for all - other streams we're done */ - if (type == CX18_ENC_STREAM_TYPE_MPG && - cx->vbi.insert_mpeg && !cx18_raw_vbi(cx)) { - vbi_type = CX18_ENC_STREAM_TYPE_VBI; - } else { + /* + * CX18_ENC_STREAM_TYPE_MPG needs to claim: + * CX18_ENC_STREAM_TYPE_VBI, if VBI insertion is on for sliced VBI, or + * CX18_ENC_STREAM_TYPE_IDX, if VBI insertion is off for sliced VBI + * (We don't yet fix up MPEG Index entries for our inserted packets). + * + * For all other streams we're done. + */ + if (type != CX18_ENC_STREAM_TYPE_MPG) return 0; - } - s_vbi = &cx->streams[vbi_type]; - set_bit(CX18_F_S_CLAIMED, &s_vbi->s_flags); + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + if (cx->vbi.insert_mpeg && !cx18_raw_vbi(cx)) + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + else if (!cx18_stream_enabled(s_assoc)) + return 0; + + set_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); /* mark that it is used internally */ - set_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags); + set_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags); return 0; } +EXPORT_SYMBOL(cx18_claim_stream); /* This function releases a previously claimed stream. It will take into account associated VBI streams. */ -static void cx18_release_stream(struct cx18_stream *s) +void cx18_release_stream(struct cx18_stream *s) { struct cx18 *cx = s->cx; - struct cx18_stream *s_vbi; + struct cx18_stream *s_assoc; s->id = -1; + if (s->type == CX18_ENC_STREAM_TYPE_IDX) { + /* + * The IDX stream is only used internally, and can + * only be indirectly unclaimed by unclaiming the MPG stream. + */ + return; + } + if (s->type == CX18_ENC_STREAM_TYPE_VBI && test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) { /* this stream is still in use internally */ @@ -105,25 +126,36 @@ static void cx18_release_stream(struct cx18_stream *s) cx18_flush_queues(s); - /* CX18_ENC_STREAM_TYPE_MPG needs to release CX18_ENC_STREAM_TYPE_VBI, - for all other streams we're done */ - if (s->type == CX18_ENC_STREAM_TYPE_MPG) - s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; - else + /* + * CX18_ENC_STREAM_TYPE_MPG needs to release the + * CX18_ENC_STREAM_TYPE_VBI and/or CX18_ENC_STREAM_TYPE_IDX streams. + * + * For all other streams we're done. + */ + if (s->type != CX18_ENC_STREAM_TYPE_MPG) return; - /* clear internal use flag */ - if (!test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags)) { - /* was already cleared */ - return; + /* Unclaim the associated MPEG Index stream */ + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) { + clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); + cx18_flush_queues(s_assoc); } - if (s_vbi->id != -1) { - /* VBI stream still claimed by a file descriptor */ - return; + + /* Unclaim the associated VBI stream */ + s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) { + if (s_assoc->id == -1) { + /* + * The VBI stream is not still claimed by a file + * descriptor, so completely unclaim it. + */ + clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); + cx18_flush_queues(s_assoc); + } } - clear_bit(CX18_F_S_CLAIMED, &s_vbi->s_flags); - cx18_flush_queues(s_vbi); } +EXPORT_SYMBOL(cx18_release_stream); static void cx18_dualwatch(struct cx18 *cx) { @@ -177,9 +209,7 @@ static struct cx18_mdl *cx18_get_mdl(struct cx18_stream *s, int non_block, *err = 0; while (1) { if (s->type == CX18_ENC_STREAM_TYPE_MPG) { - /* Process pending program info updates and pending - VBI data */ - + /* Process pending program updates and VBI data */ if (time_after(jiffies, cx->dualwatch_jiffies + msecs_to_jiffies(1000))) { cx->dualwatch_jiffies = jiffies; cx18_dualwatch(cx); @@ -362,18 +392,6 @@ static size_t cx18_copy_buf_to_user(struct cx18_stream *s, return len; } -/** - * list_entry_is_past_end - check if a previous loop cursor is off list end - * @pos: the type * previously used as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Check if the entry's list_head is the head of the list, thus it's not a - * real entry but was the loop cursor that walked past the end - */ -#define list_entry_is_past_end(pos, head, member) \ - (&pos->member == (head)) - static size_t cx18_copy_mdl_to_user(struct cx18_stream *s, struct cx18_mdl *mdl, char __user *ubuf, size_t ucount) { @@ -498,6 +516,7 @@ int cx18_start_capture(struct cx18_open_id *id) struct cx18 *cx = id->cx; struct cx18_stream *s = &cx->streams[id->type]; struct cx18_stream *s_vbi; + struct cx18_stream *s_idx; if (s->type == CX18_ENC_STREAM_TYPE_RAD) { /* you cannot read from these stream types. */ @@ -516,25 +535,33 @@ int cx18_start_capture(struct cx18_open_id *id) return 0; } - /* Start VBI capture if required */ + /* Start associated VBI or IDX stream capture if required */ s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; - if (s->type == CX18_ENC_STREAM_TYPE_MPG && - test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) && - !test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) { - /* Note: the CX18_ENC_STREAM_TYPE_VBI is claimed - automatically when the MPG stream is claimed. - We only need to start the VBI capturing. */ - if (cx18_start_v4l2_encode_stream(s_vbi)) { - CX18_DEBUG_WARN("VBI capture start failed\n"); - - /* Failure, clean up and return an error */ - clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); - clear_bit(CX18_F_S_STREAMING, &s->s_flags); - /* also releases the associated VBI stream */ - cx18_release_stream(s); - return -EIO; + s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + if (s->type == CX18_ENC_STREAM_TYPE_MPG) { + /* + * The VBI and IDX streams should have been claimed + * automatically, if for internal use, when the MPG stream was + * claimed. We only need to start these streams capturing. + */ + if (test_bit(CX18_F_S_INTERNAL_USE, &s_idx->s_flags) && + !test_and_set_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { + if (cx18_start_v4l2_encode_stream(s_idx)) { + CX18_DEBUG_WARN("IDX capture start failed\n"); + clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags); + goto start_failed; + } + CX18_DEBUG_INFO("IDX capture started\n"); + } + if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) { + if (cx18_start_v4l2_encode_stream(s_vbi)) { + CX18_DEBUG_WARN("VBI capture start failed\n"); + clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); + goto start_failed; + } + CX18_DEBUG_INFO("VBI insertion started\n"); } - CX18_DEBUG_INFO("VBI insertion started\n"); } /* Tell the card to start capturing */ @@ -547,19 +574,29 @@ int cx18_start_capture(struct cx18_open_id *id) return 0; } - /* failure, clean up */ +start_failed: CX18_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name); - /* Note: the CX18_ENC_STREAM_TYPE_VBI is released - automatically when the MPG stream is released. - We only need to stop the VBI capturing. */ - if (s->type == CX18_ENC_STREAM_TYPE_MPG && - test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) { - cx18_stop_v4l2_encode_stream(s_vbi, 0); - clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); + /* + * The associated VBI and IDX streams for internal use are released + * automatically when the MPG stream is released. We only need to stop + * the associated stream. + */ + if (s->type == CX18_ENC_STREAM_TYPE_MPG) { + /* Stop the IDX stream which is always for internal use */ + if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { + cx18_stop_v4l2_encode_stream(s_idx, 0); + clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags); + } + /* Stop the VBI stream, if only running for internal use */ + if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) { + cx18_stop_v4l2_encode_stream(s_vbi, 0); + clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); + } } clear_bit(CX18_F_S_STREAMING, &s->s_flags); - cx18_release_stream(s); + cx18_release_stream(s); /* Also releases associated streams */ return -EIO; } @@ -618,6 +655,8 @@ void cx18_stop_capture(struct cx18_open_id *id, int gop_end) { struct cx18 *cx = id->cx; struct cx18_stream *s = &cx->streams[id->type]; + struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; + struct cx18_stream *s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; CX18_DEBUG_IOCTL("close() of %s\n", s->name); @@ -625,17 +664,19 @@ void cx18_stop_capture(struct cx18_open_id *id, int gop_end) /* Stop capturing */ if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) { - struct cx18_stream *s_vbi = - &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; - CX18_DEBUG_INFO("close stopping capture\n"); - /* Special case: a running VBI capture for VBI insertion - in the mpeg stream. Need to stop that too. */ - if (id->type == CX18_ENC_STREAM_TYPE_MPG && - test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) && - !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) { - CX18_DEBUG_INFO("close stopping embedded VBI capture\n"); - cx18_stop_v4l2_encode_stream(s_vbi, 0); + if (id->type == CX18_ENC_STREAM_TYPE_MPG) { + /* Stop internal use associated VBI and IDX streams */ + if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) { + CX18_DEBUG_INFO("close stopping embedded VBI " + "capture\n"); + cx18_stop_v4l2_encode_stream(s_vbi, 0); + } + if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { + CX18_DEBUG_INFO("close stopping IDX capture\n"); + cx18_stop_v4l2_encode_stream(s_idx, 0); + } } if (id->type == CX18_ENC_STREAM_TYPE_VBI && test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) diff --git a/drivers/media/video/cx18/cx18-fileops.h b/drivers/media/video/cx18/cx18-fileops.h index 92e2d5dab93..5c8fcb884f0 100644 --- a/drivers/media/video/cx18/cx18-fileops.h +++ b/drivers/media/video/cx18/cx18-fileops.h @@ -34,3 +34,6 @@ void cx18_stop_capture(struct cx18_open_id *id, int gop_end); void cx18_mute(struct cx18 *cx); void cx18_unmute(struct cx18 *cx); +/* Shared with cx18-alsa module */ +int cx18_claim_stream(struct cx18_open_id *id, int type); +void cx18_release_stream(struct cx18_stream *s); diff --git a/drivers/media/video/cx18/cx18-ioctl.c b/drivers/media/video/cx18/cx18-ioctl.c index 3e4fc192fde..b81dd0ea8eb 100644 --- a/drivers/media/video/cx18/cx18-ioctl.c +++ b/drivers/media/video/cx18/cx18-ioctl.c @@ -775,10 +775,143 @@ static int cx18_g_sliced_vbi_cap(struct file *file, void *fh, return 0; } +static int _cx18_process_idx_data(struct cx18_buffer *buf, + struct v4l2_enc_idx *idx) +{ + int consumed, remaining; + struct v4l2_enc_idx_entry *e_idx; + struct cx18_enc_idx_entry *e_buf; + + /* Frame type lookup: 1=I, 2=P, 4=B */ + const int mapping[8] = { + -1, V4L2_ENC_IDX_FRAME_I, V4L2_ENC_IDX_FRAME_P, + -1, V4L2_ENC_IDX_FRAME_B, -1, -1, -1 + }; + + /* + * Assumption here is that a buf holds an integral number of + * struct cx18_enc_idx_entry objects and is properly aligned. + * This is enforced by the module options on IDX buffer sizes. + */ + remaining = buf->bytesused - buf->readpos; + consumed = 0; + e_idx = &idx->entry[idx->entries]; + e_buf = (struct cx18_enc_idx_entry *) &buf->buf[buf->readpos]; + + while (remaining >= sizeof(struct cx18_enc_idx_entry) && + idx->entries < V4L2_ENC_IDX_ENTRIES) { + + e_idx->offset = (((u64) le32_to_cpu(e_buf->offset_high)) << 32) + | le32_to_cpu(e_buf->offset_low); + + e_idx->pts = (((u64) (le32_to_cpu(e_buf->pts_high) & 1)) << 32) + | le32_to_cpu(e_buf->pts_low); + + e_idx->length = le32_to_cpu(e_buf->length); + + e_idx->flags = mapping[le32_to_cpu(e_buf->flags) & 0x7]; + + e_idx->reserved[0] = 0; + e_idx->reserved[1] = 0; + + idx->entries++; + e_idx = &idx->entry[idx->entries]; + e_buf++; + + remaining -= sizeof(struct cx18_enc_idx_entry); + consumed += sizeof(struct cx18_enc_idx_entry); + } + + /* Swallow any partial entries at the end, if there are any */ + if (remaining > 0 && remaining < sizeof(struct cx18_enc_idx_entry)) + consumed += remaining; + + buf->readpos += consumed; + return consumed; +} + +static int cx18_process_idx_data(struct cx18_stream *s, struct cx18_mdl *mdl, + struct v4l2_enc_idx *idx) +{ + if (s->type != CX18_ENC_STREAM_TYPE_IDX) + return -EINVAL; + + if (mdl->curr_buf == NULL) + mdl->curr_buf = list_first_entry(&mdl->buf_list, + struct cx18_buffer, list); + + if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) { + /* + * For some reason we've exhausted the buffers, but the MDL + * object still said some data was unread. + * Fix that and bail out. + */ + mdl->readpos = mdl->bytesused; + return 0; + } + + list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) { + + /* Skip any empty buffers in the MDL */ + if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused) + continue; + + mdl->readpos += _cx18_process_idx_data(mdl->curr_buf, idx); + + /* exit when MDL drained or request satisfied */ + if (idx->entries >= V4L2_ENC_IDX_ENTRIES || + mdl->curr_buf->readpos < mdl->curr_buf->bytesused || + mdl->readpos >= mdl->bytesused) + break; + } + return 0; +} + static int cx18_g_enc_index(struct file *file, void *fh, struct v4l2_enc_idx *idx) { - return -EINVAL; + struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; + struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + s32 tmp; + struct cx18_mdl *mdl; + + if (!cx18_stream_enabled(s)) /* Module options inhibited IDX stream */ + return -EINVAL; + + /* Compute the best case number of entries we can buffer */ + tmp = s->buffers - + s->bufs_per_mdl * CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN; + if (tmp <= 0) + tmp = 1; + tmp = tmp * s->buf_size / sizeof(struct cx18_enc_idx_entry); + + /* Fill out the header of the return structure */ + idx->entries = 0; + idx->entries_cap = tmp; + memset(idx->reserved, 0, sizeof(idx->reserved)); + + /* Pull IDX MDLs and buffers from q_full and populate the entries */ + do { + mdl = cx18_dequeue(s, &s->q_full); + if (mdl == NULL) /* No more IDX data right now */ + break; + + /* Extract the Index entry data from the MDL and buffers */ + cx18_process_idx_data(s, mdl, idx); + if (mdl->readpos < mdl->bytesused) { + /* We finished with data remaining, push the MDL back */ + cx18_push(s, mdl, &s->q_full); + break; + } + + /* We drained this MDL, schedule it to go to the firmware */ + cx18_enqueue(s, mdl, &s->q_free); + + } while (idx->entries < V4L2_ENC_IDX_ENTRIES); + + /* Tell the work handler to send free IDX MDLs to the firmware */ + cx18_stream_load_fw_queue(s); + return 0; } static int cx18_encoder_cmd(struct file *file, void *fh, diff --git a/drivers/media/video/cx18/cx18-mailbox.c b/drivers/media/video/cx18/cx18-mailbox.c index f231dd09c72..6dcce297752 100644 --- a/drivers/media/video/cx18/cx18-mailbox.c +++ b/drivers/media/video/cx18/cx18-mailbox.c @@ -29,6 +29,7 @@ #include "cx18-mailbox.h" #include "cx18-queue.h" #include "cx18-streams.h" +#include "cx18-alsa-pcm.h" /* FIXME make configurable */ static const char *rpu_str[] = { "APU", "CPU", "EPU", "HPU" }; @@ -157,6 +158,34 @@ static void cx18_mdl_send_to_dvb(struct cx18_stream *s, struct cx18_mdl *mdl) } } + +static void cx18_mdl_send_to_alsa(struct cx18 *cx, struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + struct cx18_buffer *buf; + + if (mdl->bytesused == 0) + return; + + /* We ignore mdl and buf readpos accounting here - it doesn't matter */ + + /* The likely case */ + if (list_is_singular(&mdl->buf_list)) { + buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, + list); + if (buf->bytesused) + cx->pcm_announce_callback(cx->alsa, buf->buf, + buf->bytesused); + return; + } + + list_for_each_entry(buf, &mdl->buf_list, list) { + if (buf->bytesused == 0) + break; + cx->pcm_announce_callback(cx->alsa, buf->buf, buf->bytesused); + } +} + static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order) { u32 handle, mdl_ack_count, id; @@ -223,11 +252,21 @@ static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order) CX18_DEBUG_HI_DMA("%s recv bytesused = %d\n", s->name, mdl->bytesused); - if (s->type != CX18_ENC_STREAM_TYPE_TS) - cx18_enqueue(s, mdl, &s->q_full); - else { + if (s->type == CX18_ENC_STREAM_TYPE_TS) { cx18_mdl_send_to_dvb(s, mdl); cx18_enqueue(s, mdl, &s->q_free); + } else if (s->type == CX18_ENC_STREAM_TYPE_PCM) { + /* Pass the data to cx18-alsa */ + if (cx->pcm_announce_callback != NULL) { + cx18_mdl_send_to_alsa(cx, s, mdl); + cx18_enqueue(s, mdl, &s->q_free); + } else { + cx18_enqueue(s, mdl, &s->q_full); + } + } else { + cx18_enqueue(s, mdl, &s->q_full); + if (s->type == CX18_ENC_STREAM_TYPE_IDX) + cx18_stream_rotate_idx_mdls(cx); } } /* Put as many MDLs as possible back into fw use */ diff --git a/drivers/media/video/cx18/cx18-queue.c b/drivers/media/video/cx18/cx18-queue.c index 63304823cef..aefc8c8cf3c 100644 --- a/drivers/media/video/cx18/cx18-queue.c +++ b/drivers/media/video/cx18/cx18-queue.c @@ -419,6 +419,9 @@ void cx18_stream_free(struct cx18_stream *s) { struct cx18_mdl *mdl; struct cx18_buffer *buf; + struct cx18 *cx = s->cx; + + CX18_DEBUG_INFO("Deallocating buffers for %s stream\n", s->name); /* move all buffers to buf_pool and all MDLs to q_idle */ cx18_unload_queues(s); diff --git a/drivers/media/video/cx18/cx18-streams.c b/drivers/media/video/cx18/cx18-streams.c index 987a9308d93..054450f65a6 100644 --- a/drivers/media/video/cx18/cx18-streams.c +++ b/drivers/media/video/cx18/cx18-streams.c @@ -319,11 +319,27 @@ void cx18_streams_cleanup(struct cx18 *cx, int unregister) /* Teardown all streams */ for (type = 0; type < CX18_MAX_STREAMS; type++) { - if (cx->streams[type].dvb.enabled) { - cx18_dvb_unregister(&cx->streams[type]); - cx->streams[type].dvb.enabled = false; + + /* No struct video_device, but can have buffers allocated */ + if (type == CX18_ENC_STREAM_TYPE_TS) { + if (cx->streams[type].dvb.enabled) { + cx18_dvb_unregister(&cx->streams[type]); + cx->streams[type].dvb.enabled = false; + cx18_stream_free(&cx->streams[type]); + } + continue; + } + + /* No struct video_device, but can have buffers allocated */ + if (type == CX18_ENC_STREAM_TYPE_IDX) { + if (cx->stream_buffers[type] != 0) { + cx->stream_buffers[type] = 0; + cx18_stream_free(&cx->streams[type]); + } + continue; } + /* If struct video_device exists, can have buffers allocated */ vdev = cx->streams[type].video_dev; cx->streams[type].video_dev = NULL; @@ -447,6 +463,32 @@ static void cx18_vbi_setup(struct cx18_stream *s) cx18_api(cx, CX18_CPU_SET_RAW_VBI_PARAM, 6, data); } +void cx18_stream_rotate_idx_mdls(struct cx18 *cx) +{ + struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + struct cx18_mdl *mdl; + + if (!cx18_stream_enabled(s)) + return; + + /* Return if the firmware is not running low on MDLs */ + if ((atomic_read(&s->q_free.depth) + atomic_read(&s->q_busy.depth)) >= + CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN) + return; + + /* Return if there are no MDLs to rotate back to the firmware */ + if (atomic_read(&s->q_full.depth) < 2) + return; + + /* + * Take the oldest IDX MDL still holding data, and discard its index + * entries by scheduling the MDL to go back to the firmware + */ + mdl = cx18_dequeue(s, &s->q_full); + if (mdl != NULL) + cx18_enqueue(s, mdl, &s->q_free); +} + static struct cx18_queue *_cx18_stream_put_mdl_fw(struct cx18_stream *s, struct cx18_mdl *mdl) @@ -546,8 +588,9 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) struct cx18 *cx = s->cx; int captype = 0; struct cx18_api_func_private priv; + struct cx18_stream *s_idx; - if (s->video_dev == NULL && s->dvb.enabled == 0) + if (!cx18_stream_enabled(s)) return -EINVAL; CX18_DEBUG_INFO("Start encoder stream %s\n", s->name); @@ -561,6 +604,9 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) cx->search_pack_header = 0; break; + case CX18_ENC_STREAM_TYPE_IDX: + captype = CAPTURE_CHANNEL_TYPE_INDEX; + break; case CX18_ENC_STREAM_TYPE_TS: captype = CAPTURE_CHANNEL_TYPE_TS; break; @@ -635,11 +681,13 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) cx18_vbi_setup(s); /* - * assign program index info. - * Mask 7: select I/P/B, Num_req: 400 max - * FIXME - currently we have this hardcoded as disabled + * Select to receive I, P, and B frame index entries, if the + * index stream is enabled. Otherwise disable index entry + * generation. */ - cx18_vapi_result(cx, data, CX18_CPU_SET_INDEXTABLE, 1, 0); + s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + cx18_vapi_result(cx, data, CX18_CPU_SET_INDEXTABLE, 2, + s->handle, cx18_stream_enabled(s_idx) ? 7 : 0); /* Call out to the common CX2341x API setup for user controls */ priv.cx = cx; @@ -697,6 +745,7 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) atomic_inc(&cx->tot_capturing); return 0; } +EXPORT_SYMBOL(cx18_start_v4l2_encode_stream); void cx18_stop_all_captures(struct cx18 *cx) { @@ -705,7 +754,7 @@ void cx18_stop_all_captures(struct cx18 *cx) for (i = CX18_MAX_STREAMS - 1; i >= 0; i--) { struct cx18_stream *s = &cx->streams[i]; - if (s->video_dev == NULL && s->dvb.enabled == 0) + if (!cx18_stream_enabled(s)) continue; if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) cx18_stop_v4l2_encode_stream(s, 0); @@ -717,7 +766,7 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end) struct cx18 *cx = s->cx; unsigned long then; - if (s->video_dev == NULL && s->dvb.enabled == 0) + if (!cx18_stream_enabled(s)) return -EINVAL; /* This function assumes that you are allowed to stop the capture @@ -762,6 +811,7 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end) return 0; } +EXPORT_SYMBOL(cx18_stop_v4l2_encode_stream); u32 cx18_find_handle(struct cx18 *cx) { @@ -789,7 +839,7 @@ struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle) s = &cx->streams[i]; if (s->handle != handle) continue; - if (s->video_dev || s->dvb.enabled) + if (cx18_stream_enabled(s)) return s; } return NULL; diff --git a/drivers/media/video/cx18/cx18-streams.h b/drivers/media/video/cx18/cx18-streams.h index 4a01db5e5a3..0bff0fa2976 100644 --- a/drivers/media/video/cx18/cx18-streams.h +++ b/drivers/media/video/cx18/cx18-streams.h @@ -28,6 +28,16 @@ int cx18_streams_setup(struct cx18 *cx); int cx18_streams_register(struct cx18 *cx); void cx18_streams_cleanup(struct cx18 *cx, int unregister); +#define CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN (3) +void cx18_stream_rotate_idx_mdls(struct cx18 *cx); + +static inline bool cx18_stream_enabled(struct cx18_stream *s) +{ + return s->video_dev || s->dvb.enabled || + (s->type == CX18_ENC_STREAM_TYPE_IDX && + s->cx->stream_buffers[CX18_ENC_STREAM_TYPE_IDX] != 0); +} + /* Related to submission of mdls to firmware */ static inline void cx18_stream_load_fw_queue(struct cx18_stream *s) { diff --git a/drivers/media/video/cx18/cx18-version.h b/drivers/media/video/cx18/cx18-version.h index 9c0b5bb1b01..3e1aec4bcfd 100644 --- a/drivers/media/video/cx18/cx18-version.h +++ b/drivers/media/video/cx18/cx18-version.h @@ -24,7 +24,7 @@ #define CX18_DRIVER_NAME "cx18" #define CX18_DRIVER_VERSION_MAJOR 1 -#define CX18_DRIVER_VERSION_MINOR 3 +#define CX18_DRIVER_VERSION_MINOR 4 #define CX18_DRIVER_VERSION_PATCHLEVEL 0 #define CX18_VERSION __stringify(CX18_DRIVER_VERSION_MAJOR) "." __stringify(CX18_DRIVER_VERSION_MINOR) "." __stringify(CX18_DRIVER_VERSION_PATCHLEVEL) diff --git a/drivers/media/video/cx18/cx23418.h b/drivers/media/video/cx18/cx23418.h index 868806effdc..2c00980acfc 100644 --- a/drivers/media/video/cx18/cx23418.h +++ b/drivers/media/video/cx18/cx23418.h @@ -191,7 +191,8 @@ #define CX18_CPU_SET_MEDIAN_CORING (CPU_CMD_MASK_CAPTURE | 0x000E) /* Description: This command set the picture type mask for index file - IN[0] - 0 = disable index file output + IN[0] - Task handle (ignored by firmware) + IN[1] - 0 = disable index file output 1 = output I picture 2 = P picture 4 = B picture |