diff options
Diffstat (limited to 'drivers/media/usb/stk1160')
-rw-r--r-- | drivers/media/usb/stk1160/Kconfig | 21 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/Makefile | 8 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160-ac97.c | 155 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160-core.c | 431 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160-i2c.c | 284 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160-reg.h | 127 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160-v4l.c | 847 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160-video.c | 536 | ||||
-rw-r--r-- | drivers/media/usb/stk1160/stk1160.h | 202 |
9 files changed, 2611 insertions, 0 deletions
diff --git a/drivers/media/usb/stk1160/Kconfig b/drivers/media/usb/stk1160/Kconfig new file mode 100644 index 000000000..4f50fb7db --- /dev/null +++ b/drivers/media/usb/stk1160/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_STK1160_COMMON + tristate "STK1160 USB video capture support" + depends on VIDEO_DEV && I2C + + 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. + + This driver only provides support for video capture. For audio + capture, you need to select the snd-usb-audio driver (i.e. + CONFIG_SND_USB_AUDIO). + +config VIDEO_STK1160 + tristate + depends on VIDEO_STK1160_COMMON + default y + select VIDEOBUF2_VMALLOC + select VIDEO_SAA711X diff --git a/drivers/media/usb/stk1160/Makefile b/drivers/media/usb/stk1160/Makefile new file mode 100644 index 000000000..b943db01c --- /dev/null +++ b/drivers/media/usb/stk1160/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +stk1160-y := stk1160-core.o \ + stk1160-v4l.o \ + stk1160-video.o \ + stk1160-i2c.o \ + stk1160-ac97.o + +obj-$(CONFIG_VIDEO_STK1160) += stk1160.o diff --git a/drivers/media/usb/stk1160/stk1160-ac97.c b/drivers/media/usb/stk1160/stk1160-ac97.c new file mode 100644 index 000000000..79762db84 --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160-ac97.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STK1160 driver + * + * Copyright (C) 2012 Ezequiel Garcia + * <elezegarcia--a.t--gmail.com> + * + * Copyright (C) 2016 Marcel Hasler + * <mahasler--a.t--gmail.com> + * + * Based on Easycap driver by R.M. Thomas + * Copyright (C) 2010 R.M. Thomas + * <rmthomas--a.t--sciolus.org> + */ + +#include <linux/delay.h> + +#include "stk1160.h" +#include "stk1160-reg.h" + +static int stk1160_ac97_wait_transfer_complete(struct stk1160 *dev) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(STK1160_AC97_TIMEOUT); + u8 value; + + /* Wait for AC97 transfer to complete */ + while (time_is_after_jiffies(timeout)) { + stk1160_read_reg(dev, STK1160_AC97CTL_0, &value); + + if (!(value & (STK1160_AC97CTL_0_CR | STK1160_AC97CTL_0_CW))) + return 0; + + usleep_range(50, 100); + } + + stk1160_err("AC97 transfer took too long, this should never happen!"); + return -EBUSY; +} + +static void stk1160_write_ac97(struct stk1160 *dev, u16 reg, u16 value) +{ + /* Set codec register address */ + stk1160_write_reg(dev, STK1160_AC97_ADDR, reg); + + /* Set codec command */ + stk1160_write_reg(dev, STK1160_AC97_CMD, value & 0xff); + stk1160_write_reg(dev, STK1160_AC97_CMD + 1, (value & 0xff00) >> 8); + + /* Set command write bit to initiate write operation */ + stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x8c); + + /* Wait for command write bit to be cleared */ + stk1160_ac97_wait_transfer_complete(dev); +} + +#ifdef DEBUG +static u16 stk1160_read_ac97(struct stk1160 *dev, u16 reg) +{ + u8 vall = 0; + u8 valh = 0; + + /* Set codec register address */ + stk1160_write_reg(dev, STK1160_AC97_ADDR, reg); + + /* Set command read bit to initiate read operation */ + stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x8b); + + /* Wait for command read bit to be cleared */ + if (stk1160_ac97_wait_transfer_complete(dev) < 0) + return 0; + + + /* Retrieve register value */ + stk1160_read_reg(dev, STK1160_AC97_CMD, &vall); + stk1160_read_reg(dev, STK1160_AC97_CMD + 1, &valh); + + return (valh << 8) | vall; +} + +void stk1160_ac97_dump_regs(struct stk1160 *dev) +{ + u16 value; + + value = stk1160_read_ac97(dev, 0x12); /* CD volume */ + stk1160_dbg("0x12 == 0x%04x", value); + + value = stk1160_read_ac97(dev, 0x10); /* Line-in volume */ + stk1160_dbg("0x10 == 0x%04x", value); + + value = stk1160_read_ac97(dev, 0x0e); /* MIC volume (mono) */ + stk1160_dbg("0x0e == 0x%04x", value); + + value = stk1160_read_ac97(dev, 0x16); /* Aux volume */ + stk1160_dbg("0x16 == 0x%04x", value); + + value = stk1160_read_ac97(dev, 0x1a); /* Record select */ + stk1160_dbg("0x1a == 0x%04x", value); + + value = stk1160_read_ac97(dev, 0x02); /* Master volume */ + stk1160_dbg("0x02 == 0x%04x", value); + + value = stk1160_read_ac97(dev, 0x1c); /* Record gain */ + stk1160_dbg("0x1c == 0x%04x", value); +} +#endif + +static int stk1160_has_audio(struct stk1160 *dev) +{ + u8 value; + + stk1160_read_reg(dev, STK1160_POSV_L, &value); + return !(value & STK1160_POSV_L_ACDOUT); +} + +static int stk1160_has_ac97(struct stk1160 *dev) +{ + u8 value; + + stk1160_read_reg(dev, STK1160_POSV_L, &value); + return !(value & STK1160_POSV_L_ACSYNC); +} + +void stk1160_ac97_setup(struct stk1160 *dev) +{ + if (!stk1160_has_audio(dev)) { + stk1160_info("Device doesn't support audio, skipping AC97 setup."); + return; + } + + if (!stk1160_has_ac97(dev)) { + stk1160_info("Device uses internal 8-bit ADC, skipping AC97 setup."); + return; + } + + /* Two-step reset AC97 interface and hardware codec */ + stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x94); + stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x8c); + + /* Set 16-bit audio data and choose L&R channel*/ + stk1160_write_reg(dev, STK1160_AC97CTL_1 + 2, 0x01); + stk1160_write_reg(dev, STK1160_AC97CTL_1 + 3, 0x00); + + /* Setup channels */ + stk1160_write_ac97(dev, 0x12, 0x8808); /* CD volume */ + stk1160_write_ac97(dev, 0x10, 0x0808); /* Line-in volume */ + stk1160_write_ac97(dev, 0x0e, 0x0008); /* MIC volume (mono) */ + stk1160_write_ac97(dev, 0x16, 0x0808); /* Aux volume */ + stk1160_write_ac97(dev, 0x1a, 0x0404); /* Record select */ + stk1160_write_ac97(dev, 0x02, 0x0000); /* Master volume */ + stk1160_write_ac97(dev, 0x1c, 0x0808); /* Record gain */ + +#ifdef DEBUG + stk1160_ac97_dump_regs(dev); +#endif +} diff --git a/drivers/media/usb/stk1160/stk1160-core.c b/drivers/media/usb/stk1160/stk1160-core.c new file mode 100644 index 000000000..ce717502e --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160-core.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + * + * TODO: + * + * 1. Support stream at lower speed: lower frame rate or lower frame size. + */ + +#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/i2c/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 const 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; + int pipe = usb_rcvctrlpipe(dev->udev, 0); + u8 *buf; + + *value = 0; + + buf = kmalloc(sizeof(u8), GFP_KERNEL); + if (!buf) + return -ENOMEM; + ret = usb_control_msg(dev->udev, pipe, 0x00, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x00, reg, buf, sizeof(u8), 1000); + if (ret < 0) { + stk1160_err("read failed on reg 0x%x (%d)\n", + reg, ret); + kfree(buf); + return ret; + } + + *value = *buf; + kfree(buf); + return 0; +} + +int stk1160_write_reg(struct stk1160 *dev, u16 reg, u16 value) +{ + int ret; + int pipe = usb_sndctrlpipe(dev->udev, 0); + + ret = usb_control_msg(dev->udev, pipe, 0x01, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, reg, NULL, 0, 1000); + if (ret < 0) { + stk1160_err("write failed on reg 0x%x (%d)\n", + reg, ret); + return ret; + } + + return 0; +} + +void stk1160_select_input(struct stk1160 *dev) +{ + int route; + static const u8 gctrl[] = { + 0x98, 0x90, 0x88, 0x80, 0x98 + }; + + if (dev->ctl_input == STK1160_SVIDEO_INPUT) + route = SAA7115_SVIDEO3; + else + route = SAA7115_COMPOSITE0; + + if (dev->ctl_input < ARRAY_SIZE(gctrl)) { + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing, + route, 0, 0); + stk1160_write_reg(dev, STK1160_GCTRL, gctrl[dev->ctl_input]); + } +} + +/* TODO: We should break this into pieces */ +static void stk1160_reg_reset(struct stk1160 *dev) +{ + int i; + + static const struct regval ctl[] = { + {STK1160_GCTRL+2, 0x0078}, + + {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} + }; + + 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_dbg("releasing all resources\n"); + + stk1160_i2c_unregister(dev); + + v4l2_ctrl_handler_free(&dev->ctrl_handler); + v4l2_device_unregister(&dev->v4l2_dev); + mutex_destroy(&dev->v4l_lock); + mutex_destroy(&dev->vb_queue_lock); + 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; + const struct usb_endpoint_descriptor *desc; + + bool has_video = false, has_audio = false; + const 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_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); + + /* This should never happen, since we rejected audio interfaces */ + if (has_audio) + dev_warn(&udev->dev, "audio interface %d found.\n\ + This is not implemented by this driver,\ + you should use snd-usb-audio instead\n", ifnum); + + if (has_video) + dev_info(&udev->dev, "video interface %d found\n", + ifnum); + + /* + * 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_warn(&udev->dev, "must be connected to a high-speed USB 2.0 port\n\ + You may not be able to stream video smoothly\n"); + + return 0; +} + +static int stk1160_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int rc = 0; + + unsigned int *alt_max_pkt_size; /* array of wMaxPacketSize */ + struct usb_device *udev; + struct stk1160 *dev; + + udev = interface_to_usbdev(interface); + + /* + * Since usb audio class is supported by snd-usb-audio, + * we reject audio interface. + */ + if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) + return -ENODEV; + + /* Alloc an array for all possible max_pkt_size */ + alt_max_pkt_size = kmalloc_array(interface->num_altsetting, + sizeof(alt_max_pkt_size[0]), + GFP_KERNEL); + if (alt_max_pkt_size == NULL) + return -ENOMEM; + + /* + * Scan usb possibilities 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); + mutex_init(&dev->vb_queue_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); + + /* i2c reset saa711x */ + v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0); + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0); + + /* reset stk1160 to default values */ + stk1160_reg_reset(dev); + + /* select default input */ + stk1160_select_input(dev); + + stk1160_ac97_setup(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; +} + +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->vb_queue_lock); + mutex_lock(&dev->v4l_lock); + + /* Here is the only place where isoc get released */ + stk1160_uninit_isoc(dev); + + stk1160_clear_queue(dev, VB2_BUF_STATE_ERROR); + + 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); + mutex_unlock(&dev->vb_queue_lock); + + /* + * This calls stk1160_release if it's the last reference. + * Otherwise, release is postponed 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/usb/stk1160/stk1160-i2c.c b/drivers/media/usb/stk1160/stk1160-i2c.c new file mode 100644 index 000000000..9848b783a --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160-i2c.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#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_busy_wait(struct stk1160 *dev, u8 wait_bit_mask) +{ + unsigned long end; + u8 flag; + + /* Wait until read/write finish bit is set */ + end = jiffies + msecs_to_jiffies(STK1160_I2C_TIMEOUT); + while (time_is_after_jiffies(end)) { + + stk1160_read_reg(dev, STK1160_SICTL+1, &flag); + /* read/write done? */ + if (flag & wait_bit_mask) + goto done; + + usleep_range(10 * USEC_PER_MSEC, 20 * USEC_PER_MSEC); + } + + return -ETIMEDOUT; + +done: + return 0; +} + +static int stk1160_i2c_write_reg(struct stk1160 *dev, u8 addr, + u8 reg, u8 value) +{ + int rc; + + /* 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; + + rc = stk1160_i2c_busy_wait(dev, 0x04); + if (rc < 0) + return rc; + + return 0; +} + +static int stk1160_i2c_read_reg(struct stk1160 *dev, u8 addr, + u8 reg, u8 *value) +{ + int rc; + + /* 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; + + rc = stk1160_i2c_busy_wait(dev, 0x01); + if (rc < 0) + return rc; + + rc = 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; + + /* 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; + + rc = stk1160_i2c_busy_wait(dev, 0x01); + if (rc < 0) + return -ENODEV; + + return 0; +} + +/* + * 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 const struct i2c_algorithm algo = { + .master_xfer = stk1160_i2c_xfer, + .functionality = functionality, +}; + +static const struct i2c_adapter adap_template = { + .owner = THIS_MODULE, + .name = "stk1160", + .algo = &algo, +}; + +static const 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; + strscpy(dev->i2c_adap.name, "stk1160", sizeof(dev->i2c_adap.name)); + 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/usb/stk1160/stk1160-reg.h b/drivers/media/usb/stk1160/stk1160-reg.h new file mode 100644 index 000000000..cf8fd2824 --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160-reg.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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> + */ + +/* GPIO Control */ +#define STK1160_GCTRL 0x000 + +/* Remote Wakeup Control */ +#define STK1160_RMCTL 0x00c + +/* Power-on Strapping Data */ +#define STK1160_POSVA 0x010 +#define STK1160_POSV_L 0x010 +#define STK1160_POSV_M 0x011 +#define STK1160_POSV_H 0x012 +#define STK1160_POSV_L_ACDOUT BIT(3) +#define STK1160_POSV_L_ACSYNC BIT(2) + +/* + * Decoder Control Register: + * This byte controls capture start/stop + * with bit #7 (0x?? OR 0x80 to activate). + */ +#define STK1160_DCTRL 0x100 + +/* + * Decimation Control Register: + * Byte 104: Horizontal Decimation Line Unit Count + * Byte 105: Vertical Decimation Line Unit Count + * Byte 106: Decimation Control + * Bit 0 - Horizontal Decimation Control + * 0 Horizontal decimation is disabled. + * 1 Horizontal decimation is enabled. + * Bit 1 - Decimates Half or More Column + * 0 Decimates less than half from original column, + * send count unit (0x105) before each unit skipped. + * 1 Decimates half or more from original column, + * skip count unit (0x105) before each unit sent. + * Bit 2 - Vertical Decimation Control + * 0 Vertical decimation is disabled. + * 1 Vertical decimation is enabled. + * Bit 3 - Vertical Greater or Equal to Half + * 0 Decimates less than half from original row, + * send count unit (0x105) before each unit skipped. + * 1 Decimates half or more from original row, + * skip count unit (0x105) before each unit sent. + * Bit 4 - Decimation Unit + * 0 Decimation will work with 2 rows or columns per unit. + * 1 Decimation will work with 4 rows or columns per unit. + */ +#define STK1160_DMCTRL_H_UNITS 0x104 +#define STK1160_DMCTRL_V_UNITS 0x105 +#define STK1160_DMCTRL 0x106 +#define STK1160_H_DEC_EN BIT(0) +#define STK1160_H_DEC_MODE BIT(1) +#define STK1160_V_DEC_EN BIT(2) +#define STK1160_V_DEC_MODE BIT(3) +#define STK1160_DEC_UNIT_SIZE BIT(4) + +/* 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 Interface 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 + +/* AC97 Audio Control */ +#define STK1160_AC97CTL_0 0x500 +#define STK1160_AC97CTL_1 0x504 +#define STK1160_AC97CTL_0_CR BIT(1) +#define STK1160_AC97CTL_0_CW BIT(2) + +/* Use [0:6] bits of register 0x504 to set codec command address */ +#define STK1160_AC97_ADDR 0x504 +/* Use [16:31] bits of register 0x500 to set codec command data */ +#define STK1160_AC97_CMD 0x502 + +/* Audio I2S Interface */ +#define STK1160_I2SCTL 0x50c + +/* EEPROM Interface */ +#define STK1160_EEPROM_SZ 0x5f0 diff --git a/drivers/media/usb/stk1160/stk1160-v4l.c b/drivers/media/usb/stk1160/stk1160-v4l.c new file mode 100644 index 000000000..a1f785a5f --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160-v4l.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#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/v4l2-event.h> +#include <media/videobuf2-vmalloc.h> + +#include <media/i2c/saa7115.h> + +#include "stk1160.h" +#include "stk1160-reg.h" + +static bool keep_buffers; +module_param(keep_buffers, bool, 0644); +MODULE_PARM_DESC(keep_buffers, "don't release buffers upon stop streaming"); + +enum stk1160_decimate_mode { + STK1160_DECIMATE_MORE_THAN_HALF, + STK1160_DECIMATE_LESS_THAN_HALF, +}; + +struct stk1160_decimate_ctrl { + bool col_en, row_en; + enum stk1160_decimate_mode col_mode, row_mode; + unsigned int col_n, row_n; +}; + +/* supported video standards */ +static struct stk1160_fmt format[] = { + { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + } +}; + +/* + * Helper to find the next divisor that results in modulo being zero. + * This is required to guarantee valid decimation unit counts. + */ +static unsigned int +div_round_integer(unsigned int x, unsigned int y) +{ + for (;; y++) { + if (x % y == 0) + return x / y; + } +} + +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->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 { + 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); + } + +} + +static void stk1160_set_fmt(struct stk1160 *dev, + struct stk1160_decimate_ctrl *ctrl) +{ + u32 val = 0; + + if (ctrl) { + /* + * Since the format is UYVY, the device must skip or send + * a number of rows/columns multiple of four. This way, the + * colour format is preserved. The STK1160_DEC_UNIT_SIZE bit + * does exactly this. + */ + val |= STK1160_DEC_UNIT_SIZE; + val |= ctrl->col_en ? STK1160_H_DEC_EN : 0; + val |= ctrl->row_en ? STK1160_V_DEC_EN : 0; + val |= ctrl->col_mode == + STK1160_DECIMATE_MORE_THAN_HALF ? + STK1160_H_DEC_MODE : 0; + val |= ctrl->row_mode == + STK1160_DECIMATE_MORE_THAN_HALF ? + STK1160_V_DEC_MODE : 0; + + /* Horizontal count units */ + stk1160_write_reg(dev, STK1160_DMCTRL_H_UNITS, ctrl->col_n); + /* Vertical count units */ + stk1160_write_reg(dev, STK1160_DMCTRL_V_UNITS, ctrl->row_n); + + stk1160_dbg("decimate 0x%x, column units %d, row units %d\n", + val, ctrl->col_n, ctrl->row_n); + } + + /* Decimation control */ + stk1160_write_reg(dev, STK1160_DMCTRL, val); +} + +/* + * Set a new alternate setting. + * Returns true is dev->max_pkt_size has changed, false otherwise. + */ +static bool stk1160_set_alternate(struct stk1160 *dev) +{ + int i, prev_alt = dev->alt; + unsigned int min_pkt_size; + bool new_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); + stk1160_dbg("setting alt %d with wMaxPacketSize=%u\n", + dev->alt, dev->alt_max_pkt_size[dev->alt]); + usb_set_interface(dev->udev, 0, dev->alt); + } + + new_pkt_size = dev->max_pkt_size != dev->alt_max_pkt_size[dev->alt]; + dev->max_pkt_size = dev->alt_max_pkt_size[dev->alt]; + + return new_pkt_size; +} + +static int stk1160_start_streaming(struct stk1160 *dev) +{ + bool new_pkt_size; + int rc = 0; + int i; + + /* Check device presence */ + if (!dev->udev) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->v4l_lock)) + return -ERESTARTSYS; + /* + * For some reason it is mandatory to set alternate *first* + * and only *then* initialize isoc urbs. + * Someone please explain me why ;) + */ + new_pkt_size = stk1160_set_alternate(dev); + + /* + * We (re)allocate isoc urbs if: + * there is no allocated isoc urbs, OR + * a new dev->max_pkt_size is detected + */ + if (!dev->isoc_ctl.num_bufs || new_pkt_size) { + rc = stk1160_alloc_isoc(dev); + if (rc < 0) + goto out_stop_hw; + } + + /* submit urbs and enables IRQ */ + for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { + struct stk1160_urb *stk_urb = &dev->isoc_ctl.urb_ctl[i]; + + dma_sync_sgtable_for_device(stk1160_get_dmadev(dev), stk_urb->sgt, + DMA_FROM_DEVICE); + rc = usb_submit_urb(dev->isoc_ctl.urb_ctl[i].urb, GFP_KERNEL); + if (rc) { + stk1160_err("cannot submit urb[%d] (%d)\n", i, rc); + goto out_uninit; + } + } + + /* Start saa711x */ + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 1); + + dev->sequence = 0; + + /* Start stk1160 */ + stk1160_write_reg(dev, STK1160_DCTRL, 0xb3); + stk1160_write_reg(dev, STK1160_DCTRL+3, 0x00); + + stk1160_dbg("streaming started\n"); + + mutex_unlock(&dev->v4l_lock); + + return 0; + +out_uninit: + stk1160_uninit_isoc(dev); +out_stop_hw: + usb_set_interface(dev->udev, 0, 0); + stk1160_clear_queue(dev, VB2_BUF_STATE_QUEUED); + + mutex_unlock(&dev->v4l_lock); + + return rc; +} + +/* Must be called with v4l_lock hold */ +static void stk1160_stop_hw(struct stk1160 *dev) +{ + /* If the device is not physically present, there is nothing to do */ + if (!dev->udev) + return; + + /* set alternate 0 */ + dev->alt = 0; + stk1160_dbg("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); +} + +static int stk1160_stop_streaming(struct stk1160 *dev) +{ + if (mutex_lock_interruptible(&dev->v4l_lock)) + return -ERESTARTSYS; + + /* + * Once URBs are cancelled, the URB complete handler + * won't be running. This is required to safely release the + * current buffer (dev->isoc_ctl.buf). + */ + stk1160_cancel_isoc(dev); + + /* + * It is possible to keep buffers around using a module parameter. + * This is intended to avoid memory fragmentation. + */ + if (!keep_buffers) + stk1160_free_isoc(dev); + + stk1160_stop_hw(dev); + + stk1160_clear_queue(dev, VB2_BUF_STATE_ERROR); + + stk1160_dbg("streaming stopped\n"); + + mutex_unlock(&dev->v4l_lock); + + return 0; +} + +static const struct v4l2_file_operations stk1160_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +/* + * vidioc ioctls + */ +static int vidioc_querycap(struct file *file, + void *priv, struct v4l2_capability *cap) +{ + struct stk1160 *dev = video_drvdata(file); + + strscpy(cap->driver, "stk1160", sizeof(cap->driver)); + strscpy(cap->card, "stk1160", sizeof(cap->card)); + usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info)); + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + 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 stk1160_try_fmt(struct stk1160 *dev, struct v4l2_format *f, + struct stk1160_decimate_ctrl *ctrl) +{ + unsigned int width, height; + unsigned int base_width, base_height; + unsigned int col_n, row_n; + enum stk1160_decimate_mode col_mode, row_mode; + bool col_en, row_en; + + base_width = 720; + base_height = (dev->norm & V4L2_STD_525_60) ? 480 : 576; + + /* Minimum width and height is 5% the frame size */ + width = clamp_t(unsigned int, f->fmt.pix.width, + base_width / 20, base_width); + height = clamp_t(unsigned int, f->fmt.pix.height, + base_height / 20, base_height); + + /* Let's set default no decimation values */ + col_n = 0; + row_n = 0; + col_en = false; + row_en = false; + f->fmt.pix.width = base_width; + f->fmt.pix.height = base_height; + row_mode = STK1160_DECIMATE_LESS_THAN_HALF; + col_mode = STK1160_DECIMATE_LESS_THAN_HALF; + + if (width < base_width && width > base_width / 2) { + /* + * The device will send count units for each + * unit skipped. This means count unit is: + * + * n = width / (frame width - width) + * + * And the width is: + * + * width = (n / n + 1) * frame width + */ + col_n = div_round_integer(width, base_width - width); + if (col_n > 0 && col_n <= 255) { + col_en = true; + col_mode = STK1160_DECIMATE_LESS_THAN_HALF; + f->fmt.pix.width = (base_width * col_n) / (col_n + 1); + } + + } else if (width <= base_width / 2) { + + /* + * The device will skip count units for each + * unit sent. This means count is: + * + * n = (frame width / width) - 1 + * + * And the width is: + * + * width = frame width / (n + 1) + */ + col_n = div_round_integer(base_width, width) - 1; + if (col_n > 0 && col_n <= 255) { + col_en = true; + col_mode = STK1160_DECIMATE_MORE_THAN_HALF; + f->fmt.pix.width = base_width / (col_n + 1); + } + } + + if (height < base_height && height > base_height / 2) { + row_n = div_round_integer(height, base_height - height); + if (row_n > 0 && row_n <= 255) { + row_en = true; + row_mode = STK1160_DECIMATE_LESS_THAN_HALF; + f->fmt.pix.height = (base_height * row_n) / (row_n + 1); + } + + } else if (height <= base_height / 2) { + row_n = div_round_integer(base_height, height) - 1; + if (row_n > 0 && row_n <= 255) { + row_en = true; + row_mode = STK1160_DECIMATE_MORE_THAN_HALF; + f->fmt.pix.height = base_height / (row_n + 1); + } + } + + f->fmt.pix.pixelformat = dev->fmt->fourcc; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + if (ctrl) { + ctrl->col_en = col_en; + ctrl->col_n = col_n; + ctrl->col_mode = col_mode; + ctrl->row_en = row_en; + ctrl->row_n = row_n; + ctrl->row_mode = row_mode; + } + + stk1160_dbg("width %d, height %d\n", + f->fmt.pix.width, f->fmt.pix.height); + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stk1160 *dev = video_drvdata(file); + + return stk1160_try_fmt(dev, f, NULL); +} + +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; + struct stk1160_decimate_ctrl ctrl; + int rc; + + if (vb2_is_busy(q)) + return -EBUSY; + + rc = stk1160_try_fmt(dev, f, &ctrl); + if (rc < 0) + return rc; + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + stk1160_set_fmt(dev, &ctrl); + + 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_g_std(struct file *file, void *priv, v4l2_std_id *norm) +{ + struct stk1160 *dev = video_drvdata(file); + + *norm = dev->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 (dev->norm == norm) + return 0; + + if (vb2_is_busy(q)) + return -EBUSY; + + /* Check device presence */ + if (!dev->udev) + return -ENODEV; + + /* We need to set this now, before we call stk1160_set_std */ + dev->width = 720; + dev->height = (norm & V4L2_STD_525_60) ? 480 : 576; + dev->norm = norm; + + stk1160_set_std(dev); + + /* Calling with NULL disables frame decimation */ + stk1160_set_fmt(dev, NULL); + + v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, + dev->norm); + + return 0; +} + + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct stk1160 *dev = video_drvdata(file); + + if (i->index > STK1160_MAX_INPUT) + return -EINVAL; + + /* S-Video special handling */ + if (i->index == STK1160_SVIDEO_INPUT) + sprintf(i->name, "S-Video"); + else + 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; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct stk1160 *dev = video_drvdata(file); + + if (i > STK1160_MAX_INPUT) + return -EINVAL; + + dev->ctl_input = i; + + stk1160_select_input(dev); + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct stk1160 *dev = video_drvdata(file); + int rc; + u8 val; + + /* Match host */ + rc = stk1160_read_reg(dev, reg->reg, &val); + reg->val = val; + reg->size = 1; + + return rc; +} + +static int vidioc_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct stk1160 *dev = video_drvdata(file); + + /* Match host */ + return stk1160_write_reg(dev, reg->reg, reg->val); +} +#endif + +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 = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + + /* vb2 takes care of these */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +/********************************************************************/ + +/* + * Videobuf2 operations + */ +static int queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + 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); + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + /* This means a packed colorformat */ + *nplanes = 1; + + sizes[0] = size; + + stk1160_dbg("%s: buffer count %d, each %ld bytes\n", + __func__, *nbuffers, size); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + unsigned long flags; + struct stk1160 *dev = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct stk1160_buffer *buf = + container_of(vbuf, 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, 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 less 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, 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); + return stk1160_start_streaming(dev); +} + +/* abort streaming and wait for last buffer */ +static void stop_streaming(struct vb2_queue *vq) +{ + struct stk1160 *dev = vb2_get_drv_priv(vq); + stk1160_stop_streaming(dev); +} + +static const struct vb2_ops stk1160_video_qops = { + .queue_setup = queue_setup, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static const 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, +}; + +/********************************************************************/ + +/* Must be called with both v4l_lock and vb_queue_lock hold */ +void stk1160_clear_queue(struct stk1160 *dev, enum vb2_buffer_state vb2_state) +{ + struct stk1160_buffer *buf; + unsigned long flags; + + /* 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, vb2_state); + stk1160_dbg("buffer [%p/%d] aborted\n", + buf, buf->vb.vb2_buf.index); + } + + /* It's important to release the current buffer */ + if (dev->isoc_ctl.buf) { + buf = dev->isoc_ctl.buf; + dev->isoc_ctl.buf = NULL; + + vb2_buffer_done(&buf->vb.vb2_buf, vb2_state); + stk1160_dbg("buffer [%p/%d] aborted\n", + buf, buf->vb.vb2_buf.index); + } + spin_unlock_irqrestore(&dev->buf_lock, flags); +} + +int stk1160_vb2_setup(struct stk1160 *dev) +{ + int rc; + struct vb2_queue *q; + + q = &dev->vb_vidq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct stk1160_buffer); + q->ops = &stk1160_video_qops; + q->mem_ops = &vb2_vmalloc_memops; + q->lock = &dev->vb_queue_lock; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + 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.queue = &dev->vb_vidq; + + /* + * Provide mutexes for v4l2 core and for videobuf2 queue. + * 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; + dev->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + + /* NTSC is default */ + dev->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, video, s_std, + dev->norm); + + video_set_drvdata(&dev->vdev, dev); + rc = video_register_device(&dev->vdev, VFL_TYPE_VIDEO, -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/usb/stk1160/stk1160-video.c b/drivers/media/usb/stk1160/stk1160-video.c new file mode 100644 index 000000000..4e966f6bf --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160-video.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + */ + +#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 synchronously"; + break; + case -ECONNRESET: + errmsg = "unlinked asynchronously"; + 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 */ + WARN_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; + + buf->vb.sequence = dev->sequence++; + buf->vb.field = V4L2_FIELD_INTERLACED; + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->bytesused); + vb2_buffer_done(&buf->vb.vb2_buf, 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. + */ + + 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; + } + + /* Check if the copy is done */ + if (lencopy == 0 || remain == 0) + return; + + /* Let the bug hunt begin! sanity checks! */ + if (lencopy < 0) { + stk1160_dbg("copy skipped: negative lencopy\n"); + return; + } + + if ((unsigned long)dst + lencopy > + (unsigned long)buf->mem + buf->length) { + printk_ratelimited(KERN_WARNING "stk1160: buffer overflow detected\n"); + return; + } + + 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; + } + + /* Check if the copy is done */ + if (lencopy == 0 || remain == 0) + return; + + if (lencopy < 0) { + printk_ratelimited(KERN_WARNING "stk1160: negative lencopy detected\n"); + return; + } + + if ((unsigned long)dst + lencopy > + (unsigned long)buf->mem + buf->length) { + printk_ratelimited(KERN_WARNING "stk1160: buffer overflow detected\n"); + return; + } + + memcpy(dst, src, lencopy); + remain -= lencopy; + + buf->bytesused += lencopy; + buf->pos += lencopy; + } +} + +/* + * 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_urb *stk_urb = urb->context; + struct stk1160 *dev = stk_urb->dev; + struct device *dma_dev = stk1160_get_dmadev(dev); + + 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; + } + + invalidate_kernel_vmap_range(stk_urb->transfer_buffer, + urb->transfer_buffer_length); + dma_sync_sgtable_for_cpu(dma_dev, stk_urb->sgt, DMA_FROM_DEVICE); + + 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; + } + + dma_sync_sgtable_for_device(dma_dev, stk_urb->sgt, DMA_FROM_DEVICE); + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc) + stk1160_err("urb re-submit failed (%d)\n", rc); +} + +/* + * Cancel urbs + * This function can't be called in atomic context + */ +void stk1160_cancel_isoc(struct stk1160 *dev) +{ + int i, num_bufs = dev->isoc_ctl.num_bufs; + + /* + * This check is not necessary, but we add it + * to avoid a spurious debug message + */ + if (!num_bufs) + return; + + stk1160_dbg("killing %d urbs...\n", num_bufs); + + for (i = 0; i < num_bufs; i++) { + + /* + * To kill urbs we can't be in atomic context. + * We don't care for NULL pointer since + * usb_kill_urb allows it. + */ + usb_kill_urb(dev->isoc_ctl.urb_ctl[i].urb); + } + + stk1160_dbg("all urbs killed\n"); +} + +static void stk_free_urb(struct stk1160 *dev, struct stk1160_urb *stk_urb) +{ + struct device *dma_dev = stk1160_get_dmadev(dev); + + dma_vunmap_noncontiguous(dma_dev, stk_urb->transfer_buffer); + dma_free_noncontiguous(dma_dev, stk_urb->urb->transfer_buffer_length, + stk_urb->sgt, DMA_FROM_DEVICE); + usb_free_urb(stk_urb->urb); + + stk_urb->transfer_buffer = NULL; + stk_urb->sgt = NULL; + stk_urb->urb = NULL; + stk_urb->dev = NULL; + stk_urb->dma = 0; +} + +/* + * Releases urb and transfer buffers + * Obviusly, associated urb must be killed before releasing it. + */ +void stk1160_free_isoc(struct stk1160 *dev) +{ + int i, num_bufs = dev->isoc_ctl.num_bufs; + + stk1160_dbg("freeing %d urb buffers...\n", num_bufs); + + for (i = 0; i < num_bufs; i++) + stk_free_urb(dev, &dev->isoc_ctl.urb_ctl[i]); + + dev->isoc_ctl.num_bufs = 0; + + stk1160_dbg("all urb buffers freed\n"); +} + +/* + * Helper for cancelling and freeing urbs + * This function can't be called in atomic context + */ +void stk1160_uninit_isoc(struct stk1160 *dev) +{ + stk1160_cancel_isoc(dev); + stk1160_free_isoc(dev); +} + +static int stk1160_fill_urb(struct stk1160 *dev, struct stk1160_urb *stk_urb, + int sb_size, int max_packets) +{ + struct device *dma_dev = stk1160_get_dmadev(dev); + + stk_urb->urb = usb_alloc_urb(max_packets, GFP_KERNEL); + if (!stk_urb->urb) + return -ENOMEM; + stk_urb->sgt = dma_alloc_noncontiguous(dma_dev, sb_size, + DMA_FROM_DEVICE, GFP_KERNEL, 0); + + /* + * If the buffer allocation failed, we exit but return 0 since + * we allow the driver working with less buffers + */ + if (!stk_urb->sgt) + goto free_urb; + + stk_urb->transfer_buffer = dma_vmap_noncontiguous(dma_dev, sb_size, + stk_urb->sgt); + if (!stk_urb->transfer_buffer) + goto free_sgt; + + stk_urb->dma = stk_urb->sgt->sgl->dma_address; + stk_urb->dev = dev; + return 0; +free_sgt: + dma_free_noncontiguous(dma_dev, sb_size, stk_urb->sgt, DMA_FROM_DEVICE); + stk_urb->sgt = NULL; +free_urb: + usb_free_urb(stk_urb->urb); + stk_urb->urb = NULL; + + return 0; +} +/* + * Allocate URBs + */ +int stk1160_alloc_isoc(struct stk1160 *dev) +{ + struct urb *urb; + int i, j, k, sb_size, max_packets, num_bufs; + int ret; + + /* + * It may be necessary to release isoc here, + * since isoc are only released on disconnection. + * (see new_pkt_size flag) + */ + if (dev->isoc_ctl.num_bufs) + stk1160_uninit_isoc(dev); + + stk1160_dbg("allocating urbs...\n"); + + 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; + + /* allocate urbs and transfer buffers */ + for (i = 0; i < num_bufs; i++) { + + ret = stk1160_fill_urb(dev, &dev->isoc_ctl.urb_ctl[i], + sb_size, max_packets); + if (ret) + goto free_i_bufs; + + urb = dev->isoc_ctl.urb_ctl[i].urb; + + if (!urb) { + /* Not enough transfer buffers, so just give up */ + if (i < STK1160_MIN_BUFS) + goto free_i_bufs; + goto nomore_tx_bufs; + } + memset(dev->isoc_ctl.urb_ctl[i].transfer_buffer, 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.urb_ctl[i].transfer_buffer; + urb->transfer_buffer_length = sb_size; + urb->complete = stk1160_isoc_irq; + urb->context = &dev->isoc_ctl.urb_ctl[i]; + urb->interval = 1; + urb->start_frame = 0; + urb->number_of_packets = max_packets; + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = dev->isoc_ctl.urb_ctl[i].dma; + + 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; + } + } + + stk1160_dbg("%d urbs allocated\n", num_bufs); + + /* At last we can say we have some buffers */ + dev->isoc_ctl.num_bufs = num_bufs; + + return 0; + +nomore_tx_bufs: + /* + * Failed to allocate desired buffer count. However, we may have + * enough to work fine, so we just free the extra urb, + * store the allocated count and keep going, fingers crossed! + */ + + stk1160_warn("%d urbs allocated. Trying to continue...\n", i); + + dev->isoc_ctl.num_bufs = i; + + return 0; + +free_i_bufs: + /* Save the allocated buffers so far, so we can properly free them */ + dev->isoc_ctl.num_bufs = i; + stk1160_free_isoc(dev); + return -ENOMEM; +} + diff --git a/drivers/media/usb/stk1160/stk1160.h b/drivers/media/usb/stk1160/stk1160.h new file mode 100644 index 000000000..7b498d14e --- /dev/null +++ b/drivers/media/usb/stk1160/stk1160.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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> + */ + +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/ac97_codec.h> +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#define STK1160_VERSION "0.9.5" +#define STK1160_VERSION_NUM 0x000905 + +/* Decide on number of packets for each buffer */ +#define STK1160_NUM_PACKETS 64 + +/* Number of buffers for isoc transfers */ +#define STK1160_NUM_BUFS 16 +#define STK1160_MIN_BUFS 1 + +/* 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 + +#define STK1160_MAX_INPUT 4 +#define STK1160_SVIDEO_INPUT 4 + +#define STK1160_AC97_TIMEOUT 50 + +#define STK1160_I2C_TIMEOUT 100 + +/* 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. + * + */ +#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_v4l2_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_urb { + struct urb *urb; + char *transfer_buffer; + struct sg_table *sgt; + struct stk1160 *dev; + dma_addr_t dma; +}; + +struct stk1160_isoc_ctl { + /* max packet size of isoc transaction */ + int max_pkt_size; + + /* number of allocated urbs */ + int num_bufs; + + struct stk1160_urb urb_ctl[STK1160_NUM_BUFS]; + + /* current buffer */ + struct stk1160_buffer *buf; +}; + +struct stk1160_fmt { + 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; + + /* frame properties */ + int width; /* current frame width */ + int height; /* current frame height */ + unsigned int ctl_input; /* selected input */ + v4l2_std_id norm; /* current norm */ + struct stk1160_fmt *fmt; /* selected format */ + + unsigned int sequence; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + + struct mutex v4l_lock; + struct mutex vb_queue_lock; + spinlock_t buf_lock; + + struct file *fh_owner; /* filehandle ownership */ + + /* EXPERIMENTAL */ + struct snd_card *snd_card; +}; + +struct regval { + u16 reg; + u16 val; +}; + +/* Provided by stk1160-v4l.c */ +int stk1160_vb2_setup(struct stk1160 *dev); +int stk1160_video_register(struct stk1160 *dev); +void stk1160_video_unregister(struct stk1160 *dev); +void stk1160_clear_queue(struct stk1160 *dev, enum vb2_buffer_state vb2_state); + +/* Provided by stk1160-video.c */ +int stk1160_alloc_isoc(struct stk1160 *dev); +void stk1160_free_isoc(struct stk1160 *dev); +void stk1160_cancel_isoc(struct stk1160 *dev); +void stk1160_uninit_isoc(struct stk1160 *dev); + +/* Provided by stk1160-i2c.c */ +int stk1160_i2c_register(struct stk1160 *dev); +int stk1160_i2c_unregister(struct stk1160 *dev); + +/* Provided by stk1160-core.c */ +int stk1160_read_reg(struct stk1160 *dev, u16 reg, u8 *value); +int stk1160_write_reg(struct stk1160 *dev, 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); +void stk1160_select_input(struct stk1160 *dev); + +/* Provided by stk1160-ac97.c */ +void stk1160_ac97_setup(struct stk1160 *dev); + +static inline struct device *stk1160_get_dmadev(struct stk1160 *dev) +{ + return bus_to_hcd(dev->udev->bus)->self.sysdev; +} |