This driver adds support for stk1160 usb bridge as used in some video/audio usb capture devices. It is a complete rewrite of staging/media/easycap driver and it's expected as a future replacement. Signed-off-by: Ezequiel Garcia <elezegarcia@xxxxxxxxx> --- As of today testing has been performed using both vlc and mplayer on a gentoo machine, including hot unplug and on-the-fly standard change using a device with 1-cvs and 1-audio output. However more testing is underway with another device and/or another distribution. Support for multiple input devices is a missing feature. Alsa sound support is a missing feature. (I'm working on both) As this is my first complete driver, the patch (obviously) intended as RFC only. Any comments/reviews of *any* kind will be greatly appreciated. This new version tries to solve the issues pointed out by Hans Verkuil and Sylwester Nawrocki (thanks to both!) Changes from v1: * Use media control framework * Register video device as the last thing * Use v4l_device release to release all resources * Add explicit locking for file operations * Add vb2 buffer sanity check * Minor style cleanups --- drivers/media/video/Kconfig | 2 + drivers/media/video/Makefile | 1 + drivers/media/video/stk1160/Kconfig | 11 + drivers/media/video/stk1160/Makefile | 6 + drivers/media/video/stk1160/stk1160-core.c | 449 ++++++++++++++ drivers/media/video/stk1160/stk1160-i2c.c | 302 ++++++++++ drivers/media/video/stk1160/stk1160-reg.h | 78 +++ drivers/media/video/stk1160/stk1160-v4l.c | 852 +++++++++++++++++++++++++++ drivers/media/video/stk1160/stk1160-video.c | 507 ++++++++++++++++ drivers/media/video/stk1160/stk1160.h | 185 ++++++ 10 files changed, 2393 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/stk1160/Kconfig create mode 100644 drivers/media/video/stk1160/Makefile create mode 100644 drivers/media/video/stk1160/stk1160-core.c create mode 100644 drivers/media/video/stk1160/stk1160-i2c.c create mode 100644 drivers/media/video/stk1160/stk1160-reg.h create mode 100644 drivers/media/video/stk1160/stk1160-v4l.c create mode 100644 drivers/media/video/stk1160/stk1160-video.c create mode 100644 drivers/media/video/stk1160/stk1160.h diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 99937c9..8d94d56 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -661,6 +661,8 @@ source "drivers/media/video/hdpvr/Kconfig" source "drivers/media/video/em28xx/Kconfig" +source "drivers/media/video/stk1160/Kconfig" + source "drivers/media/video/tlg2300/Kconfig" source "drivers/media/video/cx231xx/Kconfig" diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index d209de0..7698b25 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -125,6 +125,7 @@ obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o obj-$(CONFIG_STA2X11_VIP) += sta2x11_vip.o obj-$(CONFIG_VIDEO_TIMBERDALE) += timblogiw.o +obj-$(CONFIG_VIDEO_STK1160) += stk1160/ obj-$(CONFIG_VIDEOBUF_GEN) += videobuf-core.o obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o diff --git a/drivers/media/video/stk1160/Kconfig b/drivers/media/video/stk1160/Kconfig new file mode 100644 index 0000000..d481b8e --- /dev/null +++ b/drivers/media/video/stk1160/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_STK1160 + tristate "STK1160 USB video capture support" + depends on VIDEO_DEV && I2C && EASYCAP!=m && EASYCAP!=y + select VIDEOBUF2_VMALLOC + select VIDEO_SAA711X if VIDEO_HELPER_CHIPS_AUTO + + ---help--- + This is a video4linux driver for STK1160 based video capture devices + + To compile this driver as a module, choose M here: the + module will be called stk1160 diff --git a/drivers/media/video/stk1160/Makefile b/drivers/media/video/stk1160/Makefile new file mode 100644 index 0000000..30ca209 --- /dev/null +++ b/drivers/media/video/stk1160/Makefile @@ -0,0 +1,6 @@ +stk1160-y := stk1160-core.o stk1160-v4l.o stk1160-video.o stk1160-i2c.o + +obj-$(CONFIG_VIDEO_STK1160) += stk1160.o + +ccflags-y += -Wall +ccflags-y += -Idrivers/media/video diff --git a/drivers/media/video/stk1160/stk1160-core.c b/drivers/media/video/stk1160/stk1160-core.c new file mode 100644 index 0000000..b638363 --- /dev/null +++ b/drivers/media/video/stk1160/stk1160-core.c @@ -0,0 +1,449 @@ +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + * + * 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. + * + * TODO: + * 1) Should I dealloc buffers when stop or only on disconnect? + * See em28xx patch about memory fragmentation. + * (Perhaps allow a parameter to control this) + * + * 2) Add debug v4l2 ioctl. + * + * 3) How about framesize and frameinterval v4l2 ioctl? + * + * 4) Replace pr_xxx for dev_xxx. Perhaps will have to use device_{get/put}. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> + +#include <linux/usb.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <media/saa7115.h> + +#include "stk1160.h" +#include "stk1160-reg.h" + +static unsigned int input; +module_param(input, int, 0644); +MODULE_PARM_DESC(input, "Set default input"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ezequiel Garcia"); +MODULE_DESCRIPTION("STK1160 driver"); + +/* Devices supported by this driver */ +static struct usb_device_id stk1160_id_table[] = { + { USB_DEVICE(0x05e1, 0x0408) }, + { } +}; +MODULE_DEVICE_TABLE(usb, stk1160_id_table); + +/* saa7113 I2C address */ +static unsigned short saa7113_addrs[] = { + 0x4a >> 1, + I2C_CLIENT_END +}; + +/* + * Read/Write stk registers + */ +int stk1160_read_reg(struct stk1160 *dev, u16 reg, u8 *value) +{ + int ret; + + *value = 0; + ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x00, + reg, + value, + sizeof(u8), + HZ); + if (ret < 0) { + stk1160_err("read failed on reg 0x%x (%d)\n", + reg, ret); + return ret; + } + return 0; +} + +int stk1160_write_reg(struct stk1160 *dev, u16 reg, u16 value) +{ + int ret; + + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + 0x01, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + reg, + NULL, + 0, + HZ); + if (ret < 0) { + stk1160_err("write failed on reg 0x%x (%d)\n", + reg, ret); + return ret; + } + + return 0; +} + +/* TODO: We should break this into pieces */ +void stk1160_reg_reset(struct stk1160 *dev) +{ + int i; + + static struct regval ctl[] = { + {STK1160_GCTRL, 0x0080}, + {STK1160_GCTRL+2, 0x0000}, + {STK1160_GCTRL+1, 0x0003}, + {STK1160_GCTRL+3, 0x0080}, + + {STK1160_RMCTL+1, 0x0000}, + {STK1160_RMCTL+3, 0x0002}, + + {STK1160_PLLSO, 0x0010}, + {STK1160_PLLSO+1, 0x0000}, + {STK1160_PLLSO+2, 0x0014}, + {STK1160_PLLSO+3, 0x000E}, + + {STK1160_PLLFD, 0x0046}, + + /* Timing generator setup */ + {STK1160_TIGEN, 0x0012}, + {STK1160_TICTL, 0x002D}, + {STK1160_TICTL+1, 0x0001}, + {STK1160_TICTL+2, 0x0000}, + {STK1160_TICTL+3, 0x0000}, + {STK1160_TIGEN, 0x0080}, + + {0xffff, 0xffff} + }; + +#if 0 + switch (dev->ctl_input) { + case 0: + stk1160_write_reg(dev, STK1160_GCTRL, 0x98); + break; + case 1: + stk1160_write_reg(dev, STK1160_GCTRL, 0x90); + break; + case 2: + stk1160_write_reg(dev, STK1160_GCTRL, 0x88); + break; + case 3: + stk1160_write_reg(dev, STK1160_GCTRL, 0x80); + break; + } +#endif + + for (i = 0; ctl[i].reg != 0xffff; i++) + stk1160_write_reg(dev, ctl[i].reg, ctl[i].val); +} + +static void stk1160_release(struct v4l2_device *v4l2_dev) +{ + struct stk1160 *dev = container_of(v4l2_dev, struct stk1160, v4l2_dev); + + stk1160_info("releasing all resources\n"); + + stk1160_i2c_unregister(dev); + + v4l2_ctrl_handler_free(&dev->ctrl_handler); + v4l2_device_unregister(&dev->v4l2_dev); + kfree(dev->alt_max_pkt_size); + kfree(dev); +} + +/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) + +/* + * Scan usb interface and populate max_pkt_size array + * with information on each alternate setting. + * The array should be allocated by the caller. + */ +static int stk1160_scan_usb(struct usb_interface *intf, struct usb_device *udev, + unsigned int *max_pkt_size) +{ + int i, e, sizedescr, size, ifnum, inputnum; + const struct usb_endpoint_descriptor *desc; + + bool has_video = false, has_audio = false; + char *speed; + + ifnum = intf->altsetting[0].desc.bInterfaceNumber; + + /* Get endpoints */ + for (i = 0; i < intf->num_altsetting; i++) { + + for (e = 0; e < intf->altsetting[i].desc.bNumEndpoints; e++) { + + /* This isn't clear enough, at least to me */ + desc = &intf->altsetting[i].endpoint[e].desc; + sizedescr = le16_to_cpu(desc->wMaxPacketSize); + size = sizedescr & 0x7ff; + + if (udev->speed == USB_SPEED_HIGH) + size = size * hb_mult(sizedescr); + + if (usb_endpoint_xfer_isoc(desc) && + usb_endpoint_dir_in(desc)) { + switch (desc->bEndpointAddress) { + case STK1160_EP_AUDIO: + has_audio = true; + break; + case STK1160_EP_VIDEO: + has_video = true; + max_pkt_size[i] = size; + break; + } + } + } + } + + /* Is this even possible? */ + if (!(has_audio || has_video)) { + dev_err(&udev->dev, "no audio or video endpoints found\n"); + return -ENODEV; + } + + switch (udev->speed) { + case USB_SPEED_LOW: + speed = "1.5"; + break; + case USB_SPEED_UNKNOWN: + case USB_SPEED_FULL: + speed = "12"; + break; + case USB_SPEED_HIGH: + speed = "480"; + break; + default: + speed = "unknown"; + } + + dev_info(&udev->dev, "New device %s %s @ %s Mbps (%04x:%04x, interface %d, class %d)\n", + udev->manufacturer ? udev->manufacturer : "", + udev->product ? udev->product : "", + speed, + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + ifnum, + intf->altsetting->desc.bInterfaceNumber); + + if (has_audio) + dev_info(&udev->dev, "audio vendor class interface %d found\n", + ifnum); + + /* TODO: Check if inputnum shows relevant information */ + if (has_video) + dev_info(&udev->dev, "video interface %d found, %d inputs\n", + ifnum, inputnum); + + /* + * Make sure we have 480 Mbps of bandwidth, otherwise things like + * video stream wouldn't likely work, since 12 Mbps is generally + * not enough even for most streams. + */ + if (udev->speed != USB_SPEED_HIGH) { + dev_err(&udev->dev, "must be connected to a high-speed USB 2.0 port\n"); + return -ENODEV; + } + + return 0; +} + +static int stk1160_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int ifnum; + int rc = 0; + + unsigned int *alt_max_pkt_size; /* array of wMaxPacketSize */ + struct usb_device *udev; + struct stk1160 *dev; + + ifnum = interface->altsetting[0].desc.bInterfaceNumber; + udev = interface_to_usbdev(interface); + + /* Don't register audio interfaces */ + if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) { + /* Why spit an error message? */ + stk1160_err("audio device (%04x:%04x): interface %i, class %i\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + ifnum, + interface->altsetting[0].desc.bInterfaceClass); + return -ENODEV; + } + + /* Alloc an array for all possible max_pkt_size */ + alt_max_pkt_size = kmalloc(sizeof(alt_max_pkt_size[0]) * + interface->num_altsetting, GFP_KERNEL); + if (alt_max_pkt_size == NULL) + return -ENOMEM; + + /* + * Scan usb posibilities and populate alt_max_pkt_size array. + * Also, check if device speed is fast enough. + */ + rc = stk1160_scan_usb(interface, udev, alt_max_pkt_size); + if (rc < 0) { + kfree(alt_max_pkt_size); + return rc; + } + + dev = kzalloc(sizeof(struct stk1160), GFP_KERNEL); + if (dev == NULL) { + kfree(alt_max_pkt_size); + return -ENOMEM; + } + + dev->alt_max_pkt_size = alt_max_pkt_size; + dev->udev = udev; + dev->num_alt = interface->num_altsetting; + dev->ctl_input = input; + + /* We save struct device for debug purposes only */ + dev->dev = &interface->dev; + + usb_set_intfdata(interface, dev); + + /* initialize videobuf2 stuff */ + rc = stk1160_vb2_setup(dev); + if (rc < 0) + goto free_err; + + /* + * There is no need to take any locks here in probe + * because we register the device node as the *last* thing. + */ + spin_lock_init(&dev->buf_lock); + mutex_init(&dev->v4l_lock); + + rc = v4l2_ctrl_handler_init(&dev->ctrl_handler, 0); + if (rc) { + stk1160_err("v4l2_ctrl_handler_init failed (%d)\n", rc); + goto free_err; + } + + /* + * We obtain a v4l2_dev but defer + * registration of video device node as the last thing. + * There is no need to set the name if we give a device struct + */ + dev->v4l2_dev.release = stk1160_release; + dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler; + rc = v4l2_device_register(dev->dev, &dev->v4l2_dev); + if (rc) { + stk1160_err("v4l2_device_register failed (%d)\n", rc); + goto free_ctrl; + } + + rc = stk1160_i2c_register(dev); + if (rc < 0) + goto unreg_v4l2; + + /* + * To the best of my knowledge stk1160 boards only have + * saa7113, but it doesn't hurt to support them all. + */ + dev->sd_saa7115 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, + "saa7115_auto", 0, saa7113_addrs); + + stk1160_info("driver ver %s successfully loaded\n", + STK1160_VERSION); + + /* i2c reset saa711x */ + v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0); + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing, + 0, 0, 0); + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0); + + /* reset stk1160 to default values */ + stk1160_reg_reset(dev); + + rc = stk1160_video_register(dev); + if (rc < 0) + goto unreg_i2c; + + return 0; + +unreg_i2c: + stk1160_i2c_unregister(dev); +unreg_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); +free_ctrl: + v4l2_ctrl_handler_free(&dev->ctrl_handler); +free_err: + kfree(alt_max_pkt_size); + kfree(dev); + + return rc; +} + +/* + * TODO: What happens if device gets diconnected while probing? + */ +static void stk1160_disconnect(struct usb_interface *interface) +{ + struct stk1160 *dev; + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* + * Wait until all current v4l2 operation are finished + * then deallocate resources + */ + mutex_lock(&dev->v4l_lock); + + stk1160_stop_streaming(dev, false); + + video_unregister_device(&dev->vdev); + v4l2_device_disconnect(&dev->v4l2_dev); + + /* This way current users can detect device is gone */ + dev->udev = NULL; + + mutex_unlock(&dev->v4l_lock); + + /* + * This calls stk1160_release if it's the last reference. + * therwise, release is posponed until there are no users left. + */ + v4l2_device_put(&dev->v4l2_dev); +} + +static struct usb_driver stk1160_usb_driver = { + .name = "stk1160", + .id_table = stk1160_id_table, + .probe = stk1160_probe, + .disconnect = stk1160_disconnect, +}; + +module_usb_driver(stk1160_usb_driver); diff --git a/drivers/media/video/stk1160/stk1160-i2c.c b/drivers/media/video/stk1160/stk1160-i2c.c new file mode 100644 index 0000000..879517c --- /dev/null +++ b/drivers/media/video/stk1160/stk1160-i2c.c @@ -0,0 +1,302 @@ +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/i2c.h> + +#include "stk1160.h" +#include "stk1160-reg.h" + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#define dprintk_i2c(fmt, args...) \ +do { \ + if (i2c_debug) \ + printk(KERN_DEBUG fmt, ##args); \ +} while (0) + +static int stk1160_i2c_write_reg(struct stk1160 *dev, u8 addr, + u8 reg, u8 value) +{ + int rc, timeout; + u8 flag; + + /* Set serial device address */ + rc = stk1160_write_reg(dev, STK1160_SICTL_SDA, addr); + if (rc < 0) + return rc; + + /* Set i2c device register sub-address */ + rc = stk1160_write_reg(dev, STK1160_SBUSW_WA, reg); + if (rc < 0) + return rc; + + /* Set i2c device register value */ + rc = stk1160_write_reg(dev, STK1160_SBUSW_WD, value); + if (rc < 0) + return rc; + + /* Start write now */ + rc = stk1160_write_reg(dev, STK1160_SICTL, 0x01); + if (rc < 0) + return rc; + + /* Wait until Write Finish bit is set */ + for (timeout = 100; timeout > 0; + timeout -= 20) { + stk1160_read_reg(dev, STK1160_SICTL+1, &flag); + /* write done? */ + if (flag & 0x04) + break; + + msleep(20); + } + + if (timeout == 0) + /* return EBUSY, or EFAULT, or what ? */ + return -EIO; + + return 0; +} + +static int stk1160_i2c_read_reg(struct stk1160 *dev, u8 addr, + u8 reg, u8 *value) +{ + int rc, timeout; + u8 flag; + + /* Set serial device address */ + rc = stk1160_write_reg(dev, STK1160_SICTL_SDA, addr); + if (rc < 0) + return rc; + + /* Set i2c device register sub-address */ + rc = stk1160_write_reg(dev, STK1160_SBUSR_RA, reg); + if (rc < 0) + return rc; + + /* Start read now */ + rc = stk1160_write_reg(dev, STK1160_SICTL, 0x20); + if (rc < 0) + return rc; + + /* Wait until Read Finish bit is set */ + for (timeout = 100; timeout > 0; + timeout -= 20) { + stk1160_read_reg(dev, STK1160_SICTL+1, &flag); + /* read done? */ + if (flag & 0x01) + break; + + msleep(20); + } + + if (timeout == 0) + /* return EBUSY, or EFAULT, or what ? */ + return -EIO; + + stk1160_read_reg(dev, STK1160_SBUSR_RD, value); + if (rc < 0) + return rc; + + return 0; +} + +/* + * stk1160_i2c_check_for_device() + * check if there is a i2c_device at the supplied address + */ +static int stk1160_i2c_check_for_device(struct stk1160 *dev, + unsigned char addr) +{ + int rc, write_timeout; + u8 flag; + + /* Set serial device address */ + rc = stk1160_write_reg(dev, STK1160_SICTL_SDA, addr); + if (rc < 0) + return rc; + + /* Set device sub-address, we'll chip version reg */ + rc = stk1160_write_reg(dev, STK1160_SBUSR_RA, 0x00); + if (rc < 0) + return rc; + + /* Start read now */ + rc = stk1160_write_reg(dev, STK1160_SICTL, 0x20); + if (rc < 0) + return rc; + + /* Wait until Read Finish bit is set */ + for (write_timeout = 40; write_timeout > 0; + write_timeout -= 20) { + stk1160_read_reg(dev, STK1160_SICTL+1, &flag); + if (flag & 0x01) + return 0; + + msleep(20); + } + + return -ENODEV; +} + +/* + * stk1160_i2c_xfer() + * the main i2c transfer function + */ +static int stk1160_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], int num) +{ + struct stk1160 *dev = i2c_adap->algo_data; + int addr, rc, i; + + for (i = 0; i < num; i++) { + addr = msgs[i].addr << 1; + dprintk_i2c("%s: addr=%x", __func__, addr); + + if (!msgs[i].len) { + /* no len: check only for device presence */ + rc = stk1160_i2c_check_for_device(dev, addr); + if (rc < 0) { + dprintk_i2c(" no device\n"); + return rc; + } + + } else if (msgs[i].flags & I2C_M_RD) { + /* read request without preceding register selection */ + dprintk_i2c(" subaddr not selected"); + rc = -EOPNOTSUPP; + goto err; + + } else if (i + 1 < num && msgs[i].len <= 2 && + (msgs[i + 1].flags & I2C_M_RD) && + msgs[i].addr == msgs[i + 1].addr) { + + if (msgs[i].len != 1 || msgs[i + 1].len != 1) { + dprintk_i2c(" len not supported"); + rc = -EOPNOTSUPP; + goto err; + } + + dprintk_i2c(" subaddr=%x", msgs[i].buf[0]); + + rc = stk1160_i2c_read_reg(dev, addr, msgs[i].buf[0], + msgs[i + 1].buf); + + dprintk_i2c(" read=%x", *msgs[i + 1].buf); + + /* consumed two msgs, so we skip one of them */ + i++; + + } else { + if (msgs[i].len != 2) { + dprintk_i2c(" len not supported"); + rc = -EOPNOTSUPP; + goto err; + } + + dprintk_i2c(" subaddr=%x write=%x", + msgs[i].buf[0], msgs[i].buf[1]); + + rc = stk1160_i2c_write_reg(dev, addr, msgs[i].buf[0], + msgs[i].buf[1]); + } + + if (rc < 0) + goto err; + dprintk_i2c(" OK\n"); + } + + return num; +err: + dprintk_i2c(" ERROR: %d\n", rc); + return num; +} + +/* + * functionality(), what da heck is this? + */ +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm algo = { + .master_xfer = stk1160_i2c_xfer, + .functionality = functionality, +}; + +static struct i2c_adapter adap_template = { + .owner = THIS_MODULE, + .name = "stk1160", + .algo = &algo, +}; + +static struct i2c_client client_template = { + .name = "stk1160 internal", +}; + +/* + * stk1160_i2c_register() + * register i2c bus + */ +int stk1160_i2c_register(struct stk1160 *dev) +{ + int rc; + + dev->i2c_adap = adap_template; + dev->i2c_adap.dev.parent = dev->dev; + strcpy(dev->i2c_adap.name, "stk1160"); + dev->i2c_adap.algo_data = dev; + + i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev); + + rc = i2c_add_adapter(&dev->i2c_adap); + if (rc < 0) { + stk1160_err("cannot add i2c adapter (%d)\n", rc); + return rc; + } + + dev->i2c_client = client_template; + dev->i2c_client.adapter = &dev->i2c_adap; + + /* Set i2c clock divider device address */ + stk1160_write_reg(dev, STK1160_SICTL_CD, 0x0f); + + /* ??? */ + stk1160_write_reg(dev, STK1160_ASIC + 3, 0x00); + + return 0; +} + +/* + * stk1160_i2c_unregister() + * unregister i2c_bus + */ +int stk1160_i2c_unregister(struct stk1160 *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} diff --git a/drivers/media/video/stk1160/stk1160-reg.h b/drivers/media/video/stk1160/stk1160-reg.h new file mode 100644 index 0000000..99cc155 --- /dev/null +++ b/drivers/media/video/stk1160/stk1160-reg.h @@ -0,0 +1,78 @@ +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + * + * 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. + * + */ + +/* GPIO Control */ +#define STK1160_GCTRL 0x000 + +/* Remote Wakup Control */ +#define STK1160_RMCTL 0x00C + +/* + * Decoder Control Register: + * This byte controls capture start/stop + * with bit #7 (0x?? OR 0x80 to activate). + */ +#define STK1160_DCTRL 0x100 + +/* Capture Frame Start Position */ +#define STK116_CFSPO 0x110 +#define STK116_CFSPO_STX_L 0x110 +#define STK116_CFSPO_STX_H 0x111 +#define STK116_CFSPO_STY_L 0x112 +#define STK116_CFSPO_STY_H 0x113 + +/* Capture Frame End Position */ +#define STK116_CFEPO 0x114 +#define STK116_CFEPO_ENX_L 0x114 +#define STK116_CFEPO_ENX_H 0x115 +#define STK116_CFEPO_ENY_L 0x116 +#define STK116_CFEPO_ENY_H 0x117 + +/* Serial Interface Control */ +#define STK1160_SICTL 0x200 +#define STK1160_SICTL_CD 0x202 +#define STK1160_SICTL_SDA 0x203 + +/* Serial Bus Write */ +#define STK1160_SBUSW 0x204 +#define STK1160_SBUSW_WA 0x204 +#define STK1160_SBUSW_WD 0x205 + +/* Serial Bus Read */ +#define STK1160_SBUSR 0x208 +#define STK1160_SBUSR_RA 0x208 +#define STK1160_SBUSR_RD 0x209 + +/* Alternate Serial Inteface Control */ +#define STK1160_ASIC 0x2FC + +/* PLL Select Options */ +#define STK1160_PLLSO 0x018 + +/* PLL Frequency Divider */ +#define STK1160_PLLFD 0x01C + +/* Timing Generator */ +#define STK1160_TIGEN 0x300 + +/* Timing Control Parameter */ +#define STK1160_TICTL 0x350 diff --git a/drivers/media/video/stk1160/stk1160-v4l.c b/drivers/media/video/stk1160/stk1160-v4l.c new file mode 100644 index 0000000..67c1b2b --- /dev/null +++ b/drivers/media/video/stk1160/stk1160-v4l.c @@ -0,0 +1,852 @@ +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fh.h> +#include <media/videobuf2-vmalloc.h> + +#include <media/saa7115.h> + +#include "stk1160.h" +#include "stk1160-reg.h" + +static unsigned int vidioc_debug; +module_param(vidioc_debug, int, 0644); +MODULE_PARM_DESC(vidioc_debug, "enable debug messages [vidioc]"); + +/* supported video standards */ +static struct stk1160_fmt format[] = { + { + .name = "16 bpp YUY2, 4:2:2, packed", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + } +}; + +static void stk1160_set_std(struct stk1160 *dev) +{ + int i; + + static struct regval std525[] = { + + /* 720x480 */ + + /* Frame start */ + {STK116_CFSPO_STX_L, 0x0000}, + {STK116_CFSPO_STX_H, 0x0000}, + {STK116_CFSPO_STY_L, 0x0003}, + {STK116_CFSPO_STY_H, 0x0000}, + + /* Frame end */ + {STK116_CFEPO_ENX_L, 0x05a0}, + {STK116_CFEPO_ENX_H, 0x0005}, + {STK116_CFEPO_ENY_L, 0x00f3}, + {STK116_CFEPO_ENY_H, 0x0000}, + + {0xffff, 0xffff} + }; + + static struct regval std625[] = { + + /* 720x576 */ + + /* TODO: Each line of frame has some junk at the end */ + /* Frame start */ + {STK116_CFSPO, 0x0000}, + {STK116_CFSPO+1, 0x0000}, + {STK116_CFSPO+2, 0x0001}, + {STK116_CFSPO+3, 0x0000}, + + /* Frame end */ + {STK116_CFEPO, 0x05a0}, + {STK116_CFEPO+1, 0x0005}, + {STK116_CFEPO+2, 0x0121}, + {STK116_CFEPO+3, 0x0001}, + + {0xffff, 0xffff} + }; + + if (dev->vdev.current_norm & V4L2_STD_525_60) { + stk1160_dbg("registers to NTSC like standard\n"); + for (i = 0; std525[i].reg != 0xffff; i++) + stk1160_write_reg(dev, std525[i].reg, std525[i].val); + } else if (dev->vdev.current_norm & V4L2_STD_625_50) { + stk1160_dbg("registers to PAL like standard\n"); + for (i = 0; std625[i].reg != 0xffff; i++) + stk1160_write_reg(dev, std625[i].reg, std625[i].val); + } else { + BUG(); + } + +} + +static int stk1160_set_alternate(struct stk1160 *dev) +{ + int i, prev_alt = dev->alt; + unsigned int min_pkt_size; + + /* + * If we don't set right alternate, + * then we will get a green screen with junk. + */ + min_pkt_size = STK1160_MIN_PKT_SIZE; + + for (i = 0; i < dev->num_alt; i++) { + /* stop when the selected alt setting offers enough bandwidth */ + if (dev->alt_max_pkt_size[i] >= min_pkt_size) { + dev->alt = i; + break; + /* + * otherwise make sure that we end up with the maximum bandwidth + * because the min_pkt_size equation might be wrong... + */ + } else if (dev->alt_max_pkt_size[i] > + dev->alt_max_pkt_size[dev->alt]) + dev->alt = i; + } + + stk1160_dbg("setting alternate %d\n", dev->alt); + + if (dev->alt != prev_alt) { + stk1160_dbg("minimum isoc packet size: %u (alt=%d)\n", + min_pkt_size, dev->alt); + dev->max_pkt_size = dev->alt_max_pkt_size[dev->alt]; + stk1160_dbg("setting alt %d with wMaxPacketSize=%u\n", + dev->alt, dev->max_pkt_size); + usb_set_interface(dev->udev, 0, dev->alt); + } + return 0; +} + +static bool stk1160_acquire_owner(struct stk1160 *dev, struct file *file) +{ + /* If there is an owner and it's not this filehandle */ + if (dev->fh_owner != NULL && dev->fh_owner != file) + return false; + + /* We are the owner of this queue and queue operations */ + dev->fh_owner = file; + + return true; +} + +static void stk1160_drop_owner(struct stk1160 *dev) +{ + dev->fh_owner = NULL; +} + +static bool stk1160_is_owner(struct stk1160 *dev, struct file *file) +{ + return dev->fh_owner == file; +} + +static int stk1160_start_streaming(struct stk1160 *dev) +{ + int i, rc; + + /* Check device presence */ + if (!dev->udev) + return -ENODEV; + + /* + * For some reason it is mandatory to set alternate *first* + * and only *then* initialize isoc urbs. + * Someone please explain me why ;) + */ + rc = stk1160_set_alternate(dev); + if (rc < 0) + return rc; + + /* If we haven't allocated any isoc urbs */ + if (!dev->isoc_ctl.num_bufs) { + rc = stk1160_init_isoc(dev); + if (rc < 0) + return rc; + } + + /* submit urbs and enables IRQ */ + for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { + rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_KERNEL); + if (rc) { + stk1160_err("cannot submit urb[%d] (%d)\n", i, rc); + stk1160_uninit_isoc(dev); + return rc; + } + } + + /* Start saa711x */ + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 1); + + /* Start stk1160 */ + stk1160_write_reg(dev, STK1160_DCTRL, 0xb3); + stk1160_write_reg(dev, STK1160_DCTRL+3, 0x00); + + stk1160_dbg("streaming started\n"); + + return 0; +} + +int stk1160_stop_streaming(struct stk1160 *dev, bool connected) +{ + struct stk1160_buffer *buf; + unsigned long flags = 0; + + stk1160_uninit_isoc(dev); + + /* If the device is physically plugged */ + if (connected && dev->udev) { + + /* set alternate 0 */ + dev->alt = 0; + stk1160_info("setting alternate %d\n", dev->alt); + usb_set_interface(dev->udev, 0, 0); + + /* Stop stk1160 */ + stk1160_write_reg(dev, STK1160_DCTRL, 0x00); + stk1160_write_reg(dev, STK1160_DCTRL+3, 0x00); + + /* Stop saa711x */ + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0); + } + + /* Release all active buffers */ + spin_lock_irqsave(&dev->buf_lock, flags); + while (!list_empty(&dev->avail_bufs)) { + buf = list_first_entry(&dev->avail_bufs, + struct stk1160_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + stk1160_info("buffer [%p/%d] aborted\n", + buf, buf->vb.v4l2_buf.index); + } + spin_unlock_irqrestore(&dev->buf_lock, flags); + + stk1160_dbg("streaming stopped\n"); + return 0; +} + +/* fops */ +static ssize_t stk1160_read(struct file *file, + char __user *data, size_t count, loff_t *ppos) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + mutex_lock(&dev->v4l_lock); + /* + * Read operation is emulated by videobuf2. + * When vb2 calls reqbufs it acquires ownership of queue. + * When the transfer is done, vb2 calls reqbufs with zero count, + * dropping ownership. + */ + rc = vb2_read(&dev->vb_vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); + + mutex_unlock(&dev->v4l_lock); + return rc; +} + +static unsigned int +stk1160_poll(struct file *file, struct poll_table_struct *wait) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + mutex_lock(&dev->v4l_lock); + rc = vb2_poll(&dev->vb_vidq, file, wait); + mutex_unlock(&dev->v4l_lock); + + return rc; +} + +static int stk1160_close(struct file *file) +{ + struct stk1160 *dev = video_drvdata(file); + + mutex_lock(&dev->v4l_lock); + /* + * If this is the owner handle we stop + * streaming to free/dequeue all buffers. + * Also, we drop ownership. + */ + if (stk1160_is_owner(dev, file)) { + vb2_queue_release(&dev->vb_vidq); + stk1160_drop_owner(dev); + } + mutex_unlock(&dev->v4l_lock); + + return v4l2_fh_release(file); +} + +static int stk1160_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + stk1160_dbg("vma=0x%08lx\n", (unsigned long)vma); + + /* TODO: Lock or trylock? */ + mutex_lock(&dev->v4l_lock); + rc = vb2_mmap(&dev->vb_vidq, vma); + mutex_unlock(&dev->v4l_lock); + + stk1160_dbg("vma start=0x%08lx, size=%ld (%d)\n", + (unsigned long)vma->vm_start, + (unsigned long)vma->vm_end - (unsigned long)vma->vm_start, + rc); + return rc; +} + +static struct v4l2_file_operations stk1160_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = stk1160_close, + .read = stk1160_read, + .poll = stk1160_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = stk1160_mmap, +}; + +/* + * vb2 ioctls + */ +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + if (!stk1160_acquire_owner(dev, file)) + return -EBUSY; + + rc = vb2_reqbufs(&dev->vb_vidq, p); + + /* + * If reqbufs has been called with count == 0 + * it means the owner is releasing the queue, + * thus dropping ownership. + */ + if (p->count == 0) + stk1160_drop_owner(dev); + + return rc; +} + +static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + if (!stk1160_is_owner(dev, file)) + return -EBUSY; + + rc = vb2_querybuf(&dev->vb_vidq, p); + + return rc; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + if (!stk1160_is_owner(dev, file)) + return -EBUSY; + + rc = vb2_qbuf(&dev->vb_vidq, p); + + return rc; +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + if (!stk1160_is_owner(dev, file)) + return -EBUSY; + + rc = vb2_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK); + + return rc; +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + if (!stk1160_is_owner(dev, file)) + return -EBUSY; + + rc = vb2_streamon(&dev->vb_vidq, i); + + return rc; +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + + if (!stk1160_is_owner(dev, file)) + return -EBUSY; + + rc = vb2_streamoff(&dev->vb_vidq, i); + + return rc; +} + +/* + * vidioc ioctls + */ +static int vidioc_querycap(struct file *file, + void *priv, struct v4l2_capability *cap) +{ + strcpy(cap->driver, "stk1160"); + strcpy(cap->card, "stk1160"); + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + strlcpy(f->description, format[f->index].name, sizeof(f->description)); + f->pixelformat = format[f->index].fourcc; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stk1160 *dev = video_drvdata(file); + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.pixelformat = dev->fmt->fourcc; + f->fmt.pix.bytesperline = dev->width * 2; + f->fmt.pix.sizeimage = dev->height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stk1160 *dev = video_drvdata(file); + + if (f->fmt.pix.pixelformat != format[0].fourcc) { + stk1160_err("fourcc format 0x%08x invalid\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + /* + * User can't choose size at his own will, + * so we just return him the current size chosen + * at standard selection. + * TODO: Implement frame scaling? + */ + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.bytesperline = dev->width * 2; + f->fmt.pix.sizeimage = dev->height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stk1160 *dev = video_drvdata(file); + struct vb2_queue *q = &dev->vb_vidq; + int rc; + + if (!stk1160_acquire_owner(dev, file)) + return -EBUSY; + + rc = vidioc_try_fmt_vid_cap(file, priv, f); + if (rc < 0) + return rc; + + if (vb2_is_streaming(q)) { + stk1160_err("device busy\n"); + return -EBUSY; + } + + return 0; +} + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *norm) +{ + struct stk1160 *dev = video_drvdata(file); + v4l2_device_call_all(&dev->v4l2_dev, 0, video, querystd, norm); + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *norm) +{ + struct stk1160 *dev = video_drvdata(file); + struct vb2_queue *q = &dev->vb_vidq; + + if (!stk1160_acquire_owner(dev, file)) + return -EBUSY; + + if (vb2_is_streaming(q)) { + stk1160_err("device busy\n"); + return -EBUSY; + } + + /* Check device presence */ + if (!dev->udev) + return -ENODEV; + + /* This is taken from saa7115 video decoder */ + if (dev->vdev.current_norm & V4L2_STD_525_60) { + dev->width = 720; + dev->height = 480; + } else if (dev->vdev.current_norm & V4L2_STD_625_50) { + dev->width = 720; + dev->height = 576; + } else { + stk1160_err("invalid standard\n"); + return -EINVAL; + } + + /* We need to set this now, before we call stk1160_set_std */ + dev->vdev.current_norm = *norm; + + stk1160_set_std(dev); + + v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, + dev->vdev.current_norm); + + return 0; +} + +/* FIXME: Extend support for other inputs */ +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct stk1160 *dev = video_drvdata(file); + + if (i->index > 0) + return -EINVAL; + + sprintf(i->name, "Composite%d", i->index); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = dev->vdev.tvnorms; + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct stk1160 *dev = video_drvdata(file); + *i = dev->ctl_input; + return 0; +} + +/* FIXME: Extend support for other inputs */ +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct stk1160 *dev = video_drvdata(file); + + if (!stk1160_acquire_owner(dev, file)) + return -EBUSY; + + if (i > 0) + return -EINVAL; + + /* + * Right now we only support one input + * This is the next feature we'll implement. + */ +#if 0 + dev->ctl_input = i; + + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing, + dev->ctl_input, 0, 0); + + switch (dev->ctl_input) { + case 1: + stk1160_write_reg(dev, 0x0000, 0x0098); + break; + case 2: + stk1160_write_reg(dev, 0x0000, 0x0090); + break; + case 3: + stk1160_write_reg(dev, 0x0000, 0x0088); + break; + case 4: + stk1160_write_reg(dev, 0x0000, 0x0080); + break; + } + stk1160_write_reg(dev, 0x0002, 0x0078 /* 0x0093 */); +#endif + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + /* TODO: Is this needed? */ + return -EINVAL; +} + +static int vidioc_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + /* TODO: Is this needed? */ + return -EINVAL; +} + +static const struct v4l2_ioctl_ops stk1160_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_querystd = vidioc_querystd, + .vidioc_g_std = NULL, /* don't worry v4l handles this */ + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + + /* vb2 takes care of these */ + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + + /* TODO: Add debug ioctls: s/g_register, log_status, etc */ +}; + +/********************************************************************/ + +/* + * Videobuf2 operations + */ +static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *v4l_fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct stk1160 *dev = vb2_get_drv_priv(vq); + unsigned long size; + + size = dev->width * dev->height * 2; + + /* + * Here we can change the number of buffers being requested. + * So, we set a minimum and a maximum like this: + */ + *nbuffers = clamp_t(unsigned int, *nbuffers, + STK1160_MIN_VIDEO_BUFFERS, STK1160_MAX_VIDEO_BUFFERS); + + /* This means a packed colorformat */ + *nplanes = 1; + + sizes[0] = size; + + stk1160_info("%s: buffer count %d, each %ld bytes\n", + __func__, *nbuffers, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + unsigned long flags = 0; + struct stk1160 *dev = vb2_get_drv_priv(vb->vb2_queue); + struct stk1160_buffer *buf = + container_of(vb, struct stk1160_buffer, vb); + + spin_lock_irqsave(&dev->buf_lock, flags); + if (!dev->udev) { + /* + * If the device is disconnected return the buffer to userspace + * directly. The next QBUF call will fail with -ENODEV. + */ + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } else { + + buf->mem = vb2_plane_vaddr(vb, 0); + buf->length = vb2_plane_size(vb, 0); + buf->bytesused = 0; + buf->pos = 0; + + /* + * If buffer length is different from expected then we return + * the buffer to userspace directly. + */ + if (buf->length != dev->width * dev->height * 2) + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + else + list_add_tail(&buf->list, &dev->avail_bufs); + + } + spin_unlock_irqrestore(&dev->buf_lock, flags); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct stk1160 *dev = vb2_get_drv_priv(vq); + int rc; + + rc = stk1160_start_streaming(dev); + + return rc; +} + +/* abort streaming and wait for last buffer */ +static int stop_streaming(struct vb2_queue *vq) +{ + struct stk1160 *dev = vb2_get_drv_priv(vq); + int rc; + + rc = stk1160_stop_streaming(dev, true); + + return rc; +} + +static void stk1160_lock(struct vb2_queue *vq) +{ + struct stk1160 *dev = vb2_get_drv_priv(vq); + mutex_lock(&dev->v4l_lock); +} + +static void stk1160_unlock(struct vb2_queue *vq) +{ + struct stk1160 *dev = vb2_get_drv_priv(vq); + mutex_unlock(&dev->v4l_lock); +} + +static struct vb2_ops stk1160_video_qops = { + .queue_setup = queue_setup, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = stk1160_unlock, + .wait_finish = stk1160_lock, +}; + +static struct video_device v4l_template = { + .name = "stk1160", + .tvnorms = V4L2_STD_525_60 | V4L2_STD_625_50, + .fops = &stk1160_fops, + .ioctl_ops = &stk1160_ioctl_ops, + .release = video_device_release_empty, +}; + +/********************************************************************/ + +int stk1160_vb2_setup(struct stk1160 *dev) +{ + int rc; + struct vb2_queue *q; + + q = &dev->vb_vidq; + memset(q, 0, sizeof(dev->vb_vidq)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct stk1160_buffer); + q->ops = &stk1160_video_qops; + q->mem_ops = &vb2_vmalloc_memops; + + rc = vb2_queue_init(q); + if (rc < 0) + return rc; + + /* initialize video dma queue */ + INIT_LIST_HEAD(&dev->avail_bufs); + + return 0; +} + +int stk1160_video_register(struct stk1160 *dev) +{ + int rc; + + /* Initialize video_device with a template structure */ + dev->vdev = v4l_template; + dev->vdev.debug = vidioc_debug; + + /* + * Provide a mutex to v4l2 core. + * It will be used to protect *only* v4l2 ioctls. + */ + dev->vdev.lock = &dev->v4l_lock; + + /* This will be used to set video_device parent */ + dev->vdev.v4l2_dev = &dev->v4l2_dev; + + /* NTSC is default. Could be any other, but has to match + * with video_device current_norm field */ + dev->vdev.current_norm = V4L2_STD_NTSC_M; + dev->width = 720; + dev->height = 480; + + /* set default format */ + dev->fmt = &format[0]; + stk1160_set_std(dev); + + v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, + dev->vdev.current_norm); + + video_set_drvdata(&dev->vdev, dev); + rc = video_register_device(&dev->vdev, VFL_TYPE_GRABBER, -1); + if (rc < 0) { + stk1160_err("video_register_device failed (%d)\n", rc); + return rc; + } + + v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n", + video_device_node_name(&dev->vdev)); + + return 0; +} diff --git a/drivers/media/video/stk1160/stk1160-video.c b/drivers/media/video/stk1160/stk1160-video.c new file mode 100644 index 0000000..3a74550 --- /dev/null +++ b/drivers/media/video/stk1160/stk1160-video.c @@ -0,0 +1,507 @@ +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/ratelimit.h> + +#include "stk1160.h" + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +static inline void print_err_status(struct stk1160 *dev, + int packet, int status) +{ + char *errmsg = "Unknown"; + + switch (status) { + case -ENOENT: + errmsg = "unlinked synchronuously"; + break; + case -ECONNRESET: + errmsg = "unlinked asynchronuously"; + break; + case -ENOSR: + errmsg = "Buffer error (overrun)"; + break; + case -EPIPE: + errmsg = "Stalled (device not responding)"; + break; + case -EOVERFLOW: + errmsg = "Babble (bad cable?)"; + break; + case -EPROTO: + errmsg = "Bit-stuff error (bad cable?)"; + break; + case -EILSEQ: + errmsg = "CRC/Timeout (could be anything)"; + break; + case -ETIME: + errmsg = "Device does not respond"; + break; + } + + if (packet < 0) + printk_ratelimited(KERN_WARNING "URB status %d [%s].\n", + status, errmsg); + else + printk_ratelimited(KERN_INFO "URB packet %d, status %d [%s].\n", + packet, status, errmsg); +} + +static inline +struct stk1160_buffer *stk1160_next_buffer(struct stk1160 *dev) +{ + struct stk1160_buffer *buf = NULL; + unsigned long flags = 0; + + /* Current buffer must be NULL when this functions gets called */ + BUG_ON(dev->isoc_ctl.buf); + + spin_lock_irqsave(&dev->buf_lock, flags); + if (!list_empty(&dev->avail_bufs)) { + buf = list_first_entry(&dev->avail_bufs, + struct stk1160_buffer, list); + list_del(&buf->list); + } + spin_unlock_irqrestore(&dev->buf_lock, flags); + + return buf; +} + +static inline +void stk1160_buffer_done(struct stk1160 *dev) +{ + struct stk1160_buffer *buf = dev->isoc_ctl.buf; + + dev->field_count++; + + buf->vb.v4l2_buf.sequence = dev->field_count >> 1; + buf->vb.v4l2_buf.field = V4L2_FIELD_INTERLACED; + buf->vb.v4l2_buf.bytesused = buf->bytesused; + do_gettimeofday(&buf->vb.v4l2_buf.timestamp); + + vb2_set_plane_payload(&buf->vb, 0, buf->bytesused); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE); + + dev->isoc_ctl.buf = NULL; +} + +static inline +void stk1160_copy_video(struct stk1160 *dev, u8 *src, int len) +{ + int linesdone, lineoff, lencopy; + int bytesperline = dev->width * 2; + struct stk1160_buffer *buf = dev->isoc_ctl.buf; + u8 *dst = buf->mem; + int remain; + + /* + * TODO: These stk1160_dbg are very spammy! + * We should 1) check why we are getting them + * and 2) add ratelimit. + * + * UPDATE: One of the reasons (the only one?) for getting these + * is incorrect standard (mismatch between expected and configured). + * So perhaps, we could add a counter for errors. When the counter + * reaches some value, we simply stop streaming. + */ + + /* Sanity check */ + if (buf->mem == NULL || buf->length == 0) { + stk1160_dbg("copy skipped: null buffer\n"); + return; + } + + len -= 4; + src += 4; + + remain = len; + + linesdone = buf->pos / bytesperline; + lineoff = buf->pos % bytesperline; /* offset in current line */ + + if (!buf->odd) + dst += bytesperline; + + /* Multiply linesdone by two, to take account of the other field */ + dst += linesdone * bytesperline * 2 + lineoff; + + /* Copy the remaining of current line */ + if (remain < (bytesperline - lineoff)) + lencopy = remain; + else + lencopy = bytesperline - lineoff; + + /* + * Check if we have enough space left in the buffer. + * In that case, we force loop exit after copy. + */ + if (lencopy > buf->bytesused - buf->length) { + lencopy = buf->bytesused - buf->length; + remain = lencopy; + } + + /* Let the bug hunt begin! sanity checks! */ + if (lencopy < 0) { + stk1160_dbg("copy skipped: negative lencopy\n"); + return; + } + + if (buf->pos > buf->length) { + stk1160_dbg("copy skipped: pos > length\n"); + return; + } + + if (buf->pos + lencopy > buf->length) { + stk1160_dbg("copy skipped: pos + lencopy > length\n"); + return; + } + + if ((unsigned long)dst + lencopy > + (unsigned long)buf->mem + buf->length) { + + stk1160_dbg("copy skipped: dst + lencopy > mem + length\n"); + return; + } + + if (lencopy > len) { + stk1160_dbg("copy skipped: lencopy > len)\n"); + return; + } + + if ((unsigned long)dst > (unsigned long)buf->mem + buf->length) { + stk1160_dbg("copy skipped: dst > mem + length\n"); + return; + } + + /* Crash was found here */ + memcpy(dst, src, lencopy); + + buf->bytesused += lencopy; + buf->pos += lencopy; + remain -= lencopy; + + /* Copy current field line by line, interlacing with the other field */ + while (remain > 0) { + + dst += lencopy + bytesperline; + src += lencopy; + + /* Copy one line at a time */ + if (remain < bytesperline) + lencopy = remain; + else + lencopy = bytesperline; + + /* + * Check if we have enough space left in the buffer. + * In that case, we force loop exit after copy. + */ + if (lencopy > buf->bytesused - buf->length) { + lencopy = buf->bytesused - buf->length; + remain = lencopy; + } + + /* Let the bug hunt begin! sanity checks */ + if (lencopy <= 0) { + stk1160_dbg("copy skipped: negative lencopy\n"); + return; + } + + if (buf->pos > buf->length) { + stk1160_dbg("copy skipped: pos > length\n"); + return; + } + + if (buf->pos + lencopy > buf->length) { + stk1160_dbg("copy skipped: pos + lencopy > length\n"); + return; + } + + if ((unsigned long)dst + lencopy > + (unsigned long)buf->mem + buf->length) { + stk1160_dbg("copy skipped: dst + lencopy > mem + length\n"); + return; + } + + if (lencopy > len) { + stk1160_dbg("copy skipped: lencopy > len)\n"); + return; + } + + memcpy(dst, src, lencopy); + remain -= lencopy; + + buf->bytesused += lencopy; + buf->pos += lencopy; + } + + if (remain != 0) + stk1160_dbg("remain should be null, instead of %d\n", remain); +} + +/* + * Controls the isoc copy of each urb packet + */ +static void stk1160_process_isoc(struct stk1160 *dev, struct urb *urb) +{ + int i, len, status; + u8 *p; + + if (!dev) { + stk1160_warn("%s called with null device\n", __func__); + return; + } + + if (urb->status < 0) { + /* Print status and drop current packet (or field?) */ + print_err_status(dev, -1, urb->status); + return; + } + + for (i = 0; i < urb->number_of_packets; i++) { + status = urb->iso_frame_desc[i].status; + if (status < 0) { + print_err_status(dev, i, status); + continue; + } + + /* Get packet actual length and pointer to data */ + p = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + len = urb->iso_frame_desc[i].actual_length; + + /* Empty packet */ + if (len <= 4) + continue; + + /* + * An 8-byte packet sequence means end of field. + * So if we don't have any packet, we start receiving one now + * and if we do have a packet, then we are done with it. + * + * These end of field packets are always 0xc0 or 0x80, + * but not always 8-byte long so we don't check packet length. + */ + if (p[0] == 0xc0) { + + /* + * If first byte is 0xc0 then we received + * second field, and frame has ended. + */ + if (dev->isoc_ctl.buf != NULL) + stk1160_buffer_done(dev); + + dev->isoc_ctl.buf = stk1160_next_buffer(dev); + if (dev->isoc_ctl.buf == NULL) + return; + } + + /* + * If we don't have a buffer here, then it means we + * haven't found the start mark sequence. + */ + if (dev->isoc_ctl.buf == NULL) + continue; + + if (p[0] == 0xc0 || p[0] == 0x80) { + + /* We set next packet parity and + * continue to get next one + */ + dev->isoc_ctl.buf->odd = *p & 0x40; + dev->isoc_ctl.buf->pos = 0; + continue; + } + + stk1160_copy_video(dev, p, len); + } +} + + +/* + * IRQ callback, called by URB callback + */ +static void stk1160_isoc_irq(struct urb *urb) +{ + int i, rc; + struct stk1160 *dev = urb->context; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: /* kill */ + case -ENOENT: + case -ESHUTDOWN: + /* TODO: check uvc driver: he frees the queue here */ + return; + default: + stk1160_err("urb error! status %d\n", urb->status); + return; + } + + stk1160_process_isoc(dev, urb); + + /* Reset urb buffers */ + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = 0; + } + + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc) + stk1160_err("urb re-submit failed (%d)\n", rc); +} + +/* + * Deallocate URBs + * This function can't be called in atomic context + */ +void stk1160_uninit_isoc(struct stk1160 *dev) +{ + struct urb *urb; + int i; + + stk1160_dbg("killing urbs...\n"); + + for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { + + urb = dev->isoc_ctl.urb[i]; + if (urb) { + /* To kill urbs we can't be in atomic context */ + usb_kill_urb(urb); + + if (dev->isoc_ctl.transfer_buffer[i]) { +#ifndef CONFIG_DMA_NONCOHERENT + usb_free_coherent(dev->udev, + urb->transfer_buffer_length, + dev->isoc_ctl.transfer_buffer[i], + urb->transfer_dma); +#else + kfree(dev->isoc_ctl.transfer_buffer[i]); +#endif + } + usb_free_urb(urb); + dev->isoc_ctl.urb[i] = NULL; + } + dev->isoc_ctl.transfer_buffer[i] = NULL; + } + + kfree(dev->isoc_ctl.urb); + kfree(dev->isoc_ctl.transfer_buffer); + + dev->isoc_ctl.urb = NULL; + dev->isoc_ctl.transfer_buffer = NULL; + dev->isoc_ctl.num_bufs = 0; + + stk1160_dbg("all urbs killed\n"); +} + +/* + * Allocate URBs + */ +int stk1160_init_isoc(struct stk1160 *dev) +{ + struct urb *urb; + int i, j, k, sb_size, max_packets, num_bufs; + + num_bufs = STK1160_NUM_BUFS; + max_packets = STK1160_NUM_PACKETS; + sb_size = max_packets * dev->max_pkt_size; + + dev->isoc_ctl.buf = NULL; + dev->isoc_ctl.max_pkt_size = dev->max_pkt_size; + dev->isoc_ctl.urb = kzalloc(sizeof(void *)*num_bufs, GFP_KERNEL); + if (!dev->isoc_ctl.urb) { + stk1160_err("out of memory for urb array\n"); + return -ENOMEM; + } + + dev->isoc_ctl.transfer_buffer = kzalloc(sizeof(void *)*num_bufs, + GFP_KERNEL); + if (!dev->isoc_ctl.transfer_buffer) { + stk1160_err("out of memory for usb transfers\n"); + kfree(dev->isoc_ctl.urb); + return -ENOMEM; + } + + /* allocate urbs and transfer buffers */ + for (i = 0; i < num_bufs; i++) { + + urb = usb_alloc_urb(max_packets, GFP_KERNEL); + if (!urb) { + stk1160_err("cannot alloc urb[%d]\n", i); + stk1160_uninit_isoc(dev); + return -ENOMEM; + } + dev->isoc_ctl.urb[i] = urb; + +#ifndef CONFIG_DMA_NONCOHERENT + dev->isoc_ctl.transfer_buffer[i] = usb_alloc_coherent(dev->udev, + sb_size, GFP_KERNEL, &urb->transfer_dma); +#else + dev->isoc_ctl.transfer_buffer[i] = kmalloc(sb_size, GFP_KERNEL); +#endif + if (!dev->isoc_ctl.transfer_buffer[i]) { + stk1160_err("cannot alloc %d bytes for tx buffer\n", + sb_size); + stk1160_uninit_isoc(dev); + return -ENOMEM; + } + memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size); + + /* + * FIXME: Where can I get the endpoint? + */ + urb->dev = dev->udev; + urb->pipe = usb_rcvisocpipe(dev->udev, STK1160_EP_VIDEO); + urb->transfer_buffer = dev->isoc_ctl.transfer_buffer[i]; + urb->transfer_buffer_length = sb_size; + urb->complete = stk1160_isoc_irq; + urb->context = dev; + urb->interval = 1; + urb->start_frame = 0; + urb->number_of_packets = max_packets; +#ifndef CONFIG_DMA_NONCOHERENT + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; +#else + urb->transfer_flags = URB_ISO_ASAP; +#endif + + k = 0; + for (j = 0; j < max_packets; j++) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = + dev->isoc_ctl.max_pkt_size; + k += dev->isoc_ctl.max_pkt_size; + } + } + + /* At last we can say we have some buffers */ + dev->isoc_ctl.num_bufs = num_bufs; + + return 0; +} + diff --git a/drivers/media/video/stk1160/stk1160.h b/drivers/media/video/stk1160/stk1160.h new file mode 100644 index 0000000..67148de --- /dev/null +++ b/drivers/media/video/stk1160/stk1160.h @@ -0,0 +1,185 @@ +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + * + * 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. + * + */ + +#include <linux/i2c.h> +#include <media/videobuf2-core.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +#define STK1160_VERSION "0.9.2" +#define STK1160_VERSION_NUM 0x000902 + +/* TODO: Decide on number of packets for each buffer */ +#define STK1160_NUM_PACKETS 64 + +/* Number of buffers for isoc transfers */ +#define STK1160_NUM_BUFS 16 /* TODO */ + +/* TODO: This endpoint address should be retrieved */ +#define STK1160_EP_VIDEO 0x82 +#define STK1160_EP_AUDIO 0x81 + +/* Max and min video buffers */ +#define STK1160_MIN_VIDEO_BUFFERS 8 +#define STK1160_MAX_VIDEO_BUFFERS 32 + +#define STK1160_MIN_PKT_SIZE 3072 + +/* TODO: Print helpers + * I could use dev_xxx, pr_xxx, v4l2_xxx or printk. + * However, there isn't a solid consensus on which + * new drivers should use. + * + */ +#define DEBUG +#ifdef DEBUG +#define stk1160_dbg(fmt, args...) \ + printk(KERN_DEBUG "stk1160: " fmt, ## args) +#else +#define stk1160_dbg(fmt, args...) +#endif + +#define stk1160_info(fmt, args...) \ + pr_info("stk1160: " fmt, ## args) + +#define stk1160_warn(fmt, args...) \ + pr_warn("stk1160: " fmt, ## args) + +#define stk1160_err(fmt, args...) \ + pr_err("stk1160: " fmt, ## args) + +/* Buffer for one video frame */ +struct stk1160_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_buffer vb; + struct list_head list; + + void *mem; + unsigned int length; /* buffer length */ + unsigned int bytesused; /* bytes written */ + int odd; /* current oddity */ + + /* + * Since we interlace two fields per frame, + * this is different from bytesused. + */ + unsigned int pos; /* current pos inside buffer */ +}; + +struct stk1160_isoc_ctl { + /* max packet size of isoc transaction */ + int max_pkt_size; + + /* number of allocated urbs */ + int num_bufs; + + /* urb for isoc transfers */ + struct urb **urb; + + /* transfer buffers for isoc transfer */ + char **transfer_buffer; + + /* current buffer */ + struct stk1160_buffer *buf; +}; + +struct stk1160_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; +}; + +struct stk1160 { + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct v4l2_ctrl_handler ctrl_handler; + + struct device *dev; + struct usb_device *udev; + + /* saa7115 subdev */ + struct v4l2_subdev *sd_saa7115; + + /* isoc control struct */ + struct list_head avail_bufs; + + /* video capture */ + struct vb2_queue vb_vidq; + + /* max packet size of isoc transaction */ + int max_pkt_size; + /* array of wMaxPacketSize */ + unsigned int *alt_max_pkt_size; + /* alternate */ + int alt; + /* Number of alternative settings */ + int num_alt; + + struct stk1160_isoc_ctl isoc_ctl; + char urb_buf[255]; /* urb control msg buffer */ + + /* frame properties */ + int width; /* current frame width */ + int height; /* current frame height */ + unsigned int ctl_input; /* selected input */ + struct stk1160_fmt *fmt; /* selected format */ + + unsigned int field_count; /* not sure ??? */ + enum v4l2_field field; /* also not sure :/ */ + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + + struct mutex v4l_lock; + spinlock_t buf_lock; + + struct file *fh_owner; /* filehandle ownership */ +}; + +struct regval { + u16 reg; + u16 val; +}; + +/* Provided by stk1160-v4l.c */ +int stk1160_vb2_setup(struct stk1160 *cap); +int stk1160_video_register(struct stk1160 *cap); +void stk1160_video_unregister(struct stk1160 *cap); +int stk1160_stop_streaming(struct stk1160 *cap, bool connected); + +/* Provided by stk1160-video.c */ +void stk1160_uninit_isoc(struct stk1160 *cap); +int stk1160_init_isoc(struct stk1160 *cap); + +/* Provided by stk1160-i2c.c */ +int stk1160_i2c_register(struct stk1160 *cap); +int stk1160_i2c_unregister(struct stk1160 *cap); + +/* Provided by stk1160-core.c */ +void stk1160_reg_reset(struct stk1160 *dev); +int stk1160_read_reg(struct stk1160 *cap, u16 reg, u8 *value); +int stk1160_write_reg(struct stk1160 *cap, u16 reg, u16 value); +int stk1160_write_regs_req(struct stk1160 *dev, u8 req, u16 reg, + char *buf, int len); +int stk1160_read_reg_req_len(struct stk1160 *dev, u8 req, u16 reg, + char *buf, int len); -- 1.7.3.4 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html