diff options
author | Andy Walls <awalls@md.metrocast.net> | 2012-09-02 22:13:14 (GMT) |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-09-18 16:26:50 (GMT) |
commit | 269c11fbac4f7b4ed58e77f3049b64b55a342234 (patch) | |
tree | 53e01d0a8f9cd1cd488f2d3e2838a79edf1ccfe6 /drivers/media/pci | |
parent | 6d60805fd2e8103fafa02fcf6448446229ebd511 (diff) | |
download | linux-fsl-qoriq-269c11fbac4f7b4ed58e77f3049b64b55a342234.tar.xz |
[media] ivtv, ivtv-alsa: Add initial ivtv-alsa interface driver for ivtv
This is a cut-and-paste port of the cx18-alsa driver to
create an ivtv-alsa interface module for the ivtv driver.
It is not actually hooked-up to the PCM stream DMA buffers
from the ivtv driver yet. That will be done in a coming change,
since that portion is so very different from the cx18 driver.
This code has all or more of the bugs and shortcomings of the
cx18-alsa interface driver: inconsistent use of itvsc->slock,
ivtv-alsa-mixer.c is dead code, assumes 48 ksps regardless
of the actual setting of the audio capture, problems with
proper struct ivtv and struct ivtv_stream housekeeping,
struct ivtv_open_id.v4l2_fh abuse, and $DIETY knows what else.
Signed-off-by: Andy Walls <awalls@md.metrocast.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/pci')
-rw-r--r-- | drivers/media/pci/ivtv/Kconfig | 11 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/Makefile | 2 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa-main.c | 303 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa-mixer.c | 175 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa-mixer.h | 23 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa-pcm.c | 357 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa-pcm.h | 27 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-alsa.h | 75 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-driver.c | 37 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-driver.h | 10 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-fileops.c | 4 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-fileops.h | 4 | ||||
-rw-r--r-- | drivers/media/pci/ivtv/ivtv-streams.c | 2 |
13 files changed, 1027 insertions, 3 deletions
diff --git a/drivers/media/pci/ivtv/Kconfig b/drivers/media/pci/ivtv/Kconfig index 89f6591..3906b88 100644 --- a/drivers/media/pci/ivtv/Kconfig +++ b/drivers/media/pci/ivtv/Kconfig @@ -28,6 +28,17 @@ config VIDEO_IVTV To compile this driver as a module, choose M here: the module will be called ivtv. +config VIDEO_IVTV_ALSA + tristate "Conexant cx23415/cx23416 PCM audio capture support" + depends on VIDEO_IVTV && SND && EXPERIMENTAL + select SND_PCM + ---help--- + This is an ALSA interface driver for direct PCM audio capture from + Conexant cx23415/cx23416 based PCI TV cards using the ivtv driver. + + To compile this driver as a module, choose M here: the + module will be called ivtv-alsa. + config VIDEO_FB_IVTV tristate "Conexant cx23415 framebuffer support" depends on VIDEO_IVTV && FB diff --git a/drivers/media/pci/ivtv/Makefile b/drivers/media/pci/ivtv/Makefile index 1408c9f..0eaa882 100644 --- a/drivers/media/pci/ivtv/Makefile +++ b/drivers/media/pci/ivtv/Makefile @@ -3,8 +3,10 @@ ivtv-objs := ivtv-routing.o ivtv-cards.o ivtv-controls.o \ ivtv-gpio.o ivtv-i2c.o ivtv-ioctl.o ivtv-irq.o \ ivtv-mailbox.o ivtv-queue.o ivtv-streams.o ivtv-udma.o \ ivtv-vbi.o ivtv-yuv.o +ivtv-alsa-objs := ivtv-alsa-main.o ivtv-alsa-pcm.o obj-$(CONFIG_VIDEO_IVTV) += ivtv.o +obj-$(CONFIG_VIDEO_IVTV_ALSA) += ivtv-alsa.o obj-$(CONFIG_VIDEO_FB_IVTV) += ivtvfb.o ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/pci/ivtv/ivtv-alsa-main.c b/drivers/media/pci/ivtv/ivtv-alsa-main.c new file mode 100644 index 0000000..8deab16 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-main.c @@ -0,0 +1,303 @@ +/* + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.net> + * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * Portions of this work were sponsored by ONELAN Limited for the cx18 driver + * + * 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/slab.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 "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-alsa.h" +#include "ivtv-alsa-mixer.h" +#include "ivtv-alsa-pcm.h" + +int ivtv_alsa_debug; + +#define IVTV_DEBUG_ALSA_INFO(fmt, arg...) \ + do { \ + if (ivtv_alsa_debug & 2) \ + pr_info("%s: " fmt, "ivtv-alsa", ## arg); \ + } while (0) + +module_param_named(debug, ivtv_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("CX23415/CX23416 ALSA Interface"); +MODULE_SUPPORTED_DEVICE("CX23415/CX23416 MPEG2 encoder"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(IVTV_VERSION); + +static inline +struct snd_ivtv_card *to_snd_ivtv_card(struct v4l2_device *v4l2_dev) +{ + return to_ivtv(v4l2_dev)->alsa; +} + +static inline +struct snd_ivtv_card *p_to_snd_ivtv_card(struct v4l2_device **v4l2_dev) +{ + return container_of(v4l2_dev, struct snd_ivtv_card, v4l2_dev); +} + +static void snd_ivtv_card_free(struct snd_ivtv_card *itvsc) +{ + if (itvsc == NULL) + return; + + if (itvsc->v4l2_dev != NULL) + to_ivtv(itvsc->v4l2_dev)->alsa = NULL; + + /* FIXME - take any other stopping actions needed */ + + kfree(itvsc); +} + +static void snd_ivtv_card_private_free(struct snd_card *sc) +{ + if (sc == NULL) + return; + snd_ivtv_card_free(sc->private_data); + sc->private_data = NULL; + sc->private_free = NULL; +} + +static int snd_ivtv_card_create(struct v4l2_device *v4l2_dev, + struct snd_card *sc, + struct snd_ivtv_card **itvsc) +{ + *itvsc = kzalloc(sizeof(struct snd_ivtv_card), GFP_KERNEL); + if (*itvsc == NULL) + return -ENOMEM; + + (*itvsc)->v4l2_dev = v4l2_dev; + (*itvsc)->sc = sc; + + sc->private_data = *itvsc; + sc->private_free = snd_ivtv_card_private_free; + + return 0; +} + +static int snd_ivtv_card_set_names(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct snd_card *sc = itvsc->sc; + + /* sc->driver is used by alsa-lib's configurator: simple, unique */ + strlcpy(sc->driver, "CX2341[56]", sizeof(sc->driver)); + + /* sc->shortname is a symlink in /proc/asound: IVTV-M -> cardN */ + snprintf(sc->shortname, sizeof(sc->shortname), "IVTV-%d", + itv->instance); + + /* sc->longname is read from /proc/asound/cards */ + snprintf(sc->longname, sizeof(sc->longname), + "CX2341[56] #%d %s TV/FM Radio/Line-In Capture", + itv->instance, itv->card_name); + + return 0; +} + +static int snd_ivtv_init(struct v4l2_device *v4l2_dev) +{ + struct ivtv *itv = to_ivtv(v4l2_dev); + struct snd_card *sc = NULL; + struct snd_ivtv_card *itvsc; + 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 itv->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) { + IVTV_ALSA_ERR("%s: snd_card_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + /* (3) Create a main component */ + ret = snd_ivtv_card_create(v4l2_dev, sc, &itvsc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_card_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + /* (4) Set the driver ID and name strings */ + snd_ivtv_card_set_names(itvsc); + + /* (5) Create other components: mixer, PCM, & proc files */ +#if 0 + ret = snd_ivtv_mixer_create(itvsc); + if (ret) { + IVTV_ALSA_WARN("%s: snd_ivtv_mixer_create() failed with err %d:" + " proceeding anyway\n", __func__, ret); + } +#endif + + ret = snd_ivtv_pcm_create(itvsc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_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 */ + itv->alsa = itvsc; + + /* (6) Register the card instance */ + ret = snd_card_register(sc); + if (ret) { + itv->alsa = NULL; + IVTV_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); + kfree(itvsc); +err_exit: + return ret; +} + +int ivtv_alsa_load(struct ivtv *itv) +{ + struct v4l2_device *v4l2_dev = &itv->v4l2_dev; + struct ivtv_stream *s; + + if (v4l2_dev == NULL) { + pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + itv = to_ivtv(v4l2_dev); + if (itv == NULL) { + pr_err("ivtv-alsa itv is NULL\n"); + return 0; + } + + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + if (s->vdev == NULL) { + IVTV_DEBUG_ALSA_INFO("%s: PCM stream for card is disabled - " + "skipping\n", __func__); + return 0; + } + + if (itv->alsa != NULL) { + IVTV_ALSA_ERR("%s: struct snd_ivtv_card * already exists\n", + __func__); + return 0; + } + + if (snd_ivtv_init(v4l2_dev)) { + IVTV_ALSA_ERR("%s: failed to create struct snd_ivtv_card\n", + __func__); + } else { + IVTV_DEBUG_ALSA_INFO("%s: created ivtv ALSA interface instance " + "\n", __func__); + } + return 0; +} + +static int __init ivtv_alsa_init(void) +{ + pr_info("ivtv-alsa: module loading...\n"); + ivtv_ext_init = &ivtv_alsa_load; + return 0; +} + +static void __exit snd_ivtv_exit(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + + /* FIXME - pointer checks & shutdown itvsc */ + + snd_card_free(itvsc->sc); + itv->alsa = NULL; +} + +static int __exit ivtv_alsa_exit_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct snd_ivtv_card *itvsc; + + if (v4l2_dev == NULL) { + pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + itvsc = to_snd_ivtv_card(v4l2_dev); + if (itvsc == NULL) { + IVTV_ALSA_WARN("%s: struct snd_ivtv_card * is NULL\n", + __func__); + return 0; + } + + snd_ivtv_exit(itvsc); + return 0; +} + +static void __exit ivtv_alsa_exit(void) +{ + struct device_driver *drv; + int ret; + + pr_info("ivtv-alsa: module unloading...\n"); + + drv = driver_find("ivtv", &pci_bus_type); + ret = driver_for_each_device(drv, NULL, NULL, ivtv_alsa_exit_callback); + (void)ret; /* suppress compiler warning */ + + ivtv_ext_init = NULL; + pr_info("ivtv-alsa: module unload complete\n"); +} + +module_init(ivtv_alsa_init); +module_exit(ivtv_alsa_exit); diff --git a/drivers/media/pci/ivtv/ivtv-alsa-mixer.c b/drivers/media/pci/ivtv/ivtv-alsa-mixer.c new file mode 100644 index 0000000..33ec05b --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-mixer.c @@ -0,0 +1,175 @@ +/* + * ALSA mixer controls for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.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 "ivtv-alsa.h" +#include "ivtv-driver.h" + +/* + * Note the cx25840-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_cx25840_vol(int dB) +{ + if (dB < -96) + dB = -96; + else if (dB > 8) + dB = 8; + return (dB + 119) << 9; +} + +static inline int cx25840_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_ivtv_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_ivtv_mixer_tv_vol_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_ivtv_card *itvsc = snd_kcontrol_chip(kctl); + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct v4l2_control vctrl; + int ret; + + vctrl.id = V4L2_CID_AUDIO_VOLUME; + vctrl.value = dB_to_cx25840_vol(uctl->value.integer.value[0]); + + snd_ivtv_lock(itvsc); + ret = v4l2_subdev_call(itv->sd_audio, core, g_ctrl, &vctrl); + snd_ivtv_unlock(itvsc); + + if (!ret) + uctl->value.integer.value[0] = cx25840_vol_to_dB(vctrl.value); + return ret; +} + +static int snd_ivtv_mixer_tv_vol_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_ivtv_card *itvsc = snd_kcontrol_chip(kctl); + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct v4l2_control vctrl; + int ret; + + vctrl.id = V4L2_CID_AUDIO_VOLUME; + vctrl.value = dB_to_cx25840_vol(uctl->value.integer.value[0]); + + snd_ivtv_lock(itvsc); + + /* Fetch current state */ + ret = v4l2_subdev_call(itv->sd_audio, core, g_ctrl, &vctrl); + + if (ret || + (cx25840_vol_to_dB(vctrl.value) != uctl->value.integer.value[0])) { + + /* Set, if needed */ + vctrl.value = dB_to_cx25840_vol(uctl->value.integer.value[0]); + ret = v4l2_subdev_call(itv->sd_audio, core, s_ctrl, &vctrl); + if (!ret) + ret = 1; /* Indicate control was changed w/o error */ + } + snd_ivtv_unlock(itvsc); + + return ret; +} + + +/* This is a bit of overkill, the slider is already in dB internally */ +static DECLARE_TLV_DB_SCALE(snd_ivtv_mixer_tv_vol_db_scale, -9600, 100, 0); + +static struct snd_kcontrol_new snd_ivtv_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_ivtv_mixer_tv_volume_info, + .get = snd_ivtv_mixer_tv_volume_get, + .put = snd_ivtv_mixer_tv_volume_put, + .tlv.p = snd_ivtv_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 I2S volume */ + +int __init snd_ivtv_mixer_create(struct snd_ivtv_card *itvsc) +{ + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct snd_card *sc = itvsc->sc; + int ret; + + strlcpy(sc->mixername, "CX2341[56] Mixer", sizeof(sc->mixername)); + + ret = snd_ctl_add(sc, snd_ctl_new1(snd_ivtv_mixer_tv_vol, itvsc)); + if (ret) { + IVTV_ALSA_WARN("%s: failed to add %s control, err %d\n", + __func__, snd_ivtv_mixer_tv_vol.name, ret); + } + return ret; +} diff --git a/drivers/media/pci/ivtv/ivtv-alsa-mixer.h b/drivers/media/pci/ivtv/ivtv-alsa-mixer.h new file mode 100644 index 0000000..cdde367 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-mixer.h @@ -0,0 +1,23 @@ +/* + * ALSA mixer controls for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.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_ivtv_mixer_create(struct snd_ivtv_card *itvsc); diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.c b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c new file mode 100644 index 0000000..82c708e --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c @@ -0,0 +1,357 @@ +/* + * ALSA PCM device for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.net> + * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * Portions of this work were sponsored by ONELAN Limited for the cx18 driver + * + * 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 <linux/printk.h> + +#include <media/v4l2-device.h> + +#include <sound/core.h> +#include <sound/pcm.h> + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-streams.h" +#include "ivtv-fileops.h" +#include "ivtv-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) \ + pr_info("ivtv-alsa-pcm %s: " fmt, __func__, ##arg); \ + } while (0) + +static struct snd_pcm_hardware snd_ivtv_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 ivtv_alsa_announce_pcm_data(struct snd_ivtv_card *itvsc, 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("ivtv alsa announce ptr=%p data=%p num_bytes=%zd\n", itvsc, + pcm_data, num_bytes); + + substream = itvsc->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 = itvsc->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); + + itvsc->hwptr_done_capture += length; + if (itvsc->hwptr_done_capture >= + runtime->buffer_size) + itvsc->hwptr_done_capture -= + runtime->buffer_size; + + itvsc->capture_transfer_done += length; + if (itvsc->capture_transfer_done >= + runtime->period_size) { + itvsc->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_ivtv_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + struct ivtv_stream *s; + struct ivtv_open_id item; + int ret; + + /* Instruct the CX2341[56] to start sending packets */ + snd_ivtv_lock(itvsc); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + + v4l2_fh_init(&item.fh, s->vdev); + item.itv = itv; + item.type = s->type; + + /* See if the stream is available */ + if (ivtv_claim_stream(&item, item.type)) { + /* No, it's already in use */ + snd_ivtv_unlock(itvsc); + return -EBUSY; + } + + if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || + test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + /* We're already streaming. No additional action required */ + snd_ivtv_unlock(itvsc); + return 0; + } + + + runtime->hw = snd_ivtv_hw_capture; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + itvsc->capture_pcm_substream = substream; + runtime->private_data = itv; + + itv->pcm_announce_callback = ivtv_alsa_announce_pcm_data; + + /* Not currently streaming, so start it up */ + set_bit(IVTV_F_S_STREAMING, &s->s_flags); + ret = ivtv_start_v4l2_encode_stream(s); + snd_ivtv_unlock(itvsc); + + return ret; +} + +static int snd_ivtv_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + struct ivtv_stream *s; + + /* Instruct the ivtv to stop sending packets */ + snd_ivtv_lock(itvsc); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + ivtv_stop_v4l2_encode_stream(s, 0); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + + ivtv_release_stream(s); + + itv->pcm_announce_callback = NULL; + snd_ivtv_unlock(itvsc); + + return 0; +} + +static int snd_ivtv_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + int ret; + + snd_ivtv_lock(itvsc); + ret = snd_pcm_lib_ioctl(substream, cmd, arg); + snd_ivtv_unlock(itvsc); + return ret; +} + + +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_ivtv_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + dprintk("%s called\n", __func__); + + return snd_pcm_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int snd_ivtv_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + unsigned long flags; + + spin_lock_irqsave(&itvsc->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(&itvsc->slock, flags); + + return 0; +} + +static int snd_ivtv_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + + itvsc->hwptr_done_capture = 0; + itvsc->capture_transfer_done = 0; + + return 0; +} + +static int snd_ivtv_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return 0; +} + +static +snd_pcm_uframes_t snd_ivtv_pcm_pointer(struct snd_pcm_substream *substream) +{ + unsigned long flags; + snd_pcm_uframes_t hwptr_done; + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + + spin_lock_irqsave(&itvsc->slock, flags); + hwptr_done = itvsc->hwptr_done_capture; + spin_unlock_irqrestore(&itvsc->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_ivtv_pcm_capture_ops = { + .open = snd_ivtv_pcm_capture_open, + .close = snd_ivtv_pcm_capture_close, + .ioctl = snd_ivtv_pcm_ioctl, + .hw_params = snd_ivtv_pcm_hw_params, + .hw_free = snd_ivtv_pcm_hw_free, + .prepare = snd_ivtv_pcm_prepare, + .trigger = snd_ivtv_pcm_trigger, + .pointer = snd_ivtv_pcm_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + +int snd_ivtv_pcm_create(struct snd_ivtv_card *itvsc) +{ + struct snd_pcm *sp; + struct snd_card *sc = itvsc->sc; + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + int ret; + + ret = snd_pcm_new(sc, "CX2341[56] PCM", + 0, /* PCM device 0, the only one for this card */ + 0, /* 0 playback substreams */ + 1, /* 1 capture substream */ + &sp); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + spin_lock_init(&itvsc->slock); + + snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, + &snd_ivtv_pcm_capture_ops); + sp->info_flags = 0; + sp->private_data = itvsc; + strlcpy(sp->name, itv->card_name, sizeof(sp->name)); + + return 0; + +err_exit: + return ret; +} diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.h b/drivers/media/pci/ivtv/ivtv-alsa-pcm.h new file mode 100644 index 0000000..5ab1831 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.h @@ -0,0 +1,27 @@ +/* + * ALSA PCM device for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.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_ivtv_pcm_create(struct snd_ivtv_card *itvsc); + +/* Used by ivtv driver to announce the PCM data to the module */ +void ivtv_alsa_announce_pcm_data(struct snd_ivtv_card *card, u8 *pcm_data, + size_t num_bytes); diff --git a/drivers/media/pci/ivtv/ivtv-alsa.h b/drivers/media/pci/ivtv/ivtv-alsa.h new file mode 100644 index 0000000..4a0d8f2 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa.h @@ -0,0 +1,75 @@ +/* + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.net> + * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * 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_ivtv_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 ivtv_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_ivtv_lock(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + mutex_lock(&itv->serialize_lock); +} + +static inline void snd_ivtv_unlock(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + mutex_unlock(&itv->serialize_lock); +} + +#define IVTV_ALSA_DBGFLG_WARN (1 << 0) +#define IVTV_ALSA_DBGFLG_INFO (1 << 1) + +#define IVTV_ALSA_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_alsa_debug) \ + pr_info("%s-alsa: " type ": " fmt, \ + v4l2_dev->name , ## args); \ + } while (0) + +#define IVTV_ALSA_DEBUG_WARN(fmt, args...) \ + IVTV_ALSA_DEBUG(IVTV_ALSA_DBGFLG_WARN, "warning", fmt , ## args) + +#define IVTV_ALSA_DEBUG_INFO(fmt, args...) \ + IVTV_ALSA_DEBUG(IVTV_ALSA_DBGFLG_INFO, "info", fmt , ## args) + +#define IVTV_ALSA_ERR(fmt, args...) \ + pr_err("%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define IVTV_ALSA_WARN(fmt, args...) \ + pr_warn("%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define IVTV_ALSA_INFO(fmt, args...) \ + pr_info("%s-alsa: " fmt, v4l2_dev->name , ## args) diff --git a/drivers/media/pci/ivtv/ivtv-driver.c b/drivers/media/pci/ivtv/ivtv-driver.c index 5462ce2..eed95a3 100644 --- a/drivers/media/pci/ivtv/ivtv-driver.c +++ b/drivers/media/pci/ivtv/ivtv-driver.c @@ -68,6 +68,10 @@ setting this to 1 you ensure that radio0 is now also radio1. */ int ivtv_first_minor; +/* Callback for registering extensions */ +int (*ivtv_ext_init)(struct ivtv *); +EXPORT_SYMBOL(ivtv_ext_init); + /* add your revision and whatnot here */ static struct pci_device_id ivtv_pci_tbl[] __devinitdata = { {PCI_VENDOR_ID_ICOMP, PCI_DEVICE_ID_IVTV15, @@ -279,6 +283,34 @@ MODULE_LICENSE("GPL"); MODULE_VERSION(IVTV_VERSION); +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct ivtv *dev = container_of(work, struct ivtv, request_module_wk); + + /* Make sure ivtv-alsa module is loaded */ + request_module("ivtv-alsa"); + + /* Initialize ivtv-alsa for this instance of the cx18 device */ + if (ivtv_ext_init != NULL) + ivtv_ext_init(dev); +} + +static void request_modules(struct ivtv *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct ivtv *dev) +{ + flush_work_sync(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask) { itv->irqmask &= ~mask; @@ -1253,6 +1285,9 @@ static int __devinit ivtv_probe(struct pci_dev *pdev, goto free_streams; } IVTV_INFO("Initialized card: %s\n", itv->card_name); + + /* Load ivtv submodules (ivtv-alsa) */ + request_modules(itv); return 0; free_streams: @@ -1380,6 +1415,8 @@ static void ivtv_remove(struct pci_dev *pdev) IVTV_DEBUG_INFO("Removing card\n"); + flush_request_modules(itv); + if (test_bit(IVTV_F_I_INITED, &itv->i_flags)) { /* Stop all captures */ IVTV_DEBUG_INFO("Stopping all streams\n"); diff --git a/drivers/media/pci/ivtv/ivtv-driver.h b/drivers/media/pci/ivtv/ivtv-driver.h index a7e00f8..1918e73 100644 --- a/drivers/media/pci/ivtv/ivtv-driver.h +++ b/drivers/media/pci/ivtv/ivtv-driver.h @@ -669,6 +669,13 @@ struct ivtv { atomic_t capturing; /* count number of active capture streams */ atomic_t decoding; /* count number of active decoding streams */ + /* ALSA interface for PCM capture stream */ + struct snd_ivtv_card *alsa; + void (*pcm_announce_callback)(struct snd_ivtv_card *card, u8 *pcm_data, + size_t num_bytes); + + /* Used for ivtv-alsa module loading */ + struct work_struct request_module_wk; /* Interrupts & DMA */ u32 irqmask; /* active interrupts */ @@ -752,6 +759,9 @@ static inline struct ivtv *to_ivtv(struct v4l2_device *v4l2_dev) return container_of(v4l2_dev, struct ivtv, v4l2_dev); } +/* ivtv extensions to be loaded */ +extern int (*ivtv_ext_init)(struct ivtv *); + /* Globals */ extern int ivtv_first_minor; diff --git a/drivers/media/pci/ivtv/ivtv-fileops.c b/drivers/media/pci/ivtv/ivtv-fileops.c index 88bce90..7f2eb5f 100644 --- a/drivers/media/pci/ivtv/ivtv-fileops.c +++ b/drivers/media/pci/ivtv/ivtv-fileops.c @@ -41,7 +41,7 @@ associated VBI streams are also automatically claimed. Possible error returns: -EBUSY if someone else has claimed the stream or 0 on success. */ -static int ivtv_claim_stream(struct ivtv_open_id *id, int type) +int ivtv_claim_stream(struct ivtv_open_id *id, int type) { struct ivtv *itv = id->itv; struct ivtv_stream *s = &itv->streams[type]; @@ -96,6 +96,7 @@ static int ivtv_claim_stream(struct ivtv_open_id *id, int type) set_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags); return 0; } +EXPORT_SYMBOL(ivtv_claim_stream); /* This function releases a previously claimed stream. It will take into account associated VBI streams. */ @@ -146,6 +147,7 @@ void ivtv_release_stream(struct ivtv_stream *s) clear_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags); ivtv_flush_queues(s_vbi); } +EXPORT_SYMBOL(ivtv_release_stream); static void ivtv_dualwatch(struct ivtv *itv) { diff --git a/drivers/media/pci/ivtv/ivtv-fileops.h b/drivers/media/pci/ivtv/ivtv-fileops.h index 049a292..5e08800 100644 --- a/drivers/media/pci/ivtv/ivtv-fileops.h +++ b/drivers/media/pci/ivtv/ivtv-fileops.h @@ -37,8 +37,8 @@ void ivtv_mute(struct ivtv *itv); void ivtv_unmute(struct ivtv *itv); /* Utilities */ - -/* Release a previously claimed stream. */ +/* Shared with ivtv-alsa module */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type); void ivtv_release_stream(struct ivtv_stream *s); #endif diff --git a/drivers/media/pci/ivtv/ivtv-streams.c b/drivers/media/pci/ivtv/ivtv-streams.c index f08ec17..ea61352 100644 --- a/drivers/media/pci/ivtv/ivtv-streams.c +++ b/drivers/media/pci/ivtv/ivtv-streams.c @@ -629,6 +629,7 @@ int ivtv_start_v4l2_encode_stream(struct ivtv_stream *s) atomic_inc(&itv->capturing); return 0; } +EXPORT_SYMBOL(ivtv_start_v4l2_encode_stream); static int ivtv_setup_v4l2_decode_stream(struct ivtv_stream *s) { @@ -885,6 +886,7 @@ int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end) return 0; } +EXPORT_SYMBOL(ivtv_stop_v4l2_encode_stream); int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts) { |