/*
 * Sensoray 2253 USB Linux driver
 * The 2253 is an MPEG and raw video USB capture/output board
 *
 * Copyright (C) 2010-2026 Sensoray Company Inc.
 *
 *  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, version 2.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Some code references/uses uvc_driver which is:
 *      Copyright (C) 2005-2009
 *          Laurent Pinchart (laurent.pinchart@skynet.be)
 */

/* set this to 0 if compiling for a kernel, else 1 for v4l-dvb tree */
#define V4L_DVB_TREE 0

#include <linux/version.h>
#include <linux/module.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) || V4L_DVB_TREE
#include <linux/slab.h>
#endif
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/usb.h>
#include <linux/firmware.h>
#include <linux/mutex.h>
#include <linux/mm.h>
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 38)
#include <linux/smp_lock.h>
#endif
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/device.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
#include <linux/swab.h>
#else
#include <asm/byteorder.h>
#endif
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24)
#include <sound/driver.h>
#endif
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <linux/irq.h>
#ifdef CONFIG_GPIOLIB
#include <linux/gpio.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
#include <linux/gpio/driver.h>
#endif
#endif

#include <linux/videodev2.h>

#if defined(HAVE_V4L2_COMMON) || V4L_DVB_TREE || LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
#include <media/v4l2-common.h>
#endif
#if defined(HAVE_V4L2_DEV) || V4L_DVB_TREE || LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
#include <media/v4l2-dev.h>
#else
#error need v4l2-dev.h
#endif
#if defined(HAVE_V4L2_DEVICE) || V4L_DVB_TREE || LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
#include <media/v4l2-device.h>
#endif
#if defined(HAVE_V4L2_EVENT) || V4L_DVB_TREE || LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
#include <media/v4l2-event.h>
#endif
#if defined(HAVE_V4L2_IOCTL) || V4L_DVB_TREE || LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
#include <media/v4l2-ioctl.h>
#endif

#ifndef IS_ENABLED
// copied from include/linux/kconfig.h
#define __ARG_PLACEHOLDER_1 0,
#define config_enabled(cfg) _config_enabled(cfg)
#define _config_enabled(value) __config_enabled(__ARG_PLACEHOLDER_##value)
#define __config_enabled(arg1_or_junk) ___config_enabled(arg1_or_junk 1, 0)
#define ___config_enabled(__ignored, val, ...) val
#define IS_ENABLED(option) \
        (config_enabled(option) || config_enabled(option##_MODULE))
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 4) && IS_ENABLED(CONFIG_VIDEOBUF2_VMALLOC)
// Can't use VIDEOBUF1 on 5.1.0 to 5.3.3 due to NULL pointer dereference in poll_wait
// https://www.mail-archive.com/linux-media@vger.kernel.org/msg149688.html
// https://www.mail-archive.com/linux-media@vger.kernel.org/msg149693.html
#define USE_VIDEOBUF2 1
#elif IS_ENABLED(CONFIG_VIDEOBUF_VMALLOC) || LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
// VIDEOBUF2 not default yet unless no videobuf vmalloc or old kernel
#define USE_VIDEOBUF2 0
#else
#define USE_VIDEOBUF2 IS_ENABLED(CONFIG_VIDEOBUF2_VMALLOC)
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
#include <media/video-buf.h>
#include <linux/highmem.h>
#include <linux/dma-mapping.h>
#elif USE_VIDEOBUF2
#include <media/videobuf2-vmalloc.h>
#elif IS_ENABLED(CONFIG_VIDEOBUF_VMALLOC)
#include <media/videobuf-vmalloc.h>
#else
#error Kernel config CONFIG_VIDEOBUF_VMALLOC or CONFIG_VIDEOBUF2_VMALLOC must be enabled!
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
#include <linux/videodev.h>
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0) && LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 16)
#define HAVE_CURRENT_NORM
#endif


#include "compat.h"
#include "s2253.h"
#include "s2253ioctl.h"

MODULE_DESCRIPTION("Sensoray 2253 driver");
MODULE_AUTHOR("D.A., P.E.(linux-dev@sensoray.com)");
MODULE_LICENSE("GPL");

/* driver version for VIDIOC_QUERYCAP */
#define DRV_VERSION KERNEL_VERSION(1, 2, 26)

/* kernel version V4L2 dev introduced */
#define KERNEL_VERSION_V4L2DEV KERNEL_VERSION(2, 6, 29)
/* kernel version inode removed from V4L2 open, close functions */
#define KERNEL_VERSION_NOINODE KERNEL_VERSION(2, 6, 29)

#ifndef MODULE_FIRMWARE
#define MODULE_FIRMWARE(x)
#endif

#if V4L_DVB_TREE && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35)
/* usb_buffer_alloc and free were renamed in 2.6.35 */
#define usb_alloc_coherent(dev, size, mem_flags, dma) usb_buffer_alloc(dev, size, mem_flags, dma)
#define usb_free_coherent(dev, size, addr, dma) usb_buffer_free(dev, size, addr, dma)
#endif

/* const was added to some v4l2_ioctl_ops (see v4l2-ioctl.h) */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
#define V4L_CONST const
#else
#define V4L_CONST
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
#define V4L2_CTRL_CLASS_MPEG            V4L2_CTRL_CLASS_CODEC
#define V4L2_CID_MPEG_CLASS             V4L2_CID_CODEC_CLASS
#define V4L2_CID_MPEG_BASE              V4L2_CID_CODEC_BASE
#define V4L2_CID_MPEG_CX2341X_BASE      V4L2_CID_CODEC_CX2341X_BASE
#define V4L2_CID_MPEG_MFC51_BASE        V4L2_CID_CODEC_MFC51_BASE
#endif


/* This is defined for Rev.0 boards which do not have the bootloader in ROM */
//#define DM365_BOOT
#ifdef DM365_BOOT
#define USB_DM365_VENDOR_ID	0x0451
#define USB_DM365_PRODUCT_ID	0x9815
#define USB_DM365_FIRMWARE	"s2253boot.ubt"
MODULE_FIRMWARE(USB_DM365_FIRMWARE);
#endif

#define USB_S2253_VENDOR_ID	0x1943
#define USB_S2253_BOOTUP_ID	0xa253	/* cold */
#define USB_S2253_PRODUCT_ID	0x2253	/* warm */
#define USB_S2253_FIRMWARE	"s2253.fw"
MODULE_FIRMWARE(USB_S2253_FIRMWARE);
#define FIRMWARE_VERSION_EXPECTED 9170

/* table of devices that work with this driver */
static struct usb_device_id s2253_table[] = {
	{ USB_DEVICE(USB_S2253_VENDOR_ID, USB_S2253_BOOTUP_ID) },
	{ USB_DEVICE(USB_S2253_VENDOR_ID, USB_S2253_PRODUCT_ID) },
#ifdef DM365_BOOT
	{ USB_DEVICE(USB_DM365_VENDOR_ID, USB_DM365_PRODUCT_ID) },
#endif
	{ }	/* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, s2253_table);


#define DEF_BRIGHT	128
#define DEF_CONTRAST	128
#define DEF_SATURATION	128
#define DEF_HUE		0

#define S2253_MODULE_NAME "s2253"
#define S2253_DEF_BUFS 16
#define S2253_URBS     8
/* Maximum number of packets per URB. 64*512=32k */
#define S2253_MAX_PACKETS 64

static unsigned int debug = 0;
static int *s2253_debug = &debug;
module_param(debug, uint, 0644);
MODULE_PARM_DESC(debug, "debug level(0-100) default 0");
static unsigned int video_nr = -1;
module_param(video_nr, uint, 0644);
MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect");
static unsigned int vid_limit = 16;	/* Video memory limit, in Mb */
module_param(vid_limit, uint, 0644);
MODULE_PARM_DESC(vid_limit, "video memory limit(Mb)");
static unsigned int audio = 1;
module_param(audio, uint, 0644);
MODULE_PARM_DESC(audio, "audio enable, default 1");

#define dprintk(level, fmt, arg...)					\
	do {								\
		if (*s2253_debug >= (level)) {				\
			printk(KERN_DEBUG S2253_MODULE_NAME		\
				": " fmt, ##arg);			\
		}							\
	} while (0)


/* private V4L2 controls */
/*
 * The following chart displays how COLORFILTER should be set
 *  =========================================================
 *  =     fourcc              =     COLORFILTER             =
 *  =                         ===============================
 *  =                         =   0             =    1      =
 *  =========================================================
 *  =  V4L2_PIX_FMT_GREY(Y8)  = monochrome from = monochrome=
 *  =                         = s-video or      = composite =
 *  =                         = B/W camera      = input     =
 *  =========================================================
 *  =    other                = color, svideo   = color,    =
 *  =                         =                 = composite =
 *  =========================================================
 *
 * If COLORFILTER is 0 with a composite color camera connected,
 * the output will appear monochrome but hatching
 * will occur.
 * COLORFILTER is different from "color killer" and "color effects"
 * for reasons above.
 */
#define S2253_V4L2_YC_ON  1
#define S2253_V4L2_YC_OFF 0
#define V4L2_CID_PRIVATE_COLORFILTER (V4L2_CID_PRIVATE_BASE + 0)


struct s2253_fmt {
	char *name;
	u32 fourcc;
	int depth;
	u32 flags;
	u32 types;
	int format;
	int device_depth;
};

#define TYPE_CAPTURE (1<<V4L2_BUF_TYPE_VIDEO_CAPTURE)
#define TYPE_OUTPUT  (1<<V4L2_BUF_TYPE_VIDEO_OUTPUT)

/* image formats.  */
static const struct s2253_fmt formats[] = {
	{
		.name = "MJPEG",
		.fourcc = V4L2_PIX_FMT_MJPEG,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_JPEG,
		.device_depth = 16,
	},
	{
		.name = "JPEG",
		.fourcc = V4L2_PIX_FMT_JPEG,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_JPEG,
		.device_depth = 16,
	},
	{
		.name = "MPEG-4 VES",
		.fourcc = V4L2_PIX_FMT_MP4V,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_MPEG4,
		.device_depth = 12,
	},
	{
		.name = "H.264(AVC) VES",
		.fourcc = V4L2_PIX_FMT_H264,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_H264,
		.device_depth = 12,
	},
	{
		.name = "MPEG-2 VES",
		.fourcc = V4L2_PIX_FMT_MP2V,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_MPEG2,
		.device_depth = 12,
	},
	{
		.name = "MP4",
		.fourcc = V4L2_PIX_FMT_MP42,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_MP4,
		.device_depth = 12,
	},
	{
		.name = "MPEG",
		.fourcc = V4L2_PIX_FMT_MPEG,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_MPEG_TS,
		.device_depth = 12,
	},
	{
		.name = "MPEG",
		.fourcc = V4L2_PIX_FMT_MPEG,
		.depth = 8,
		.flags = V4L2_FMT_FLAG_COMPRESSED,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_MPEG_PS,
		.device_depth = 12,
	},
	{
		.name = "4:2:0, semi-planar, NV12",
		.fourcc = V4L2_PIX_FMT_NV12,
		.depth = 12,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_YUV420PSEMI,
		.device_depth = 12,
	},
	{
		.name = "8bpp GREY",
		.fourcc = V4L2_PIX_FMT_GREY,
		.depth = 8,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_Y8,
		.device_depth = 12,
	},
	{
		.name = "4:2:2, packed, UYVY",
		.fourcc = V4L2_PIX_FMT_UYVY,
		.depth = 16,
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_UYVY,
		.device_depth = 16,
	},
	{
		.name = "4:2:2, packed, YUYV",
		.fourcc = V4L2_PIX_FMT_YUYV,
		.depth = 16,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) || V4L_DVB_TREE
		.flags = V4L2_FMT_FLAG_EMULATED,
#endif
		.types = TYPE_CAPTURE | TYPE_OUTPUT,
		.format = S2253_FORMAT_UYVY,
		.device_depth = 16,
	},
	{
		.name = "BGR24",
		.fourcc = V4L2_PIX_FMT_BGR24,
		.depth = 24,
		.types = TYPE_CAPTURE,
		.format = S2253_FORMAT_RGB24,
		.device_depth = 24,
	},
	{
		.name = "RGB565",
		.fourcc = V4L2_PIX_FMT_RGB565,
		.depth = 16,
		.types = TYPE_CAPTURE,
		.format = S2253_FORMAT_RGB565,
		.device_depth = 16,
	},
};

static const struct s2253_fmt * s2253_find_fmt(enum s2253_format format)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(formats); i++)
		if (formats[i].format == format)
			return &formats[i];
	return NULL;
}

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
static struct v4l2_tvnorm s2253_tvnorms[] = {
	{
		.name = "NTSC",
		.id = V4L2_STD_NTSC,
	},{
		.name = "PAL",
		.id = V4L2_STD_PAL,
	}
};
#endif

struct s2253_board;
struct s2253_snd;

/* buffer for one video frame */
struct s2253_buffer {
	/* common v4l buffer stuff -- must be first */
#if USE_VIDEOBUF2
	struct vb2_buffer	vb;
	struct list_head	list;	// on dev->vidq
#else
	struct videobuf_buffer	vb;
#endif
	const struct s2253_fmt	*fmt;
	__u32 ts;
};

struct s2253_video_urb {
	struct urb urb;
	char *urb_buffer;
	dma_addr_t urb_dma;
	struct list_head list;
};

static inline struct s2253_video_urb *to_s2253_video_urb(struct urb *urb)
{
	return container_of(urb, struct s2253_video_urb, urb);
}

#define FDEC_1		1	/* capture every frame. default */
#define FDEC_2		2	/* capture every 2nd frame */
#define FDEC_3		3	/* capture every 3rd frame */
#define FDEC_5		5	/* capture every 5th frame */

struct s2253_stream {
	struct s2253_board *board;
	struct usb_interface *interface;
	u8 endpoint;

	unsigned int urb_size;
	struct s2253_video_urb urbs[S2253_URBS];
	struct list_head urb_list;
	spinlock_t urb_lock; /* protect urb_list */
	wait_queue_head_t urb_wait;
	struct task_struct *video_thread;
	struct s2253_buffer *active;

	struct s2253_dev *dev; //backpointer
	__u32 width;
	__u32 height;
	__u32 bytesperline;
	__u32 fmt_bytesperline;
	unsigned int framesize;
	// analog video decoder controls
	int bright;
	int contrast;
	int saturation;
	int hue;
	int color_filter;
	struct v4l2_captureparm cap_parm;
	int fdec; /* frame decimation */
	struct v4l2_jpegcompression jc;	/* jpeg compression */
	const struct s2253_fmt *fmt;
	enum s2253_format video_format; /* for muxing */
	enum s2253_format audio_format; /* for muxing */
	int mpeg_bitrate;	/* in kbps */
	enum s2253_bitrate_mode mpeg_bitrate_mode;	/* VBR or CBR */
	int last_fid;		/* last frame id, a la UVC */
	int last_eof;		/* when fid changes, verify end of frame */
	int param_shift;	/* shift stream-specific parameter's index */
#define STARTSEQ_NOTINIT 0xffffffffffffffffULL
	__u64 startseq; /*start sequence number */
	__u64 last_seq;		/* to detect dropped or repeated frames */
	__u32 last_scr;
	__u32 last_ts;	/* to detect timestamp wrap */
	struct timeval ts_off;	/* offset to make timestamp sychronized with monotonic clock */
	struct {
		int sequence;	/* count of out of sequence errors */
		int eof;	/* count of end-of-frame errors */
		int urb;	/* count of urbs that complete with error (aside from unlink errors) */
	} errors;
	unsigned char *audio;	/* audio multiplexed on the video endpoint */
	unsigned int audio_size, audio_len;
	int audio_balance, audio_volume, volume_param;
	int should_stop; 	/* for stopping video_thread asynchronously */
	enum v4l2_field field;
	__u32 cur_ts;
};

struct s2253_dmaqueue {
	struct list_head	active;
	struct s2253_dev	*dev;
	wait_queue_head_t	wait;
};

#if !USE_VIDEOBUF2
struct s2253_fh {
	struct videobuf_queue vb_vidq;
};
#endif

struct s2253_dev {
	struct video_device vdev;
	struct s2253_stream stream;
#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
	struct v4l2_device v4l2_dev;
#endif
	enum v4l2_buf_type buf_type; /* V4L2_BUF_TYPE_VIDEO_CAPTURE or OUTPUT */
	struct s2253_dmaqueue vidq;
	spinlock_t slock; /* protects: vidq */
#if USE_VIDEOBUF2
	struct vb2_queue vb_vidq;
#else
	// in VB2 this is replaced by vb_vidq->owner
	struct file *in_use_by;	/* limit streaming access to this file */
#endif
	struct mutex lock; /* protects: in_use_by or vb_vidq */
	struct s2253_osd osd; /* OSD struct, modifiable by private controls */
	unsigned char is_ntsc;
};

struct s2253_snd {
	struct s2253_board *board;
	struct snd_card *card;
	struct snd_pcm *pcm;
	struct snd_pcm_substream *capture_substream;
	struct snd_pcm_substream *playback_substream;

	spinlock_t lock;
	struct urb ctrl_urb;
	struct usb_ctrlrequest ctrl_req;

	int capturing;
	unsigned int hw_ptr, w_idx, avail, urb_ptr;
	u8 capture_endpoint;
	struct urb capture_urb;

	int playingback;
	unsigned int pb_ptr, pb_idx, pb_avail;
	u8 playback_endpoint;
	struct urb playback_urb;

	unsigned int current_source;
	unsigned int current_agc;
	unsigned int current_record_volume;
	unsigned int current_playback_volume;

	unsigned int captured_bytes;
};

struct s2253_gpio_chip {
	struct s2253_board *board;
#ifdef CONFIG_GPIOLIB
	struct gpio_chip chip;
#endif
	struct usb_endpoint_descriptor *endpoint;
	int irq;
	struct urb urb; /* interrupt urb */
	unsigned char urb_buf[8];
	char label[16]; /* storage for chip.label */
};

struct s2253_board {
	struct usb_device *usb_dev;
	void *old_usb_drvdata;
	atomic_t refcount;
	struct s2253_dev stream_a;
	struct s2253_dev stream_b;
	struct s2253_dev stream_output;
	struct s2253_snd snd;
	struct s2253_gpio_chip gpio;
	struct s2253_mfg_info info;  /* product id, serial no, fw build no */
	struct s2253_stream overlay;
	int osd_timezone;
	void *ctrl_buf; /* used for control transfers */
};

#define CTRL_BUF_SIZE 16384

#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
static inline struct s2253_dev *to_s2253_dev(struct v4l2_device *v4l2_dev)
{
	return container_of(v4l2_dev, struct s2253_dev, v4l2_dev);
}
#endif

static void s2253_board_get(struct s2253_board *board, const char *who)
{
	dprintk(100, "%s: by %s\n", __func__, who);
	atomic_inc(&board->refcount);
}

static void s2253_board_put(struct s2253_board *board, const char *who)
{
	dprintk(100, "%s: by %s\n", __func__, who);
	if (atomic_dec_and_test(&board->refcount)) {
		dprintk(1, "%s: freeing board %p\n", __func__, board);
		kfree(board);
	} else {
		dprintk(100, "%s: refcount is now %d\n", __func__, atomic_read(&board->refcount));
	}
}

static const struct s2253_fmt *format_by_fourcc(unsigned int fourcc)
{
	unsigned int i;
	for (i = 0; i < ARRAY_SIZE(formats); i++)
		if (formats[i].fourcc == fourcc)
			return formats + i;
	return NULL;
}


/* ------------------------------------------------------------------
   Parameter operations
   ------------------------------------------------------------------*/

static int s2253_get_param_data(struct s2253_board *board, __u16 index, void *data, size_t size)
{
	struct usb_device *usb_dev = board->usb_dev;
	int rc;
	if (!usb_dev)
                return -ENODEV;
	BUG_ON(size > CTRL_BUF_SIZE);
        rc = usb_control_msg (usb_dev, usb_rcvctrlpipe (usb_dev, 0),
				USB_REQ_S2253_PARAM, USB_DIR_IN | USB_TYPE_VENDOR, 0, index,
				board->ctrl_buf, size, USB_CTRL_GET_TIMEOUT);
	if (rc >= 0)
		memcpy(data, board->ctrl_buf, size);
	else
		dprintk(1, "%s rc=%d\n", __func__, rc);
	return rc;
}

static int s2253_set_param_data(struct s2253_board *board, __u16 index, __u16 value, void *data, size_t size)
{
	struct usb_device *usb_dev = board->usb_dev;
	int rc;
        if (!usb_dev)
                return -ENODEV;
	BUG_ON(size > CTRL_BUF_SIZE);
	memcpy(board->ctrl_buf, data, size);
	rc = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
				USB_REQ_S2253_PARAM, USB_DIR_OUT | USB_TYPE_VENDOR, value, index,
				board->ctrl_buf, size, USB_CTRL_SET_TIMEOUT);
	if (rc < 0) dprintk(1, "%s rc=%d\n", __func__, rc);
	return rc;
}

static int s2253_set_param(struct s2253_board *board, __u16 index, __u16 value)
{
	return s2253_set_param_data(board, index, value, NULL, 0);
}

static int s2253_get_param(struct s2253_board *board, __u16 index)
{
	__u16 tmp;
	int rc;
	rc = s2253_get_param_data(board, index, &tmp, sizeof tmp);
	return (rc < 0) ? rc : le16_to_cpu(tmp);
}

static int s2253_dev_set_param(struct s2253_dev *dev, __u16 index, __u16 value)
{
	return s2253_set_param_data(dev->stream.board, index, value, NULL, 0);
}

static int s2253_dev_get_param(struct s2253_dev *dev, __u16 index)
{
	__u16 tmp;
	int rc;
	rc = s2253_get_param_data(dev->stream.board, index, &tmp, sizeof tmp);
	return (rc < 0) ? rc : le16_to_cpu(tmp);
}


#if !USE_VIDEOBUF2

/* ------------------------------------------------------------------
   Videobuf operations
   ------------------------------------------------------------------*/

static int buffer_setup(struct videobuf_queue *vq, unsigned int *count,
			unsigned int *size)
{
	struct s2253_dev *dev = vq->priv_data;
	struct s2253_stream *stream = &dev->stream;

	dprintk(1, "%s: stream %p size=%u\n", __func__, stream, stream->framesize);

	*size = stream->framesize;

	if (0 == *count)
		*count = S2253_DEF_BUFS;

	while (*size * (*count) > vid_limit * 1024 * 1024)
		(*count)--;

	return 0;
}

static void free_buffer(struct videobuf_queue *vq, struct s2253_buffer *buf)
{
	dprintk(4, "%s state=%d memory=%d streaming=%d\n", __func__, buf->vb.state, buf->vb.memory, vq->streaming);
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) && \
     LINUX_VERSION_CODE != KERNEL_VERSION(2, 6, 23)) || V4L_DVB_TREE
	videobuf_vmalloc_free(&buf->vb);
#else
	videobuf_waiton(&buf->vb, 0, 0);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 16)
	videobuf_dma_unmap(vq, &buf->vb.dma);
#else
	videobuf_dma_pci_unmap(NULL, &buf->vb.dma);
#endif
	videobuf_dma_free(&buf->vb.dma);
#endif
	buf->vb.state = VIDEOBUF_NEEDS_INIT;
}

static int norm_maxw(struct video_device *vdev)
{
	return S2253_MAX_IMAGE_WIDTH;
}

static int norm_maxh(struct video_device *vdev)
{
	return S2253_MAX_IMAGE_HEIGHT;
}

static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
			  enum v4l2_field field)
{
	struct s2253_dev *dev = vq->priv_data;
	struct s2253_buffer *buf = container_of(vb, struct s2253_buffer, vb);
	struct s2253_stream *stream = &dev->stream;
	int rc;
	dprintk(4, "%s, field=%d\n", __func__, field);
	if (stream->fmt == NULL)
		return -EINVAL;

	if ((stream->width > norm_maxw(&dev->vdev)) ||
	    (stream->height > norm_maxh(&dev->vdev))) {
		dprintk(1, "invalid buffer prepare\n");
		return -EINVAL;
	}
	buf->vb.size = stream->framesize;
	if (buf->vb.size > S2253_MAX_VIDEO_BUFFER_SIZE) {
		dprintk(4, "buffer size %lu too large\n", buf->vb.size);
		return -EINVAL;
	}
	if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) {
		dprintk(4, "invalid buffer prepare %zu < %lu\n",
			buf->vb.bsize, buf->vb.size);
		return -EINVAL;
	}

	buf->fmt = stream->fmt;
	buf->vb.width = stream->width;
	buf->vb.height = stream->height;
	buf->vb.bytesperline = stream->bytesperline;
	buf->vb.field = field;
	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 16)
		rc = videobuf_iolock(vq, &buf->vb, NULL);
#else
		rc = videobuf_iolock(NULL, &buf->vb, NULL);
#endif
		if (rc < 0) {
			dprintk(1, "videobuf_iolock returned %d\n", rc);
			free_buffer(vq, buf);
			return rc;
		}
	}

	buf->vb.state = VIDEOBUF_PREPARED;
	return 0;
}

static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
	struct s2253_buffer *buf = container_of(vb, struct s2253_buffer, vb);
	struct s2253_dev *dev = vq->priv_data;
	struct s2253_dmaqueue *vidq = &dev->vidq;
	dprintk(4, "%s streaming=%d dev=%p vidq=%p vidq->dev=%p\n", __func__, vq ? vq->streaming: 0, dev, vidq, vidq ? vidq->dev : NULL);
	buf->vb.state = VIDEOBUF_QUEUED;
	buf->ts = dev->stream.cur_ts;
	list_add_tail(&buf->vb.queue, &vidq->active);
	wake_up(&vidq->wait);
}

static void buffer_release(struct videobuf_queue *vq,
			   struct videobuf_buffer *vb)
{
	struct s2253_buffer *buf = container_of(vb, struct s2253_buffer, vb);
	dprintk(4, "%s streaming=%d\n", __func__, vq->streaming);
	wait_event(vb->done, vb->state != VIDEOBUF_ACTIVE);
	free_buffer(vq, buf);
}

static struct videobuf_queue_ops s2253_video_qops = {
	.buf_setup = buffer_setup,
	.buf_prepare = buffer_prepare,
	.buf_queue = buffer_queue,
	.buf_release = buffer_release,
};

static int vidioc_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *req)
{
	struct s2253_fh *fh = priv;

	dprintk(4, "%s\n", __func__);
	return videobuf_reqbufs(&fh->vb_vidq, req);
}

static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	struct s2253_fh *fh = priv;

	dprintk(4, "%s\n", __func__);
	return videobuf_querybuf(&fh->vb_vidq, p);
}

// NOTE: this is called with dev->slock locked
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	struct s2253_fh *fh = priv;
	int ret = videobuf_qbuf(&fh->vb_vidq, p);

	dprintk(4, "%s %d\n", __func__, ret);
	return ret;
}

// NOTE: this is called with dev->slock locked
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	struct s2253_fh *fh = priv;

	dprintk(4, "%s %d\n", __func__, file->f_flags & O_NONBLOCK);
	return videobuf_dqbuf(&fh->vb_vidq, p, file->f_flags & O_NONBLOCK);
}

#if defined(CONFIG_VIDEO_V4L1_COMPAT) && \
    LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) && \
    LINUX_VERSION_CODE != KERNEL_VERSION(2, 6, 23)
static int vidioc_cgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
{
	struct s2253_fh *fh = priv;
	dprintk(4, "%s\n", __func__);
	return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8);
}
#endif


static int res_get(struct s2253_dev *dev, struct file *file)
{
	/* is it free? */
	mutex_lock(&dev->lock);
	if (dev->in_use_by != NULL) {
		/* no, someone else uses it */
		mutex_unlock(&dev->lock);
		return 0;
	}
	/* it's free, grab it */
	dev->in_use_by = file;
	dprintk(1, "s2253: res: get\n");
	mutex_unlock(&dev->lock);
	return 1;
}

static int res_locked(struct s2253_dev *dev, struct file *file)
{
	return dev->in_use_by != NULL;
}

static int res_check(struct s2253_dev *dev, struct file *file)
{
	return dev->in_use_by == file;
}

static void res_free(struct s2253_dev *dev, struct file *file)
{
	mutex_lock(&dev->lock);
	if (dev->in_use_by == file)
		dev->in_use_by = NULL;
	mutex_unlock(&dev->lock);
	dprintk(1, "res: put\n");
}

#else /* USE_VIDEOBUF2 */

/* ------------------------------------------------------------------
   Videobuf2 operations
   ------------------------------------------------------------------*/

static int queue_setup(struct vb2_queue *vq,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
		       const struct v4l2_format *fmt,
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
		       const void *parg,
#endif
		       unsigned int *nbuffers,
		       unsigned int *nplanes,
#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 39)
		       unsigned long sizes[], 
#else
		       unsigned int sizes[], 
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
		       void *alloc_ctxs[]
#else
		       struct device *alloc_devs[]
#endif
		       )
{
	struct s2253_dev *dev = vb2_get_drv_priv(vq);
	if (0 == *nbuffers)
		*nbuffers = S2253_DEF_BUFS;
	*nplanes = 1;
	sizes[0] = dev->stream.framesize;
	return 0;
}

static int buffer_prepare(struct vb2_buffer *vb)
{
	struct s2253_buffer *buf = container_of(vb, struct s2253_buffer, vb);
	struct s2253_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
	struct s2253_stream *stream = &dev->stream;

	if (stream->fmt == NULL)
		return -EINVAL;
	buf->fmt = stream->fmt;
	if (vb2_plane_size(vb, 0) < stream->framesize) {
		return -EINVAL;
	}
	vb2_set_plane_payload(vb, 0, stream->framesize);
	return 0;
}

static void buffer_queue(struct vb2_buffer *vb)
{
	struct s2253_buffer *buf = container_of(vb, struct s2253_buffer, vb);
	struct s2253_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
	unsigned long flags = 0;

	dprintk(4, "%s %d\n", __func__, vb->index);
	spin_lock_irqsave(&dev->slock, flags);
	buf->ts = dev->stream.cur_ts;
	list_add_tail(&buf->list, &dev->vidq.active);
	spin_unlock_irqrestore(&dev->slock, flags);
	wake_up(&dev->vidq.wait); /* wake output_processing_thread in case it is waiting */
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
static void vb2_ops_wait_finish(struct vb2_queue *vq)
{
	struct s2253_dev *dev = vb2_get_drv_priv(vq);
	mutex_lock(&dev->lock);
}

static void vb2_ops_wait_prepare(struct vb2_queue *vq)
{
	struct s2253_dev *dev = vb2_get_drv_priv(vq);
	mutex_unlock(&dev->lock);
}
#endif

static int s2253_init_stream(struct s2253_stream *stream);
static void s2253_uninit_stream(struct s2253_stream *stream, int free_buffers, int release);

static int start_streaming(struct vb2_queue *vq
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)
			 , unsigned int count
#endif
) {
	struct s2253_dev *dev = vb2_get_drv_priv(vq);
	return s2253_init_stream(&dev->stream);
}

/* abort streaming and wait for last buffer */
static
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
void
#else
int
#endif
stop_streaming(struct vb2_queue *vq)
{
	struct s2253_dev *dev = vb2_get_drv_priv(vq);
	s2253_uninit_stream(&dev->stream, 1, 0);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)
	return 0;
#endif
}

static struct vb2_ops s2253_vb2_ops = {
	.queue_setup = queue_setup,
	.buf_prepare = buffer_prepare,
	.buf_queue = buffer_queue,
	.start_streaming = start_streaming,
	.stop_streaming = stop_streaming,
#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
	.wait_prepare = vb2_ops_wait_prepare,
	.wait_finish = vb2_ops_wait_finish,
#endif
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
/* The queue is busy if there is a owner and you are not that owner. */
static inline bool vb2_queue_is_busy(struct video_device *vdev, struct file *file)
{
	return vdev->queue->owner && vdev->queue->owner != file->private_data;
}
#endif

static int s2253_vb2_fop_release(struct file *file)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	struct video_device *vdev = video_devdata(file);

	/* Special case for output endpoint, delay returning from release
	 * until the stream has finished playback */
	if (stream->endpoint == S2253_ENDPOINT_OUTPUT &&
	    file->private_data == vdev->queue->owner) {
		s2253_uninit_stream(stream, 1, 1);
	}
	return vb2_fop_release(file);
}

#endif /* USE_VIDEOBUF2 */

static int vidioc_querycap(struct file *file, void *priv,
			   struct v4l2_capability *cap)
{
	struct s2253_dev *dev = video_drvdata(file);

	memset(cap, 0, sizeof *cap);
	strscpy(cap->driver, "s2253", sizeof(cap->driver));
	snprintf(cap->card, sizeof(cap->card) - 1, "Sensoray Model %d SN#%06d",
		dev->stream.board->info.model_no,
		dev->stream.board->info.serial_no);
	usb_make_path(dev->stream.board->usb_dev, cap->bus_info, sizeof(cap->bus_info));
	cap->version = DRV_VERSION;
	if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT)
		cap->capabilities = V4L2_CAP_VIDEO_OUTPUT;
	else
		cap->capabilities = V4L2_CAP_VIDEO_CAPTURE;
	cap->capabilities |= V4L2_CAP_AUDIO | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
	cap->device_caps = cap->capabilities;
	cap->capabilities |= V4L2_CAP_DEVICE_CAPS | V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_CAPTURE;
#endif

	return 0;
}

static int vidioc_enum_fmt(struct file *file, void *priv,
			   struct v4l2_fmtdesc *f)
{
	struct s2253_dev *dev = video_drvdata(file);
	unsigned int index = 0;
	if (!f)
		return -EFAULT;
	index = f->index;
	if (index >= ARRAY_SIZE(formats))
		return -EINVAL;
	while ((formats[index].types & (1 << f->type)) == 0) {
		index = ++f->index;
		if (index >= ARRAY_SIZE(formats))
			return -EINVAL;
	}
	if (formats[index].format == S2253_FORMAT_MPEG2 &&
		(dev->stream.board->info.features & S2253_FEATURE_MPEG2) == 0)
		index = ++f->index; // skip MPEG2 if not supported
	if (formats[index].format == S2253_FORMAT_MPEG_PS)
		index = ++f->index; // skip secondary MPEG format
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
	if (formats[index].format == S2253_FORMAT_MP4)
		index = ++f->index; // skip "Unknown pixelformat 0x3234504d"
		// unfortunately, this breaks MP4 recording in demo.py
#endif
	dprintk(4, "name %s %x\n", formats[index].name, dev->buf_type /*== V4L2_BUF_TYPE_VIDEO_OUTPUT ? "output" : "capture"*/);
	strscpy(f->description, formats[index].name, sizeof(f->description));
	f->pixelformat = formats[index].fourcc;
	f->type = dev->buf_type;
	f->flags = formats[index].flags;
	return 0;
}

static int vidioc_g_fmt(struct file *file, void *priv,
			struct v4l2_format *f)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	f->fmt.pix.width = stream->width;
	f->fmt.pix.height = stream->height;
	f->fmt.pix.field = stream->field;
	f->fmt.pix.pixelformat = stream->fmt->fourcc;
	f->fmt.pix.bytesperline = stream->fmt_bytesperline;
	f->fmt.pix.sizeimage = stream->framesize;
	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
	f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
	f->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT;
	f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT;
#endif
	return 0;
}

static __u32 s2253_bytesperline(struct v4l2_format *f, const struct s2253_fmt *fmt)
{
	int bytesperline;

	if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) {
		f->fmt.pix.bytesperline = 0;
		bytesperline = f->fmt.pix.width / 2;
		f->fmt.pix.sizeimage = f->fmt.pix.height * bytesperline;
		return bytesperline;
	}
	if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) {
		bytesperline = f->fmt.pix.width;
	} else {
		bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
	}
	if (f->fmt.pix.bytesperline < bytesperline) {
		f->fmt.pix.bytesperline = bytesperline;
	}
	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
	if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) {
		f->fmt.pix.sizeimage = f->fmt.pix.sizeimage * 3 / 2;
	}
	return bytesperline;
}

static int vidioc_try_fmt(struct file *file, void *priv,
			  struct v4l2_format *f)
{
	struct s2253_dev *dev = video_drvdata(file);
	const struct s2253_fmt *fmt;
	enum v4l2_field field;
	int  b_any_field = 0;
	fmt = format_by_fourcc(f->fmt.pix.pixelformat);

	if (fmt == NULL || (fmt->types & (1 << dev->buf_type)) == 0) {
		dprintk(1, "%s: invalid pixelformat 0x%x %c%c%c%c %p\n", __func__, f->fmt.pix.pixelformat,
			f->fmt.pix.pixelformat & 0xff,
			f->fmt.pix.pixelformat >> 8,
			f->fmt.pix.pixelformat >> 16,
			f->fmt.pix.pixelformat >> 24,
			fmt);
		return -EINVAL;
	}
	field = f->fmt.pix.field;
	if (field == V4L2_FIELD_ANY)
		b_any_field = 1;
	dprintk(2, "try format %s %c%c%c%c width %d height %d bytesperline %d field=%d\n",
		dev->is_ntsc ? "NTSC" : "PAL",
		f->fmt.pix.pixelformat & 0xff,
		f->fmt.pix.pixelformat >> 8,
		f->fmt.pix.pixelformat >> 16,
		f->fmt.pix.pixelformat >> 24,
		f->fmt.pix.width, f->fmt.pix.height,
		f->fmt.pix.bytesperline,
		field);
        if (b_any_field) {
		if (s2253_dev_get_param(dev, S2253_CAPTURE_INTERLACE + dev->stream.param_shift) == 1) {
			field = dev->is_ntsc ? V4L2_FIELD_SEQ_BT : V4L2_FIELD_SEQ_TB;
		} else {
			switch (s2253_dev_get_param(dev, S2253_INTERPOLATE)) {
			default:
				dprintk(4, "error getting interpolate param\n");
				fallthrough;
			case 0: field = dev->is_ntsc ? V4L2_FIELD_INTERLACED_BT : V4L2_FIELD_INTERLACED_TB;
				break;
			case 1: field = V4L2_FIELD_NONE;
				break;
			}
		}
	}

	v4l_bound_align_image(&f->fmt.pix.width, S2253_MIN_IMAGE_WIDTH,
			      S2253_MAX_IMAGE_WIDTH, 4,
			      &f->fmt.pix.height, S2253_MIN_IMAGE_HEIGHT,
			      S2253_MAX_IMAGE_HEIGHT, 4, 0);

	dprintk(50, "width %d height %d field %d \n", f->fmt.pix.width,
		f->fmt.pix.height, f->fmt.pix.field);
	if ((f->fmt.pix.width * f->fmt.pix.height * fmt->device_depth) >> 3 > S2253_MAX_VIDEO_BUFFER_SIZE) {
		dprintk(1, "image size is too big\n");
		return -EINVAL; /* image size is too big */
	}

	f->fmt.pix.field = field;
	s2253_bytesperline(f, fmt);
	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
	f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
	f->fmt.pix.quantization = 
		s2253_dev_get_param(dev, S2253_CAPTURE_PIXELRANGE + dev->stream.param_shift) == 1 ?
			V4L2_QUANTIZATION_FULL_RANGE : V4L2_QUANTIZATION_LIM_RANGE;
	f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT;
#endif
	return 0;
}


static int s2253_stream_set_format(struct s2253_stream *stream)
{
	int format = stream->fmt->format;

	if (format == S2253_FORMAT_MP4 ||
	    format == S2253_FORMAT_MPEG_TS ||
	    format == S2253_FORMAT_MPEG_PS)
		format |= stream->video_format | stream->audio_format;
	return s2253_dev_set_param(stream->dev,
		stream->endpoint == S2253_ENDPOINT_OUTPUT ?
			S2253_OUTPUT_FORMAT :
			S2253_CAPTURE_FORMAT + stream->param_shift,
		format);
}

static int vidioc_s_fmt(struct file *file, void *priv,
			struct v4l2_format *f)
{
#if !USE_VIDEOBUF2
	struct s2253_fh *fh = priv;
#endif
	struct s2253_dev *dev = video_drvdata(file);
	const struct s2253_fmt *fmt;
	int rc;
	struct s2253_stream *stream = &dev->stream;
	dprintk(4, "%s: fourcc %x\n", __func__, stream->fmt->fourcc);
	rc = vidioc_try_fmt(file, priv, f);
	if (rc < 0) {
		dprintk(1, "%s: try_fmt failed with %d\n", __func__, rc);
		return rc;
	}
	fmt = format_by_fourcc(f->fmt.pix.pixelformat);

	if (fmt == NULL || (fmt->types & (1 << dev->buf_type)) == 0) {
		dprintk(1, "%s: unsupported type\n", __func__);
		return -EINVAL;
	}


#if USE_VIDEOBUF2
	if (vb2_is_busy(&dev->vb_vidq)) {
		dprintk(1, "%s queue busy\n", __func__);
		return -EBUSY;
	}
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) || V4L_DVB_TREE
	mutex_lock(&fh->vb_vidq.vb_lock);
#endif
	rc = videobuf_queue_is_busy(&fh->vb_vidq);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) || V4L_DVB_TREE
	mutex_unlock(&fh->vb_vidq.vb_lock);
#endif
	if (rc) {
		dprintk(1, "%s queue busy\n", __func__);
		return -EBUSY;
	}

	if (res_locked(dev, file)) {
		dprintk(1, "can't change format after streamon\n");
		return -EBUSY;
	}
#endif

	stream->fmt = fmt;
	stream->width = f->fmt.pix.width;
	stream->height = f->fmt.pix.height;
	stream->fmt_bytesperline = f->fmt.pix.bytesperline;
	stream->bytesperline = s2253_bytesperline(f, fmt);
	stream->framesize = f->fmt.pix.sizeimage;
	stream->field = f->fmt.pix.field;
	dprintk(1, "stream framesize %d, field %d\n", stream->framesize, stream->field);

	rc = 0;
	dprintk(1, "fourcc %x (%s)\n", stream->fmt->fourcc, stream->fmt->name);

	if (stream->endpoint != S2253_ENDPOINT_OUTPUT) {
		switch (f->fmt.pix.field) {
		case V4L2_FIELD_INTERLACED:
		case V4L2_FIELD_INTERLACED_TB:
		case V4L2_FIELD_INTERLACED_BT:
			s2253_dev_set_param(dev, S2253_INTERPOLATE, 0);
			s2253_dev_set_param(dev, S2253_CAPTURE_INTERLACE + stream->param_shift, 0);
			break;
		case V4L2_FIELD_NONE:
		case V4L2_FIELD_TOP:
		case V4L2_FIELD_BOTTOM:
			s2253_dev_set_param(dev, S2253_INTERPOLATE, 1);
			s2253_dev_set_param(dev, S2253_CAPTURE_INTERLACE + stream->param_shift, 0);
			break;
		case V4L2_FIELD_SEQ_TB:
		case V4L2_FIELD_SEQ_BT:
			s2253_dev_set_param(dev, S2253_INTERPOLATE, 0);
			s2253_dev_set_param(dev, S2253_CAPTURE_INTERLACE + stream->param_shift, 1);
			break;
		default:
			break;
		}
		dprintk(1, "stream: width %d height %d format %x\n", stream->width, stream->height, fmt->format);
		rc = s2253_stream_set_format(stream);
		if (rc == 0)
        		rc = s2253_dev_set_param(dev, S2253_CAPTURE_WIDTH + stream->param_shift, stream->width);
		if (rc == 0)
        		rc = s2253_dev_set_param(dev, S2253_CAPTURE_HEIGHT + stream->param_shift, stream->height);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
		if (rc == 0) {
			switch (f->fmt.pix.quantization) {
			case V4L2_QUANTIZATION_FULL_RANGE:
				rc = s2253_dev_set_param(dev, S2253_CAPTURE_PIXELRANGE + stream->param_shift, 1);
				break;
			case V4L2_QUANTIZATION_DEFAULT: // PIXELRANGE is only used for YUV encoding so this is ok
			case V4L2_QUANTIZATION_LIM_RANGE:
				rc = s2253_dev_set_param(dev, S2253_CAPTURE_PIXELRANGE + stream->param_shift, 0);
				break;
			default: break;
			}
		}
#endif
	} else {
		if (stream->field == V4L2_FIELD_ALTERNATE)
			stream->height *= 2;
		rc = s2253_stream_set_format(stream);
		if (rc == 0)
        		rc = s2253_dev_set_param(dev, S2253_OUTPUT_WIDTH, stream->width);
		if (rc == 0)
        		rc = s2253_dev_set_param(dev, S2253_OUTPUT_HEIGHT, stream->height);
	}
	return rc;
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29) || V4L_DVB_TREE
static int vidioc_enum_framesizes(struct file *file, void *priv,
				  struct v4l2_frmsizeenum *f)
{
	struct s2253_dev *dev = video_drvdata(file);
	static const __u32 w [] = {720, 704, 640, 368, 352, 320, 192, 176, 160};
	static const __u32 hn[] = {480, 480, 480, 240, 240, 240, 128, 128, 128};
	static const __u32 hp[] = {576, 576, 480, 288, 288, 240, 144, 144, 128};

	if (f->index < 0 || f->index >= ARRAY_SIZE(w))
		return -EINVAL;
	f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
	f->discrete.width = w[f->index];
	f->discrete.height = (dev->is_ntsc ? hn : hp)[f->index];
	return 0;
}
#endif

// this sets dev->is_ntsc based on device video standard
static int s2253_g_std(struct s2253_dev *dev)
{
	int param = dev->stream.endpoint == S2253_ENDPOINT_OUTPUT ?
		S2253_OUTPUT_VIDEO_STANDARD : S2253_INPUT_VIDEO_STANDARD;
	int rc;

	rc = s2253_dev_get_param(dev, param);
	if (rc == S2253_STANDARD_NTSC)
		dev->is_ntsc = 1;
	else if (rc == S2253_STANDARD_PAL)
		dev->is_ntsc = 0;
	else
		return rc;
	return 0;
}


#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0)
static int vidioc_cropcap(struct file *file, void *priv, struct v4l2_cropcap *cropcap)
{
	struct s2253_dev *dev = video_drvdata(file);
	if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT)
		return -EINVAL;
	cropcap->defrect.left = 8;
	cropcap->defrect.top = 0;
	cropcap->defrect.width = 704;
	cropcap->defrect.height = 576;

	return 0;
}

static int vidioc_s_crop(struct file *file, void *priv, V4L_CONST struct v4l2_crop *crop)
{
	struct s2253_dev *dev = video_drvdata(file);
	int rc;

	if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT ||
	    crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;

	rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_LEFT, crop->c.left);
	if (rc < 0) return rc;
	rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_TOP, crop->c.top);
	if (rc < 0) return rc;
	rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_WIDTH, crop->c.width);
	if (rc < 0) return rc;
	rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_HEIGHT, crop->c.height);
	return rc;
}

static int vidioc_g_crop(struct file *file, void *priv, struct v4l2_crop *crop)
{
	struct s2253_dev *dev = video_drvdata(file);
	int x, y, w, h;

	if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT ||
	    crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;

	x = s2253_dev_get_param(dev, S2253_INPUT_CROP_LEFT);
	if (x < 0) return x;
	y = s2253_dev_get_param(dev, S2253_INPUT_CROP_TOP);
	if (y < 0) return y;
	w = s2253_dev_get_param(dev, S2253_INPUT_CROP_WIDTH);
	if (w < 0) return w;
	h = s2253_dev_get_param(dev, S2253_INPUT_CROP_HEIGHT);
	if (h < 0) return h;
	crop->c.left = x;
	crop->c.top = y;
	crop->c.width = w;
	crop->c.height = h;

	return 0;
}
#else // alternate interface in v5.0.0
static int vidioc_g_pixelaspect(struct file *file, void *fh,
				    int buf_type, struct v4l2_fract *aspect)
{
	struct s2253_dev *dev = video_drvdata(file);
	int rc = s2253_g_std(dev);

	if (rc)
		return rc;
	if (buf_type != (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT ? 
			V4L2_BUF_TYPE_VIDEO_OUTPUT : V4L2_BUF_TYPE_VIDEO_CAPTURE))
		return -EINVAL;

	if (dev->is_ntsc) {
		aspect->numerator = 11;
		aspect->denominator = 10;
	} else {
		aspect->numerator = 54;
		aspect->denominator = 59;
	}
	return 0;
}
static int vidioc_g_selection(struct file *file, void *fh,
				  struct v4l2_selection *s)
{
	struct s2253_dev *dev = video_drvdata(file);

	if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT)
		return -ENODATA;
	if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;
	if (s->flags != 0)
		return -ERANGE; // no flags supported

	if (s->target == V4L2_SEL_TGT_CROP) {
		int x, y, w, h;

		x = s2253_dev_get_param(dev, S2253_INPUT_CROP_LEFT);
		if (x < 0) return x;
		y = s2253_dev_get_param(dev, S2253_INPUT_CROP_TOP);
		if (y < 0) return y;
		w = s2253_dev_get_param(dev, S2253_INPUT_CROP_WIDTH);
		if (w < 0) return w;
		h = s2253_dev_get_param(dev, S2253_INPUT_CROP_HEIGHT);
		if (h < 0) return h;
		s->r.left = x;
		s->r.top = y;
		s->r.width = w;
		s->r.height = h;

	} else if (s->target == V4L2_SEL_TGT_CROP_DEFAULT) {
		s->r.left = 8;
		s->r.top = 0;
		s->r.width = 704;
		s->r.height = dev->is_ntsc ? 480 : 576;
	} else {
		return -EINVAL;
	}

	return 0;
}

static int vidioc_s_selection(struct file *file, void *fh,
				  struct v4l2_selection *s)
{
	struct s2253_dev *dev = video_drvdata(file);

	if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT)
		return -ENODATA;
	if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;
	if (s->flags != 0)
		return -ERANGE; // no flags supported
	if (s->target == V4L2_SEL_TGT_CROP) {
		int rc;
		rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_LEFT, s->r.left);
		if (rc < 0) return rc;
		rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_TOP, s->r.top);
		if (rc < 0) return rc;
		rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_WIDTH, s->r.width);
		if (rc < 0) return rc;
		rc = s2253_dev_set_param(dev, S2253_INPUT_CROP_HEIGHT, s->r.height);
		return rc;
	} else {
		return -EINVAL;
	}

	return 0;
}



#endif


static void s2253_free_urb_buffers(struct s2253_stream *stream)
{
	unsigned int i;
	for (i = 0; i < S2253_URBS; ++i) {
		if (stream->urbs[i].urb.status == -EINPROGRESS) {
			dprintk(1, "waiting for urb %d\n", i);
			wait_event(stream->urb_wait,
				stream->urbs[i].urb.status != -EINPROGRESS);
		}
		if (stream->urbs[i].urb_buffer) {
			usb_free_coherent(stream->board->usb_dev,
					  stream->urb_size,
					  stream->urbs[i].urb_buffer,
					  stream->urbs[i].urb_dma);
			stream->urbs[i].urb_buffer = NULL;
		}
	}
	stream->urb_size = 0;
	dprintk(4, "%s\n", __func__);
}

static void s2253_killfree_urbs(struct s2253_stream *stream)
{
	unsigned int i;

	for (i = 0; i < S2253_URBS; ++i)
		usb_kill_urb(&stream->urbs[i].urb);
}

/* forward function prototype */
static void s2253_uninit_stream(struct s2253_stream *stream, int free_buffers, int release);

/* From uvc driver.
 *      Copyright (C) 2005-2009
 *          Laurent Pinchart (laurent.pinchart@skynet.be)
 * Allocate transfer buffers. This function can be called with buffers
 * already allocated when resuming from suspend, in which case it will
 * return without touching the buffers.
 *
 * Limit the buffer size to S2253_MAX_PACKETS bulk/isochronous packets. If the
 * system is too low on memory try successively smaller numbers of packets
 * until allocation succeeds.
 *
 * Return the number of allocated packets on success or 0 when out of memory.
 */
static int s2253_alloc_urb_buffers(struct s2253_stream *stream,
				   unsigned int size, unsigned int psize)
{
	unsigned int npackets;
	unsigned int i;

	/* Buffers are already allocated, bail out. */
	if (stream->urb_size)
		return stream->urb_size / psize;

	/* Compute the number of packets. Bulk endpoints might transfer UVC
	 * payloads accross multiple URBs.
	 */
	npackets = DIV_ROUND_UP(size, psize);
	if (npackets > S2253_MAX_PACKETS)
		npackets = S2253_MAX_PACKETS;

	/* Retry allocations until one succeed. */
	for (; npackets > 1; npackets /= 2) {
		for (i = 0; i < S2253_URBS; ++i) {
			stream->urb_size = psize * npackets;
			stream->urbs[i].urb_buffer = usb_alloc_coherent(
				stream->board->usb_dev, stream->urb_size,
				GFP_KERNEL | __GFP_NOWARN,
				&stream->urbs[i].urb_dma);
			if (!stream->urbs[i].urb_buffer) {
				s2253_free_urb_buffers(stream);
				break;
			}
		}

		if (i == S2253_URBS) {
			//uvc_trace(UVC_TRACE_VIDEO, "Allocated %u URB buffers "
			//	"of %ux%u bytes each.\n", UVC_URBS, npackets,
			//	psize);
			return npackets;
		}
	}
	dprintk(1, "failed allocate urbs\n");
	return 0;
}
// end uvc_driver.c code



#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
static void s2253_video_urb_complete(struct urb *urb, struct pt_regs *regs)
#else
static void s2253_video_urb_complete(struct urb *urb)
#endif
{
	struct s2253_stream *stream = urb->context;
	unsigned long flags;
	struct s2253_video_urb *vurb = to_s2253_video_urb(urb);
	int i;

	if (!stream)
		return;
	for (i = 0; i < S2253_URBS; i++)
		if (urb == &stream->urbs[i].urb)
			break;

	dprintk(5, "%s: urb=%d length=%d actual_length=%d status=%d\n", __func__,
		i, urb->transfer_buffer_length, urb->actual_length, urb->status);

	spin_lock_irqsave(&stream->urb_lock, flags);
	list_add_tail(&vurb->list, &stream->urb_list);
	spin_unlock_irqrestore(&stream->urb_lock, flags);
	wake_up(&stream->urb_wait);
}

static int s2253_process_data(struct s2253_dev *dev, void *usbbuf, int size)
{
	struct s2253_dmaqueue *dma_q = &dev->vidq;
	struct s2253_stream *stream = &dev->stream;
	struct s2253_buffer *buf;
	char *vbuf;
	struct s2253_payload_header *header;
	unsigned char *ptr = usbbuf;
	int rc = 0;
	__u64 curseq;
	__u32 scr;

	header = usbbuf;
	ptr = (unsigned char *)usbbuf + header->length;
	size -= header->length;
	//dprintk(1, "%s: fid=%d eof=%d len=%d %02x %02x %02x %02x %02x %02x %02x\n", __func__, header->fid, header->eof, size, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6]);
	if (size <= 0) {
		dprintk(1, "short packet size=%d\n", size);
		return 0;
	}

	scr = header->SCR[0] +
		(header->SCR[1] << 8) +
		(header->SCR[2] << 16) +
		(header->SCR[3] << 24);
	if (stream->last_scr != 0 && scr != stream->last_scr + 1) {
		dprintk(1, "packet discontinuity: %u lost\n",
			scr - stream->last_scr - 1);
	}
	stream->last_scr = scr;


	if (header->res != 0) {
		__u32 pts;
		static __u32 lastpts;
		pts = header->PTS[0] +
			(header->PTS[1] << 8) +
			(header->PTS[2] << 16) +
			(header->PTS[3] << 24);
		dprintk(1, "%s, audio packet SEQ=%d size=%d dur=%d PTS=%02x%02x%02x%02x SCR=%02x%02x%02x%02x\n", __func__,
			(int)header->seq, size, pts-lastpts,
			header->PTS[3], header->PTS[2], header->PTS[1], header->PTS[0],
			header->SCR[3], header->SCR[2], header->SCR[1], header->SCR[0]);
		lastpts = pts;
		stream->last_fid = header->fid;
		stream->last_eof = header->eof;
		spin_lock(&dev->slock);
		if (!stream->audio) {
			stream->audio = kmalloc(8192, GFP_KERNEL);
			stream->audio_size = 8192;
			stream->audio_len = 0;
		}
		if (stream->audio) {
			if (size > (int)(stream->audio_size - stream->audio_len)) {
				size = stream->audio_size - stream->audio_len;
				dprintk(1, "discarding audio packet %d bytes avail\n", size);
			}
			memcpy(stream->audio + stream->audio_len, ptr, size);
			stream->audio_len += size;
		}
		spin_unlock(&dev->slock);
		return 0;
	}

	spin_lock(&dev->slock);

	buf = stream->active;
	if (!buf) {
		/* get a new active buffer from the dma_q */
		if (list_empty(&dma_q->active)) {
			dprintk(4, "No active queue to serve\n");
			spin_unlock(&dev->slock);
			/* keep waiting until a buffer is queued */
			if (wait_event_interruptible(dma_q->wait,
					!list_empty(&dma_q->active) ||
					kthread_should_stop())) {
				dprintk(4, "dma_q wait interrupted\n");
				return 0; /* return if interrupted */
			}
			if (kthread_should_stop())
				return 0;
			dprintk(4, "There's a buffer from the queue\n");
			spin_lock(&dev->slock);
		}
#if !USE_VIDEOBUF2
		buf = list_entry(dma_q->active.next, struct s2253_buffer, vb.queue);
#else
		buf = list_entry(dma_q->active.next, struct s2253_buffer, list);
#endif
		if (buf == NULL) {
			dprintk(1, "NULL buf\n");
			goto unlock;
		}
#if !USE_VIDEOBUF2
		/* Sanity check our buffer state here. If the streamoff call
		 * changes the buffer states before some urbs complete, we can
		 * get here with state such as VIDEOBUF_NEEDS_INIT, which can
		 * cause crashes (and hangs with irqs disabled!) */
		if (buf->vb.state != VIDEOBUF_QUEUED) {
			dprintk(1, "VIDEOBUF NOT QUEUED, state is %d\n", buf->vb.state);
			goto unlock;
		}
		list_del(&buf->vb.queue);
		buf->vb.state = VIDEOBUF_ACTIVE;
#else
		if (buf->vb.state != VB2_BUF_STATE_ACTIVE) {
			dprintk(1, "VIDEOBUF NOT QUEUED, state is %d\n", buf->vb.state);
			goto unlock;
		}
		list_del(&buf->list);
#endif
		stream->active = buf;
		if (header->fid == stream->last_fid) {
			dprintk(1, "can't start a new buf in the middle of a frame\n");
			goto debug_unlock;
		}
	}


	if (header->fid != dev->stream.last_fid) {
		__u32 pts;
		static __u32 lastpts;
		struct timeval tv;

		if (stream->last_fid != -1 && !stream->last_eof) {
			dprintk(1, "%s: missing last eof - possible stream error\n", __func__);
			stream->errors.eof++;
		}
		stream->last_fid = header->fid;
		pts = header->PTS[0] +
			(header->PTS[1] << 8) +
			(header->PTS[2] << 16) +
			(header->PTS[3] << 24);

		/* detect wrap */
		if (pts < stream->last_ts) {
			__u64 max_ts = 0x100000000ULL;
			stream->ts_off.tv_sec += max_ts / S2253_TS_HZ;
			stream->ts_off.tv_usec += ((max_ts % S2253_TS_HZ) * 100) / (S2253_TS_HZ / 10000);
			if (stream->ts_off.tv_usec > USEC_PER_SEC) {
				stream->ts_off.tv_sec += 1;
				stream->ts_off.tv_usec -= USEC_PER_SEC;
			}
		}
		stream->last_ts = pts;

		/* adjust timestamp to monotonic clock */
		tv = stream->ts_off;
		tv.tv_sec += pts / S2253_TS_HZ;
		tv.tv_usec += ((pts % S2253_TS_HZ) * 100) / (S2253_TS_HZ / 10000);
		if (tv.tv_usec >= USEC_PER_SEC) {
			tv.tv_sec += 1;
			tv.tv_usec -= USEC_PER_SEC;
		}

#if !USE_VIDEOBUF2
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
		/* struct videobuf_buffer.ts is u64 nanoseconds starting in v5.1 */
		buf->vb.ts = timeval_to_ns(&tv);
#else
		buf->vb.ts = tv;
#endif
		buf->vb.size = 0;
#else
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
		buf->vb.v4l2_buf.timestamp = tv;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
		buf->vb.timestamp = timeval_to_ns(&tv);
#else
		// FIXME: this timestamp field doesn't exist on 4.4
		#warning Linux v4.4 will not have timestamps on video buffers (please enable CONFIG_VIDEOBUF_VMALLOC)		
#endif
		vb2_set_plane_payload(&buf->vb, 0, 0);
#endif
		curseq = le64_to_cpu(header->seq);
		dprintk(5, "sequence num %lld ts %u (%u)\n", curseq, pts, pts - lastpts);
		lastpts = pts;
		if (stream->startseq == STARTSEQ_NOTINIT)
			stream->startseq = curseq;
		else if (curseq != stream->last_seq + stream->fdec) {
			dprintk(1, "sequence num %llu out of order! last=%llu\n", curseq, stream->last_seq);
			stream->errors.sequence++;
		}
		stream->last_seq = curseq;

		/* adjust sequence number to be 0 indiced */
		curseq -= stream->startseq;
		/* sequence number is 64 bits.  Ideally field count
		 * in Linux would also be 64 bit, but this is not critical.
		 * wrap could be handled by user program if necessary.
		 * At 29.97fps, 32-bit sequence will take 4.5 years to wrap.
		 */
#if !USE_VIDEOBUF2
		buf->vb.field_count = (__u32) (curseq * 2);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
		buf->vb.v4l2_buf.sequence = curseq;
#else
		// FIXME: this probably won't work
		to_vb2_v4l2_buffer(&buf->vb)->sequence = curseq;
#endif
		//dprintk(2, "field count %d\n", buf->vb.field_count);
	}
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) && \
     LINUX_VERSION_CODE != KERNEL_VERSION(2, 6, 23)) || V4L_DVB_TREE
#if !USE_VIDEOBUF2
	vbuf = videobuf_to_vmalloc(&buf->vb);
#else
	vbuf = vb2_plane_vaddr(&buf->vb, 0);
#endif
	if (vbuf) {
		// active video bytes per line
		unsigned int active = stream->fmt->flags & V4L2_FMT_FLAG_COMPRESSED ? 
				stream->bytesperline :
				(stream->width * stream->fmt->depth) >> 3;
#if !USE_VIDEOBUF2
		unsigned long vb_size = buf->vb.size;
		unsigned long vb_bsize = buf->vb.bsize;
#else
		unsigned long vb_size = vb2_get_plane_payload(&buf->vb, 0);
		unsigned long vb_bsize = vb2_plane_size(&buf->vb, 0);
#endif
		// field mode, or bytesperline requires copying individual lines
		if (header->sti || stream->bytesperline != active) {
			int len;

			if (stream->fmt->flags & V4L2_FMT_FLAG_COMPRESSED) {
				dprintk(0, "invalid header for compressed stream (USB corruption?)\n");
				goto unlock;
			}
			// how much space is left on this line
			len = active - (vb_size % stream->bytesperline);
			while (size >= len && vb_size < vb_bsize) {
				memcpy(vbuf + vb_size, ptr, len);
				ptr += len;
				size -= len;
				vb_size += len + stream->bytesperline - active;
				len = active; // vb_size is now aligned on stream->bytesperline
				if (!header->sti)
					continue;
				vb_size += stream->bytesperline; // skip every other line in field mode
				if (vb_size < stream->framesize)
					continue;
				if (vb_size == stream->framesize) {
					// finished first field
					//dprintk(1, "field 0 done %lu\n", vb_size);
					vb_size = stream->bytesperline; // start other field
				} else if (vb_size == stream->framesize + stream->bytesperline) {
					// finished second field
					//dprintk(1, "field 1 done %lu\n", vb_size);
 					vb_size -= stream->bytesperline;
				} else {
					dprintk(1, "field overflow %lu\n", vb_size);
				}
		 	}
			if (size > 0) {
				// copy incomplete line
				memcpy(vbuf + vb_size, ptr, size);
				vb_size += size;
			}
		} else {
			if (vb_size + size > vb_bsize) {
				dprintk(0, "buffer would have overflowed (USB corruption?)\n");
				size = vb_bsize - vb_size; /* prevent overflow */
			}
			BUG_ON(size < 0);
			memcpy(vbuf + vb_size, ptr, size);
			vb_size += size;
		}
#if !USE_VIDEOBUF2
		buf->vb.size = vb_size;
#else
		vb2_set_plane_payload(&buf->vb, 0, vb_size);
#endif
		if (header->eof && buf->fmt->fourcc == V4L2_PIX_FMT_YUYV) {
			unsigned int i;
			/* convert UYVY to YUYV */
			for (i = 0; i < vb_size; i += 2)
				swab16s((__u16 *) &vbuf[i]);
		}
	} else {
		dprintk(1, "%s no buf!\n", __func__);
	}
#else
	{
		int i, pos = 0;

		if (buf->vb.size + size > buf->vb.bsize)
			size = buf->vb.bsize - buf->vb.size; /* prevent overflow */

		/* TODO: this doesn't handle header->sti, field-based xfer */
		for (i = 0; i < buf->vb.dma.nr_pages; i++) {
			struct scatterlist *sg = &buf->vb.dma.sglist[i];
			// do we need to write to this page?
			if (buf->vb.size + size >= pos &&
			    buf->vb.size < pos + sg->length) {
				struct page *pg = pfn_to_page(sg->dma_address >> PAGE_SHIFT);
				char *p = kmap_atomic(pg, KM_BOUNCE_READ) + sg->offset;
				int start, end;

				start = buf->vb.size - pos;
				end = buf->vb.size + size - pos;
				if (end > sg->length)
					end = sg->length;
				if (start < 0)
					memcpy(p, ptr - start, end);
				else
					memcpy(p + start, ptr, end - start);
				kunmap_atomic(p, KM_BOUNCE_READ);
			}
			pos += sg->length;
		}
		buf->vb.size += size;

		if (header->eof && buf->fmt->fourcc == V4L2_PIX_FMT_YUYV) {
			/* convert UYVY to YUYV */
			for (i = 0; i < buf->vb.dma.nr_pages; i++) {
				struct scatterlist *sg = &buf->vb.dma.sglist[i];
				struct page *pg = pfn_to_page(sg->dma_address >> PAGE_SHIFT);
				char *p = kmap_atomic(pg, KM_BOUNCE_READ) + sg->offset;
				int j;

				for (j = 0; j < sg->length; j += 2)
					swab16s((__u16 *) &p[j]);
				kunmap_atomic(p, KM_BOUNCE_READ);
			}
		}
	}
#endif

	/* end of frame? */
	stream->last_eof = header->eof;
	if (header->eof) {
#if !USE_VIDEOBUF2
		buf->vb.state = VIDEOBUF_DONE;
		if ((buf->fmt->fourcc == V4L2_PIX_FMT_YUYV ||
		     buf->fmt->fourcc == V4L2_PIX_FMT_UYVY ||
		     buf->fmt->fourcc == V4L2_PIX_FMT_NV12) &&
			buf->vb.size != stream->framesize) {
			dprintk(2, "buffer format %08x size %lu != expected %d",
			buf->fmt->fourcc, buf->vb.size, stream->framesize);
			buf->vb.state = VIDEOBUF_ERROR;
                }
		wake_up(&buf->vb.done);
		dprintk(5, "wakeup [buf/i] [%p/%d] size %lu\n", buf, buf->vb.i, buf->vb.size);
#else
		if ((buf->fmt->fourcc == V4L2_PIX_FMT_YUYV ||
		     buf->fmt->fourcc == V4L2_PIX_FMT_UYVY ||
		     buf->fmt->fourcc == V4L2_PIX_FMT_NV12) &&
		     vb2_get_plane_payload(&buf->vb, 0) != stream->framesize) {
			vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
		} else {
			vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
		}
#endif
		stream->active = NULL;
	}
	goto unlock;
debug_unlock:
	dprintk(1, "%s: lastfid=%d lastseq=%llu lasteof=%d active=%p\n", __func__, stream->last_fid, stream->last_seq, stream->last_eof, stream->active);
	dprintk(1, "%s: fid=%d eof=%d seq=%llu size=%d\n", __func__, header->fid, header->eof, header->seq, size);
unlock:
	spin_unlock(&dev->slock);
	return rc;
}

static int stream_urb_list_empty(struct s2253_stream *stream)
{
	unsigned long flags;
	int rc;

	spin_lock_irqsave(&stream->urb_lock, flags);
	rc = list_empty(&stream->urb_list);
	spin_unlock_irqrestore(&stream->urb_lock, flags);
	return rc;
}

/* Returns video URB from stream->urb_list when available, otherwise NULL if timeout or interrupt.
 * Set 'interruptible' to 0 after the kthread has received kthread_stop() to flush the stream packets.
 */
static struct s2253_video_urb *get_vurb(struct s2253_stream *stream, bool interruptible)
{
	struct s2253_video_urb *vurb = NULL;
	unsigned long flags;

	while (!vurb) {
		int rc;
		if (interruptible) {
			rc = wait_event_interruptible_timeout(stream->urb_wait,
				!stream_urb_list_empty(stream) ||
				stream->board->usb_dev == NULL,
				HZ);
		} else {
			rc = wait_event_timeout(stream->urb_wait,
				!stream_urb_list_empty(stream) ||
				stream->board->usb_dev == NULL,
				HZ);
		}
		if (rc <= 0) {
			dprintk(1, "%s: %s\n", __func__, signal_pending(current) ? "interrupted" : "timeout");
			return NULL;  /* timeout, or interrupted */
		}

		spin_lock_irqsave(&stream->urb_lock, flags);
		if (!list_empty(&stream->urb_list)) {
			vurb = list_entry(stream->urb_list.next,
				  struct s2253_video_urb, list);
			list_del(&vurb->list);
		}
		spin_unlock_irqrestore(&stream->urb_lock, flags);
	}
	return vurb;
}

static int s2253_capture_processing_thread(void *data)
{
	struct s2253_stream *stream = data;
	struct s2253_video_urb *vurb = NULL;
	struct urb *urb;
	int i, rc;
	__u32 ts, usec;
	struct timeval tv;

	dprintk(1, "%s: started\n", __func__);

	/* Submit the URBs. */
	for (i = 0; i < S2253_URBS; ++i) {
		rc = usb_submit_urb(&stream->urbs[i].urb, GFP_KERNEL);
		if (rc < 0) {
			dprintk(1, "Failed to submit URB %u "
			       "(%d).\n", i, rc);
			s2253_uninit_stream(stream, 1, 0);
			goto exit;
		}
		dprintk(3, "Submitted urb %p ret=%d\n", &stream->urbs[i].urb, rc);
	}

	stream->last_ts = 0;
	s2253_get_param_data(stream->board, S2253_TIMESTAMP, &ts, 4);
	ts = le32_to_cpu(ts);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,1,0)
	tv = ns_to_timeval(ktime_get_ns());
#else
	v4l2_get_timestamp(&tv);
#endif
	tv.tv_sec -= ts / S2253_TS_HZ;
	usec = ((ts % S2253_TS_HZ) * 100) / (S2253_TS_HZ / 10000);
	if (usec > tv.tv_usec) {
		tv.tv_sec -= 1;
		tv.tv_usec += USEC_PER_SEC - usec;
	} else {
		tv.tv_usec -= usec;
	}
	stream->ts_off = tv;

	s2253_dev_set_param(stream->dev, S2253_CAPTURE_STATE +
			stream->param_shift, S2253_STATE_STREAMING);

	while (!kthread_should_stop()) {
		vurb = get_vurb(stream, true); // interruptible
		if (!vurb) {
			dprintk(1, "%s: vurb is nil\n", __func__);
			continue;
		}

		urb = &vurb->urb;
		if (urb->status != 0) {
			dprintk(1, "%s: urb->status = %d\n", __func__, urb->status);
			spin_lock(&stream->dev->slock);
			stream->errors.urb++;
			spin_unlock(&stream->dev->slock);
		} else {
			struct s2253_payload_header *payload = urb->transfer_buffer;
			if (payload->eof && payload->eoh && payload->err && urb->actual_length == payload->length) {
				dprintk(1, "lastpacket recv'd\n");
				break;
			}
			s2253_process_data(stream->dev,
					   urb->transfer_buffer,
					   urb->actual_length);
		}

		if (kthread_should_stop())
			break;
		dprintk(5, "submitting urb %p.\n", urb);
		if ((rc = usb_submit_urb(urb, GFP_KERNEL)) < 0) {
			dprintk(1, "Failed to resubmit video URB (%d).\n", rc);
			break;
		}
	}
	dprintk(1, "%s: shutting down\n", __func__);
	s2253_dev_set_param(stream->dev, S2253_CAPTURE_STATE + stream->param_shift, S2253_STATE_IDLE);

	// flush endpoint until urbs timeout or lastpacket
	do {
		vurb = get_vurb(stream, false); // not interruptible
		if (!vurb)
			break;
		urb = &vurb->urb;
		if (urb->status == 0) {
			struct s2253_payload_header *payload = urb->transfer_buffer;
			if (payload->eof && payload->eoh && payload->err && urb->actual_length == payload->length) {
				dprintk(1, "lastpacket recv'd\n");
				break;
			}
		}
		dprintk(5, "Flushed urb %p actual_length=%d status=%d\n", urb, urb->actual_length, urb->status);
		if ((rc = usb_submit_urb(urb, GFP_KERNEL)) < 0) {
			dprintk(1, "Failed to resubmit video URB (%d).\n", rc);
			break;
		}
	} while (vurb);

exit:
	s2253_killfree_urbs(stream);

	while (!kthread_should_stop()) {
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(HZ);
		set_current_state(TASK_RUNNING);
	}

	dprintk(1, "%s: exit\n", __func__);
	return 0;
}

enum is_user_data {
	not_user_data = 0,
	is_user_data = 1,
	field_data = 2,
	audio_data = 3,
};
enum use_eof {
	no_eof = 0,
	has_eof = 1,
};

/* returns the number of bytes remaining to be written if interrupted,
 * otherwise zero on success or negative error number */
static int s2253_video_output(struct s2253_stream *stream,
			      const char *vbuf,
			      unsigned int size,
			      enum is_user_data data_kind,
			      enum use_eof eof,
			      __u32 ts)
{
	struct s2253_payload_header *ptr;
	struct s2253_video_urb *vurb;
	struct urb *urb;
	unsigned int urbsize;
	int rc;

	if (!stream)
		return -EIO;

	/* for each 32k urb chunk in frame */
	while (size > 0 && !signal_pending(current)) {
		__u8 *dest;
		
		/* wait for an available urb */
		vurb = get_vurb(stream, true); // interruptible
		if (!vurb) {
			if (!stream->dev) {
				dprintk(1, "%s: stream->dev is NULL\n", __func__);
				return -ENODEV;
			}
			continue;
		}
		urb = &vurb->urb;

		dest = urb->transfer_buffer + sizeof(struct s2253_payload_header);
		ptr = (struct s2253_payload_header *) urb->transfer_buffer;
		memset(ptr, 0, sizeof(struct s2253_payload_header));
		ptr->length = sizeof(struct s2253_payload_header);
		ptr->fid = stream->last_fid;
		ptr->seq = cpu_to_le64(stream->last_seq);
		ptr->res = data_kind == audio_data;
		*(__u32*)(ptr->PTS) = cpu_to_le32(ts);

		/* fill and submit it */
#if 1
		urbsize = S2253_USB_PACKET_SIZE-1 - ptr->length;
#else
		urbsize = 511 - ptr->length; // new urbsize fixing endpoint freeze
#endif
		if (urbsize > size)
			urbsize = size;
		if (((urbsize + ptr->length) & 511) == 0) {
			// urb size is multiple of 512, must split (ZLP won't work)
			urbsize -= 1;
		}
		if (size == urbsize)
			ptr->eof = eof;
		//dprintk(1, "copying data\n");
		if (stream->field == V4L2_FIELD_ALTERNATE) {
			ptr->sti = 1;
		}

		if (data_kind == field_data) {
			ptr->sti = 1;
		} else if (data_kind != is_user_data) {
			memcpy(dest, vbuf, urbsize);
		} else if (copy_from_user(dest, vbuf, urbsize)) {
			return -EFAULT;
		}

		//dprintk(1, "submitting urb len=%d\n", urbsize + ptr->length);

#if 1
		urb->transfer_buffer_length = urbsize + ptr->length;
#else
		// forced transfer buffer length fixing endpoint freeze
		urb->transfer_buffer_length = 511; //urbsize + ptr->length;
		memset(dest + ptr->length + urbsize, 0, urb->transfer_buffer_length - (urbsize + ptr->length));
#endif
		//dprintk(1, "%s: submitting urb size %d payload %d\n", __func__, urb->transfer_buffer_length, urbsize);
		rc = usb_submit_urb(urb, GFP_KERNEL);
		if (rc < 0) {
			dprintk(1, "%s usb_submit_urb %d\n", __func__, rc);
			return -EIO;
		}

		size -= urbsize;
		vbuf += urbsize;
	}
	++stream->last_seq;
	return size;
}

static void send_lastpacket(struct s2253_stream *stream)
{
	struct s2253_payload_header *ptr;
	struct s2253_video_urb *vurb;
	struct urb *urb;
	static struct s2253_payload_header lastpacket = S2253_LASTPACKET_INITIALIZER;
	int rc = 5; // tries to get an urb

	do {
		vurb = get_vurb(stream, false); // uninterruptible
		if (!vurb && rc-- <= 0) {
			s2253_killfree_urbs(stream);
			s2253_dev_set_param(stream->dev, S2253_OUTPUT_STATE, S2253_STATE_IDLE);
			return; /* interrupt or timeout */
		}
	} while (!vurb);

	urb = &vurb->urb;

	ptr = (struct s2253_payload_header *) urb->transfer_buffer;
	*ptr = lastpacket;
	ptr->seq = stream->last_seq+1;
	dprintk(1, "%s: eof=%d err=%d eoh=%d seq=%llu\n", __func__, ptr->eof, ptr->err, ptr->eoh, ptr->seq);
	cpu_to_le64s(&ptr->seq);

	urb->transfer_buffer_length = ptr->length;
	urb->status = 0;
	rc = usb_submit_urb(urb, GFP_KERNEL);
	if (rc < 0) {
		dprintk(1, "%s usb_submit_urb rc=%d\n", __func__, rc);
		urb->status = 0;
	}
}

static int dev_vidq_empty(struct s2253_dev *dev)
{
	int ret;
	spin_lock(&dev->slock);
	ret = list_empty(&dev->vidq.active);
	spin_unlock(&dev->slock);
	return ret;
}

static int s2253_output_processing_thread(void *data)
{
	struct s2253_stream *stream = data;
	struct s2253_dmaqueue *dma_q = &stream->dev->vidq;
	struct s2253_buffer *buf;
	int rc;
#if USE_VIDEOBUF2
	int first_packet = 1;
#endif

	dprintk(1, "%s: started\n", __func__);

	s2253_dev_set_param(stream->dev, S2253_OUTPUT_STATE, S2253_STATE_STREAMING);

	while (!kthread_should_stop() && !stream->should_stop && stream->dev != NULL) {
		int eof = has_eof;
		int count = 0;
		struct list_head *pos;

		/* get a new active buffer from the dma_q */
		if (dev_vidq_empty(stream->dev)) {
			dprintk(4, "No active queue to serve\n");

			/* keep waiting until a buffer is queued */
			rc = wait_event_interruptible_timeout(dma_q->wait,
					!dev_vidq_empty(stream->dev) ||
					kthread_should_stop() ||
					stream->should_stop ||
					stream->dev == NULL,
					HZ);
			if (rc < 0) {
				dprintk(4, "dma_q wait interrupted\n");
				break;
			}
			if (rc == 0) {
				dprintk(0, "%s: timeout/lockup detected, issuing reset\n", __func__);
				rc = s2253_dev_set_param(stream->dev, S2253_SYSTEM_REBOOT, 0);
				continue;
			}
			if (kthread_should_stop() || stream->should_stop || stream->dev == NULL)
				break;
			dprintk(4, "There's a buffer from the queue\n");
		}
		spin_lock(&stream->dev->slock);
		list_for_each(pos, &dma_q->active) {
			count++;
		}
#if !USE_VIDEOBUF2
		buf = list_entry(dma_q->active.next, struct s2253_buffer, vb.queue);
#else
		buf = list_entry(dma_q->active.next, struct s2253_buffer, list);
#endif
		spin_unlock(&stream->dev->slock);
		if (buf == NULL) {
			dprintk(1, "NULL buf\n");
			continue;
		}

		/* Sanity check our buffer state here. If the streamoff call
		 * changes the buffer states before some urbs complete, we can
		 * get here with state such as VIDEOBUF_NEEDS_INIT, which can
		 * cause crashes (and hangs with irqs disabled!) */
#if !USE_VIDEOBUF2
		if (buf->vb.state != VIDEOBUF_QUEUED) {
#else
		if (buf->vb.state != VB2_BUF_STATE_ACTIVE) {
#endif
			dprintk(1, "VIDEOBUF NOT QUEUED, state is %d\n", buf->vb.state);
			break;
		}
		stream->active = buf;

		if (stream->field == V4L2_FIELD_ALTERNATE) {
#if !USE_VIDEOBUF2
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
		/* struct videobuf_buffer.ts is u64 nanoseconds starting in v5.1 */
			__u32 buf_ts = (__u32)buf->vb.ts;
#else
			__u32 buf_ts = (__u32)buf->vb.ts.tv_usec;
#endif
#else
			// videobuf2 struct vb2_buffer
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
			__u32 buf_ts = buf->vb.v4l2_buf.timestamp.tv_usec;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
			// struct vb2_buffer.timestamp is u64 nanoseconds starting in v4.5
			__u32 buf_ts = (__u32)buf->vb.timestamp;
#else
			// FIXME: this timestamp field doesn't exist on 4.4
			__u32 buf_ts = 0;
#endif
#endif
			dprintk(4, "buf field=%d ts=%u buf->ts=%u fid=%d last_eof=%d  count=%d\n",
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0)
					buf->vb.field,
#else
					0,
#endif
					buf_ts, buf->ts,
					stream->last_fid, stream->last_eof,
					count);

			// two fields from the same frame will have the same timestamp
			if (stream->last_ts != buf_ts) {
				// first field
				stream->last_ts = buf_ts;
				eof = stream->last_eof == has_eof ? no_eof : has_eof;
			} else { // second field
				eof = has_eof;
			}
		}
#if USE_VIDEOBUF2
		spin_lock(&stream->dev->slock);
		list_del(&buf->list);
		spin_unlock(&stream->dev->slock);
		if (first_packet) {
			// FIXME: why is this only done in the VIDEOBUF2 case?
			if (stream->fmt == NULL && vb2_get_plane_payload(&buf->vb, 0) > 8) {
				char tmp[8];
				int rc;
				unsigned char *pchar = vb2_plane_vaddr(&buf->vb, 0);
				memcpy(tmp, pchar, sizeof(tmp));
				// send first 8 bytes to device to guess format
				rc = s2253_set_param_data(stream->board,
					S2253_OUTPUT_FORMAT, 0, tmp, sizeof(tmp));
				if (rc < 0) {
					dprintk(1, "%s couldn't set format, continuing\n", __func__);
					vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
					stream->active = NULL;
					continue;
				}
				rc = s2253_dev_get_param(stream->dev, S2253_OUTPUT_FORMAT);
				if (rc == S2253_FORMAT_NOVIDEO) {
					dprintk(1, "%s couldn't set format, continuing\n", __func__);
					vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
					stream->active = NULL;
					continue;
				}
			}
			stream->last_seq = 0;
			stream->last_fid = 0;
			first_packet = 0;
		}

		rc = s2253_video_output(stream, vb2_plane_vaddr(&buf->vb, 0),
					vb2_get_plane_payload(&buf->vb, 0),
					not_user_data, eof, buf->ts);
		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
#else
		spin_lock(&stream->dev->slock);
		list_del(&buf->vb.queue);
		spin_unlock(&stream->dev->slock);
		buf->vb.state = VIDEOBUF_ACTIVE;
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) && \
     LINUX_VERSION_CODE != KERNEL_VERSION(2, 6, 23)) || V4L_DVB_TREE
		rc = s2253_video_output(stream, videobuf_to_vmalloc(&buf->vb),
					buf->vb.size, not_user_data, eof, buf->ts);
#else
		rc = 0;
		{
			int i;
			for (i = 0; i < buf->vb.dma.nr_pages; i++) {
				struct scatterlist *sg = &buf->vb.dma.sglist[i];
				struct page *pg = pfn_to_page(sg->dma_address >> PAGE_SHIFT);
				char *p = kmap_atomic(pg, KM_BOUNCE_READ) + sg->offset;

				rc = s2253_video_output(stream, p, sg->length, not_user_data,
					i == buf->vb.dma.nr_pages-1 ? eof : no_eof, buf->ts);
				kunmap_atomic(p, KM_BOUNCE_READ);
			}
		}
#endif
		buf->vb.state = VIDEOBUF_DONE;

		wake_up(&buf->vb.done);
		dprintk(2, "wakeup [buf/i] [%p/%d] size %lu\n", buf, buf->vb.i, buf->vb.size);
#endif
		stream->active = NULL;

		if (eof) {
			/* next frame */
			stream->last_seq++;
			stream->last_fid ^= 1;
		}
		stream->last_eof = eof;

		if (rc < 0)
			break;
	}
	send_lastpacket(stream); /* send lastpacket signal */

	/* dont set state to idle - it will stop automatically when the playback ringbuffer is empty,
	 * or the user calls VIDIOC_DECODER_CMD V4L2_DEC_CMD_STOP */
	while (!kthread_should_stop()) {
#ifdef HAVE_V4L2_EVENT
		if (s2253_dev_get_param(stream->dev, S2253_OUTPUT_STATE) == S2253_STATE_IDLE) {
			/* send an end-of-stream event after the decoder has stopped */
			struct v4l2_event ev = { .type = V4L2_EVENT_EOS };
			v4l2_event_queue(&stream->dev->vdev, &ev);
		}
#endif
		msleep(100);
	}

	dprintk(1, "%s: exit\n", __func__);
	return 0;
}

static const char *s2253_stream_name(struct s2253_stream *stream)
{
	return	stream->endpoint == S2253_ENDPOINT_STREAM_A ? "capture a" :
		stream->endpoint == S2253_ENDPOINT_STREAM_B ? "capture b" :
		stream->endpoint == S2253_ENDPOINT_OUTPUT ? "output" :
		stream->endpoint == S2253_ENDPOINT_OVERLAY ? "overlay" : "???";
}

static void s2253_uninit_stream(struct s2253_stream *stream, int free_buffers, int release)
{
	struct s2253_dev *dev = stream->dev;
	dprintk(1, "%s %s\n", __func__, s2253_stream_name(stream));

	if (!dev) {
		// stream may not have dev if device was removed while streaming
	} else if (stream->endpoint == S2253_ENDPOINT_OUTPUT) {
		stream->should_stop = 1;
		wake_up(&dev->vidq.wait); /* wake output_processing_thread to exit */
		if (release) {
			/* we're going to violate the V4L2 spec here a little bit:
			 * see http://linuxtv.http://linuxtv.org/downloads/v4l-dvb-apis/vidioc-decoder-cmd.html )org/downloads/v4l-dvb-apis/vidioc-decoder-cmd.html
			 * If the user closes the fd but hasn't called VIDIOC_STREAMOFF, we're going
			 * to wait for the end-of-stream so that users can watch videos completely
			 * with this command:
			 *   cat movie.mpg > /dev/videoX
			 */
			/* if the stream is paused, we'll just stop now */
			dprintk(1, "%s: waiting for output stream to stop\n", __func__);
			while (dev && s2253_dev_get_param(dev, S2253_OUTPUT_STATE) == S2253_STATE_STREAMING && !signal_pending(current)) {
				msleep(100);
				dev = stream->dev;
			}
		}
		s2253_dev_set_param(dev, S2253_OUTPUT_STATE, S2253_STATE_IDLE);
	} else {
		// if paused, we need to stop now to get lastpacket sent ASAP
		if (s2253_dev_get_param(dev, S2253_CAPTURE_STATE + stream->param_shift) == S2253_STATE_PAUSED)
			s2253_dev_set_param(dev, S2253_CAPTURE_STATE + stream->param_shift, S2253_STATE_IDLE);
	}

	if (stream->video_thread) {
		struct task_struct *thr = stream->video_thread;
		stream->video_thread = NULL;
		dprintk(1, "%s stopping %s thread\n", __func__,s2253_stream_name(stream));
		kthread_stop(thr);
		dprintk(1, "thread stopped\n");
	}
	if (dev) {
#if USE_VIDEOBUF2
		struct s2253_buffer *buf, *n;
#endif
		dprintk(1, "cleaning up, active=%p\n", stream->active);
		/* clean up active buffer */
		spin_lock(&stream->dev->slock);
		if (stream->active) {
#if !USE_VIDEOBUF2
			dprintk(1, "clearing up active buffer\n");
			stream->active->vb.state = VIDEOBUF_ERROR;
			wake_up(&stream->active->vb.done);
#else
			dprintk(1, "clearing up active buffer %d\n", stream->active->vb.index);
			vb2_buffer_done(&stream->active->vb, VB2_BUF_STATE_ERROR);
#endif
			stream->active = NULL;
		}
#if USE_VIDEOBUF2
		dprintk(1, "clearing up active buffers VB2\n");
		list_for_each_entry_safe(buf, n, &dev->vidq.active, list) {
			list_del(&buf->list);
			dprintk(1, "clearing up active buffer %d\n", buf->vb.index);
			vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
		}
#endif
		spin_unlock(&stream->dev->slock);

#if !USE_VIDEOBUF2
		if (dev->in_use_by) {
			struct s2253_fh *fh = stream->dev->in_use_by->private_data;
			dprintk(1, "videobuf_stop\n");
			videobuf_stop(&fh->vb_vidq);
		}
#else
// Note: Only call vb2_streamoff from this context if releasing the board.
// In VB2, streamoff is taken care of internally through stop_streaming.
		if (release && vb2_is_streaming(&dev->vb_vidq)) {
			vb2_streamoff(&dev->vb_vidq, dev->buf_type);
		}
#endif
	}
	if (free_buffers) {
		dprintk(1, "free urb buffers\n");
		s2253_free_urb_buffers(stream);
	}

	dprintk(1, "%s %s exit\n", __func__, s2253_stream_name(stream));
}

/* initialize urbs for raw stream */
static int s2253_init_stream(struct s2253_stream *stream)
{
	struct urb *urb;
	unsigned int npackets, pipe, i;
	u16 psize;
	u32 size;

	dprintk(1, "%s %s\n", __func__, s2253_stream_name(stream));
	psize = 512;//le16_to_cpu(ep->desc.wMaxPacketSize) & 0x07ff;
	size = S2253_MAX_VIDEO_BUFFER_SIZE;//stream->ctrl.dwMaxPayloadTransferSize;
//	stream->bulk.max_payload_size = size;
	npackets = s2253_alloc_urb_buffers(stream, size, psize);
	dprintk(1, "%s packets %d\n", __func__, npackets);
	if (npackets == 0)
		return -ENOMEM;

	size = npackets * psize;
	if ((stream->endpoint & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
		pipe = usb_sndbulkpipe(stream->board->usb_dev,
				       stream->endpoint);
	else
		pipe = usb_rcvbulkpipe(stream->board->usb_dev,
				       stream->endpoint);
	dprintk(1,"%s: endpoint=%x dev=%p dev->devnum=%x pipe=%x\n",
		__func__, stream->endpoint, stream->board->usb_dev,
		stream->board->usb_dev->devnum, pipe);

	for (i = 0; i < S2253_URBS; ++i) {
		urb = &stream->urbs[i].urb;
		usb_init_urb(urb);
		dprintk(4, "urb=%p dev=%p pipe=%x buffer=%p size=%d complete=%p stream=%p status=%d\n",
			urb, stream->board->usb_dev, pipe,
				  stream->urbs[i].urb_buffer, size,
				  s2253_video_urb_complete,
				  stream,
				  urb->status);
		usb_fill_bulk_urb(urb, stream->board->usb_dev, pipe,
				  stream->urbs[i].urb_buffer, size,
				  s2253_video_urb_complete,
				  stream);
		urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
		urb->transfer_dma = stream->urbs[i].urb_dma;
	}

	stream->last_fid = -1;
	stream->last_eof = 1;
	stream->startseq = STARTSEQ_NOTINIT;
	stream->last_scr = 0;
	stream->active = NULL;
	INIT_LIST_HEAD(&stream->urb_list);
	spin_lock_init(&stream->urb_lock);
	init_waitqueue_head(&stream->urb_wait);

	/* Make the URBs available for use. */
	if ((stream->endpoint & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
		for (i = 0; i < S2253_URBS; ++i)
			list_add_tail(&stream->urbs[i].list, &stream->urb_list);
	stream->should_stop = 0;
	if (stream->endpoint == S2253_ENDPOINT_OUTPUT) {
		stream->last_fid = 0;
		stream->video_thread = kthread_run(s2253_output_processing_thread,
						   stream, "s2253/%d", stream->dev->vdev.minor);
	} else if (stream->endpoint != S2253_ENDPOINT_OVERLAY) {
		stream->video_thread = kthread_run(s2253_capture_processing_thread,
						   stream, "s2253/%d", stream->dev->vdev.minor);
	}
	if (stream->video_thread == ERR_PTR(-ENOMEM)) {
		dprintk(1, "Failed to create video processing thread\n");
		s2253_uninit_stream(stream, 1, 0);
		return -ENOMEM;
	}

	return 0;
}

#if !(USE_VIDEOBUF2 && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0))



static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
	struct s2253_fh *fh = priv;
	int rc;
	struct s2253_dev *dev = video_drvdata(file);
	int res_held;
	dprintk(4, "%s\n", __func__);
	if ((i != V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->stream.endpoint != S2253_ENDPOINT_OUTPUT)
	 || (i != V4L2_BUF_TYPE_VIDEO_OUTPUT && dev->stream.endpoint == S2253_ENDPOINT_OUTPUT)) {
		dev_err(&dev->stream.board->usb_dev->dev, "invalid buf type\n");
		return -EINVAL;
	}
	res_held = res_check(dev, file);
	if (!res_held && !res_get(dev, file)) {
		dev_err(&dev->stream.board->usb_dev->dev, "stream busy\n");
		return -EBUSY;
	}
	if (dev->stream.board->usb_dev == NULL)
		rc = -ENODEV;
	else
		rc = videobuf_streamon(&fh->vb_vidq);
	if (rc == 0)
		rc = s2253_init_stream(&dev->stream);
	else if (!res_held) {
		dprintk(1, "%s: videobuf_streamon rc=%d\n", __func__, rc);
		res_free(dev, file); /* unget res if we didn't have it previously */
	}
	dprintk(4, "%s streaming=%d\n", __func__, fh->vb_vidq.streaming);
	return rc;
}

static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
	struct s2253_fh *fh = priv;
	struct s2253_dev *dev = video_drvdata(file);
	int rc = 0;
	dprintk(1, "%s streaming=%d\n", __func__, fh->vb_vidq.streaming);
	if ((i != V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->stream.endpoint != S2253_ENDPOINT_OUTPUT)
	 || (i != V4L2_BUF_TYPE_VIDEO_OUTPUT && dev->stream.endpoint == S2253_ENDPOINT_OUTPUT)) {
		dprintk(1, "invalid buf type\n");
		return -EINVAL;
	}
	if (res_check(dev, file)) {
		struct s2253_buffer *buf;
		spin_lock(&dev->slock);
		buf = dev->stream.active;
		if (buf) {
			/* clear the active buf (videobuf_streamoff will wait otherwise) */
			buf->vb.state = VIDEOBUF_ERROR;
			wake_up(&buf->vb.done);
			dev->stream.active = NULL;
		}
		spin_unlock(&dev->slock);
		
		rc = videobuf_streamoff(&fh->vb_vidq);
		if (rc != 0) {
			/* xawtv unmaps buffers while streaming,
			 * which implicitly turns off the stream. */
			dprintk(1, "%s: stream is already off\n", __func__);
			rc = 0; /* required for xawtv */
		}
		s2253_uninit_stream(&dev->stream, 1, 0);
		res_free(dev, file);
	} else
		dprintk(1, "%s: don't have the streaming lock\n", __func__);
	return rc;
}
#endif

static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *i)
{
	struct s2253_dev *dev = video_drvdata(file);
	int rc = s2253_g_std(dev);

	if (rc)
		return rc;
	
	*i = dev->is_ntsc ? V4L2_STD_NTSC : V4L2_STD_PAL;
	dprintk(1, "%s %d (0x%x)\n", __func__, (int)*i, (int)*i);
	// Note: this code below may not be necessary because
	// if you are using vidioc_g_std, you shouldn't be using
	// current_norm.
#ifdef HAVE_CURRENT_NORM
	dev->vdev.current_norm = *i;
#endif
	return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) || \
    LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16) || \
    LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 18)
static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id _i)
{
	v4l2_std_id *i = &_i;
#elif LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) || V4L_DVB_TREE
static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *i)
{
#else
static int vidioc_s_std(struct file *file, void *priv, int _i)
{
	v4l2_std_id *i = (_i >= 0 && _i < ARRAY_SIZE(s2253_tvnorms)) ?
		&s2253_tvnorms[_i].id : NULL;
#endif
	struct s2253_dev *dev = video_drvdata(file);
	int rc = 0;
	int param = dev->stream.endpoint == S2253_ENDPOINT_OUTPUT ?
		S2253_OUTPUT_VIDEO_STANDARD : S2253_INPUT_VIDEO_STANDARD;
	v4l2_std_id cur_std;

	if (!i)
		return -EINVAL;
	dprintk(1, "%s %d (0x%x)\n", __func__, (int)*i, (int)*i);

#if USE_VIDEOBUF2
	if (vb2_is_streaming(&dev->vb_vidq))
		return -EBUSY;
#else
	/* check to see if we're streaming */
	if (res_locked(dev, file) && !res_check(dev, file))
		return -EBUSY;
#endif

	/* get the current standard from the device */
	rc = vidioc_g_std(file, priv, &cur_std);
	if (rc < 0)
		return rc;
	if (*i == cur_std)
		return 0; /* no need to change */

	/* don't attempt to change the standard if the other stream is busy */
	if (dev->stream.endpoint != S2253_ENDPOINT_OUTPUT) {
		rc = s2253_dev_get_param(dev, dev->stream.param_shift ? S2253_CAPTURE_STATE : S2253_PREVIEW_STATE);
		if (rc == S2253_STATE_STREAMING) {
			dprintk(1, "Cant change standard - other stream is running\n");
			rc = -EBUSY;
		}
	    	if (rc < 0)
			return rc;
	}

	if (*i & V4L2_STD_NTSC) {
		dprintk(4, "%s NTSC\n", __func__);
		dev->is_ntsc = 1;
		rc = s2253_dev_set_param(dev, param, S2253_STANDARD_NTSC);
	} else if (*i & V4L2_STD_PAL) {
		dprintk(4, "%s PAL\n", __func__);
		dev->is_ntsc = 0;
		rc = s2253_dev_set_param(dev, param, S2253_STANDARD_PAL);
	} else
		rc = -EINVAL;
#ifdef HAVE_CURRENT_NORM
	dev->vdev.current_norm = *i;
#endif
	return rc;
}

static int vidioc_enum_input(struct file *file, void *priv,
			     struct v4l2_input *inp)
{
	struct s2253_dev *dev = video_drvdata(file);

	if (inp->index != 0)
		return -EINVAL;
	memset(inp, 0, sizeof *inp);
	strscpy(inp->name, "Composite", sizeof(inp->name));
	inp->type = V4L2_INPUT_TYPE_CAMERA;
	inp->std = V4L2_STD_NTSC | V4L2_STD_PAL;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 32)
	inp->capabilities = V4L2_IN_CAP_STD;
#endif

	if (s2253_dev_get_param(dev, S2253_INPUT_HVLOCK) == 0)
		inp->status |= V4L2_IN_ST_NO_H_LOCK;
	return 0;
}

static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
{
	*i = 0;
	return 0;
}
static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
{
	if (i != 0)
		return -EINVAL;
	return 0;
}

//#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
static int vidioc_enum_output(struct file *file, void *priv,
			     struct v4l2_output *out)
{
	if (out->index != 0)
		return -EINVAL;
	memset(out, 0, sizeof *out);
	strscpy(out->name, "Composite", sizeof(out->name));
	out->type = V4L2_OUTPUT_TYPE_ANALOG;
	out->std = V4L2_STD_NTSC | V4L2_STD_PAL;
	return 0;
}
//#endif

static int vidioc_g_output(struct file *file, void *priv, unsigned int *i)
{
	*i = 0;
	return 0;
}
static int vidioc_s_output(struct file *file, void *priv, unsigned int i)
{
	if (i != 0)
		return -EINVAL;
	return 0;
}


/* We need a way to set the clock on the device using a v4l2 control -
 * The kernel does not know the current timezone, so we cannot set the clock
 * automatically from gettimeofday - instead we present a list of common
 * timezone offsets and let the user choose (perhaps repeatedly until the
 * correct local time appears in the OSD) assuming kernel clock is UTC.
 * This may also require the user to adjust for daylight savings.
 */
static const struct {
	char *name;
	int delta;
} timezones[] = {
	{"UTC - 12",	-12 * 60 * 60},
	{"UTC - 11",	-11 * 60 * 60},
	{"UTC - 10",	-10 * 60 * 60},
	{"UTC - 09:30",	-(9 * 60 + 30) * 60},
	{"UTC - 09",	-9 * 60 * 60},
	{"UTC - 08",	-8 * 60 * 60},
	{"UTC - 07",	-7 * 60 * 60},
	{"UTC - 06",	-6 * 60 * 60},
	{"UTC - 05",	-5 * 60 * 60},
	{"UTC - 04:30",	-(4 * 60 + 30) * 60},
	{"UTC - 04",	-4 * 60 * 60},
	{"UTC - 03:30",	-(3 * 60 + 30) * 60},
	{"UTC - 03",	-3 * 60 * 60},
	{"UTC - 02",	-2 * 60 * 60},
	{"UTC - 01",	-1 * 60 * 60},
	{"UTC",		0},
	{"UTC + 01",	1 * 60 * 60},
	{"UTC + 02",	2 * 60 * 60},
	{"UTC + 03",	3 * 60 * 60},
	{"UTC + 03:30",	(3 * 60 + 30) * 60},
	{"UTC + 04",	4 * 60 * 60},
	{"UTC + 04:30",	(4 * 60 + 30) * 60},
	{"UTC + 05",	5 * 60 * 60},
	{"UTC + 05:30",	(5 * 60 + 30) * 60},
	{"UTC + 05:45",	(5 * 60 + 45) * 60},
	{"UTC + 06",	6 * 60 * 60},
	{"UTC + 06:30",	(6 * 60 + 30) * 60},
	{"UTC + 07",	7 * 60 * 60},
	{"UTC + 08",	8 * 60 * 60},
	{"UTC + 09",	9 * 60 * 60},
	{"UTC + 09:30",	(9 * 60 + 30) * 60},
	{"UTC + 10",	10 * 60 * 60},
	{"UTC + 10:30",	(10 * 60 + 30) * 60},
	{"UTC + 11",	11 * 60 * 60},
	{"UTC + 11:30",	(11 * 60 + 30) * 60},
	{"UTC + 12",	12 * 60 * 60},
	{"UTC + 12:45",	(12 * 60 + 45) * 60},
	{"UTC + 13",	13 * 60 * 60},
	{"UTC + 14",	14 * 60 * 60},
};

static int vidioc_querymenu(struct file *file, void *priv,
			    struct v4l2_querymenu *qmenu)
{
	static const char *onoff[] = {
		"Off",
		"On",
		NULL
	};
	static const char *position[] = {
		"Bottom",
		"Top",
	};
	static const char *dateformat[] = {
		"MM-DD-YYYY",
		"DD-MM-YYYY",
		"MM-DD-YY",
		"DD-MM-YY",
		"MMM-DD-YYYY",
		"DD-MMM-YYYY",
		"MMM-DD-YY",
		"DD-MMM-YY",
		"MMM DD, YYYY",
		"DDMMMYYYY",
		"MMM DD, YY",
		"DDMMMYY",
	};
	static const char *timeformat[] = {
		"HH:MM:SS",
		"HH:MM:SS.0",
		"HH:MM:SS.00",
		"HH:MM:SS.000",
	};
	static const char *fonts[] = {
		"Off",
		"Font 1",
		"Font 2",
	};
	static const char *trans[] = {
		"0%",
		"100%",
		"50%",
		"25%",
	};
	static const char *tv_vcr[] = {
		"Auto",
		"",
		"VCR",
		"TV",
	};
	static const char *gpi_event[] = {
		"None",
		"Rise",
		"Fall",
		"High",
		"Low",
	};
	static const char *onoff_vsync[] = {
		"Off",
		"On",
		"VSync",
	};

	switch (qmenu->id) {
	case V4L2_CID_MPEG_STREAM_TYPE:
		if (qmenu->index == V4L2_MPEG_STREAM_TYPE_MPEG_ELEM) {
		    	strscpy(qmenu->name, "MPEG Elementary Stream",
				sizeof(qmenu->name));
			return 0;
		}
		break;

	case V4L2_CID_MPEG_VIDEO_ENCODING:
		if (qmenu->index == V4L2_MPEG_VIDEO_ENCODING_MPEG_4) {
		    	strscpy(qmenu->name, "MPEG-4", sizeof(qmenu->name));
			return 0;
		} else if (qmenu->index == V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC) {
		    	//strscpy(qmenu->name, "MPEG-4 AVC (H.264)", sizeof(qmenu->name));
			strscpy(qmenu->name, "H.264(AVC)", sizeof(qmenu->name));
			return 0;
		} else if (qmenu->index == V4L2_MPEG_VIDEO_ENCODING_MPEG_2) {
			struct s2253_dev *dev = video_drvdata(file);
			if (dev->stream.board->info.features & S2253_FEATURE_MPEG2)
				strscpy(qmenu->name, "MPEG-2", sizeof(qmenu->name));
			else
				memset(qmenu->name, 0, sizeof(qmenu->name));
			return 0;
		} else if (qmenu->index < V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC)
			memset(qmenu->name, 0, sizeof(qmenu->name));
		break;
	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
		if (qmenu->index <= V4L2_MPEG_VIDEO_BITRATE_MODE_VBR1) {
			static const char *bitrate_modes[] = {
				"Variable Bitrate",
				"Constant Bitrate",
				"Fixed QP",
				"Constrained Variable",
				"Fixed Frame Size",
				"Custom CBR1",
				"Custom VBR1",
			};
			strscpy(qmenu->name, bitrate_modes[qmenu->index], sizeof(qmenu->name));
			return 0;
		}
		break;
	case V4L2_CID_MPEG_AUDIO_ENCODING:
		/* only support AAC or None */
		if (qmenu->index == V4L2_MPEG_AUDIO_ENCODING_NONE)
			strscpy(qmenu->name, "No Audio", sizeof(qmenu->name));
		else if (qmenu->index == V4L2_MPEG_AUDIO_ENCODING_AAC)
			strscpy(qmenu->name, "AAC", sizeof(qmenu->name));
#ifdef ENABLE_MP2
		else if (qmenu->index == V4L2_MPEG_AUDIO_ENCODING_LAYER_2)
			strscpy(qmenu->name, "MP2", sizeof(qmenu->name));
#endif
#ifdef ENABLE_MP3
		else if (qmenu->index == V4L2_MPEG_AUDIO_ENCODING_LAYER_3)
			strscpy(qmenu->name, "MP3", sizeof(qmenu->name));
#endif
#ifdef ENABLE_PCM
		else if (qmenu->index == V4L2_MPEG_AUDIO_ENCODING_PCM)
			strscpy(qmenu->name, "PCM", sizeof(qmenu->name));
#endif
		else
			memset(qmenu->name, 0, sizeof(qmenu->name));
		return 0;
	case V4L2_CID_MPEG_VIDEO_ASPECT:
		if (qmenu->index == V4L2_MPEG_VIDEO_ASPECT_1x1) {
			// "None" means no aspect information, will produce square pixels
			strscpy(qmenu->name, "None", sizeof(qmenu->name));
			return 0;
		}
		break;
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
	case V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE:
		switch (qmenu->index) {
		case V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE:
		case V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN:
		case V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH:
			break;
		case V4L2_MPEG_VIDEO_MPEG2_PROFILE_SNR_SCALABLE:
		case V4L2_MPEG_VIDEO_MPEG2_PROFILE_SPATIALLY_SCALABLE:
		case V4L2_MPEG_VIDEO_MPEG2_PROFILE_MULTIVIEW:
			memset(qmenu->name, 0, sizeof(qmenu->name));
			return 0;
		default:
			return -EINVAL;
		}
		break;
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
	case V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL:
		switch (qmenu->index) {
		case V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW:
		case V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN:
		case V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440:
		case V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH:
			break;
		default:
			return -EINVAL;
		}
		break;
#endif
	case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
		switch (qmenu->index) {
		case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
		case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
		case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
			break;
		case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
		case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED:
			memset(qmenu->name, 0, sizeof(qmenu->name));
			return 0;
		default:
			return -EINVAL;
		}
		break;
	case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
		switch (qmenu->index) {
		case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
		case V4L2_MPEG_VIDEO_H264_LEVEL_1B:
		case V4L2_MPEG_VIDEO_H264_LEVEL_1_1:
		case V4L2_MPEG_VIDEO_H264_LEVEL_1_2:
		case V4L2_MPEG_VIDEO_H264_LEVEL_1_3:
		case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
		case V4L2_MPEG_VIDEO_H264_LEVEL_2_1:
		case V4L2_MPEG_VIDEO_H264_LEVEL_2_2:
		case V4L2_MPEG_VIDEO_H264_LEVEL_3_0:
		case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
		case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
		case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
		case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
		case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
		case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
			break;
		default:
			return -EINVAL;
		}
		break;
        case S2253_CID_GPO:
                if (qmenu->index > 2)
                        return -EINVAL;
		strscpy(qmenu->name, onoff_vsync[qmenu->index], sizeof(qmenu->name));
		return 0;

	case S2253_CID_LOW_LATENCY:
	case S2253_CID_INTERPOLATE:
	case S2253_CID_DEINTERLACE:
		if (qmenu->index > 1)
			return -EINVAL;
		strscpy(qmenu->name, onoff[qmenu->index], sizeof(qmenu->name));
		return 0;

	case S2253_CID_OSD_ENABLE:
		if (qmenu->index > 2)
			return -EINVAL;
		strscpy(qmenu->name, fonts[qmenu->index], sizeof(qmenu->name));
		return 0;

	case S2253_CID_OSD_TRANSPARENT:
		if (qmenu->index > 3)
			return -EINVAL;
		strscpy(qmenu->name, trans[qmenu->index], sizeof(qmenu->name));
		return 0;

	case S2253_CID_OSD_POSITION:
		if (qmenu->index > 1)
			return -EINVAL;
		strscpy(qmenu->name, position[qmenu->index],
			sizeof(qmenu->name));
		return 0;

	case S2253_CID_OSD_DATEFORMAT:
		if (qmenu->index > 11)
			return -EINVAL;
		strscpy(qmenu->name, dateformat[qmenu->index],
			sizeof(qmenu->name));
		return 0;

	case S2253_CID_OSD_TIMEFORMAT:
		if (qmenu->index > 3)
			return -EINVAL;
		strscpy(qmenu->name, timeformat[qmenu->index],
			sizeof(qmenu->name));
		return 0;

	case S2253_CID_OSD_TIMEZONE:
		if (qmenu->index >= ARRAY_SIZE(timezones))
			return -EINVAL;
		strscpy(qmenu->name, timezones[qmenu->index].name,
			sizeof(qmenu->name));
		return 0;

	case V4L2_CID_PRIVATE_COLORFILTER: {
		unsigned int i;
		const char **menu_items = onoff;
		for (i = 0; i < qmenu->index && menu_items[i]; i++)
			; /* do nothing (from v4l2-common.c) */
		if (menu_items[i] == NULL || menu_items[i][0] == '\0')
			return -EINVAL;
		strscpy(qmenu->name, menu_items[qmenu->index],
			sizeof(qmenu->name));
		return 0;
		}
	case S2253_CID_OUTPUT_MODE:
		switch (qmenu->index) {
		case S2253_OUTPUT_IDLE:
			strscpy(qmenu->name, "Idle", sizeof(qmenu->name));
			break;
		case S2253_OUTPUT_CAPTURE:
			strscpy(qmenu->name, "Pass-through", sizeof(qmenu->name));
			break;
		case S2253_OUTPUT_COLORBARS:
			strscpy(qmenu->name, "Color Bars", sizeof(qmenu->name));
			break;
		case S2253_OUTPUT_FLASH:
			strscpy(qmenu->name, "Flash", sizeof(qmenu->name));
			break;
		case S2253_OUTPUT_STREAM:
			strscpy(qmenu->name, "Video Stream", sizeof(qmenu->name));
			break;
		default:
			return -EINVAL;
		}
		return 0;
	case S2253_CID_XLED_MODE:
		switch (qmenu->index) {
		case S2253_XLED_MODE_HVLOCK_BLINK:
			strscpy(qmenu->name, "HVLock Blink", sizeof(qmenu->name));
			break;
		case S2253_XLED_MODE_HVLOCK_SOLID:
			strscpy(qmenu->name, "HVLock Solid", sizeof(qmenu->name));
			break;
		case S2253_XLED_MODE_ON:
			strscpy(qmenu->name, "On", sizeof(qmenu->name));
			break;
		case S2253_XLED_MODE_OFF:
			strscpy(qmenu->name, "Off", sizeof(qmenu->name));
			break;
		default:
			return -EINVAL;
		}
		return 0;
	case S2253_CID_TV_VCR_MODE:
		if (qmenu->index < 0 || qmenu->index > 3)
			return -EINVAL;
		strscpy(qmenu->name, tv_vcr[qmenu->index], sizeof(qmenu->name));
		return 0;
	case S2253_CID_FIELDMODE:
		if (qmenu->index == 0)
			strscpy(qmenu->name, "Normal", sizeof(qmenu->name));
		else if (qmenu->index == 1)
			strscpy(qmenu->name, "Invert", sizeof(qmenu->name));
		else
			return -EINVAL;
		return 0;
	case V4L2_CID_MPEG_AUDIO_MODE:
		if (qmenu->index != V4L2_MPEG_AUDIO_MODE_STEREO &&
		    qmenu->index != V4L2_MPEG_AUDIO_MODE_MONO) {
			memset(qmenu->name, 0, sizeof(qmenu->name));
			return 0;
		}
		break;
	case S2253_CID_GPI_PAUSE:
	case S2253_CID_GPI_FREEZE:
		if (qmenu->index < 0 || qmenu->index > 4)
			return -EINVAL;
		strscpy(qmenu->name, gpi_event[qmenu->index], sizeof(qmenu->name));
		return 0;
	}
	return v4l2_ctrl_query_menu(qmenu, NULL, NULL);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0)
static void ctrl_fill(struct v4l2_queryctrl *ctrl, enum v4l2_ctrl_type type,
	const char *name, __s32 min, __s32 max, __s32 step, __s32 def, __u32 flags)
#else
static void ctrl_fill(struct v4l2_query_ext_ctrl *ctrl, enum v4l2_ctrl_type type,
	const char *name, __s32 min, __s32 max, __s32 step, __s32 def, __u32 flags)
#endif
{
	ctrl->type = type;
	strscpy(ctrl->name, name, sizeof(ctrl->name));
	ctrl->minimum = min;
	ctrl->maximum = max;
	ctrl->step = step;
	ctrl->default_value = def;
	ctrl->flags = flags;
	ctrl->reserved[0] = 0;
	ctrl->reserved[1] = 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)
static int s2253_ctrl_query_fill(struct v4l2_query_ext_ctrl
			*qctrl, s32 _min, s32 _max, s32 _step, s32 _def)
{
	const char *name;
	s64 min = _min;
	s64 max = _max;
	u64 step = _step;
	s64 def = _def;
	v4l2_ctrl_fill(qctrl->id, &name, &qctrl->type,
		       &min, &max, &step, &def, &qctrl->flags);
	if (name == NULL)
		return -EINVAL;
	qctrl->minimum = min;
	qctrl->maximum = max;
	qctrl->step = step;
	qctrl->default_value = def;
	qctrl->reserved[0] = qctrl->reserved[1] = 0;
	strscpy(qctrl->name, name, sizeof(qctrl->name));
	return 0;
}
#else
static int s2253_ctrl_query_fill(struct v4l2_queryctrl
			*qctrl, s32 _min, s32 _max, s32 _step, s32 _def)
{
	return v4l2_ctrl_query_fill(qctrl, _min, _max, _step, _def);
}
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0)
static int vidioc_queryctrl(struct file *file, void *priv,
			    struct v4l2_queryctrl *ctrl)
#else
static int vidioc_query_ext_ctrl(struct file *file, void *priv,
			    struct v4l2_query_ext_ctrl *ctrl)
#endif
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	// Must be in increasing order
	static const u32 capture_ctrls[] = {
		V4L2_CID_USER_CLASS,
		V4L2_CID_BRIGHTNESS,
		V4L2_CID_CONTRAST,
		V4L2_CID_SATURATION,
		V4L2_CID_HUE,
		V4L2_CID_AUDIO_VOLUME,
		V4L2_CID_AUDIO_BALANCE,
		//V4L2_CID_AUDIO_MUTE,
		V4L2_CID_AUTOGAIN,
		V4L2_CID_HFLIP,
		V4L2_CID_VFLIP,
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		V4L2_CID_HCENTER,
		V4L2_CID_VCENTER,
#endif
		V4L2_CID_AUTOBRIGHTNESS,
		S2253_CID_DFLIP,
		S2253_CID_INTERPOLATE,
		S2253_CID_DEINTERLACE,
		S2253_CID_TV_VCR_MODE,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
		S2253_CID_HCENTER,
		S2253_CID_VCENTER,
#endif
		S2253_CID_FIELDMODE,
		S2253_CID_FREEZE,
		0
	};
	static const u32 output_ctrls[] = {
		V4L2_CID_USER_CLASS,
		V4L2_CID_AUDIO_VOLUME,
		V4L2_CID_AUDIO_BALANCE,
		//V4L2_CID_AUDIO_MUTE,
		S2253_CID_OUTPUT_MODE,
		S2253_CID_LOW_LATENCY,
                S2253_CID_OUTPUT_VGA,
		0
	};
	static const u32 io_ctrls[] = {
		S2253_CID_IO_CLASS,
		S2253_CID_GPI,
		S2253_CID_GPO,
		S2253_CID_GPI_PAUSE,
		S2253_CID_GPI_FREEZE,
		S2253_CID_XLED_MODE,
		S2253_CID_CPU_LOAD,
		0
	};
	static const u32 osd_ctrls[] = {
		S2253_CID_OSD_CLASS,
		S2253_CID_OSD_ENABLE,
		S2253_CID_OSD_TRANSPARENT,
		S2253_CID_OSD_POSITION,
		S2253_CID_OSD_DATEFORMAT,
		S2253_CID_OSD_TIMEFORMAT,
		S2253_CID_OSD_TIMEZONE,
#ifdef HAS_V4L2_CTRL_TYPE_STRING
		S2253_CID_OSD_MESSAGE,
#endif
		S2253_CID_OSD_XOFF,
		S2253_CID_OSD_YOFF,
		0
	};

	// Must be in increasing order
	static const u32 mpeg_ctrls[] = {
		V4L2_CID_MPEG_CLASS,
		V4L2_CID_MPEG_STREAM_TYPE,
		V4L2_CID_MPEG_STREAM_PID_PMT,
		V4L2_CID_MPEG_STREAM_PID_AUDIO,
		V4L2_CID_MPEG_STREAM_PID_VIDEO,
		V4L2_CID_MPEG_STREAM_PID_PCR,
		V4L2_CID_MPEG_STREAM_PES_ID_AUDIO,
		V4L2_CID_MPEG_STREAM_PES_ID_VIDEO,
		V4L2_CID_MPEG_STREAM_VBI_FMT,
//		V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ,
		V4L2_CID_MPEG_AUDIO_ENCODING,
//		V4L2_CID_MPEG_AUDIO_L2_BITRATE,
#ifdef ENABLE_MP3
		V4L2_CID_MPEG_AUDIO_L3_BITRATE,
#endif
		V4L2_CID_MPEG_AUDIO_MODE,
		V4L2_CID_MPEG_AUDIO_AAC_BITRATE,
		V4L2_CID_MPEG_VIDEO_ENCODING,
		V4L2_CID_MPEG_VIDEO_ASPECT,
// 		V4L2_CID_MPEG_VIDEO_B_FRAMES,
		V4L2_CID_MPEG_VIDEO_GOP_SIZE,
//		V4L2_CID_MPEG_VIDEO_GOP_CLOSURE,
		V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
		V4L2_CID_MPEG_VIDEO_BITRATE,
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
		V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL,
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
		V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE,
#endif
		V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP,
		V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP,
		V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
		V4L2_CID_MPEG_VIDEO_H264_LEVEL,
		V4L2_CID_MPEG_VIDEO_H264_PROFILE,
		S2253_CID_AUDIO_DELAY,
		0
	};
	static const u32 mpeg_elementary_stream_ctrls[] = {
		V4L2_CID_MPEG_CLASS,
		V4L2_CID_MPEG_VIDEO_ASPECT,
		V4L2_CID_MPEG_VIDEO_GOP_SIZE,
//		V4L2_CID_MPEG_VIDEO_GOP_CLOSURE,
		V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
		V4L2_CID_MPEG_VIDEO_BITRATE,
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
		V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL,
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
		V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE,
#endif
		V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP,
		V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP,
		V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
		V4L2_CID_MPEG_VIDEO_H264_LEVEL,
		V4L2_CID_MPEG_VIDEO_H264_PROFILE,
		0
	};
	const u32 *ctrl_classes[] = {
		capture_ctrls,
		io_ctrls,
		osd_ctrls,
		NULL,
		NULL,
	};
	int old_id;

	if (stream->endpoint == S2253_ENDPOINT_OUTPUT)
		ctrl_classes[0] = output_ctrls;
	else if (stream->fmt->format == S2253_FORMAT_MPEG4 ||
		 stream->fmt->format == S2253_FORMAT_H264 ||
		 stream->fmt->format == S2253_FORMAT_MPEG2) {
		ctrl_classes[1] = mpeg_elementary_stream_ctrls;
		ctrl_classes[2] = osd_ctrls;
	} else if (stream->fmt->format == S2253_FORMAT_MPEG_TS ||
		   stream->fmt->format == S2253_FORMAT_MPEG_PS ||
		   stream->fmt->format == S2253_FORMAT_MP4) {
		ctrl_classes[1] = mpeg_ctrls;
		ctrl_classes[2] = osd_ctrls;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)
	ctrl->nr_of_dims = 0;
	ctrl->elems = 1;
	ctrl->elem_size = 4;
#endif
	old_id = ctrl->id;
ctrl_next:
	ctrl->id = v4l2_ctrl_next(ctrl_classes, old_id);
	switch (ctrl->id) {
	case V4L2_CID_USER_CLASS:
	case V4L2_CID_MPEG_CLASS:
		s2253_ctrl_query_fill(ctrl, 0, 0, 0, 0);
		break;
	case S2253_CID_OSD_CLASS:
		strscpy(ctrl->name, "OSD Controls", sizeof(ctrl->name));
		ctrl->type = V4L2_CTRL_TYPE_CTRL_CLASS;
		break;
	case S2253_CID_IO_CLASS:
		strscpy(ctrl->name, "I/O Controls", sizeof(ctrl->name));
		ctrl->type = V4L2_CTRL_TYPE_CTRL_CLASS;
		break;
	case V4L2_CID_BRIGHTNESS:
		s2253_ctrl_query_fill(ctrl, 0, 255, 1, DEF_BRIGHT);
		break;
	case V4L2_CID_CONTRAST:
		s2253_ctrl_query_fill(ctrl, 0, 255, 1, DEF_CONTRAST);
		break;
	case V4L2_CID_SATURATION:
		s2253_ctrl_query_fill(ctrl, 0, 255, 1, DEF_SATURATION);
		break;
	case V4L2_CID_HUE:
		s2253_ctrl_query_fill(ctrl, -128, 127, 1, DEF_HUE);
		break;
	case V4L2_CID_AUDIO_VOLUME:
		if (stream->endpoint == S2253_ENDPOINT_OUTPUT)
			s2253_ctrl_query_fill(ctrl, S2253_PLAYBACK_VOLUME_MIN,
						   S2253_PLAYBACK_VOLUME_MAX, 1,
						   S2253_PLAYBACK_VOLUME_MAX);
		else
			s2253_ctrl_query_fill(ctrl, S2253_RECORD_VOLUME_MIN,
						   S2253_RECORD_VOLUME_MAX, 1,
						   S2253_RECORD_VOLUME_MAX);
		break;
	case V4L2_CID_AUDIO_BALANCE:
		s2253_ctrl_query_fill(ctrl, -100, 100, 1, 0);
		break;
	case V4L2_CID_AUTOGAIN:
	case V4L2_CID_AUTOBRIGHTNESS:
		s2253_ctrl_query_fill(ctrl, 0, 1, 1, 1);
		break;
	case V4L2_CID_HFLIP:
	case V4L2_CID_VFLIP:
		s2253_ctrl_query_fill(ctrl, 0, 1, 1, 0);
		break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
	case V4L2_CID_HCENTER:
		s2253_ctrl_query_fill(ctrl, -256, 255, 1, 0);
		break;
#endif
	case S2253_CID_HCENTER:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Horizontal Center", -256, 255, 1, 0, 0);
		break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
	case V4L2_CID_VCENTER:
		s2253_ctrl_query_fill(ctrl, -128, 127, 1, 0);
		break;
#endif
	case S2253_CID_VCENTER:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Vertical Center", -128, 127, 1, 0, 0);
		break;
	case V4L2_CID_PRIVATE_COLORFILTER:
		strscpy(ctrl->name, "Color Filter", sizeof(ctrl->name));
		ctrl->type = V4L2_CTRL_TYPE_MENU;
		ctrl->minimum = 0;
		ctrl->maximum = 1;
		ctrl->step = 1;
		ctrl->default_value = 1;
		ctrl->flags = 0;
		break;
	case S2253_CID_DFLIP:
		if (stream->fmt->format != S2253_FORMAT_MP4 &&
		    stream->fmt->format != S2253_FORMAT_MPEG_TS &&
		    stream->fmt->format != S2253_FORMAT_MPEG_PS &&
		    stream->fmt->format != S2253_FORMAT_MPEG4 &&
		    stream->fmt->format != S2253_FORMAT_H264 &&
		    stream->fmt->format != S2253_FORMAT_YUV420PSEMI &&
		    stream->fmt->format != S2253_FORMAT_MPEG2 &&
		    stream->fmt->format != S2253_FORMAT_Y8) {
			old_id++;
			goto ctrl_next;
		}
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_BOOLEAN, "Diagonal Flip", 0, 1, 1, 0, 0);
		break;
	case S2253_CID_INTERPOLATE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_BOOLEAN, "Interpolate", 0, 1, 1, 0, 0);
		break;
	case S2253_CID_DEINTERLACE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_BOOLEAN, "Deinterlace", 0, 1, 1, 0, 0);
		break;
	case S2253_CID_OUTPUT_MODE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Display Mode", 0, 3, 1, S2253_OUTPUT_IDLE, 0);
		break;
	case S2253_CID_LOW_LATENCY:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Low-Latency Mode", 0, 1, 1, 0, 0);
		break;
	case S2253_CID_GPI:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_BOOLEAN, "General-Purpose Input", 0, 1, 1, 0, 0);
		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
		break;
	case S2253_CID_GPO:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "General-Purpose Output", 0, 2, 1, 0, 0);
		break;
	case S2253_CID_XLED_MODE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "XLED Mode", 0, 3, 1, S2253_XLED_MODE_HVLOCK_BLINK, 0);
		break;
	case S2253_CID_CPU_LOAD:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "CPU Load", 0, 100, 1, 0, 0);
		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
		break;
	case S2253_CID_TV_VCR_MODE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "TV/VCR Mode", 0, 3, 1, 0, 0);
		break;
	case S2253_CID_FIELDMODE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Field Mode", 0, 1, 1, 0, 0);
		break;
	case S2253_CID_GPI_PAUSE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "GPI Pause Trigger", 0, 4, 1, 0, 0);
		break;
	case S2253_CID_FREEZE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_BOOLEAN, "Freeze Frame", 0, 1, 1, 0, 0);
		break;
	case S2253_CID_GPI_FREEZE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "GPI Freeze Trigger", 0, 4, 1, 0, 0);
		break;
	case S2253_CID_OUTPUT_VGA:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_BOOLEAN, "VGA 640x480 Mode", 0, 1, 1, 0, 0);
		break;

	case S2253_CID_AUDIO_DELAY:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Audio Delay", -500, 500, 10, 0, 0);
		break;
	case S2253_CID_OSD_ENABLE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "OSD", 0, 2, 1, 0, 0);
		break;
	case S2253_CID_OSD_TRANSPARENT:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "OSD Transparent", 0, 3, 1, 1, 0);
		break;
	case S2253_CID_OSD_POSITION:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "OSD Position", 0, 1, 1, 1, 0);
		break;
	case S2253_CID_OSD_DATEFORMAT:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "OSD Date Format", 0, 11, 1, 0, 0);
		break;
	case S2253_CID_OSD_TIMEFORMAT:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "OSD Time Format", 0, 3, 1, 0, 0);
		break;
	case S2253_CID_OSD_TIMEZONE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "OSD Time Zone", 0, 38, 1, 15, 0);
		break;
#ifdef HAS_V4L2_CTRL_TYPE_STRING
	case S2253_CID_OSD_MESSAGE:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_STRING, "OSD Message", 0, sizeof(dev->osd.line), 1, 0, 0);
		break;
#endif
	case S2253_CID_OSD_XOFF:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "OSD X Offset", 0, S2253_MAX_IMAGE_WIDTH, 1, 10, 0);
		break;
	case S2253_CID_OSD_YOFF:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "OSD Y Offset", 0, S2253_MAX_IMAGE_HEIGHT, 1, 10, 0);
		break;
	case V4L2_CID_MPEG_STREAM_TYPE:
		if (stream->fmt->format == S2253_FORMAT_MP4) {
			old_id++;
			goto ctrl_next;
		}
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_STREAM_TYPE_MPEG2_PS,
				     V4L2_MPEG_STREAM_TYPE_MPEG2_TS, 1,
				     V4L2_MPEG_STREAM_TYPE_MPEG2_TS);
		break;
	case V4L2_CID_MPEG_STREAM_PID_PMT:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Stream PMT Program ID", 0, 0x1fff, 1, 0, 0);
		break;
	case V4L2_CID_MPEG_STREAM_PID_AUDIO:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Stream Audio Program ID", 0, 0x1fff, 1, 0, 0);
		break;
	case V4L2_CID_MPEG_STREAM_PID_VIDEO:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Stream Video Program ID", 0, 0x1fff, 1, 0, 0);
		break;
	case V4L2_CID_MPEG_STREAM_PID_PCR:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Stream PCR Program ID", 0, 0x1fff, 1, 0, 0);
		break;
	case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Stream PES Audio ID", 0, 0xff, 1, 0, 0);
		break;
	case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO:
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Stream PES Video ID", 0, 0xff, 1, 0, 0);
		break;

	case V4L2_CID_MPEG_STREAM_VBI_FMT:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_STREAM_VBI_FMT_NONE,
				     V4L2_MPEG_STREAM_VBI_FMT_IVTV, 1,
				     V4L2_MPEG_STREAM_VBI_FMT_NONE);
		break;
	case V4L2_CID_MPEG_AUDIO_ENCODING:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_AUDIO_ENCODING_LAYER_2,
				     V4L2_MPEG_AUDIO_ENCODING_NONE, 1,
				     V4L2_MPEG_AUDIO_ENCODING_AAC);
		break;
	case V4L2_CID_MPEG_AUDIO_AAC_BITRATE:
		s2253_ctrl_query_fill(ctrl,
				     S2253_MIN_AAC_BITRATE * 1000,
				     S2253_MAX_AAC_BITRATE * 1000, 16000,
				     S2253_DEF_AAC_BITRATE * 1000);
		break;
#ifdef ENABLE_MP3
	case V4L2_CID_MPEG_AUDIO_L3_BITRATE:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_AUDIO_L3_BITRATE_32K,
				     V4L2_MPEG_AUDIO_L3_BITRATE_320K, 1,
				     V4L2_MPEG_AUDIO_L3_BITRATE_192K);
		break;
#endif
	case V4L2_CID_MPEG_AUDIO_MODE:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_AUDIO_MODE_STEREO,
				     V4L2_MPEG_AUDIO_MODE_MONO, 1,
				     V4L2_MPEG_AUDIO_MODE_STEREO);
		break;
	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
		s2253_ctrl_query_fill(ctrl, 0, 255, 1, 0);
		break;
	case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE:
		s2253_ctrl_query_fill(ctrl, 0, 1, 1, 0);
		break;
	case V4L2_CID_MPEG_VIDEO_ENCODING:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_VIDEO_ENCODING_MPEG_2,
				     V4L2_MPEG_VIDEO_ENCODING_MPEG_4, 1,
				     V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC);
		break;
	case V4L2_CID_MPEG_VIDEO_ASPECT:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_VIDEO_ASPECT_1x1,
				     V4L2_MPEG_VIDEO_ASPECT_221x100, 1,
				     V4L2_MPEG_VIDEO_ASPECT_1x1);
		break;
	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
		s2253_ctrl_query_fill(ctrl,
				     V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
				     V4L2_MPEG_VIDEO_BITRATE_MODE_VBR1, 1,
				     V4L2_MPEG_VIDEO_BITRATE_MODE_VBR1);
		break;
	case V4L2_CID_MPEG_VIDEO_BITRATE:
		s2253_ctrl_query_fill(ctrl,
				     S2253_MIN_MPEG_BITRATE * 1000,
				     S2253_MAX_MPEG_BITRATE * 1000,
				     100000, /* increment by 100k */
				     S2253_DEF_MPEG_BITRATE * 1000);
		break;
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
        case V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE:
                ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Video MPEG-2 Profile",
                                     V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE,
                                     V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH,
                                     1,
                                     V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN,
				     0);
                break;
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
        case V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL:
                ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Video MPEG-2 Level",
                                     V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW,
                                     V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH,
                                     1,
                                     V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440,
				     0);
                break;
#endif
        case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
                ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Video H.264 Level",
                                     V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
                                     V4L2_MPEG_VIDEO_H264_LEVEL_5_0,
                                     1,
                                     V4L2_MPEG_VIDEO_H264_LEVEL_4_0,
				     0);
                break;
        case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
                ctrl_fill(ctrl, V4L2_CTRL_TYPE_MENU, "Video H.264 Profile",
                                     V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
                                     V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
                                     1,
                                     V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
				     0);
                break;
        case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
                ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Video H.264 I-Frame QP",
                                     -1, 51, 1, -1, 0);
                break;
        case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
                ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Video H.264 P-Frame QP",
                                     -1, 51, 1, -1, 0);
                break;
	case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
		if (stream->fmt->format != S2253_FORMAT_MP4 &&
		    stream->fmt->format != S2253_FORMAT_MPEG_TS &&
		    stream->fmt->format != S2253_FORMAT_MPEG_PS &&
		    stream->fmt->format != S2253_FORMAT_H264) {
			old_id++;
			goto ctrl_next;
		}
		ctrl_fill(ctrl, V4L2_CTRL_TYPE_INTEGER, "Video H.264 IDR Period", 0, 100, 1, 0, 0);
		break;
	default:
		dprintk(1, "%s: v4l2_ctrl_next returned %x\n", __func__, ctrl->id);
		ctrl->id = old_id;
		return -EINVAL;
	}
	dprintk(4, "%s, id %x\n", __func__, ctrl->id);
	return 0;
}


static int vidioc_try_ext_ctrls(struct file *file, void *priv,
				struct v4l2_ext_controls *ctrls)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct v4l2_ext_control *ctrl = ctrls->controls;
	unsigned int i;

	/* for invalid controls, clamp or set them to default value */
	for (i = 0; i < ctrls->count; i++, ctrl++) {
		switch(ctrl->id) {
		case V4L2_CID_BRIGHTNESS:
		case V4L2_CID_CONTRAST:
		case V4L2_CID_SATURATION:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 255)
				ctrl->value = 255;
			break;
		case V4L2_CID_HUE:
			if (ctrl->value < -128)
				ctrl->value = -128;
			else if (ctrl->value > 127)
				ctrl->value = 127;
			break;
		case V4L2_CID_AUDIO_VOLUME:
			if (dev->stream.endpoint == S2253_ENDPOINT_OUTPUT) {
				if (ctrl->value < S2253_PLAYBACK_VOLUME_MIN)
					ctrl->value = S2253_PLAYBACK_VOLUME_MIN;
				else if (ctrl->value > S2253_PLAYBACK_VOLUME_MAX)
					ctrl->value = S2253_PLAYBACK_VOLUME_MAX;
			} else {
				if (ctrl->value < S2253_RECORD_VOLUME_MIN)
					ctrl->value = S2253_RECORD_VOLUME_MIN;
				else if (ctrl->value > S2253_RECORD_VOLUME_MAX)
					ctrl->value = S2253_RECORD_VOLUME_MAX;
			}
			break;
		case V4L2_CID_AUDIO_BALANCE:
			if (ctrl->value < -100)
				ctrl->value = -100;
			else if (ctrl->value > 100)
				ctrl->value = 100;
			break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		case V4L2_CID_HCENTER:
#endif
		case S2253_CID_HCENTER:
			if (ctrl->value < -256)
				ctrl->value = -256;
			else if (ctrl->value > 255)
				ctrl->value = 255;
			break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		case V4L2_CID_VCENTER:
#endif
		case S2253_CID_VCENTER:
			if (ctrl->value < -128)
				ctrl->value = -128;
			else if (ctrl->value > 127)
				ctrl->value = 127;
			break;
		case S2253_CID_XLED_MODE:
		case S2253_CID_TV_VCR_MODE:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 3)
				ctrl->value = 3;
			break;
		case S2253_CID_AUDIO_DELAY:
			if (ctrl->value < -500)
				ctrl->value = -500;
			else if (ctrl->value > 500)
				ctrl->value = 500;
			else
				ctrl->value -= ctrl->value % 10;
			break;
		case V4L2_CID_AUTOGAIN:
		case V4L2_CID_AUTOBRIGHTNESS:
		case V4L2_CID_HFLIP:
		case V4L2_CID_VFLIP:
		case S2253_CID_DFLIP:
		case V4L2_CID_PRIVATE_COLORFILTER:
		case S2253_CID_INTERPOLATE:
		case S2253_CID_DEINTERLACE:
		case S2253_CID_FIELDMODE:
		case S2253_CID_FREEZE:
                case S2253_CID_OUTPUT_VGA:
			if (ctrl->value != 0)
				ctrl->value = 1;
			break;
		case S2253_CID_GPI_PAUSE:
		case S2253_CID_GPI_FREEZE:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 4)
				ctrl->value = 4;
			break;

		case S2253_CID_GPO:
		case S2253_CID_OSD_ENABLE:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 2)
				ctrl->value = 2;
			break;
		case S2253_CID_OSD_TRANSPARENT:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 3)
				ctrl->value = 3;
			break;
		case S2253_CID_OSD_POSITION:
			if (ctrl->value != 0)
				ctrl->value = 1;
			break;
		case S2253_CID_OSD_DATEFORMAT:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 11)
				ctrl->value = 11;
			break;
		case S2253_CID_OUTPUT_MODE:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 3)
				ctrl->value = 3;
			break;
		case S2253_CID_LOW_LATENCY:
			if (ctrl->value != 0)
				ctrl->value = 1;
			break;
		case S2253_CID_OSD_TIMEFORMAT:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 3)
				ctrl->value = 3;
			break;
		case S2253_CID_OSD_TIMEZONE:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value >= (int)ARRAY_SIZE(timezones))
				ctrl->value = ARRAY_SIZE(timezones) - 1;
			break;
#ifdef HAS_V4L2_CTRL_TYPE_STRING
		case S2253_CID_OSD_MESSAGE:
			break;
#endif
		case S2253_CID_OSD_XOFF:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > S2253_MAX_IMAGE_WIDTH)
				ctrl->value = S2253_MAX_IMAGE_WIDTH;
			break;
		case S2253_CID_OSD_YOFF:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > S2253_MAX_IMAGE_HEIGHT)
				ctrl->value = S2253_MAX_IMAGE_HEIGHT;
			break;
		case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 100)
				ctrl->value = 100;
			break;
		case V4L2_CID_MPEG_STREAM_TYPE:
			if (ctrl->value != V4L2_MPEG_STREAM_TYPE_MPEG2_PS)
				ctrl->value = V4L2_MPEG_STREAM_TYPE_MPEG2_TS;
			break;
		case V4L2_CID_MPEG_STREAM_PID_PMT:
		case V4L2_CID_MPEG_STREAM_PID_AUDIO:
		case V4L2_CID_MPEG_STREAM_PID_VIDEO:
		case V4L2_CID_MPEG_STREAM_PID_PCR:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 0xffff)
				ctrl->value = 0xffff;
			break;
		case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO:
		case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 0xff)
				ctrl->value = 0xff;
			break;
		case V4L2_CID_MPEG_STREAM_VBI_FMT:
			if (ctrl->value != V4L2_MPEG_STREAM_VBI_FMT_IVTV)
				ctrl->value = V4L2_MPEG_STREAM_VBI_FMT_NONE;
			break;
		case V4L2_CID_MPEG_AUDIO_ENCODING:
			if (ctrl->value != V4L2_MPEG_AUDIO_ENCODING_AAC &&
			    ctrl->value != V4L2_MPEG_AUDIO_ENCODING_LAYER_2 &&
			    ctrl->value != V4L2_MPEG_AUDIO_ENCODING_LAYER_3 &&
			    ctrl->value != V4L2_MPEG_AUDIO_ENCODING_PCM)
				ctrl->value = V4L2_MPEG_AUDIO_ENCODING_NONE;
			break;
		case V4L2_CID_MPEG_AUDIO_AAC_BITRATE:
			if (ctrl->value < S2253_MIN_AAC_BITRATE * 1000)
				ctrl->value = S2253_MIN_AAC_BITRATE * 1000;
			else if (ctrl->value > S2253_MAX_AAC_BITRATE * 1000)
				ctrl->value = S2253_MAX_AAC_BITRATE * 1000;
			else
				ctrl->value -= ctrl->value % 16000;
			break;
#ifdef ENABLE_MP3
		case V4L2_CID_MPEG_AUDIO_L3_BITRATE:
			if (ctrl->value < V4L2_MPEG_AUDIO_L3_BITRATE_32K)
				ctrl->value = V4L2_MPEG_AUDIO_L3_BITRATE_32K;
			else if (ctrl->value > V4L2_MPEG_AUDIO_L3_BITRATE_320K)
				ctrl->value = V4L2_MPEG_AUDIO_L3_BITRATE_320K;
			break;
#endif
		case V4L2_CID_MPEG_AUDIO_MODE:
			if (ctrl->value != V4L2_MPEG_AUDIO_MODE_MONO)
				ctrl->value = V4L2_MPEG_AUDIO_MODE_STEREO;
			break;
		case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
			if (ctrl->value < 0)
				ctrl->value = 0;
			else if (ctrl->value > 255)
				ctrl->value = 255;
			break;
		case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE:
			if (ctrl->value != 0)
				ctrl->value = 1;
			break;
		case V4L2_CID_MPEG_VIDEO_BITRATE:
			if (ctrl->value < S2253_MIN_MPEG_BITRATE * 1000)
				ctrl->value = S2253_MIN_MPEG_BITRATE * 1000;
			else if (ctrl->value > S2253_MAX_MPEG_BITRATE * 1000)
				ctrl->value = S2253_MAX_MPEG_BITRATE * 1000;
			else
				ctrl->value -= ctrl->value % 100000;
			break;
		case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
			if (ctrl->value < V4L2_MPEG_VIDEO_BITRATE_MODE_VBR)
				ctrl->value = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR;
			else if (ctrl->value > V4L2_MPEG_VIDEO_BITRATE_MODE_VBR1)
				ctrl->value = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR1;
			break;
		case V4L2_CID_MPEG_VIDEO_ENCODING: {
			int tmp = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC;
			if (dev->stream.board->info.features & S2253_FEATURE_MPEG2)
				tmp = V4L2_MPEG_VIDEO_ENCODING_MPEG_2;
			if (ctrl->value < tmp)
				ctrl->value = tmp;
			else if (ctrl->value > V4L2_MPEG_VIDEO_ENCODING_MPEG_4)
				ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4;
			break;
		}
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
                case V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL:
			switch (ctrl->value) {
			case V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW:
			case V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN:
			case V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440:
			case V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH:
				break;
			default:
				ctrl->value = V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN;
				break;
			}
			break;
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
                case V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE:
			switch (ctrl->value) {
			case V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE:
			case V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN:
			case V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH:
				break;
			default:
				ctrl->value = V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN;
				break;
			}
			break;
#endif
                case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
                        switch (ctrl->value) {
                        case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_1B:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_1_1:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_1_2:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_1_3:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_2_1:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_2_2:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_3_0:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
                        case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
                                break;
                        default:
                                ctrl->value = V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
                                break;
                        }
                        break;
                case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
                        switch (ctrl->value) {
                        case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
                        case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
                        case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
                                break;
                        default:
                                ctrl->value = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
                                break;
                        }
                        break;
		case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
		case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
			if (ctrl->value < -1)
				ctrl->value = -1;
			else if (ctrl->value > 51)
				ctrl->value = 51;
			break;
		case V4L2_CID_MPEG_VIDEO_ASPECT:
			if (ctrl->value < V4L2_MPEG_VIDEO_ASPECT_1x1)
				ctrl->value = V4L2_MPEG_VIDEO_ASPECT_1x1;
			else if (ctrl->value > V4L2_MPEG_VIDEO_ASPECT_221x100)
				ctrl->value = V4L2_MPEG_VIDEO_ASPECT_221x100;
			break;
		case S2253_CID_GPI:
		case S2253_CID_CPU_LOAD:
		default:
			ctrls->error_idx = i;
			return -EINVAL;
		}
	}
	return 0;
}

static int stream_get_audio_volume_balance(struct s2253_stream *stream)
{
	int lv, rv;

	lv = s2253_dev_get_param(stream->dev, stream->volume_param);
	if (lv < 0)
		return lv;
	rv = (lv >> 8) & 0xff;
	lv &= 0xff;
	if (rv < lv) {
		stream->audio_volume = lv;
		stream->audio_balance = (rv - lv) * 100 / lv;
	} else if (rv > 0) {
		stream->audio_volume = rv;
		stream->audio_balance = (rv - lv) * 100 / rv;
	} else {
		stream->audio_volume = 0;
		stream->audio_balance = 0;
	}
	dprintk(1, "%s: lv=%d rv=%d vol=%d bal=%d\n", __func__, lv, rv,
		stream->audio_volume, stream->audio_balance);
	return 0;
}

static int stream_set_audio_volume_balance(struct s2253_stream *stream)
{
	int lv, rv;
	lv = stream->audio_volume;
	rv = stream->audio_volume;

	if (stream->audio_balance < 0)
		rv = rv * (100 + stream->audio_balance) / 100;
	else if (stream->audio_balance > 0)
		lv = lv * (100 - stream->audio_balance) / 100;
	dprintk(1, "%s: lv=%d rv=%d vol=%d bal=%d\n", __func__, lv, rv,
		stream->audio_volume, stream->audio_balance);
	return s2253_dev_set_param(stream->dev, stream->volume_param, (rv << 8) | lv);
}


static int vidioc_g_ext_ctrls(struct file *file, void *priv,
			      struct v4l2_ext_controls *ctrls)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	struct v4l2_ext_control *ctrl = ctrls->controls;
	unsigned int i;
	int rc = 0;

	for (i = 0; i < ctrls->count; i++, ctrl++) {
		switch(ctrl->id) {
		case V4L2_CID_BRIGHTNESS:
			rc = s2253_dev_get_param(dev, S2253_BRIGHTNESS);
			break;
		case V4L2_CID_CONTRAST:
			rc = s2253_dev_get_param(dev, S2253_CONTRAST);
			break;
		case V4L2_CID_SATURATION:
			rc = s2253_dev_get_param(dev, S2253_SATURATION);
			break;
		case V4L2_CID_HUE:
			rc = s2253_dev_get_param(dev, S2253_HUE);
			if (rc < 0)
				break;
			ctrl->value = (__s8) rc;
			continue;
		case V4L2_CID_AUDIO_VOLUME:
			rc = stream_get_audio_volume_balance(stream);
			if (rc == 0)
				rc = stream->audio_volume;
			break;
		case V4L2_CID_AUDIO_BALANCE:
			rc = stream_get_audio_volume_balance(stream);
			if (rc == 0)
				rc = stream->audio_balance;
			break;
		case V4L2_CID_HFLIP:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_FLIP + stream->param_shift);
			if (rc >= 0)
				rc = (rc >> 1) & 1;
			break;
		case V4L2_CID_VFLIP:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_FLIP + stream->param_shift);
			if (rc >= 0)
				rc = rc & 1;
			break;
		case V4L2_CID_AUTOGAIN:
			rc = s2253_dev_get_param(dev, S2253_INPUT_AUTOGAIN);
			break;
		case V4L2_CID_AUTOBRIGHTNESS:
			rc = s2253_dev_get_param(dev, S2253_INPUT_AUTOBRIGHTNESS);
			break;

		case S2253_CID_DFLIP:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_FLIP + stream->param_shift);
			if (rc >= 0)
				rc = (rc >> 2) & 1;
			break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		case V4L2_CID_HCENTER:
#endif
		case S2253_CID_HCENTER:
			rc = s2253_dev_get_param(dev, S2253_INPUT_H_ADJUST);
			if (rc < 0)
				break;
			ctrl->value = (__s16) rc;
			continue;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		case V4L2_CID_VCENTER:
#endif
		case S2253_CID_VCENTER:
			rc = s2253_dev_get_param(dev, S2253_INPUT_V_ADJUST);
			if (rc < 0)
				break;
			ctrl->value = (__s16) rc;
			continue;

		case V4L2_CID_PRIVATE_COLORFILTER:
			rc = stream->color_filter;
			break;
		case S2253_CID_INTERPOLATE:
			rc = s2253_dev_get_param(dev, S2253_INTERPOLATE);
			break;
		case S2253_CID_DEINTERLACE:
			rc = s2253_dev_get_param(dev, S2253_DEINTERLACE);
			break;
		case S2253_CID_OUTPUT_MODE:
			rc = s2253_dev_get_param(dev, S2253_OUTPUT_MODE);
			break;
		case S2253_CID_LOW_LATENCY:
			rc = s2253_dev_get_param(dev, S2253_OUTPUT_LATENCY_MODE);
			break;
		case S2253_CID_GPI:
			rc = s2253_dev_get_param(dev, S2253_GPIO_INPUT_VALUE);
			if (rc >= 0)
				rc ^= 1; // GPI is inverted
			break;
		case S2253_CID_GPO:
			rc = s2253_dev_get_param(dev, S2253_GPIO_OUTPUT_VALUE);
			break;
		case S2253_CID_XLED_MODE:
			rc = s2253_dev_get_param(dev, S2253_XLED_MODE);
			break;
		case S2253_CID_CPU_LOAD:
			rc = s2253_dev_get_param(dev, S2253_CPU_LOAD);
			break;
		case S2253_CID_TV_VCR_MODE:
			rc = s2253_dev_get_param(dev, S2253_TV_VCR_MODE);
			break;
		case S2253_CID_AUDIO_DELAY:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_AUDIO_DELAY + stream->param_shift);
			break;
		case S2253_CID_FIELDMODE:
			rc = s2253_dev_get_param(dev, S2253_INPUT_FIELD_MODE);
			break;
		case S2253_CID_GPI_PAUSE:
			rc = s2253_dev_get_param(dev,
				stream->endpoint == S2253_ENDPOINT_OUTPUT ? S2253_OUTPUT_TRIGGER :
							S2253_CAPTURE_TRIGGER + stream->param_shift);
			break;
		case S2253_CID_FREEZE:
			rc = s2253_dev_get_param(dev, S2253_INPUT_FREEZE);
			break;
		case S2253_CID_GPI_FREEZE:
			rc = s2253_dev_get_param(dev, S2253_FREEZE_TRIGGER);
			break;
			break;
		case S2253_CID_OUTPUT_VGA:
			rc = s2253_dev_get_param(dev, S2253_OUTPUT_VGA);
			break;
		case S2253_CID_OSD_ENABLE:
			rc = le32_to_cpu(dev->osd.osdOn);
			break;
		case S2253_CID_OSD_TRANSPARENT:
			rc = le32_to_cpu(dev->osd.transparent);
			break;
		case S2253_CID_OSD_POSITION:
			rc = le32_to_cpu(dev->osd.positionTop);
			break;
		case S2253_CID_OSD_DATEFORMAT:
			rc = (le32_to_cpu(dev->osd.ddmm) & 1) +
			     (le32_to_cpu(dev->osd.ddmm) & ~1) * 4 +
			     le32_to_cpu(dev->osd.year2) * 2;
			break;
		case S2253_CID_OSD_TIMEFORMAT:
			rc = le32_to_cpu(dev->osd.fraction);
			break;
		case S2253_CID_OSD_TIMEZONE:
			rc = dev->stream.board->osd_timezone;
			break;
#ifdef HAS_V4L2_CTRL_TYPE_STRING
		case S2253_CID_OSD_MESSAGE:
			if (ctrl->string == NULL) {
				rc = -EINVAL;
				break;
			}
			if (copy_to_user(ctrl->string, dev->osd.line,
				     strlen(dev->osd.line)+1 < ctrl->size ?
					strlen(dev->osd.line)+1 : ctrl->size)) {
				rc = -EFAULT;
				break;
			}
			continue;
#endif
		case S2253_CID_OSD_XOFF:
			rc = le32_to_cpu(dev->osd.xOffset);
			break;
		case S2253_CID_OSD_YOFF:
			rc = le32_to_cpu(dev->osd.yOffset);
			break;
		case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_IDRFRAMES + stream->param_shift);
			break;
		case V4L2_CID_MPEG_STREAM_TYPE:
			if (stream->fmt->format == S2253_FORMAT_MPEG_TS)
				rc = V4L2_MPEG_STREAM_TYPE_MPEG2_TS;
			else if (stream->fmt->format == S2253_FORMAT_MPEG_PS)
				rc = V4L2_MPEG_STREAM_TYPE_MPEG2_PS;
			else
				rc = -EINVAL;
			break;
		case V4L2_CID_MPEG_STREAM_PID_PMT:
			rc = s2253_dev_get_param(dev, S2253_MPEG_TS_PID_PMT);
			break;
		case V4L2_CID_MPEG_STREAM_PID_AUDIO:
			rc = s2253_dev_get_param(dev, S2253_MPEG_TS_PID_AUDIO);
			break;
		case V4L2_CID_MPEG_STREAM_PID_VIDEO:
			rc = s2253_dev_get_param(dev, S2253_MPEG_TS_PID_VIDEO);
			break;
		case V4L2_CID_MPEG_STREAM_PID_PCR:
			rc = s2253_dev_get_param(dev, S2253_MPEG_TS_PID_PCR);
			break;
		case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO:
			rc = s2253_dev_get_param(dev, S2253_MPEG_TS_PES_ID_AUDIO);
			break;
		case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO:
			rc = s2253_dev_get_param(dev, S2253_MPEG_TS_PES_ID_VIDEO);
			break;
		case V4L2_CID_MPEG_STREAM_VBI_FMT:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_CC_ENABLE + stream->param_shift);
			break;
		case V4L2_CID_MPEG_AUDIO_ENCODING:
			if (stream->audio_format == S2253_FORMAT_AAC)
				rc = V4L2_MPEG_AUDIO_ENCODING_AAC;
#ifdef ENABLE_MP2
			else if (stream->audio_format == S2253_FORMAT_MP2)
				rc = V4L2_MPEG_AUDIO_ENCODING_LAYER_2;
#endif
#ifdef ENABLE_MP3
			else if (stream->audio_format == S2253_FORMAT_MP3)
				rc = V4L2_MPEG_AUDIO_ENCODING_LAYER_3;
#endif
#ifdef ENABLE_PCM
			else if (stream->audio_format == S2253_FORMAT_PCM)
				rc = V4L2_MPEG_AUDIO_ENCODING_PCM;
#endif
			else
				rc = V4L2_MPEG_AUDIO_ENCODING_NONE;
			break;
		case V4L2_CID_MPEG_AUDIO_AAC_BITRATE:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_ABITRATE + stream->param_shift);
			if (rc >= 0)
				rc *= 1000;
			break;
#ifdef ENABLE_MP3
		case V4L2_CID_MPEG_AUDIO_L3_BITRATE:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_ABITRATE + stream->param_shift);
			switch (rc) {
			case 32: rc = V4L2_MPEG_AUDIO_L3_BITRATE_32K; break;
			case 40: rc = V4L2_MPEG_AUDIO_L3_BITRATE_40K; break;
			case 48: rc = V4L2_MPEG_AUDIO_L3_BITRATE_48K; break;
			case 56: rc = V4L2_MPEG_AUDIO_L3_BITRATE_56K; break;
			case 64: rc = V4L2_MPEG_AUDIO_L3_BITRATE_64K; break;
			case 80: rc = V4L2_MPEG_AUDIO_L3_BITRATE_80K; break;
			case 96: rc = V4L2_MPEG_AUDIO_L3_BITRATE_96K; break;
			case 112: rc = V4L2_MPEG_AUDIO_L3_BITRATE_112K; break;
			case 128: rc = V4L2_MPEG_AUDIO_L3_BITRATE_128K; break;
			case 160: rc = V4L2_MPEG_AUDIO_L3_BITRATE_160K; break;
			case 192: rc = V4L2_MPEG_AUDIO_L3_BITRATE_192K; break;
			case 224: rc = V4L2_MPEG_AUDIO_L3_BITRATE_224K; break;
			case 256: rc = V4L2_MPEG_AUDIO_L3_BITRATE_256K; break;
			case 320: rc = V4L2_MPEG_AUDIO_L3_BITRATE_320K; break;
			default: rc = V4L2_MPEG_AUDIO_L3_BITRATE_192K; break;
			}
			break;
#endif
		case V4L2_CID_MPEG_AUDIO_MODE:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_ACHANNELS + stream->param_shift);
			if (rc == 1)
				rc = V4L2_MPEG_AUDIO_MODE_MONO;
			else if (rc == 2)
				rc = V4L2_MPEG_AUDIO_MODE_STEREO;
			break;
		case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_GOPSIZE + stream->param_shift);
			break;
		case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE:
			//rc = s2253_dev_get_param(dev, S2253_CAPTURE_GOPCLOSED + stream->param_shift);
			break;
		case V4L2_CID_MPEG_VIDEO_BITRATE:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_BITRATE + stream->param_shift);
			if (rc >= 0)
				rc *= 1000;
			break;
		case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
			rc = stream->mpeg_bitrate_mode;
			if (rc == S2253_BITRATE_MODE_VBR)
				rc = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR;
			else if (rc == S2253_BITRATE_MODE_CBR)
				rc = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR;
			break;
		case V4L2_CID_MPEG_VIDEO_ENCODING:
			switch (stream->video_format) {
			case S2253_FORMAT_MPEG2:
				rc = V4L2_MPEG_VIDEO_ENCODING_MPEG_2;
				break;
			case S2253_FORMAT_MPEG4:
				rc = V4L2_MPEG_VIDEO_ENCODING_MPEG_4;
				break;
			case S2253_FORMAT_H264:
				rc = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC;
				break;
			default:
				rc = -EINVAL;
				break;
			}
			break;
		case V4L2_CID_MPEG_VIDEO_ASPECT:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_ASPECT_RATIO + stream->param_shift);
			if (rc < 0)
				break;
			switch(rc) {
			default:
			case 0x0000:
				rc = V4L2_MPEG_VIDEO_ASPECT_1x1;
				break;
			case 0x0403:
				rc = V4L2_MPEG_VIDEO_ASPECT_4x3;
				break;
			case 0x1009:
				rc = V4L2_MPEG_VIDEO_ASPECT_16x9;
				break;
			case 0xdd64:
				rc = V4L2_MPEG_VIDEO_ASPECT_221x100;
				break;
			}
			break;
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
		case V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE:
			rc = s2253_dev_get_param(dev, S2253_MPEG2_PROFILE);
			break;
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
		case V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL:
			rc = s2253_dev_get_param(dev, S2253_MPEG2_LEVEL);
			break;
#endif
		case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_H264_LEVEL + stream->param_shift);
			if (rc < 0)
				break;
			switch(rc) {
			case 10:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_1_0;
				break;
			case 9:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_1B;
				break;
			case 11:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_1_1;
				break;
			case 12:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_1_2;
				break;
			case 13:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_1_3;
				break;
			case 20:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_2_0;
				break;
			case 21:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_2_1;
				break;
			case 22:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_2_2;
				break;
			case 30:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_3_0;
				break;
			case 31:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_3_1;
				break;
			case 32:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_3_2;
				break;
			case 40:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
				break;
			case 41:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_4_1;
				break;
			case 42:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_4_2;
				break;
			case 50:
				rc = V4L2_MPEG_VIDEO_H264_LEVEL_5_0;
				break;
			default:
				rc = -EINVAL;
				break;
			}
			break;
		case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_H264_PROFILE + stream->param_shift);
			if (rc < 0)
				break;
			switch (rc) {
			case 66:
				rc = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
				break;
			case 77:
				rc = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
				break;
			case 100:
				rc = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
				break;
			default:
				rc = -EINVAL;
				break;
			}
			break;
		case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_INTRAFRAMEQP + stream->param_shift);
			if (rc > 51) {
				ctrl->value = -1;
				continue;
			}
			break;
		case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
			rc = s2253_dev_get_param(dev, S2253_CAPTURE_INTERPFRAMEQP + stream->param_shift);
			if (rc > 51) {
				ctrl->value = -1;
				continue;
			}
			break;
		default:
			rc = -EINVAL;
			break;
		}
		if (rc < 0) {
			ctrls->error_idx = i;
			return rc;
		}
		ctrl->value = rc;
	}
	return 0;
}

static int vidioc_s_ext_ctrls(struct file *file, void *priv,
			      struct v4l2_ext_controls *ctrls)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	struct v4l2_ext_control *ctrl = ctrls->controls;
	unsigned int i;
	int osdchanged = 0;
	int rc = 0;

	/* verify all the control values before applying them */
	rc = vidioc_try_ext_ctrls(file, priv, ctrls);
	if (rc < 0)
		return rc;

	for (i = 0; i < ctrls->count; i++, ctrl++) {
		pr_info("%s: id=%x size=%d rsv=%x value=%llx\n", __func__,
				ctrl->id,
				ctrl->size,
				ctrl->reserved2[0],
				ctrl->value64);
#if 0 && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
		dprintk(1, "%s: %s = %d\n", __func__,
			v4l2_ctrl_get_name(ctrl->id), ctrl->value);
#endif
		switch(ctrl->id) {
		case V4L2_CID_BRIGHTNESS:
			stream->bright = ctrl->value;
			dprintk(4, "set brightness %d\n", ctrl->value);
			rc = s2253_dev_set_param(dev, S2253_BRIGHTNESS, ctrl->value);
			break;
		case V4L2_CID_CONTRAST:
			stream->contrast = ctrl->value;
			dprintk(4, "set contrast %d\n", ctrl->value);
			rc = s2253_dev_set_param(dev, S2253_CONTRAST, ctrl->value);
			break;
		case V4L2_CID_HUE:
			stream->hue = ctrl->value;
			dprintk(4, "set hue %d\n", ctrl->value);
			rc = s2253_dev_set_param(dev, S2253_HUE, ctrl->value);
			break;
		case V4L2_CID_SATURATION:
			stream->saturation = ctrl->value;
			dprintk(4, "set saturation %d\n", ctrl->value);
			rc = s2253_dev_set_param(dev, S2253_SATURATION, ctrl->value);
			break;
		case V4L2_CID_AUDIO_BALANCE:
			stream->audio_balance = ctrl->value;
			rc = stream_set_audio_volume_balance(stream);
			break;
		case V4L2_CID_AUDIO_VOLUME:
			stream->audio_volume = ctrl->value;
			rc = stream_set_audio_volume_balance(stream);
			break;
		case V4L2_CID_AUTOGAIN:
			rc = s2253_dev_set_param(dev, S2253_INPUT_AUTOGAIN, ctrl->value);
			break;
		case V4L2_CID_AUTOBRIGHTNESS:
			rc = s2253_dev_set_param(dev, S2253_INPUT_AUTOBRIGHTNESS, ctrl->value);
			break;
		case V4L2_CID_HFLIP:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_FLIP + stream->param_shift,
				(ctrl->value << 1) | (~0x2 & s2253_dev_get_param(dev, S2253_CAPTURE_FLIP + stream->param_shift)));
			break;
		case V4L2_CID_VFLIP:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_FLIP + stream->param_shift,
				ctrl->value | (~0x1 & s2253_dev_get_param(dev, S2253_CAPTURE_FLIP + stream->param_shift)));
			break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		case V4L2_CID_HCENTER:
#endif
		case S2253_CID_HCENTER:
			rc = s2253_dev_set_param(dev, S2253_INPUT_H_ADJUST, ctrl->value);
			break;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
		case V4L2_CID_VCENTER:
#endif
		case S2253_CID_VCENTER:
			rc = s2253_dev_set_param(dev, S2253_INPUT_V_ADJUST, ctrl->value);
			break;
		case S2253_CID_DFLIP:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_FLIP + stream->param_shift,
				(ctrl->value << 2) | (~0x4 & s2253_dev_get_param(dev, S2253_CAPTURE_FLIP + stream->param_shift)));
			break;

		case V4L2_CID_PRIVATE_COLORFILTER:
			stream->color_filter = ctrl->value;
			break;
		case S2253_CID_INTERPOLATE:
			rc = s2253_dev_set_param(dev, S2253_INTERPOLATE, ctrl->value);
			break;
		case S2253_CID_DEINTERLACE:
			rc = s2253_dev_set_param(dev, S2253_DEINTERLACE, ctrl->value);
			break;
		case S2253_CID_OUTPUT_MODE:
			rc = s2253_dev_set_param(dev, S2253_OUTPUT_MODE, ctrl->value);
			break;
		case S2253_CID_LOW_LATENCY:
			rc = s2253_dev_set_param(dev, S2253_OUTPUT_LATENCY_MODE, ctrl->value);
			break;
		case S2253_CID_GPO:
			rc = s2253_dev_set_param(dev, S2253_GPIO_OUTPUT_VALUE, ctrl->value);
			break;
		case S2253_CID_XLED_MODE:
			rc = s2253_dev_set_param(dev, S2253_XLED_MODE, ctrl->value);
			break;
		case S2253_CID_TV_VCR_MODE:
			rc = s2253_dev_set_param(dev, S2253_TV_VCR_MODE, ctrl->value);
			break;
		case S2253_CID_AUDIO_DELAY:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_AUDIO_DELAY + stream->param_shift, ctrl->value);
			break;
		case S2253_CID_FIELDMODE:
			rc = s2253_dev_set_param(dev, S2253_INPUT_FIELD_MODE, ctrl->value);
			break;
		case S2253_CID_GPI_PAUSE:
			rc = s2253_dev_set_param(dev, 
				stream->endpoint == S2253_ENDPOINT_OUTPUT ? S2253_OUTPUT_TRIGGER :
							S2253_CAPTURE_TRIGGER + stream->param_shift,
						ctrl->value);
			break;
		case S2253_CID_FREEZE:
			rc = s2253_dev_set_param(dev, S2253_INPUT_FREEZE, ctrl->value);
			break;
		case S2253_CID_GPI_FREEZE:
			rc = s2253_dev_set_param(dev, S2253_FREEZE_TRIGGER, ctrl->value);
			break;
		case S2253_CID_OUTPUT_VGA:
			rc = s2253_dev_set_param(dev, S2253_OUTPUT_VGA, ctrl->value);
			break;
		case S2253_CID_OSD_ENABLE:
			dev->osd.osdOn = cpu_to_le32(ctrl->value);
			osdchanged = 1;
			break;
		case S2253_CID_OSD_TRANSPARENT:
			dev->osd.transparent = cpu_to_le32(ctrl->value);
			osdchanged = 1;
			break;
		case S2253_CID_OSD_POSITION:
			dev->osd.positionTop = cpu_to_le32(ctrl->value);
			osdchanged = 1;
			break;
		case S2253_CID_OSD_DATEFORMAT:
			dev->osd.ddmm = cpu_to_le32((ctrl->value & 1) +
						    (ctrl->value >> 2) * 2);
			dev->osd.year2 = cpu_to_le32((ctrl->value >> 1) & 1);
			osdchanged = 1;
			break;
		case S2253_CID_OSD_TIMEFORMAT:
			dev->osd.fraction = cpu_to_le32(ctrl->value);
			osdchanged = 1;
			break;
		case S2253_CID_OSD_TIMEZONE: {
			struct s2253_clock clk;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
			struct timespec64 ts64;
			ktime_get_real_ts64(&ts64);

			ts64.tv_sec += timezones[ctrl->value].delta;
			clk.sec = cpu_to_le64(ts64.tv_sec);
			clk.usec = cpu_to_le32(ts64.tv_nsec / 1000);
#else
			struct timeval tv;
			do_gettimeofday(&tv);
			tv.tv_sec += timezones[ctrl->value].delta;
			clk.sec = cpu_to_le64(tv.tv_sec);
			clk.usec = cpu_to_le32(tv.tv_usec);
#endif
			rc = s2253_set_param_data(dev->stream.board, S2253_CLOCK,
						  0, &clk, sizeof clk);
			dev->stream.board->osd_timezone = ctrl->value;
			break;
		}
#ifdef HAS_V4L2_CTRL_TYPE_STRING
		case S2253_CID_OSD_MESSAGE:
			if (ctrl->string == NULL) {
				rc = -EINVAL;
				break;
			}
			if (ctrl->size == 0) {
				// Old userspace doesn't set the size, so we must
				// assume the string points to user memory.  Can't
				// assume length, so usercopy one byte at a time.
				int x = 0;
				while (x < sizeof(dev->osd.line)) {
					char c;
					if (copy_from_user(&c, ctrl->string + x, 1)) {
						ctrls->error_idx = i;
						return -EFAULT;
					}
					dev->osd.line[x++] = c;
					if (c == 0)
						break;
				}
			} else {
				strscpy(dev->osd.line, ctrl->string,
					sizeof dev->osd.line);
			}
			pr_info("OSD_MESSAGE = %s\n", dev->osd.line);
			osdchanged = 1;
			break;
#endif
		case S2253_CID_OSD_XOFF:
			dev->osd.xOffset = cpu_to_le32(ctrl->value);
			osdchanged = 1;
			break;
		case S2253_CID_OSD_YOFF:
			dev->osd.yOffset = cpu_to_le32(ctrl->value);
			osdchanged = 1;
			break;
		case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_IDRFRAMES +
						 stream->param_shift,
						 ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_TYPE:
			if (ctrl->value == V4L2_MPEG_STREAM_TYPE_MPEG2_TS)
				stream->fmt = s2253_find_fmt(S2253_FORMAT_MPEG_TS);
			else if (ctrl->value == V4L2_MPEG_STREAM_TYPE_MPEG2_PS)
				stream->fmt = s2253_find_fmt(S2253_FORMAT_MPEG_PS);
			break;
		case V4L2_CID_MPEG_STREAM_PID_PMT:
			rc = s2253_dev_set_param(dev, S2253_MPEG_TS_PID_PMT, ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_PID_AUDIO:
			rc = s2253_dev_set_param(dev, S2253_MPEG_TS_PID_AUDIO, ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_PID_VIDEO:
			rc = s2253_dev_set_param(dev, S2253_MPEG_TS_PID_VIDEO, ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_PID_PCR:
			rc = s2253_dev_set_param(dev, S2253_MPEG_TS_PID_PCR, ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_PES_ID_AUDIO:
			rc = s2253_dev_set_param(dev, S2253_MPEG_TS_PES_ID_AUDIO, ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_PES_ID_VIDEO:
			rc = s2253_dev_set_param(dev, S2253_MPEG_TS_PES_ID_VIDEO, ctrl->value);
			break;
		case V4L2_CID_MPEG_STREAM_VBI_FMT:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_CC_ENABLE +
						stream->param_shift,
						ctrl->value);
			break;
		case V4L2_CID_MPEG_AUDIO_ENCODING:
			if (ctrl->value == V4L2_MPEG_AUDIO_ENCODING_AAC)
				stream->audio_format = S2253_FORMAT_AAC;
#ifdef ENABLE_MP2
			else if (ctrl->value == V4L2_MPEG_AUDIO_ENCODING_LAYER_2)
				stream->audio_format = S2253_FORMAT_MP2;
#endif
#ifdef ENABLE_MP3
			else if (ctrl->value == V4L2_MPEG_AUDIO_ENCODING_LAYER_3)
				stream->audio_format = S2253_FORMAT_MP3;
#endif
#ifdef ENABLE_PCM
			else if (ctrl->value == V4L2_MPEG_AUDIO_ENCODING_PCM)
				stream->audio_format = S2253_FORMAT_PCM;
#endif
			else if (ctrl->value == V4L2_MPEG_AUDIO_ENCODING_NONE)
				stream->audio_format = 0;
			break;
		case V4L2_CID_MPEG_AUDIO_AAC_BITRATE:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_ABITRATE +
						 stream->param_shift,
						 ctrl->value / 1000);
			break;
#ifdef ENABLE_MP3
		case V4L2_CID_MPEG_AUDIO_L3_BITRATE: {
			int value;
			switch (ctrl->value) {
			case V4L2_MPEG_AUDIO_L3_BITRATE_32K: value = 32; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_40K: value = 40; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_48K: value = 48; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_56K: value = 56; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_64K: value = 64; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_80K: value = 80; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_96K: value = 96; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_112K: value = 112; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_128K: value = 128; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_160K: value = 160; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_192K: value = 192; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_224K: value = 224; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_256K: value = 256; break;
			case V4L2_MPEG_AUDIO_L3_BITRATE_320K: value = 320; break;
			default: value = 192; break;
			}
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_ABITRATE +
						 stream->param_shift, value);
			break; }
#endif
		case V4L2_CID_MPEG_AUDIO_MODE:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_ACHANNELS +
						 stream->param_shift,
						 1 + (ctrl->value == V4L2_MPEG_AUDIO_MODE_STEREO));
			break;
		case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_GOPSIZE +
						 stream->param_shift, ctrl->value);
			break;
		case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE:
			//rc = s2253_dev_set_param(dev, S2253_CAPTURE_GOPCLOSED +
			//		    stream->param_shift, ctrl->value);
			break;
		case V4L2_CID_MPEG_VIDEO_BITRATE:
			stream->mpeg_bitrate = ctrl->value / 1000;
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_BITRATE +
						 stream->param_shift, stream->mpeg_bitrate);
			break;
		case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
			if (ctrl->value == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)
				stream->mpeg_bitrate_mode = S2253_BITRATE_MODE_CBR;
			else if (ctrl->value == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR)
				stream->mpeg_bitrate_mode = S2253_BITRATE_MODE_VBR;
			else
				stream->mpeg_bitrate_mode = ctrl->value;
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_BITRATE_MODE +
						 stream->param_shift, stream->mpeg_bitrate_mode);
			break;
		case V4L2_CID_MPEG_VIDEO_ENCODING:
			if (ctrl->value == V4L2_MPEG_VIDEO_ENCODING_MPEG_2)
				stream->video_format = S2253_FORMAT_MPEG2;
			else if (ctrl->value == V4L2_MPEG_VIDEO_ENCODING_MPEG_4)
				stream->video_format = S2253_FORMAT_MPEG4;
			else if (ctrl->value == V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC)
				stream->video_format = S2253_FORMAT_H264;
			s2253_stream_set_format(stream);
			break;
		case V4L2_CID_MPEG_VIDEO_ASPECT:
			switch(ctrl->value) {
			default:
			case V4L2_MPEG_VIDEO_ASPECT_1x1: rc = 0x0000; break;
			case V4L2_MPEG_VIDEO_ASPECT_4x3: rc = 0x0403; break;
			case V4L2_MPEG_VIDEO_ASPECT_16x9: rc = 0x1009; break;
			case V4L2_MPEG_VIDEO_ASPECT_221x100: rc = 0xdd64; break;
			}
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_ASPECT_RATIO + stream->param_shift, rc);
			break;
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE
		case V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE:
			rc = s2253_dev_set_param(dev, S2253_MPEG2_PROFILE, ctrl->value);
			break;
#endif
#ifdef V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL
		case V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL:
			rc = s2253_dev_set_param(dev, S2253_MPEG2_LEVEL, ctrl->value);
			break;
#endif
		case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
			switch (ctrl->value) {
			case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
				rc = 10;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_1B:
				rc = 9;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_1_1:
				rc = 11;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_1_2:
				rc = 12;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_1_3:
				rc = 13;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
				rc = 20;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_2_1:
				rc = 21;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_2_2:
				rc = 22;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_3_0:
				rc = 30;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
				rc = 31;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
				rc = 32;
				break;
			default:
			case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
				rc = 40;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
				rc = 41;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
				rc = 42;
				break;
			case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
				rc = 50;
				break;
			}
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_H264_LEVEL + stream->param_shift, rc);
			break;
		case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
			switch(ctrl->value) {
			case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
				rc = 66;
				break;
			case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
				rc = 77;
				break;
			default:
			case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
				rc = 100;
				break;
			}
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_H264_PROFILE + stream->param_shift, rc);
			break;
		case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_INTRAFRAMEQP + stream->param_shift, ctrl->value);
			break;
		case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_INTERPFRAMEQP + stream->param_shift, ctrl->value);
			break;
		default:
			/* we should never get here, due to try_ext_ctrls */
			rc = -EINVAL;
			break;
		}
		if (rc < 0) {
			ctrls->error_idx = i;
			break;
		}
	}
	if (osdchanged) {
		int new_rc = s2253_set_param_data(dev->stream.board, S2253_SET_OSD, 0,
					  &dev->osd, sizeof dev->osd);
		if (rc == 0)
			rc = new_rc;
	}
	return rc;
}

static int vidioc_g_jpegcomp(struct file *file, void *priv,
			 struct v4l2_jpegcompression *jc)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	if (stream->endpoint == S2253_ENDPOINT_OUTPUT)
		return -EINVAL;
	if (stream->fmt->fourcc != V4L2_PIX_FMT_MJPEG &&
	    stream->fmt->fourcc != V4L2_PIX_FMT_JPEG)
		return -EINVAL;
	*jc = stream->jc;
	dprintk(2, "%s: quality %d\n", __func__, jc->quality);
	return 0;
}

static int vidioc_s_jpegcomp(struct file *file, void *priv,
			 V4L_CONST struct v4l2_jpegcompression *jc)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	int rc;

	if (stream->endpoint == S2253_ENDPOINT_OUTPUT)
		return -EINVAL;
	if (stream->fmt->fourcc != V4L2_PIX_FMT_MJPEG &&
	    stream->fmt->fourcc != V4L2_PIX_FMT_JPEG)
		return -EINVAL;
	if (jc->quality < S2253_MIN_JPEG_Q || jc->quality > S2253_MAX_JPEG_Q)
		return -EINVAL;
	stream->jc.quality = jc->quality;
	rc = s2253_dev_set_param(dev,
			S2253_CAPTURE_JPEG_QUALITY + stream->param_shift,
			jc->quality);
	dprintk(2, "%s: quality %d\n", __func__, jc->quality);
	return rc;
}

static int vidioc_g_parm(struct file *file, void *priv,
			 struct v4l2_streamparm *sp)
{
	struct s2253_dev *dev = video_drvdata(file);
	__u32 def_num, def_dem;
	struct s2253_stream *stream = &dev->stream;
	if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;
	memset(sp, 0, sizeof(struct v4l2_streamparm));
	sp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
	sp->parm.capture.capturemode = stream->cap_parm.capturemode;
	def_num = dev->is_ntsc ? 1001 : 1000;
	def_dem = dev->is_ntsc ? 30000 : 25000;
	sp->parm.capture.timeperframe.denominator = def_dem;
	switch (stream->fdec) {
	default:
	case FDEC_1:
		sp->parm.capture.timeperframe.numerator = def_num;
		break;
	case FDEC_2:
		sp->parm.capture.timeperframe.numerator = def_num * 2;
		break;
	case FDEC_3:
		sp->parm.capture.timeperframe.numerator = def_num * 3;
		break;
	case FDEC_5:
		sp->parm.capture.timeperframe.numerator = def_num * 5;
		break;
	}
	dprintk(4, "%s capture mode, %d timeperframe %d/%d\n", __func__,
		sp->parm.capture.capturemode,
		sp->parm.capture.timeperframe.numerator,
		sp->parm.capture.timeperframe.denominator);
	return 0;
}

static int vidioc_s_parm(struct file *file, void *priv,
			 struct v4l2_streamparm *sp)
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream = &dev->stream;
	__u32 def_num, def_den;
	__u32 num, den;
	int fdec;
	int rc;

	if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;

	num = sp->parm.capture.timeperframe.numerator;
	den = sp->parm.capture.timeperframe.denominator;
	def_num = dev->is_ntsc ? 1001 : 1000;
	def_den = dev->is_ntsc ? 30000 : 25000;
	fdec = FDEC_1;
	sp->parm.capture.timeperframe.numerator = def_num;
	sp->parm.capture.timeperframe.denominator = def_den;

	if (def_den * num >= 5 * def_num * den) {
		sp->parm.capture.timeperframe.numerator = def_num * 5;
		fdec = FDEC_5;
	} else if (def_den * num >= 3 * def_num * den) {
		sp->parm.capture.timeperframe.numerator = def_num * 3;
		fdec = FDEC_3;
	} else if (def_den * num >= 2 * def_num * den) {
		sp->parm.capture.timeperframe.numerator = def_num * 2;
		fdec = FDEC_2;
	}

	stream->fdec = fdec;
	stream->cap_parm = sp->parm.capture;
	rc = s2253_dev_set_param(dev, S2253_CAPTURE_DECIMATION + stream->param_shift, fdec);
	dprintk(4, "%s capture mode, %d timeperframe %d/%d, fdec %d\n",
		__func__,
		sp->parm.capture.capturemode,
		sp->parm.capture.timeperframe.numerator,
		sp->parm.capture.timeperframe.denominator, fdec);
	return rc;
}
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29) || V4L_DVB_TREE
static int vidioc_enum_frameintervals(struct file *file, void *priv,
			    struct v4l2_frmivalenum *fe)
{
	struct s2253_dev *dev = video_drvdata(file);
	static int frm_dec[] = {1, 2, 3, 5};

	if (fe->index >= ARRAY_SIZE(frm_dec))
		return -EINVAL;
	fe->type = V4L2_FRMIVAL_TYPE_DISCRETE;
	fe->discrete.denominator = dev->is_ntsc ? 30000 : 25000;
	fe->discrete.numerator = (dev->is_ntsc ? 1001 : 1000) * frm_dec[fe->index];
	dprintk(4, "%s discrete %d/%d\n", __func__, fe->discrete.numerator,
		fe->discrete.denominator);
	return 0;
}
#endif

static int vidioc_enumaudio(struct file *file, void *priv,
			    struct v4l2_audio *audio)
{
	static const char *names[] = { "Mic", "Line In" };

	dprintk(100, "%s: %d\n", __func__, audio->index);
	if (audio->index != 0 && audio->index != 1)
		return -EINVAL;
	audio->capability = V4L2_AUDCAP_AVL;
	strscpy(audio->name, names[audio->index], sizeof(audio->name));
	if (audio->index == 1)
		audio->capability |= V4L2_AUDCAP_STEREO;
	return 0;
}

static int vidioc_g_audio(struct file *file, void *priv,
			  struct v4l2_audio *audio)
{
	struct s2253_dev *dev = video_drvdata(file);

	dprintk(100, "%s: %d\n", __func__, audio->index);

	memset(audio, 0, sizeof(struct v4l2_audio));
	audio->index = s2253_dev_get_param(dev, S2253_RECORD_INPUT);
	vidioc_enumaudio(file, priv, audio);
	if (s2253_dev_get_param(dev, S2253_RECORD_AGC) & 0x3)
		audio->mode |= V4L2_AUDMODE_AVL;
	return 0;
}

static int vidioc_s_audio(struct file *file, void *priv, 
			  V4L_CONST struct v4l2_audio *audio)
{
	struct s2253_dev *dev = video_drvdata(file);
	int rc;

	dprintk(100, "%s: %d\n", __func__, audio->index);
	if (audio->index != 0 && audio->index != 1)
		return -EINVAL;
	rc = s2253_dev_set_param(dev, S2253_RECORD_INPUT, audio->index);
	if (rc >= 0)
		rc = s2253_dev_set_param(dev, S2253_RECORD_AGC,
			audio->mode & V4L2_AUDMODE_AVL ? 3 : 0);
	return 0;
}

static int vidioc_enumaudout(struct file *file, void *priv,
			     struct v4l2_audioout *audio)
{
	dprintk(100, "%s: %d\n", __func__, audio->index);
	if (audio->index != 0)
		return -EINVAL;
	memset(audio, 0, sizeof(struct v4l2_audioout));
	strscpy(audio->name, "Line Out", sizeof(audio->name));
	return 0;
}


static int vidioc_g_audout(struct file *file, void *priv,
			   struct v4l2_audioout *audio)
{
	dprintk(100, "%s: %d\n", __func__, audio->index);
	audio->index = 0;
	return vidioc_enumaudout(file, priv, audio);
}

static int vidioc_s_audout(struct file *file, void *priv,
			   V4L_CONST struct v4l2_audioout *audio)
{
	dprintk(100, "%s: %d\n", __func__, audio->index);
	return audio->index == 0 ? 0 : -EINVAL;
}

// introduced in Linux 2.6.29
#ifdef VIDIOC_ENCODER_CMD
static int vidioc_encoder_cmd(struct file *file, void *priv,
			      struct v4l2_encoder_cmd *cmd)
{
	struct s2253_dev *dev = video_drvdata(file);
	__u16 param = S2253_CAPTURE_STATE + dev->stream.param_shift;

	if (dev->buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;
	dprintk(100, "%s: cmd=%x flags=%x\n", __func__, cmd->cmd, cmd->flags);
#if USE_VIDEOBUF2
	if (vb2_queue_is_busy(
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
			&dev->vdev
#else
			dev->vdev.queue
#endif
			, file))
		return -EPERM;
#else
	if (!res_check(dev, file))
		return -EPERM;
#endif
	if (cmd->cmd == V4L2_ENC_CMD_PAUSE && cmd->flags == 0)
		return s2253_dev_set_param(dev, param, S2253_STATE_PAUSED);
	if (cmd->cmd == V4L2_ENC_CMD_RESUME && cmd->flags == 0)
		return s2253_dev_set_param(dev, param, S2253_STATE_STREAMING);
	return -EINVAL;
}
#endif

// introduced in Linux 3.3
#ifdef VIDIOC_DECODER_CMD
static int vidioc_decoder_cmd(struct file *file, void *priv,
			      struct v4l2_decoder_cmd *cmd)
{
	struct s2253_dev *dev = video_drvdata(file);
	__u16 param = S2253_OUTPUT_STATE;
	if (dev->buf_type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
		return -EINVAL;
	dprintk(100, "%s: cmd=%x flags=%x\n", __func__, cmd->cmd, cmd->flags);
#if USE_VIDEOBUF2
	if (vb2_queue_is_busy(
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
			&dev->vdev
#else
			dev->vdev.queue
#endif
			, file))
		return -EPERM;
#else
	if (!res_check(dev, file))
		return -EPERM;
#endif
	if (cmd->cmd == V4L2_DEC_CMD_PAUSE && cmd->flags == 0)
		return s2253_dev_set_param(dev, param, S2253_STATE_PAUSED);
	if (cmd->cmd == V4L2_DEC_CMD_RESUME && cmd->flags == 0)
		return s2253_dev_set_param(dev, param, S2253_STATE_STREAMING);
	if (cmd->cmd == V4L2_DEC_CMD_STOP && cmd->flags == V4L2_DEC_CMD_STOP_IMMEDIATELY)
		return s2253_dev_set_param(dev, param, S2253_STATE_IDLE);
	return -EINVAL;
}
#endif

#ifdef HAVE_V4L2_EVENT
static int vidioc_subscribe_event(struct v4l2_fh *fh, V4L_CONST struct v4l2_event_subscription *sub)
{
	return v4l2_event_subscribe(fh, sub
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)
	, 0
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
	, NULL
#endif
#endif
	);
}

static int vidioc_unsubscribe_event(struct v4l2_fh *fh, V4L_CONST struct v4l2_event_subscription *sub)
{
	return v4l2_event_unsubscribe(fh, sub);
}
#endif


#if !USE_VIDEOBUF2

#if LINUX_VERSION_CODE >= KERNEL_VERSION_NOINODE || V4L_DVB_TREE
static int s2253_open(struct file *file)
#else
static int s2253_open(struct inode *inode, struct file *file)
#endif
{

	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_fh *fh;
	fh = kzalloc(sizeof(*fh), GFP_KERNEL);
	if (NULL == fh) {
		return -ENOMEM;
	}
	file->private_data = fh;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24)
	videobuf_queue_vmalloc_init(&fh->vb_vidq, &s2253_video_qops,
#else
	videobuf_queue_init(&fh->vb_vidq, &s2253_video_qops,
#endif
				    NULL, &dev->slock,
				    dev->stream.endpoint != S2253_ENDPOINT_OUTPUT ? V4L2_BUF_TYPE_VIDEO_CAPTURE : V4L2_BUF_TYPE_VIDEO_OUTPUT,
				    V4L2_FIELD_INTERLACED,
				    sizeof(struct s2253_buffer), dev
#if defined(HAVE_VIDEOBUF_VMALLOC_EXT_LOCK) || LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)
				    , NULL /* ext_lock */
#endif
				    );
	return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION_NOINODE || V4L_DVB_TREE
static int s2253_release(struct file *file)
#else
static int s2253_release(struct inode *inode, struct file *file)
#endif
{
	struct s2253_fh *fh = file->private_data;
	struct s2253_dev *dev;
	struct s2253_stream *stream;

	dev = video_drvdata(file);
	if (!dev)
		return -ENODEV;
	stream = &dev->stream;

	/* turn off stream */
	if (res_check(dev, file)) {
		dprintk(2, "%s: app didn't streamoff 1\n", __func__);
		s2253_uninit_stream(stream, 1, 1);
		dprintk(2, "%s: app didn't streamoff 2\n", __func__);
		res_free(dev, file);
		dprintk(2, "%s: app didn't streamoff 3\n", __func__);
	}
	if (fh) {
		dprintk(2, "%s videobuf_stop\n", __func__);
		videobuf_stop(&fh->vb_vidq);
		dprintk(2, "%s mmap_free\n", __func__);
		videobuf_mmap_free(&fh->vb_vidq);
		kfree(fh);
	}
	dprintk(2, "%s done\n", __func__);
	return 0;
}

static ssize_t s2253_read(struct file *file, char __user *data, size_t count,
			   loff_t *ppos)
{
	struct s2253_fh *fh = file->private_data;
	struct s2253_dev *dev = video_drvdata(file);

	dprintk(1, "%s\n", __func__);
	if (!res_check(dev, file)) {
		if (!res_get(dev, file))
			return -EBUSY;
		s2253_init_stream(&dev->stream);
	}
	return videobuf_read_stream(&fh->vb_vidq, data, count, ppos,
				    0, file->f_flags & O_NONBLOCK);
}

static ssize_t s2253_write(struct file *file, const char __user *data,
			   size_t count, loff_t *ppos)
{
	struct s2253_dev *dev = video_drvdata(file);
	int rc;

	//dprintk(1, "%s %zd\n", __func__, count);
	if (dev->stream.endpoint != S2253_ENDPOINT_OUTPUT)
		return -EINVAL;
	if (!dev->stream.board->usb_dev)
		return -ENODEV;

	if (!res_check(dev, file)) {
		if (count >= 8) {
			char tmp[8];
			if (copy_from_user(tmp, (void*)data, sizeof(tmp)))
				return -EFAULT;
			// send first 8 bytes to device to guess format
			rc = s2253_set_param_data(dev->stream.board,
						  S2253_OUTPUT_FORMAT,
						  0, tmp, sizeof(tmp));
			if (rc < 0)
				return rc;
			rc = s2253_dev_get_param(dev, S2253_OUTPUT_FORMAT);
			if (rc == S2253_FORMAT_NOVIDEO)
				rc = -EPROTO; // invalid format
			if (rc < 0)
				return rc;
		}
		if (!res_get(dev, file))
			return -EBUSY;
		while ((rc = s2253_dev_get_param(dev, S2253_OUTPUT_STATE)) == S2253_STATE_STREAMING) {
			dprintk(1, "waiting for current output to stop rc=%d\n", rc);
			if (signal_pending(current))
				return -EINTR;
			msleep(100);
		}
		if (rc < 0)
			return rc;
		s2253_init_stream(&dev->stream);
		dev->stream.last_seq = 0;
		dev->stream.last_fid = 0;
	}

	rc = s2253_video_output(&dev->stream, data, count, is_user_data, no_eof, 0);
	if (rc >= 0) {			// rc contains bytes remaining
		rc = count - rc;	// convert to bytes written
		if (rc == 0)
			rc = -EINTR;
	}
	return rc;
}

static int s2253_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct s2253_fh *fh = file->private_data;
	struct videobuf_queue *q = &fh->vb_vidq;
	int rc;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
	// workaround deadlock bug introduced in 3.11 (fixed in 3.14)
	mutex_lock(&q->vb_lock);
	q->ext_lock = (struct mutex *)1;
#endif
	rc = videobuf_mmap_mapper(q, vma);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
	// workaround deadlock bug introduced in 3.11 (fixed in 3.14)
	q->ext_lock = NULL;
	mutex_unlock(&q->vb_lock);
#endif
	return rc;
}

static unsigned int s2253_poll(struct file *file, poll_table *wait)
{
	struct s2253_fh *fh = file->private_data;
	struct s2253_dev *dev = video_drvdata(file);

	dprintk(100, "%s %p\n", __func__, dev);
	if (!dev)
		return POLLERR;
	if (!res_check(dev, file)) {
		if (!res_get(dev, file))
			return POLLERR;
		s2253_init_stream(&dev->stream);
	}
	return videobuf_poll_stream(file, &fh->vb_vidq, wait);
}

#endif /* !USE_VIDEOBUF2 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION_NOINODE || V4L_DVB_TREE
static long s2253_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
#else
static int s2253_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
#endif
{
	struct s2253_dev *dev = video_drvdata(file);
	struct s2253_stream *stream;
	int rc = 0;

	//dprintk(5, "%s: ioctl_cmd '%c' %d\n", __func__, _IOC_TYPE(cmd), _IOC_NR(cmd));

	if (!dev)
		return -ENODEV;
	stream = &dev->stream;

	if (cmd == VIDIOC_QBUF && stream->endpoint == S2253_ENDPOINT_OUTPUT) {
		s2253_get_param_data(stream->board, S2253_TIMESTAMP, &stream->cur_ts, 4);
		//dprintk(0, "cur ts=%u\n", stream->cur_ts);
	}


	switch (cmd) {
	case S2253_VIDIOC_OSD:
	{
		int le32chan = dev->osd.osdChan;
		if (!arg) {
			rc = -EINVAL;
			goto unlock;
		}
		if (copy_from_user (&dev->osd, (void *)arg, sizeof dev->osd)) {
			rc = -EFAULT;
			goto unlock;
		}
		// user-data is cpu-endian
		cpu_to_le32s(&dev->osd.osdOn);
		cpu_to_le32s(&dev->osd.osdBmp);
		cpu_to_le32s(&dev->osd.transparent);
		cpu_to_le32s(&dev->osd.positionTop);
		cpu_to_le32s(&dev->osd.ddmm);
		cpu_to_le32s(&dev->osd.year2);
		cpu_to_le32s(&dev->osd.fraction);
		cpu_to_le32s(&dev->osd.xOffset);
		cpu_to_le32s(&dev->osd.yOffset);
		dprintk(4, "osd On:%d\n", dev->osd.osdOn);
		dev->osd.osdChan = le32chan;
		rc = s2253_set_param_data(stream->board, S2253_SET_OSD, 0, &dev->osd, sizeof dev->osd);
		break;
	}
	case S2253_VIDIOC_CLOCK:
	{
		struct s2253_clock clk;
		if (!arg) {
			rc = -EINVAL;
			goto unlock;
		}
		if (copy_from_user(&clk, (void *)arg, sizeof clk)) {
			rc = -EFAULT;
			goto unlock;
		}
		dprintk(4, "set clock\n");
		cpu_to_le64s(&clk.sec);
		cpu_to_le32s(&clk.usec);
		rc = s2253_set_param_data(stream->board, S2253_CLOCK, 0, &clk, sizeof clk);
		break;
	}
	case S2253_VIDIOC_DEBUG:
	case S2253_VIDIOC_LOG:
	case S2253_VIDIOC_KLOG:
	{
		char msg[256];

		if (!arg) {
			rc = -EINVAL;
			goto unlock;
		}
		memset(msg, 0, sizeof(msg));
		rc = s2253_get_param_data(stream->board,
			(cmd == S2253_VIDIOC_DEBUG) ? 	S2253_DEBUG_MESSAGE :
			(cmd == S2253_VIDIOC_LOG) ?	S2253_LOG_MESSAGE :
							S2253_KLOG_MESSAGE,
			msg, sizeof(msg)-1);
		if (rc >= 0)
			rc = copy_to_user((void __user *)arg, msg, sizeof(msg)) ? -EFAULT : 0;
		break;
	}
	case S2253_VIDIOC_EEPROM:
	{
		char msg[256];

		if (!arg) {
			rc = -EINVAL;
			goto unlock;
		}
		memset(msg, 0, sizeof(msg));
		rc = s2253_get_param_data(stream->board,
			S2253_READ_EEPROM,
			msg, sizeof(msg));
		if (rc >= 0)
			rc = copy_to_user((void __user *)arg, msg, sizeof(msg)) ? -EFAULT : 0;
		break;
	}
	case S2253_VIDIOC_READ_FILE:
	{
		char msg[256];
		if (copy_from_user(msg, (void __user *)arg, sizeof(msg))) {
        		rc = -EFAULT;
        		goto unlock;
		}
		if (msg[0]) {
        		rc = s2253_set_param_data(stream->board,
				S2253_READ_FILE, strlen(msg)+1,
				msg, sizeof(msg));
		}
    		rc = s2253_get_param_data(stream->board,
			S2253_READ_FILE,
			msg, sizeof(msg));

		if (rc >= 0 && copy_to_user((void __user *)arg, msg, sizeof(msg)))
			rc = -EFAULT;
		break;
	}



	case S2253_VIDIOC_ECHO_TS:
	{
		struct timeval tv;
		unsigned int ts;
		if (!arg) {
			rc = -EINVAL;
			goto unlock;
		}
		if (copy_from_user(&tv, (void *)arg, sizeof tv)) {
			rc = -EFAULT;
			goto unlock;
		}
		ts = cpu_to_le32(tv.tv_sec * S2253_TS_HZ +
				   (tv.tv_usec * (S2253_TS_HZ / 10000) / 100));
		dprintk(1, "echo timestamp ts %d\n", ts);
		rc = s2253_set_param_data(stream->board, S2253_ECHO_TIMESTAMP,
			0, &ts, sizeof(ts));
		break;
	}
	case S2253_VIDIOC_OVERLAY:
	{
		struct s2253_overlay_image overlay;
		unsigned int length;
		char *data = (char *)arg;
		if (!arg) {
			rc = -EINVAL;
			goto unlock;
		}
		if (copy_from_user(&overlay, data, sizeof overlay)) {
			rc = -EFAULT;
			goto unlock;
		}
		length = overlay.length;
		// overlay data is cpu-endian
		cpu_to_le32s(&overlay.xOffset);
		cpu_to_le32s(&overlay.yOffset);
		cpu_to_le32s(&overlay.length);
		if (overlay.channel == 0 && dev->osd.osdChan != cpu_to_le32(2))
			overlay.channel = le32_to_cpu(dev->osd.osdChan) + OVERLAY_CHANNEL_CAPTURE;
		if (copy_to_user(data, &overlay, sizeof overlay)) {
			rc = -EFAULT;
			goto unlock;
		}
		rc = s2253_video_output(&stream->board->overlay, data,
			sizeof(overlay) + length, is_user_data, has_eof, 0);
		break;

	}
	case S2253_VIDIOC_GET_STREAM_AUDIO:
		spin_lock(&dev->slock); /* FIXME: spinlock ok with copy_to_user? */
		rc = stream->audio_len;
		if (copy_to_user((void *)arg, stream->audio, stream->audio_len))
			rc = -EFAULT;
		else
			stream->audio_len = 0;
		spin_unlock(&dev->slock);
		break;

	case S2253_VIDIOC_PUT_STREAM_AUDIO:
		break;

	case S2253_VIDIOC_SET_STREAM_AUDIO:
	{
		int format = stream->fmt->format | arg;
		dprintk(1, "stream format w/audio is %x\n", format);
		if (stream->endpoint != S2253_ENDPOINT_OUTPUT)
			rc = s2253_dev_set_param(dev, S2253_CAPTURE_FORMAT + stream->param_shift, format);
		else
			rc = s2253_dev_set_param(dev, S2253_OUTPUT_FORMAT, format);
		break;
	}
	case S2253_VIDIOC_SET_USER_DATA:
	{
		struct s2253_user_data data;
		char *buf;
		if (copy_from_user(&data, (void*)arg, sizeof data)) {
			rc = -EFAULT;
			goto unlock;
		}
		buf = kmalloc(data.len, GFP_KERNEL);
		if (!buf) {
			rc = -ENOMEM;
			goto unlock;
		}
		if (copy_from_user(buf, data.data, data.len)) {
			rc = -EFAULT;
		} else if (stream->endpoint != S2253_ENDPOINT_OUTPUT) {
			rc = s2253_set_param_data(stream->board, S2253_CAPTURE_USER_DATA + stream->param_shift, 0, buf, data.len);
			if (rc == 0)
				rc = s2253_set_param(stream->board, S2253_CAPTURE_USER_INTERVAL + stream->param_shift, data.interval);
		}
		kfree(buf);
		break;
	}
	case S2253_VIDIOC_GET_FRAME_COUNTER:
	{
		u32 fc;
		rc = s2253_get_param_data(stream->board,
			S2253_FRAME_COUNTER, &fc, 4);
		if (rc == 0)
			rc = le32_to_cpu(fc);
		break;
	}
	case S2253_VIDIOC_SET_FRAME_COUNTER:
	{
		u32 fc = cpu_to_le32(arg);
		rc = s2253_set_param_data(stream->board,
			S2253_FRAME_COUNTER, 0, &fc, 4);
		break;
	}
	case S2253_VIDIOC_SET_EXT_CLKDIV:
	{
		rc = s2253_set_param(stream->board,
			S2253_EXT_CLKDIV, arg);
		break;
	}
	case S2253_VIDIOC_USB_RESET:
	{
		printk(KERN_INFO "%s: VIDIOC_USB_RESET\n", __func__);
		rc = s2253_set_param(stream->board, S2253_OUTPUT_VGA+1, 0);
		break;
	}
	case S2253_VIDIOC_DEV_RESET:
	{
		printk(KERN_INFO "%s: VIDIOC_DEV_RESET\n", __func__);
		rc = s2253_set_param(stream->board, S2253_SYSTEM_REBOOT, 0);
		break;
	}
	case S2253_VIDIOC_VIDREG_WRITE:
	{
		struct { __u32 addr, data; } data;
		if (arg <= 0xffff) {
			rc = s2253_set_param(stream->board, S2253_VIDREG_WRITE, arg);
		} else if (copy_from_user(&data, (void*)arg, 8)) {
			rc = -EFAULT;
		} else {
			rc = s2253_set_param_data(stream->board, S2253_VIDREG_WRITE, 0, &data, 8);
		}
		break;
	}


	/* S2253P-specific ioctls */

	case S2253P_VIDIOC_S_ENCODER_RESET:
	{
		struct s2253p_encoder enc;
		if (copy_from_user(&enc, (void*)arg, sizeof(enc)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_ENCODER_RESET, enc.chan_mask);
		break;
	}
	case S2253P_VIDIOC_S_ENCODER_READ:
	{
		struct s2253p_encoder enc;
		if (copy_from_user(&enc, (void*)arg, sizeof(enc)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_ENCODER_READ, enc.chan_mask);
		break;
	}
	case S2253P_VIDIOC_G_ENCODER_READ:
	{
		struct s2253p_encoder enc = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_ENCODER_READ, &enc.counts[0], 8);
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &enc, sizeof(enc)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_ENCODER_SCALE_FACTOR:
	{
		struct s2253p_encoder enc;
		if (copy_from_user(&enc, (void*)arg, sizeof(enc)))
			return -EFAULT;
		if (enc.chan_mask == 2) {
			rc = s2253_get_param_data(stream->board,
				S2253TB_ENCODER_SCALE_FACTOR,
				&enc.scale_factor[0], 4);
		}
		rc = s2253_set_param_data(stream->board,
			S2253TB_ENCODER_SCALE_FACTOR, 0, &enc.scale_factor[0],
			enc.chan_mask & 2 ? 8 : 4);
		break;
	}
	case S2253P_VIDIOC_G_ENCODER_READ_SCALED:
	{
		struct s2253p_encoder enc = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_ENCODER_READ_SCALED, &enc.scaled[0], 8);
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &enc, sizeof(enc)))
			return -EFAULT;
		break;
	}

	case S2253P_VIDIOC_S_GPS_DATA:
	{
		struct s2253p_data data;
		if (copy_from_user(&data, (void*)arg, sizeof(data)))
			return -EFAULT;
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_DATA, &data.data[0], data.size);
		break;
	}
	case S2253P_VIDIOC_G_GPS_DATA:
	{
		struct s2253p_data data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_DATA, &data.data[0], sizeof(data.data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_GPS_READSTATUS:
	{
		rc = s2253_set_param(stream->board,
			S2253TB_GPS_READSTATUS, 0);
		break;
	}
	case S2253P_VIDIOC_G_GPS_READSTATUS:
	{
		struct s2253p_gps gps = {};
		rc = s2253_get_param(stream->board,
			S2253TB_GPS_READSTATUS);
		if (rc < 0)
			break;
		gps.status = rc;
		if (copy_to_user((void*) arg, &gps, sizeof(gps)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_GPIO_CONFIG:
	{
		struct s2253p_gpio gpio;
		if (copy_from_user(&gpio, (void*)arg, sizeof(gpio)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_GPIO_CONFIG, (gpio.port_mask << 8) | (gpio.dir_mask));
		break;
	}
	case S2253P_VIDIOC_S_GPIO_WRITE:
	{
		struct s2253p_gpio gpio;
		if (copy_from_user(&gpio, (void*)arg, sizeof(gpio)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_GPIO_WRITE, (gpio.port_mask << 8) | (gpio.value_mask));
		break;
	}
	case S2253P_VIDIOC_S_GPIO_READ:
	{
		struct s2253p_gpio gpio;
		if (copy_from_user(&gpio, (void*)arg, sizeof(gpio)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_GPIO_READ, gpio.port_mask);
		break;
	}
	case S2253P_VIDIOC_G_GPIO_READ:
	{
		struct s2253p_gpio gpio = {};
		rc = s2253_get_param(stream->board, S2253TB_GPIO_READ);
		if (rc < 0)
			break;
		gpio.not_ready_mask = (rc >> 8) & 0xff;
		gpio.value_mask = rc & 0xff;
		if (copy_to_user((void*)arg, &gpio, sizeof(gpio)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_ECHO:
	{
		struct s2253p_data data;
		if (copy_from_user(&data, (void*)arg, sizeof(data)))
			return -EFAULT;
		rc = s2253_set_param_data(stream->board,
			S2253TB_ECHO, 0, &data.data[0], data.size);
		break;
	}
	case S2253P_VIDIOC_S_GPS_ENABLE:
	{
		struct s2253p_gps gps;
		if (copy_from_user(&gps, (void*)arg, sizeof(gps)))
			return -EFAULT;
		rc = s2253_set_param(stream->board, S2253TB_GPS_ENABLE,
			gps.enable);
		break;
	}
	case S2253P_VIDIOC_G_GPS_ENABLE:
	{
		struct s2253p_gps gps;
		rc = s2253_get_param(stream->board, S2253TB_GPS_ENABLE);
		if (rc < 0)
			break;
		gps.enable = rc;
		if (copy_from_user((void*)arg, &gps, sizeof(gps)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_VERSION_READ:
	{
		rc = s2253_set_param(stream->board, S2253TB_VERSION_READ,
			0);
		break;
	}
	case S2253P_VIDIOC_G_VERSION_READ:
	{
		struct s2253p_version data;
		rc = s2253_get_param_data(stream->board,
			S2253TB_VERSION_READ, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_COMSTAT_READ:
	{
		rc = s2253_set_param(stream->board, S2253TB_COMSTAT_READ,
			0);
		break;
	}
	case S2253P_VIDIOC_G_COMSTAT_READ:
	{
		struct s2253p_comstat data;
		rc = s2253_get_param_data(stream->board,
			S2253TB_COMSTAT_READ, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_XIO_ENABLE:
	{
		struct s2253p_xio xio;
		if (copy_from_user(&xio, (void*)arg, sizeof(xio)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_XIO_ENABLE, (xio.port_mask << 8) | (xio.enable_mask));
		break;
	}
	case S2253P_VIDIOC_S_XIO_READ:
	{
		rc = s2253_set_param(stream->board,
			S2253TB_XIO_READ, 0);
		break;
	}
	case S2253P_VIDIOC_G_XIO_READ:
	{
		struct s2253p_xio xio = {};
		rc = s2253_get_param(stream->board, S2253TB_XIO_READ);
		if (rc < 0)
			break;
		xio.not_ready_mask = (rc >> 8) & 0xff;
		xio.value_mask = rc & 0xff;
		if (copy_to_user((void*)arg, &xio, sizeof(xio)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_XIO_PAUSE:
	{
		struct s2253p_xio_pause pause;
		if (copy_from_user(&pause, (void*)arg, sizeof(pause)))
			return -EFAULT;
		if (pause.stream > 3 || pause.port > 3 || pause.mode > 4)
			return -EINVAL;
		rc = s2253_set_param(stream->board,
			S2253TB_XIO_PAUSE,
			(pause.stream << 6) | (pause.port << 4) | pause.mode);
		break;
	}
	case S2253P_VIDIOC_G_GPS_LATITUDE:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_LATITUDE, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_LONGITUDE:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_LONGITUDE, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_ALTITUDE:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_ALTITUDE, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_SPEED:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_SPEED, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_COURSE:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_COURSE, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_UTC_TIME:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_UTC_TIME, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_UTC_DATE:
	{
		struct s2253p_gps_item data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_UTC_DATE, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_SATELLITES:
	{
		struct s2253p_gps gps = {};
		rc = s2253_get_param(stream->board,
			S2253TB_GPS_SATELLITES);
		if (rc < 0)
			break;
		gps.satellites = rc;
		if (copy_to_user((void*) arg, &gps, sizeof(gps)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_LOCK:
	{
		struct s2253p_gps gps = {};
		rc = s2253_get_param(stream->board,
			S2253TB_GPS_LOCK);
		if (rc < 0)
			break;
		gps.lock = rc;
		if (copy_to_user((void*) arg, &gps, sizeof(gps)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_GGA:
	{
		struct s2253p_gps_msg data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_GGA, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_GSA:
	{
		struct s2253p_gps_msg data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_GSA, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_GSV:
	{
		struct s2253p_gps_msg data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_GSV, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_G_GPS_RMC:
	{
		struct s2253p_gps_msg data = {};
		rc = s2253_get_param_data(stream->board,
			S2253TB_GPS_RMC, &data, sizeof(data));
		if (rc < 0)
			break;
		if (copy_to_user((void*) arg, &data, sizeof(data)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_ENC_ASYNCEN:
	{
		struct s2253p_encoder enc;
		if (copy_from_user(&enc, (void*)arg, sizeof(enc)))
			return -EFAULT;
		rc = s2253_set_param(stream->board,
			S2253TB_ENC_ASYNCEN, (enc.chan_mask << 8) | (enc.enable_mask));
		break;
	}
	case S2253P_VIDIOC_S_ENCODER_LOAD: {
		int i;
		struct s2253p_encoder enc;
		if (copy_from_user(&enc, (void*)arg, sizeof(enc)))
			return -EFAULT;
		for (i = 0; i < 2; i++) {
			__u8 buf[5];
			if (((enc.chan_mask >> i) & 1) == 0)
				continue;
			buf[0] = i;
			buf[1] = enc.counts[i] & 0xff;
			buf[2] = (enc.counts[i] >> 8) & 0xff;
			buf[3] = (enc.counts[i] >> 16) & 0xff;
			buf[4] = (enc.counts[i] >> 24) & 0xff;
			rc = s2253_set_param_data(stream->board,
				S2253TB_ENCODER_LOAD, 0, &buf[0], sizeof(buf));
			if (rc < 0)
				break;
		}
		break;
	}
	case S2253P_VIDIOC_G_ONLINE:
	{
		struct s2253p_enable enable = {};
		rc = s2253_get_param(stream->board, S2253TB_ONLINE);
		if (rc < 0)
			break;
		enable.enable = rc;
		if (copy_to_user((void*)arg, &enable, sizeof(enable)))
			return -EFAULT;
		break;
	}
	case S2253P_VIDIOC_S_SUSPEND:
	{
		struct s2253p_enable enable = {};
		if (copy_from_user(&enable, (void*)arg, sizeof(enable)))
			return -EFAULT;
		rc = s2253_set_param(stream->board, S2253TB_SUSPEND, enable.enable);
		break;
	}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
	default:
#if LINUX_VERSION_CODE >= KERNEL_VERSION_NOINODE || V4L_DVB_TREE
		rc = video_ioctl2(file, cmd, arg);
#else
		rc = video_ioctl2(inode, file, cmd, arg);
		break;
#endif
#else
#define IOCTL_R(func, typename) { \
	typename x; \
	rc = func(file, file->private_data, &x); \
	if (rc == 0 && copy_to_user((void *)arg, &x, sizeof(x))) return -EFAULT; \
	return rc; }
#define IOCTL_RW(func, typename) { \
	typename x; \
	if (copy_from_user(&x, (void *)arg, sizeof(x))) return -EFAULT; \
	rc = func(file, file->private_data, &x); \
	if (rc == 0 && copy_to_user((void *)arg, &x, sizeof(x))) return -EFAULT; \
	return rc; }
#define IOCTL_W(func, typename) { \
	typename x; \
	if (copy_from_user(&x, (void *)arg, sizeof(x))) return -EFAULT; \
	rc = func(file, file->private_data, &x); \
	return rc; }
#define IOCTL_WV(func, typename) { \
	typename x; \
	if (copy_from_user(&x, (void *)arg, sizeof(x))) return -EFAULT; \
	return func(file, file->private_data, x); \
	}

	/* specific for 2.6.16 */
	case VIDIOC_QUERYCAP:
		IOCTL_R(vidioc_querycap, struct v4l2_capability);
	case VIDIOC_ENUM_FMT:
		IOCTL_RW(vidioc_enum_fmt, struct v4l2_fmtdesc);
	case VIDIOC_G_FMT:
		IOCTL_RW(vidioc_g_fmt, struct v4l2_format);
	case VIDIOC_S_FMT:
		IOCTL_RW(vidioc_s_fmt, struct v4l2_format);
	case VIDIOC_TRY_FMT:
		IOCTL_RW(vidioc_try_fmt, struct v4l2_format);

	case VIDIOC_CROPCAP:
		IOCTL_RW(vidioc_cropcap, struct v4l2_cropcap);
	case VIDIOC_G_CROP:
		IOCTL_RW(vidioc_g_crop, struct v4l2_crop);
	case VIDIOC_S_CROP:
		IOCTL_W(vidioc_s_crop, struct v4l2_crop);

	case VIDIOC_STREAMON:
		IOCTL_WV(vidioc_streamon, int);
	case VIDIOC_STREAMOFF:
		IOCTL_WV(vidioc_streamoff, int);

	case VIDIOC_G_STD:
		IOCTL_R(vidioc_g_std, v4l2_std_id);
	case VIDIOC_S_STD:
		IOCTL_WV(vidioc_s_std, v4l2_std_id);

	case VIDIOC_ENUMINPUT:
		IOCTL_RW(vidioc_enum_input, struct v4l2_input);
	case VIDIOC_G_INPUT:
		IOCTL_R(vidioc_g_input, int);
	case VIDIOC_S_INPUT:
		IOCTL_WV(vidioc_s_input, int);

	case VIDIOC_ENUMOUTPUT:
		IOCTL_RW(vidioc_enum_output, struct v4l2_output);
	case VIDIOC_G_OUTPUT:
		IOCTL_R(vidioc_g_output, int);
	case VIDIOC_S_OUTPUT:
		IOCTL_WV(vidioc_s_output, int);

	case VIDIOC_QUERYMENU:
		IOCTL_RW(vidioc_querymenu, struct v4l2_querymenu);
	case VIDIOC_QUERYCTRL:
		IOCTL_RW(vidioc_queryctrl, struct v4l2_queryctrl);
	case VIDIOC_G_CTRL:
	{
		struct v4l2_control ctl;
		struct v4l2_ext_control ctrl = {};
		struct v4l2_ext_controls ctrls = {};

		if (copy_from_user(&ctl, (void *)arg, sizeof(ctl))) return -EFAULT;

		ctrl.id = ctl.id;

		ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(ctrl.id);
		ctrls.count = 1;
		ctrls.controls = &ctrl;

		rc = vidioc_g_ext_ctrls(file, file->private_data, &ctrls);
		if (rc == 0) {
			ctl.value = ctrl.value;
			if (copy_from_user((void *)arg, &ctl, sizeof(ctl))) return -EFAULT;
		}
		break;
	}
	case VIDIOC_S_CTRL:
	{
		struct v4l2_control ctl;
		struct v4l2_ext_control ctrl = {};
		struct v4l2_ext_controls ctrls = {};

		if (copy_from_user(&ctl, (void *)arg, sizeof(ctl))) return -EFAULT;

		ctrl.id = ctl.id;
		ctrl.value = ctl.value;

		ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(ctrl.id);
		ctrls.count = 1;
		ctrls.controls = &ctrl;

		rc = vidioc_s_ext_ctrls(file, file->private_data, &ctrls);
		if (rc == 0) {
			ctl.value = ctrl.value;
			if (copy_from_user((void *)arg, &ctl, sizeof(ctl))) return -EFAULT;
		}
		break;
	}
	case VIDIOC_G_JPEGCOMP:
		IOCTL_R(vidioc_g_jpegcomp, struct v4l2_jpegcompression);
	case VIDIOC_S_JPEGCOMP:
		IOCTL_W(vidioc_s_jpegcomp, struct v4l2_jpegcompression);
	case VIDIOC_G_PARM:
		IOCTL_RW(vidioc_g_parm, struct v4l2_streamparm);
	case VIDIOC_S_PARM:
		IOCTL_RW(vidioc_s_parm, struct v4l2_streamparm);

	case VIDIOC_ENUMAUDIO:
		IOCTL_RW(vidioc_enumaudio, struct v4l2_audio);
	case VIDIOC_G_AUDIO:
		IOCTL_R(vidioc_g_audio, struct v4l2_audio);
	case VIDIOC_S_AUDIO:
		IOCTL_W(vidioc_s_audio, struct v4l2_audio);

	case VIDIOC_ENUMAUDOUT:
		IOCTL_RW(vidioc_enumaudout, struct v4l2_audioout);
	case VIDIOC_G_AUDOUT:
		IOCTL_R(vidioc_g_audout, struct v4l2_audioout);
	case VIDIOC_S_AUDOUT:
		IOCTL_W(vidioc_s_audout, struct v4l2_audioout);

	case VIDIOC_REQBUFS:
		IOCTL_RW(vidioc_reqbufs, struct v4l2_requestbuffers);
	case VIDIOC_QUERYBUF:
		IOCTL_RW(vidioc_querybuf, struct v4l2_buffer);
	case VIDIOC_QBUF:
		IOCTL_RW(vidioc_qbuf, struct v4l2_buffer);
	case VIDIOC_DQBUF:
		IOCTL_RW(vidioc_dqbuf, struct v4l2_buffer);

	default:
#endif
		break;
	}
unlock:
	return rc;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION_NOINODE || V4L_DVB_TREE
static const struct v4l2_file_operations s2253_fops_v4l = {
#else
static 
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 16)
const 
#endif
struct file_operations s2253_fops_v4l = {
#endif
	.owner		= THIS_MODULE,
#if USE_VIDEOBUF2
	.open		= v4l2_fh_open,
	.release	= s2253_vb2_fop_release,
	.read		= vb2_fop_read,
	.poll		= vb2_fop_poll,
	.mmap		= vb2_fop_mmap,
	.write		= vb2_fop_write,
#else
	.open		= s2253_open,
	.release	= s2253_release,
	.poll		= s2253_poll,
	.mmap		= s2253_mmap,
	.read		= s2253_read,
	.write		= s2253_write,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION_NOINODE || V4L_DVB_TREE
	.unlocked_ioctl	= s2253_ioctl,
#else
	.ioctl		= s2253_ioctl,
#endif
};


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) || V4L_DVB_TREE
static const struct v4l2_ioctl_ops s2253_ioctl_ops = {
	.vidioc_querycap	= vidioc_querycap,
	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt,
	.vidioc_g_fmt_vid_cap	= vidioc_g_fmt,
	.vidioc_try_fmt_vid_cap	= vidioc_try_fmt,
	.vidioc_s_fmt_vid_cap	= vidioc_s_fmt,
	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
	.vidioc_g_fmt_vid_out	= vidioc_g_fmt,
	.vidioc_try_fmt_vid_out	= vidioc_try_fmt,
	.vidioc_s_fmt_vid_out	= vidioc_s_fmt,
#if USE_VIDEOBUF2 && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
	.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,
#else
	.vidioc_reqbufs		= vidioc_reqbufs,
	.vidioc_querybuf	= vidioc_querybuf,
	.vidioc_qbuf		= vidioc_qbuf,
	.vidioc_dqbuf		= vidioc_dqbuf,
	.vidioc_streamon	= vidioc_streamon,
	.vidioc_streamoff	= vidioc_streamoff,
#endif
	.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,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
	.vidioc_enum_output	= vidioc_enum_output,
#endif
	.vidioc_g_output	= vidioc_g_output,
	.vidioc_s_output	= vidioc_s_output,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)
	.vidioc_query_ext_ctrl = vidioc_query_ext_ctrl,
#else
	.vidioc_queryctrl	= vidioc_queryctrl,
#endif
	.vidioc_querymenu	= vidioc_querymenu,
	.vidioc_g_ext_ctrls	= vidioc_g_ext_ctrls,
	.vidioc_s_ext_ctrls	= vidioc_s_ext_ctrls,
	.vidioc_try_ext_ctrls	= vidioc_try_ext_ctrls,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
	.vidiocgmbuf = vidioc_cgmbuf,
#endif
	.vidioc_g_jpegcomp	= vidioc_g_jpegcomp,
	.vidioc_s_jpegcomp	= vidioc_s_jpegcomp,
	.vidioc_g_parm		= vidioc_g_parm,
	.vidioc_s_parm		= vidioc_s_parm,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29) || V4L_DVB_TREE
	.vidioc_enum_framesizes = vidioc_enum_framesizes,
	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
#endif
	.vidioc_enumaudio	= vidioc_enumaudio,
	.vidioc_g_audio		= vidioc_g_audio,
	.vidioc_s_audio		= vidioc_s_audio,
	.vidioc_enumaudout	= vidioc_enumaudout,
	.vidioc_g_audout	= vidioc_g_audout,
	.vidioc_s_audout	= vidioc_s_audout,
#ifdef VIDIOC_ENCODER_CMD
	.vidioc_encoder_cmd	= vidioc_encoder_cmd,
#endif
#ifdef VIDIOC_DECODER_CMD
	.vidioc_decoder_cmd	= vidioc_decoder_cmd,
#endif
#ifdef VIDIOC_SUBSCRIBE_EVENT
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
	.vidioc_subscribe_event	= v4l2_ctrl_subscribe_event,
	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
#else
	.vidioc_subscribe_event	= vidioc_subscribe_event,
	.vidioc_unsubscribe_event = vidioc_unsubscribe_event,
#endif
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0)
	.vidioc_cropcap		= vidioc_cropcap,
	.vidioc_s_crop		= vidioc_s_crop,
	.vidioc_g_crop		= vidioc_g_crop,
#else
	.vidioc_g_pixelaspect	= vidioc_g_pixelaspect,
	.vidioc_g_selection	= vidioc_g_selection,
	.vidioc_s_selection	= vidioc_s_selection,
#endif
};
#endif


static void s2253_video_device_release(struct video_device *vdev)
{
	struct s2253_dev *dev = video_get_drvdata(vdev);
	dprintk(1, "%s\n", __func__);
	s2253_board_put(dev->stream.board, __func__);
	return;
}

static struct video_device s2253_template = {
	.name = "s2253",
	.fops = &s2253_fops_v4l,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 27) || V4L_DVB_TREE
	.ioctl_ops = &s2253_ioctl_ops,
#endif
	.release = s2253_video_device_release,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 16)
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18)
	.tvnorms = V4L2_STD_PAL | V4L2_STD_NTSC,
#else
	.tvnorms = s2253_tvnorms,
	.tvnormsize = 2,
#endif
#ifdef HAVE_CURRENT_NORM
	.current_norm = V4L2_STD_NTSC_M,
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) && !V4L_DVB_TREE
	.vidioc_querycap      = vidioc_querycap,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26)
	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt,
	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt,
	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt,
	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt,
	.vidioc_enum_fmt_vid_out  = vidioc_enum_fmt,
	.vidioc_g_fmt_vid_out     = vidioc_g_fmt,
	.vidioc_try_fmt_vid_out   = vidioc_try_fmt,
	.vidioc_s_fmt_vid_out     = vidioc_s_fmt,
#else
	.vidioc_enum_fmt_cap  = vidioc_enum_fmt,
	.vidioc_g_fmt_cap     = vidioc_g_fmt,
	.vidioc_try_fmt_cap   = vidioc_try_fmt,
	.vidioc_s_fmt_cap     = vidioc_s_fmt,
	.vidioc_enum_fmt_video_output  = vidioc_enum_fmt,
	.vidioc_g_fmt_video_output     = vidioc_g_fmt,
	.vidioc_try_fmt_video_output   = vidioc_try_fmt,
	.vidioc_s_fmt_video_output     = vidioc_s_fmt,
#endif
	.vidioc_reqbufs       = vidioc_reqbufs,
	.vidioc_querybuf      = vidioc_querybuf,
	.vidioc_qbuf          = vidioc_qbuf,
	.vidioc_dqbuf         = vidioc_dqbuf,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
	.vidioc_g_std         = vidioc_g_std,
#endif
	.vidioc_s_std         = vidioc_s_std,
	.vidioc_enum_input    = vidioc_enum_input,
	.vidioc_g_input       = vidioc_g_input,
	.vidioc_s_input       = vidioc_s_input,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 27)
	.vidioc_enum_output   = vidioc_enum_output,
#endif
	.vidioc_g_output      = vidioc_g_output,
	.vidioc_s_output      = vidioc_s_output,
	.vidioc_queryctrl     = vidioc_queryctrl,
	.vidioc_querymenu     = vidioc_querymenu,
	.vidioc_g_ext_ctrls	= vidioc_g_ext_ctrls,
	.vidioc_s_ext_ctrls	= vidioc_s_ext_ctrls,
	.vidioc_try_ext_ctrls	= vidioc_try_ext_ctrls,
#if defined(CONFIG_VIDEO_V4L1_COMPAT) && \
    LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) && \
    LINUX_VERSION_CODE != KERNEL_VERSION(2, 6, 23)
	.vidiocgmbuf = vidioc_cgmbuf,
#endif
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,
	.vidioc_g_jpegcomp    = vidioc_g_jpegcomp,
	.vidioc_s_jpegcomp    = vidioc_s_jpegcomp,
	.vidioc_g_parm        = vidioc_g_parm,
	.vidioc_s_parm        = vidioc_s_parm,
	.vidioc_enumaudio	= vidioc_enumaudio,
	.vidioc_g_audio		= vidioc_g_audio,
	.vidioc_s_audio		= vidioc_s_audio,
	.vidioc_enumaudout	= vidioc_enumaudout,
	.vidioc_g_audout	= vidioc_g_audout,
	.vidioc_s_audout	= vidioc_s_audout,
	.vidioc_cropcap		= vidioc_cropcap,
	.vidioc_s_crop		= vidioc_s_crop,
	.vidioc_g_crop		= vidioc_g_crop,
#endif
#endif
};


static int s2253_video_init(struct s2253_dev *dev)
{
	int rc = 0;
	struct usb_interface *interface = dev->stream.interface;
#if USE_VIDEOBUF2
	struct vb2_queue *q = &dev->vb_vidq;
#else

	dev->in_use_by = NULL;
#endif
	mutex_init(&dev->lock);
	INIT_LIST_HEAD(&dev->vidq.active);
	init_waitqueue_head(&dev->vidq.wait);
	dev->vidq.dev = dev;
	spin_lock_init(&dev->slock);

	/* register video device */
	dev->vdev = s2253_template;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18) && LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0)
	dev->vdev.debug = debug;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
	dev->vdev.dev_debug = 0; //debug;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	dev->vdev.vfl_dir = VFL_DIR_RX;
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
	dev->vdev.v4l2_dev = &dev->v4l2_dev;
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
	dev->vdev.parent = &interface->dev;
#else
	dev->vdev.dev = &interface->dev;
#endif
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
	rc = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
	if (rc)
		goto errV4L2;
	dprintk(2, "%s\n", __func__);
#else
	usb_set_intfdata(interface, dev);
#endif

	dev->stream.video_thread = NULL;
	dev->stream.dev = dev;
	dev->stream.width = 640;
	dev->stream.height = 480;
	dev->stream.bytesperline = dev->stream.width;
	dev->stream.framesize = dev->stream.bytesperline * dev->stream.height;
	dev->stream.jc.quality = S2253_DEF_JPEG_Q;
	dev->stream.fdec = FDEC_1; /* no frame decimation */
	dev->stream.contrast = s2253_dev_get_param(dev, S2253_CONTRAST);
	dev->stream.bright = s2253_dev_get_param(dev, S2253_BRIGHTNESS);
	dev->stream.hue = (__s8) s2253_dev_get_param(dev, S2253_HUE);
	dev->stream.saturation = s2253_dev_get_param(dev, S2253_SATURATION);
	dev->stream.mpeg_bitrate = S2253_DEF_MPEG_BITRATE;
	dev->stream.video_format = S2253_FORMAT_H264;
	dev->stream.audio_format = S2253_FORMAT_AAC;
	dev->stream.volume_param = S2253_RECORD_VOLUME;
	dev->stream.mpeg_bitrate_mode = S2253_BITRATE_MODE_VBR1;
	dev->stream.field = V4L2_FIELD_INTERLACED;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
	dev->vdev.device_caps = V4L2_CAP_AUDIO | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
		(dev->stream.endpoint == S2253_ENDPOINT_OUTPUT ?
			V4L2_CAP_VIDEO_OUTPUT :
			V4L2_CAP_VIDEO_CAPTURE);
#endif

	switch (dev->stream.endpoint) {
	case S2253_ENDPOINT_STREAM_A:
		dev->stream.param_shift = 0;
		dev->osd.osdChan = cpu_to_le32(0); /* select channel */
		dev->buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		//dev->stream.width = s2253_dev_get_param(dev, S2253_CAPTURE_WIDTH);
		//dev->stream.height = s2253_dev_get_param(dev, S2253_CAPTURE_HEIGHT);
		dev->stream.mpeg_bitrate = s2253_dev_get_param(dev, S2253_CAPTURE_BITRATE);
		break;
	case S2253_ENDPOINT_STREAM_B:
		dev->stream.param_shift = S2253_PREVIEW_STATE - S2253_CAPTURE_STATE;
		dev->osd.osdChan = cpu_to_le32(1);
		dev->buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		//dev->stream.width = s2253_dev_get_param(dev, S2253_PREVIEW_WIDTH);
		//dev->stream.height = s2253_dev_get_param(dev, S2253_PREVIEW_HEIGHT);
		dev->stream.mpeg_bitrate = s2253_dev_get_param(dev, S2253_PREVIEW_BITRATE);
		break;
	case S2253_ENDPOINT_OUTPUT:
		dev->stream.param_shift = S2253_OUTPUT_STATE - S2253_CAPTURE_STATE;
		dev->osd.osdChan = cpu_to_le32(2);
		dev->buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
		dev->stream.volume_param = S2253_PLAYBACK_VOLUME;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
		dev->vdev.vfl_dir = VFL_DIR_TX;
#endif
		break;
	}
	stream_get_audio_volume_balance(&dev->stream);

	dev->osd.transparent = cpu_to_le32(1);
	dev->osd.positionTop = cpu_to_le32(1);
	dev->osd.xOffset = cpu_to_le32(10);
	dev->osd.yOffset = cpu_to_le32(10);
	dev->stream.fmt = &formats[0];

#if USE_VIDEOBUF2
	q->type = dev->buf_type;
	q->io_modes = VB2_MMAP | VB2_READ | VB2_WRITE | VB2_USERPTR;
	q->drv_priv = dev;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
	dev->vdev.queue = q;
	dev->vdev.lock = &dev->lock;
	q->lock = &dev->lock;
#endif
	q->buf_struct_size = sizeof(struct s2253_buffer);
	q->mem_ops = &vb2_vmalloc_memops;
	q->ops = &s2253_vb2_ops;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
	q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
#endif
	rc = vb2_queue_init(q);
	if (rc) {
		v4l2_err(&dev->v4l2_dev, "Failed to init videobuf2 queue\n");
		goto errVDEV;
	}
#endif
	video_set_drvdata(&dev->vdev, dev);
	rc = video_register_device(&dev->vdev,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
				    VFL_TYPE_VIDEO,  /* renamed in 5.7 */
#else
				    VFL_TYPE_GRABBER,
#endif
				    video_nr);

#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 28) || V4L_DVB_TREE
	v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
		  video_device_node_name(&dev->vdev));
#else
	dprintk(0, "V4L2 device registered as %s\n",
		  video_device_node_name(&dev->vdev));
#endif
	snprintf(dev->osd.line, sizeof(dev->osd.line) - 1, "%s ^d ^t",
		 video_device_node_name(&dev->vdev));

	if (rc)
		goto errVDEV;
	s2253_board_get(dev->stream.board, __func__);
	return 0;
errVDEV:
#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
	v4l2_device_unregister(&dev->v4l2_dev);

#else
	usb_set_intfdata(interface, NULL);
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
errV4L2:
#endif
	return rc;
}

static void s2253_video_fini(struct s2253_dev *dev)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) || V4L_DVB_TREE
	v4l2_device_disconnect(&dev->v4l2_dev);
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION_V4L2DEV || V4L_DVB_TREE
	v4l2_device_unregister(&dev->v4l2_dev);
#else
	usb_set_intfdata(dev->stream.interface, NULL);
#endif
	dprintk(1, "%s\n", __func__);
	/* see comments in UVC driver about increasing nstreams here */
	s2253_board_get(dev->stream.board, __func__);
	/* driver bringup debug message only */
	dprintk(4, "before unregister\n");
	video_unregister_device(&dev->vdev);
	/* driver bringup debug message only */
	dprintk(4, "after unregister\n");

	/*
	 * USB disconnect.  make sure to set interface and  to NULL
	 * s2253_release often called after release
	 */

	s2253_uninit_stream(&dev->stream, 1, 0);

	s2253_board_put(dev->stream.board, __func__);
}



#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
/* note: requires 2.6.34 or later */
/* ALSA sound card interface */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28)
#define PCM_RUNTIME_CHECK(sub) (!(sub) || !(sub)->runtime)
#endif

#if (!defined(snd_pcm_lib_alloc_vmalloc_buffer)		\
	&& LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)) ||	\
	(LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0))
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime)
#endif

static int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
                                      size_t size, gfp_t gfp_flags)
{
        struct snd_pcm_runtime *runtime;

        if (PCM_RUNTIME_CHECK(substream))
                return -EINVAL;
        runtime = substream->runtime;
        if (runtime->dma_area) {
                if (runtime->dma_bytes >= size)
                        return 0; /* already large enough */
                vfree(runtime->dma_area);
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
        runtime->dma_area =  vzalloc(size);
#else
        runtime->dma_area = __vmalloc(size, gfp_flags, PAGE_KERNEL);
#endif
        if (!runtime->dma_area)
                return -ENOMEM;
        runtime->dma_bytes = size;
        return 1;
}

/**
 * snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer
 * @substream: the substream with a buffer allocated by
 *      snd_pcm_lib_alloc_vmalloc_buffer()
 */
static int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime;

        if (PCM_RUNTIME_CHECK(substream))
                return -EINVAL;
        runtime = substream->runtime;
        vfree(runtime->dma_area);
        runtime->dma_area = NULL;
        return 0;
}

/**
 * snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct
 * @substream: the substream with a buffer allocated by
 *      snd_pcm_lib_alloc_vmalloc_buffer()
 * @offset: offset in the buffer
 *
 * This function is to be used as the page callback in the PCM ops.
 */
static struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream,
                                          unsigned long offset)
{
        return vmalloc_to_page(substream->runtime->dma_area + offset);
}

#define snd_pcm_lib_alloc_vmalloc_buffer(subs, size) \
        _snd_pcm_lib_alloc_vmalloc_buffer \
                        (subs, size, GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO)
#define snd_pcm_lib_alloc_vmalloc_32_buffer(subs, size) \
        _snd_pcm_lib_alloc_vmalloc_buffer \
                        (subs, size, GFP_KERNEL | GFP_DMA32 | __GFP_ZERO)
#endif


static struct snd_pcm_hardware snd_2253_capture_hw = {
	.info = (SNDRV_PCM_INFO_MMAP |
		 SNDRV_PCM_INFO_INTERLEAVED |
		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
		 SNDRV_PCM_INFO_MMAP_VALID),
	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
				  SNDRV_PCM_FMTBIT_A_LAW |
				  SNDRV_PCM_FMTBIT_MU_LAW,
	.rates			= SNDRV_PCM_RATE_8000 |
				  SNDRV_PCM_RATE_48000,
	.rate_min		= 8000,
	.rate_max		= 48000,
	.channels_min		= 1,
	.channels_max		= 2,
	.buffer_bytes_max	= 1024 * 1024,
	.period_bytes_min	= 256,
	.period_bytes_max	= 512 * 1024,
	.periods_min		= 2,	// buffer_bytes_max / period_bytes_max
	.periods_max		= 4096,  // buffer_bytes_max / period_bytes_min
};


static int s2253_snd_open(struct snd_pcm_substream *substream)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);
	unsigned long flags;

	if (!snd) {
		dprintk(1, "%s: snd is NULL\n", __func__);
		return -ENODEV;
	}

	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		if (unlikely(snd->capture_substream != NULL))
			return -EBUSY;
		spin_lock_irqsave(&snd->lock, flags);
		snd->capture_substream = substream;
	} else {
		if (unlikely(snd->playback_substream != NULL))
			return -EBUSY;
		spin_lock_irqsave(&snd->lock, flags);
		snd->playback_substream = substream;
	}
	substream->private_data = snd;
	substream->runtime->hw = snd_2253_capture_hw;
	spin_unlock_irqrestore(&snd->lock, flags);
	return 0;
}

static int s2253_snd_close(struct snd_pcm_substream *substream)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);

	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
		snd->capture_substream = NULL;
	else
		snd->playback_substream = NULL;
	substream->private_data = NULL;
	return 0;
}

static int s2253_snd_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *hw_params)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);
	unsigned long flags;

	spin_lock_irqsave(&snd->lock, flags);
	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		snd->hw_ptr = 0;
		snd->w_idx = 0;
		snd->avail = 0;
	} else {
		snd->pb_ptr = 0; // frames
		snd->pb_idx = 0; // bytes
		snd->pb_avail = 0; // frames
	}
	spin_unlock_irqrestore(&snd->lock, flags);
	return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params));
}

static int s2253_snd_hw_free(struct snd_pcm_substream *substream)
{
	return snd_pcm_lib_free_vmalloc_buffer(substream);
}

static int s2253_snd_pcm_prepare(struct snd_pcm_substream *substream)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);
	int fmt;
	int rc = 0;

	dprintk(7, "%s rate=%d format=%s channels=%d buffersize=%lu periodsize=%lu %s\n", __func__,
		substream->runtime->rate,
		substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ? "S16_LE" :
		substream->runtime->format == SNDRV_PCM_FORMAT_A_LAW ? "A_LAW" :
		substream->runtime->format == SNDRV_PCM_FORMAT_MU_LAW ? "MU_LAW" : "?",
		substream->runtime->channels,
		substream->runtime->buffer_size,
		substream->runtime->period_size,
		substream->stream == SNDRV_PCM_STREAM_CAPTURE ? "capture" : "playback");

	switch (substream->runtime->format) {
	case SNDRV_PCM_FORMAT_S16_LE:
		fmt = S2253_FORMAT_PCM;
		break;
	case SNDRV_PCM_FORMAT_A_LAW:
		fmt = S2253_FORMAT_G711_ALAW;
		break;
	case SNDRV_PCM_FORMAT_MU_LAW:
		fmt = S2253_FORMAT_G711_ULAW;
		break;
	default:
		return -EINVAL;
	}
	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		rc = s2253_set_param(snd->board, S2253_RECORD_FORMAT, fmt);
		if (rc == 0)
			rc = s2253_set_param(snd->board, S2253_RECORD_SAMPLERATE,
				substream->runtime->rate);
		if (rc == 0)
			rc = s2253_set_param(snd->board, S2253_RECORD_CHANNELS,
				substream->runtime->channels);

	} else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		rc = s2253_set_param(snd->board, S2253_PLAYBACK_FORMAT, fmt);
		if (rc == 0)
			rc = s2253_set_param(snd->board, S2253_PLAYBACK_SAMPLERATE,
				substream->runtime->rate);
		if (rc == 0)
			rc = s2253_set_param(snd->board, S2253_PLAYBACK_CHANNELS,
				substream->runtime->channels);
	}
	return rc;
}

static void s2253_snd_capture_complete(struct urb *urb
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16) || \
    LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 18)
	, struct pt_regs *pt
#endif
)
{
	struct s2253_snd *snd = urb->context;
	struct snd_pcm_runtime *runtime;
	struct s2253_payload_header *header = urb->transfer_buffer;
	unsigned char *buf = urb->transfer_buffer + header->length;
	int length = urb->actual_length - header->length;
	int frames;
	
	// some systems will only transfer 64 bytes per urb
	if (urb->actual_length <= 64) {
		if (snd->urb_ptr != 0) {
			length = urb->actual_length;
			buf = urb->transfer_buffer;
		}
		if (urb->actual_length == 64) {
			snd->urb_ptr += urb->actual_length;
		} else {
			snd->urb_ptr = 0;
		}
	}
#if 0
	le64_to_cpus(&header->seq);
	dprintk(0, "%s status=%d seq=%lld length=%d avail=%d w_idx=%d PTS=%02x%02x%02x%02x SCR=%02x%02x%02x%02x %zu\n",
		__func__, urb->status, header->seq, length, snd->avail, snd->w_idx,
		header->PTS[3], header->PTS[2], header->PTS[1], header->PTS[0],
		header->SCR[3], header->SCR[2], header->SCR[1], header->SCR[0],
		sizeof(struct s2253_payload_header));
#endif
	if (urb->status == -ENODEV || urb->status == -EPROTO || !snd->capturing)
		return;
	if (urb->status != 0 || snd->capture_substream == NULL || length <= 0)
		goto resubmit;
	snd->captured_bytes += length;
	runtime = snd->capture_substream->runtime;
	frames = bytes_to_frames(runtime, length);
	spin_lock(&snd->lock);
	snd->hw_ptr += frames;
	if (snd->hw_ptr >= runtime->buffer_size)
		snd->hw_ptr -= runtime->buffer_size;
	snd->avail += frames;
	spin_unlock(&snd->lock);

	if (snd->w_idx + length > runtime->dma_bytes) {
		int cpy = runtime->dma_bytes - snd->w_idx;
		memcpy(runtime->dma_area + snd->w_idx, buf, cpy);
		length -= cpy;
		buf += cpy;
		snd->w_idx = 0;
	}
	memcpy(runtime->dma_area + snd->w_idx, buf, length);
	snd->w_idx += length;
	spin_lock(&snd->lock);
	if (snd->avail < runtime->period_size) {
		spin_unlock(&snd->lock);
		goto resubmit;
	}
	while (snd->avail >= runtime->period_size)
		snd->avail -= runtime->period_size;
	spin_unlock(&snd->lock);
	if (snd->capturing)
		snd_pcm_period_elapsed(snd->capture_substream);
resubmit:
	usb_submit_urb(urb, GFP_ATOMIC);
}

static snd_pcm_uframes_t s2253_snd_pcm_pointer_capture(struct snd_pcm_substream
							*substream)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);
	dprintk(100, "%s\n", __func__);
	return snd->hw_ptr;
}


static void s2253_fill_playback_urb(struct urb *urb)
{
	struct s2253_snd *snd = urb->context;
	struct snd_pcm_runtime *runtime;
	struct s2253_payload_header *header = urb->transfer_buffer;
	int length, frames;
	int rc;
	u8 *dest;
	static int fid = 0;

	spin_lock(&snd->lock);
	if (!header || !snd) {
		dprintk(1, "%s: transfer_buffer or snd is NULL\n", __func__);
		spin_unlock(&snd->lock);
		return;
	}

	runtime = snd->playback_substream->runtime;
	if (!runtime) {
		dprintk(1, "%s: runtime is NULL\n", __func__);
		spin_unlock(&snd->lock);
		return;
	}

	dest = urb->transfer_buffer + sizeof(struct s2253_payload_header);
	memset(header, 0, sizeof(struct s2253_payload_header));
	header->length = sizeof(struct s2253_payload_header);
	header->fid = fid;
	header->eof = 1; // end of frame
	header->pts = 1; // presentation time stamp
	header->scr = 1; // system clock ref
	header->res = 1; // audio packet
	header->sti = 0; // not still image
	header->err = 0; // not error
	header->eoh = 1; // end of header
	fid ^= 1;

	// audio buffers are 4096 bytes in the firmware:
	// 48kHz: 1024 samples per buffer
	//  8kHz: 170 samples per buffer (x6 resampled)
	frames = (runtime->rate == 48000) ? 1024 : 170;
	length = frames_to_bytes(runtime, frames);
	urb->transfer_buffer_length = header->length + length;

	dprintk(7, "%s: length=%d pb_idx=%d dma_bytes=%zd\n", __func__,
			length, snd->pb_idx, runtime->dma_bytes);


	if (snd->pb_idx + length > runtime->dma_bytes) {
		int cpy = runtime->dma_bytes - snd->pb_idx;
		memcpy(dest, runtime->dma_area + snd->pb_idx, cpy);
		length -= cpy;
		dest += cpy;
		snd->pb_idx = 0;
	}
	memcpy(dest, runtime->dma_area + snd->pb_idx, length);
	snd->pb_idx += length;

	rc = usb_submit_urb(urb, GFP_ATOMIC);
	if (rc < 0) {
		dprintk(1, "%s: usb_submit_urb rc=%d\n", __func__, rc);
	}

	snd->pb_ptr += frames;
	if (snd->pb_ptr >= runtime->buffer_size) {
		snd->pb_ptr %= runtime->buffer_size;
		spin_unlock(&snd->lock);
		if (snd->playingback)
			snd_pcm_period_elapsed(snd->playback_substream);
		return;
	}
	spin_unlock(&snd->lock);
}



static void s2253_snd_playback_complete(struct urb *urb
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16) || \
    LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 18)
	, struct pt_regs *pt
#endif
)
{
	if (!urb)
		return;
	if (urb->status == 0)
		s2253_fill_playback_urb(urb);
}

static void s2253_snd_pcm_trigger_complete(struct urb *urb
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16) || \
    LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 18)
	, struct pt_regs *pt
#endif
)
{
	if (urb->status && urb->status != -ECONNRESET)
		dprintk(0, "%s status=%d\n", __func__, urb->status);
}

static int s2253_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);
	int state;
	int rc = 0;

	dprintk(7, "%s %s %s board=%p\n", __func__, cmd==SNDRV_PCM_TRIGGER_START ? "start":"stop", substream->stream==SNDRV_PCM_STREAM_CAPTURE ? "capture" : "playback", snd->board);
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		/* Just set a flag to indicate we should signal ALSA when
		 * sound comes in */
		state = 1;
		snd->ctrl_req.wValue = cpu_to_le16(S2253_STATE_STREAMING);
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		state = 0;
		snd->ctrl_req.wValue = cpu_to_le16(S2253_STATE_IDLE);
		break;
	default:
		return -EINVAL;
	}
	switch(substream->stream) {
	case SNDRV_PCM_STREAM_CAPTURE:
		snd->ctrl_req.wIndex = cpu_to_le16(S2253_RECORD_STATE);
		snd->capturing = state;
		if (state) {
			if (snd->capture_urb.status == -EINPROGRESS)
				usb_unlink_urb(&snd->capture_urb);
			snd->captured_bytes = 0;
			rc = usb_submit_urb(&snd->capture_urb, GFP_ATOMIC);
		} else {
			dprintk(7, "%s: captured_bytes=%d\n", __func__, snd->captured_bytes);
			usb_unlink_urb(&snd->capture_urb);
		}
		break;
	case SNDRV_PCM_STREAM_PLAYBACK:
		snd->ctrl_req.wIndex = cpu_to_le16(S2253_PLAYBACK_STATE);
		snd->playingback = state;
		if (state == 1) {
			snd->pb_ptr = 0;
			snd->pb_idx = 0;
			s2253_fill_playback_urb(&snd->playback_urb);
		} else {
			usb_unlink_urb(&snd->playback_urb);
		}
		break;
	default:
		return -EINVAL;
	}
	if (snd->ctrl_urb.status == -EINPROGRESS)
		usb_unlink_urb(&snd->ctrl_urb);
	rc = usb_submit_urb(&snd->ctrl_urb, GFP_ATOMIC);
	return rc;
}

static snd_pcm_uframes_t s2253_snd_pcm_pointer_playback(struct snd_pcm_substream
							*substream)
{
	struct s2253_snd *snd = snd_pcm_substream_chip(substream);
	//struct snd_pcm_runtime *runtime = substream->runtime;
	//dprintk(0, "%s pb_ptr=%d avail_max=%lu hpb=%lu hpi=%lu\n", __func__, snd->pb_ptr,
	//	runtime->avail_max, runtime->hw_ptr_base, runtime->hw_ptr_interrupt);

	return snd->pb_ptr;
}


static struct snd_pcm_ops s2253_snd_capture_ops = {
	.open		= s2253_snd_open,
	.close		= s2253_snd_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= s2253_snd_hw_params,
	.hw_free	= s2253_snd_hw_free,
	.prepare	= s2253_snd_pcm_prepare,
	.trigger	= s2253_snd_pcm_trigger,
	.pointer	= s2253_snd_pcm_pointer_capture,
	.page		= snd_pcm_lib_get_vmalloc_page,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
	.mmap		= snd_pcm_lib_mmap_vmalloc,
#endif
};

static struct snd_pcm_ops s2253_snd_playback_ops = {
	.open		= s2253_snd_open,
	.close		= s2253_snd_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= s2253_snd_hw_params,
	.hw_free	= s2253_snd_hw_free,
	.prepare	= s2253_snd_pcm_prepare,
	.trigger	= s2253_snd_pcm_trigger,
	.pointer	= s2253_snd_pcm_pointer_playback,
	.page		= snd_pcm_lib_get_vmalloc_page,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
	.mmap		= snd_pcm_lib_mmap_vmalloc,
#endif
};


static int s2253_snd_free(struct snd_device *device)
{
	struct s2253_snd *snd = device->device_data;

	dprintk(1, "%s %p\n", __func__, snd);
	s2253_board_put(snd->board, __func__);
	return 0;
}

static struct snd_device_ops s2253_snd_device_ops = {
	.dev_free	= s2253_snd_free,
};


static int s2253_snd_control_info(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_info *uinfo)
{
	switch (kcontrol->private_value) {
	case S2253_RECORD_INPUT:
		uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
		uinfo->count = 1;
		uinfo->value.enumerated.items = 2;
		if (uinfo->value.enumerated.item > 1)
			uinfo->value.enumerated.item = 1;
		strcpy(uinfo->value.enumerated.name,
			uinfo->value.enumerated.item ? "Line" : "Mic");
		break;
	case S2253_RECORD_AGC:
		uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
		uinfo->count = 2;
		uinfo->value.enumerated.items = 2;
		if (uinfo->value.enumerated.item > 1)
			uinfo->value.enumerated.item = 1;
		strcpy(uinfo->value.enumerated.name,
			uinfo->value.enumerated.item ? "On" : "Off");
		break;
	case S2253_RECORD_VOLUME:
		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
		uinfo->count = 2;
		uinfo->value.integer.min = S2253_RECORD_VOLUME_MIN;
		uinfo->value.integer.max = S2253_RECORD_VOLUME_MAX;
		break;
	case S2253_PLAYBACK_VOLUME:
		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
		uinfo->count = 2;
		uinfo->value.integer.min = S2253_PLAYBACK_VOLUME_MIN;
		uinfo->value.integer.max = S2253_PLAYBACK_VOLUME_MAX;
		break;
	}
	return 0;
}

static int s2253_snd_control_get(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct s2253_snd *snd = snd_kcontrol_chip(kcontrol);
	int rc;

	switch (kcontrol->private_value) {
	case S2253_RECORD_INPUT:
		rc = s2253_get_param(snd->board, S2253_RECORD_INPUT);
		if (rc >= 0)
			snd->current_source = rc;
		ucontrol->value.enumerated.item[0] = snd->current_source;
		break;
	case S2253_RECORD_AGC:
		rc = s2253_get_param(snd->board, S2253_RECORD_AGC);
		if (rc >= 0)
			snd->current_agc = rc;
		ucontrol->value.enumerated.item[0] = snd->current_agc & 1;
		ucontrol->value.enumerated.item[1] = (snd->current_agc >> 1) & 1;
		break;
	case S2253_RECORD_VOLUME:
		rc = s2253_get_param(snd->board, kcontrol->private_value);
		if (rc >= 0)
			snd->current_record_volume = rc;
		ucontrol->value.integer.value[0] = snd->current_record_volume & 0xff;
		ucontrol->value.integer.value[1] = (snd->current_record_volume >> 8) & 0xff;
		break;
	case S2253_PLAYBACK_VOLUME:
		rc = s2253_get_param(snd->board, kcontrol->private_value);
		if (rc >= 0)
			snd->current_playback_volume = rc;
		ucontrol->value.integer.value[0] = snd->current_playback_volume & 0xff;
		ucontrol->value.integer.value[1] = (snd->current_playback_volume >> 8) & 0xff;
		break;
	default:
		rc = -EINVAL;
		break;
	}
	if (rc >= 0)
		rc = 0;
	dprintk(5, "%s: %s %ld %ld rc=%d\n", __func__,
		kcontrol->private_value == S2253_RECORD_INPUT ? "input" :
		kcontrol->private_value == S2253_RECORD_AGC ? "recagc" :
		kcontrol->private_value == S2253_RECORD_VOLUME ? "recvol" :
		kcontrol->private_value == S2253_PLAYBACK_VOLUME ? "pbvol" : "?",

		kcontrol->private_value == S2253_RECORD_INPUT ? ucontrol->value.enumerated.item[0] :
		kcontrol->private_value == S2253_RECORD_AGC ? ucontrol->value.enumerated.item[0] :
			ucontrol->value.integer.value[0],
		kcontrol->private_value == S2253_RECORD_INPUT ? ucontrol->value.enumerated.item[0] :
		kcontrol->private_value == S2253_RECORD_AGC ? ucontrol->value.enumerated.item[1] :
			ucontrol->value.integer.value[1],
		rc);

	return rc;
}

static int s2253_snd_control_put(struct snd_kcontrol *kcontrol,
				 struct snd_ctl_elem_value *ucontrol)
{
	struct s2253_snd *snd = snd_kcontrol_chip(kcontrol);
	unsigned int newval = 0;

	dprintk(5, "%s: %s %ld %ld\n", __func__,
		kcontrol->private_value == S2253_RECORD_INPUT ? "input" :
		kcontrol->private_value == S2253_RECORD_AGC ? "recagc" :
		kcontrol->private_value == S2253_RECORD_VOLUME ? "recvol" :
		kcontrol->private_value == S2253_PLAYBACK_VOLUME ? "pbvol" : "?",

		kcontrol->private_value == S2253_RECORD_INPUT ? ucontrol->value.enumerated.item[0] :
		kcontrol->private_value == S2253_RECORD_AGC ? ucontrol->value.enumerated.item[0] :
			ucontrol->value.integer.value[0],
		kcontrol->private_value == S2253_RECORD_INPUT ? ucontrol->value.enumerated.item[0] :
		kcontrol->private_value == S2253_RECORD_AGC ? ucontrol->value.enumerated.item[1] :
			ucontrol->value.integer.value[1]);

	switch (kcontrol->private_value) {
	case S2253_RECORD_INPUT:
		if (snd->current_source == ucontrol->value.enumerated.item[0])
			return 0;
		snd->current_source = ucontrol->value.enumerated.item[0];
		s2253_set_param(snd->board, S2253_RECORD_INPUT,
			snd->current_source);
		break;
	case S2253_RECORD_AGC:
		newval = ucontrol->value.enumerated.item[0] |
			(ucontrol->value.enumerated.item[1] << 1);
		if (snd->current_agc == newval)
			return 0;
		snd->current_agc = newval;
		s2253_set_param(snd->board, S2253_RECORD_AGC, snd->current_agc);
		break;
	case S2253_RECORD_VOLUME:
		if (ucontrol->value.integer.value[0] >= S2253_RECORD_VOLUME_MAX)
			newval |= snd->current_record_volume & 0xff;  // was S2253_RECORD_VOLUME_MAX;
		else if (ucontrol->value.integer.value[0] > 0)
			newval |= ucontrol->value.integer.value[0];
		if (ucontrol->value.integer.value[1] >= S2253_RECORD_VOLUME_MAX)
			newval |= snd->current_record_volume & 0xff00;  // was S2253_RECORD_VOLUME_MAX << 8;
		else if (ucontrol->value.integer.value[1] > 0)
			newval |= ucontrol->value.integer.value[1] << 8;
		dprintk(1, "%s: left=%d right=%d\n", __func__, newval&0xff, newval>>8);

		if (snd->current_record_volume == newval)
			return 0;
		snd->current_record_volume = newval;
		s2253_set_param(snd->board, kcontrol->private_value,
			snd->current_record_volume);
		break;
	case S2253_PLAYBACK_VOLUME:
		if (ucontrol->value.integer.value[0] >= S2253_PLAYBACK_VOLUME_MAX)
			newval |= S2253_PLAYBACK_VOLUME_MAX;
		else if (ucontrol->value.integer.value[0] > 0)
			newval |= ucontrol->value.integer.value[0];
		if (ucontrol->value.integer.value[1] >= S2253_PLAYBACK_VOLUME_MAX)
			newval |= S2253_PLAYBACK_VOLUME_MAX << 8;
		else if (ucontrol->value.integer.value[1] > 0)
			newval |= ucontrol->value.integer.value[1] << 8;
		dprintk(1, "%s: left=%d right=%d\n", __func__, newval&0xff, newval>>8);

		if (snd->current_playback_volume == newval)
			return 0;
		snd->current_playback_volume = newval;
		s2253_set_param(snd->board, kcontrol->private_value,
			snd->current_playback_volume);
		break;
	}
	return 1;
}

static struct snd_kcontrol_new s2253_snd_source = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Input Source", // NOTE: alsamixer garbles "Capture Source"
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = S2253_RECORD_INPUT,
	.info = s2253_snd_control_info,
	.get = s2253_snd_control_get,
	.put = s2253_snd_control_put,
};

static struct snd_kcontrol_new s2253_snd_agc = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Input AGC Switch", // FIXME: alsamixer puts this on playback tab
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = S2253_RECORD_AGC,
	.info = s2253_snd_control_info,
	.get = s2253_snd_control_get,
	.put = s2253_snd_control_put,
};

static struct snd_kcontrol_new s2253_snd_record_volume = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Capture Volume",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = S2253_RECORD_VOLUME,
	.info = s2253_snd_control_info,
	.get = s2253_snd_control_get,
	.put = s2253_snd_control_put,
};

static struct snd_kcontrol_new s2253_snd_playback_volume = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Playback Volume",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = S2253_PLAYBACK_VOLUME,
	.info = s2253_snd_control_info,
	.get = s2253_snd_control_get,
	.put = s2253_snd_control_put,
};


static int s2253_snd_init(struct s2253_snd *snd)
{
	int rc = 0;

	spin_lock_init(&snd->lock);
	snd->hw_ptr = snd->w_idx = snd->avail = 0;
	snd->capturing = 0;
	snd->pb_ptr = 0;
	snd->playingback = 0;
	snd->urb_ptr = 0;

//2)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
	rc = snd_card_new(&snd->board->usb_dev->dev, SNDRV_DEFAULT_IDX1, "s2253",
			  THIS_MODULE, 0, &snd->card);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30)
	rc = snd_card_create(SNDRV_DEFAULT_IDX1, "s2253", THIS_MODULE,
			      0, &snd->card);
#else
	snd->card = snd_card_new(SNDRV_DEFAULT_IDX1, "s2253", THIS_MODULE, 0);
	rc = snd->card ? 0 : -ENOMEM;
#endif
	if (rc < 0) {
		dprintk(1, "snd_card_create failed\n");
		goto cleanup;
	}
//3)
	strncpy(snd->card->driver, "s2253", sizeof(snd->card->driver));
	strncpy(snd->card->shortname, "Sensoray 2253", sizeof(snd->card->shortname));
	snprintf(snd->card->longname, sizeof(snd->card->longname),
		"Sensoray Model %d SN#%06d",
		snd->board->info.model_no,
		snd->board->info.serial_no);
	snd_card_set_dev(snd->card, NULL);

	rc = snd_device_new(snd->card, SNDRV_DEV_LOWLEVEL, snd,
				&s2253_snd_device_ops);
	if (rc < 0) {
		dprintk(1, "snd_device_new failed\n");
		goto cleanup;
	}

//4)

//5) pcm init
	rc = snd_pcm_new(snd->card, "s2253", 0, 
			  snd->playback_endpoint ? 1 : 0,
			  1, &snd->pcm);

	if (rc < 0) {
		dprintk(1, "snd_pcm_new failed\n");
		goto cleanup;
	}
	snd_pcm_set_ops(snd->pcm, SNDRV_PCM_STREAM_CAPTURE,
			&s2253_snd_capture_ops);
	if (snd->playback_endpoint)
		snd_pcm_set_ops(snd->pcm, SNDRV_PCM_STREAM_PLAYBACK,
			&s2253_snd_playback_ops);

	snd_pcm_chip(snd->pcm) = snd;
	snd->pcm->info_flags = 0;
	strcpy(snd->pcm->name, snd->card->shortname);

//6)
	rc = snd_ctl_add(snd->card, snd_ctl_new1(&s2253_snd_source, snd));
	if (rc < 0) {
		dprintk(1, "snd_ctl_add failed\n");
		goto cleanup;
	}
	rc = snd_ctl_add(snd->card, snd_ctl_new1(&s2253_snd_agc, snd));
	if (rc < 0) {
		dprintk(1, "snd_ctl_add failed\n");
		goto cleanup;
	}
	rc = snd_ctl_add(snd->card, snd_ctl_new1(&s2253_snd_record_volume, snd));
	if (rc < 0) {
		dprintk(1, "snd_ctl_add failed\n");
		goto cleanup;
	}
	rc = snd_ctl_add(snd->card, snd_ctl_new1(&s2253_snd_playback_volume, snd));
	if (rc < 0) {
		dprintk(1, "snd_ctl_add failed\n");
		goto cleanup;
	}

	rc = snd_card_register(snd->card);
	if (rc < 0) {
		dprintk(1, "snd_card_register failed\n");
		goto cleanup;
	}

	s2253_board_get(snd->board, __func__);
	dprintk(1, "%s: registered %p\n", __func__, snd);

	usb_init_urb(&snd->ctrl_urb);

	snd->ctrl_req.bRequest = USB_REQ_S2253_PARAM;
	snd->ctrl_req.bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR;
	snd->ctrl_req.wIndex = cpu_to_le16(S2253_RECORD_STATE);
	snd->ctrl_req.wLength = 0;
	usb_fill_control_urb(&snd->ctrl_urb,
			snd->board->usb_dev,
			usb_sndctrlpipe(snd->board->usb_dev, 0),
			(unsigned char *)&snd->ctrl_req,
			NULL,
			0,
			s2253_snd_pcm_trigger_complete,
			snd);

	usb_init_urb(&snd->capture_urb);
	usb_fill_bulk_urb(&snd->capture_urb,
		snd->board->usb_dev,
		usb_rcvbulkpipe(snd->board->usb_dev, snd->capture_endpoint),
		kmalloc(S2253_USB_PACKET_SIZE, GFP_KERNEL),
		S2253_USB_PACKET_SIZE,
		s2253_snd_capture_complete,
		snd);
	usb_init_urb(&snd->playback_urb);
	usb_fill_bulk_urb(&snd->playback_urb,
		snd->board->usb_dev,
		//usb_sndbulkpipe(snd->board->usb_dev, snd->playback_endpoint),
		// note: overlay endpoint is used because other doesn't work
		usb_sndbulkpipe(snd->board->usb_dev,
			snd->board->overlay.endpoint),
		kmalloc(S2253_USB_PACKET_SIZE, GFP_KERNEL),
		S2253_USB_PACKET_SIZE,
		s2253_snd_playback_complete,
		snd);
	return 0;
cleanup:
	if (snd && snd->card)
		snd_card_free(snd->card);
	return rc;
}

static int s2253_snd_remove(struct s2253_snd *snd)
{
	if (!snd || !snd->card)
		return 0;
	snd_card_disconnect(snd->card);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18)
	snd_card_free_when_closed(snd->card);
#else
	snd_card_free_in_thread(snd->card);
#endif
	usb_kill_urb(&snd->ctrl_urb);
	spin_lock(&snd->lock);
	usb_kill_urb(&snd->capture_urb);
	kfree(snd->capture_urb.transfer_buffer);
	snd->capture_urb.transfer_buffer = NULL;
	usb_kill_urb(&snd->playback_urb);
	kfree(snd->playback_urb.transfer_buffer);
	snd->playback_urb.transfer_buffer = NULL;
	spin_unlock(&snd->lock);

	return 0;
}

#else
static int s2253_snd_init(struct s2253_snd *snd)
{
	return 0;
}

static int s2253_snd_remove(struct s2253_snd *snd)
{
	return 0;
}
#endif



/* "new" GPIO functionality ---------------------------------------------- */

#ifdef CONFIG_GPIOLIB
/* note: requires 2.6.25 or later */
/* todo: edge-triggering requires kernel 2.6.32+ */

static inline struct s2253_gpio_chip *to_s2253_gpio_chip(struct gpio_chip *chip)
{
	return container_of(chip, struct s2253_gpio_chip, chip);
}

static int s2253_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
	//struct s2253_gpio_chip *ch = to_s2253_gpio_chip(chip);
	return 0;
}

static int s2253_gpio_get(struct gpio_chip *chip, unsigned offset)
{
	struct s2253_gpio_chip *ch = to_s2253_gpio_chip(chip);
	return s2253_get_param(ch->board, S2253_GPIO_INPUT_VALUE);
}

static int s2253_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
					int value)
{
	//struct s2253_gpio_chip *ch = to_s2253_gpio_chip(chip);
	return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0)
static int s2253_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
#else
static void s2253_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
#endif
{
	struct s2253_gpio_chip *ch = to_s2253_gpio_chip(chip);
	s2253_set_param(ch->board, S2253_GPIO_OUTPUT_VALUE, value);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0)
	return 0;
#endif
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
static int s2253_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
	struct s2253_gpio_chip *ch = to_s2253_gpio_chip(chip);
	dprintk(1, "%s\n", __func__);
	return ch->irq;
}
#endif

static void s2253_gpio_urb_complete(struct urb *urb)
{
	struct s2253_gpio_chip *ch = urb->context;
	//generic_handle_irq(ch->irq);
	dprintk(1, "%s: status %d, data is 0x%02x\n", __func__, urb->status, ch->urb_buf[0]);
	usb_submit_urb(urb, GFP_ATOMIC); /* resubmit until killed */
}

static const struct gpio_chip s2253_gpio_chip_template = {
	.label = "s2253",
	.direction_input = s2253_gpio_direction_input,
	.get = s2253_gpio_get,
	.direction_output = s2253_gpio_direction_output,
	.set = s2253_gpio_set,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
	.to_irq = s2253_gpio_to_irq,
#endif
	.base = -1,
	.ngpio = 2,
	.can_sleep = 1, /* get/set_param use usb_control_msg */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)
	.exported = 1,
#endif
};

static int s2253_gpio_chip_init(struct s2253_gpio_chip *gpio)
{
	int rc;

	gpio->chip = s2253_gpio_chip_template;
	gpio->chip.label = gpio->label;
	snprintf(gpio->label, sizeof(gpio->label)-1,
		"S2253 SN%06d", gpio->board->info.serial_no);
	gpio->irq = 7;
	if (gpio->endpoint) {
		usb_init_urb(&gpio->urb);
		usb_fill_int_urb(&gpio->urb, gpio->board->usb_dev,
				 usb_rcvintpipe (gpio->board->usb_dev, gpio->endpoint->bEndpointAddress),
				 gpio->urb_buf, sizeof(gpio->urb_buf),
				 s2253_gpio_urb_complete,
				 gpio, gpio->endpoint->bInterval);
		usb_submit_urb(&gpio->urb, GFP_KERNEL);
	}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0)
	rc = gpiochip_add(&gpio->chip);
#else
	rc = gpiochip_add_data(&gpio->chip, NULL);
#endif
	if (rc < 0) {
		dprintk(1, "%s: gpiochip_add rc=%d\n", __func__, rc);
		return rc;
	}
	dprintk(1, "%s: gpiochip added, base=%d, irq=%d\n", __func__, gpio->chip.base, gpio->irq);
	gpio_request(gpio->chip.base + 0, NULL);
	gpio_request(gpio->chip.base + 1, NULL);
	gpio_direction_input(gpio->chip.base + 0);
	gpio_direction_output(gpio->chip.base + 1, 0);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
	gpio_export(gpio->chip.base + 0, 1);
	gpio_export(gpio->chip.base + 1, 1);
#else
	gpiod_export(gpio_to_desc(gpio->chip.base + 0), 1);
	gpiod_export(gpio_to_desc(gpio->chip.base + 1), 1);
#endif
	return 0;
}

static void s2253_gpio_chip_fini(struct s2253_gpio_chip *gpio)
{
	if (!gpio)
		return;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
	gpio_unexport(gpio->chip.base + 0);
	gpio_unexport(gpio->chip.base + 1);
#else
	gpiod_unexport(gpio_to_desc(gpio->chip.base + 0));
	gpiod_unexport(gpio_to_desc(gpio->chip.base + 1));
#endif
	gpio_free(gpio->chip.base + 0);
	gpio_free(gpio->chip.base + 1);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)
	if (gpiochip_remove(&gpio->chip) < 0)
		dprintk(1, "%s: gpiochip_remove failed\n", __func__);
#else
	gpiochip_remove(&gpio->chip);
#endif
	if (gpio->endpoint)
		usb_kill_urb(&gpio->urb);
}

#else
static int s2253_gpio_chip_init(struct s2253_gpio_chip *gpio)
{
	return 0;
}
static void s2253_gpio_chip_fini(struct s2253_gpio_chip *gpio)
{
	return;
}
#endif


/* Firmware loading ------------------------------------------------------- */

#ifdef INCLUDE_S2253_FW

#include "s2253fw.h"

static int s2253_load_firmware(char *firmware, struct usb_device *usb_dev)
{
	int i, rc, tx = 0;
	u8 *data = NULL;
	u8 *fw_data = s2253_fw;
	int fw_size = sizeof(s2253_fw);

        /* copy firmware, see: http://www.gossamer-threads.com/lists/linux/kernel/1197593?page=last */
	data = kmalloc(S2253_USB_PACKET_SIZE, GFP_KERNEL);
	if (!data) {
		dprintk(1, "%s: unable to allocate transfer buffer\n", __func__);
		return -ENOMEM;
	}

	for (i = 0; i < fw_size || (i % 64) == 0; i += tx) {
		int size = fw_size - i;
		if (size > S2253_USB_PACKET_SIZE)
			size = S2253_USB_PACKET_SIZE;

		memcpy(data, fw_data + i, size);

		/* send firmware */
		rc = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
				data, size, &tx, 5000);

		if (rc) {
			dprintk(0, "%s: usb_bulk_msg i=%d size=%d returned %d\n",
				__func__, i, size, rc);
			break; // bootloader will timeout and reset
		}
		if (size == 0) // sent last ZLP
			break;
	}
	kfree(data);
	return 0;
}
static void s2253_fw_device_remove(struct usb_device *usb_dev)
{
}

#else

static DEFINE_MUTEX(s2253_fw_lock);
static LIST_HEAD(s2253_fw_list);
struct s2253_fw_device {
	struct list_head list;
	struct usb_device *usb_dev;
	int removed;
};

static void s2253_fw_complete(const struct firmware *fw, void *context)
{
	u8 *data = NULL;
	unsigned int i = 0;
	int rc, tx;

	mutex_lock(&s2253_fw_lock);

	if (!fw) {
		dprintk(0, "%s: %s not found in /lib/firmware\n", __func__, USB_S2253_FIRMWARE);
		while (!list_empty(&s2253_fw_list)) {
			struct s2253_fw_device *dev = list_first_entry(&s2253_fw_list, struct s2253_fw_device, list);
			list_del(&dev->list);
			kfree(dev);
		}
		mutex_unlock(&s2253_fw_lock);
		return;
	}

	dprintk(1, "%s: firmware is %d bytes at %p\n",
		__func__, (int)fw->size, fw->data);

        /* copy firmware, see: http://www.gossamer-threads.com/lists/linux/kernel/1197593?page=last */
	data = kmalloc(S2253_USB_PACKET_SIZE, GFP_KERNEL);
	if (!data) {
		dprintk(1, "%s: unable to allocate transfer buffer\n", __func__);
	}

	// Something is doing a Get String Descriptor during the firmware download, 
	// which causes board to hang.  This sleep is to try to avoid it.
	msleep(10);

	while (!list_empty(&s2253_fw_list)) {
		struct s2253_fw_device *dev = list_first_entry(&s2253_fw_list, struct s2253_fw_device, list);
		struct usb_device *usb_dev = dev->usb_dev;

		// short delay before sending first packet to bootloader
		// sometimes first usb_bulk_msg returns -71 (EPROTO)
		// and hub will reset device repeatedly 
		// or device will get corrupt firmware and won't boot
		msleep(10);

		for (i = 0; i < fw->size || (i % 64) == 0; i += tx) {
			int size = fw->size - i;
			if (size > S2253_USB_PACKET_SIZE)
				size = S2253_USB_PACKET_SIZE;

			if (dev->removed || !data)
				break;
			mutex_unlock(&s2253_fw_lock);

			memcpy(data, fw->data + i, size);

			/* send firmware */
			rc = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
					data, size, &tx, 5000);

			mutex_lock(&s2253_fw_lock);
			if (rc) {
				dprintk(0, "%s: usb_bulk_msg i=%d size=%d returned %d\n",
					__func__, i, size, rc);
				break; // bootloader will timeout and reset
			}
			if (size == 0) // sent last ZLP
				break;
		}
		dprintk(1, "%s: %d bytes transferred\n", __func__, i);

		list_del(&dev->list);

		kfree(dev); // fw download is done or device removed
	}
	mutex_unlock(&s2253_fw_lock);

	kfree(data);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)
	release_firmware(fw);
#endif
}

// load the firmware for a device
// if the firmware download is already in progress, reuse it and add the device to a list
static int s2253_load_firmware(char *firmware, struct usb_device *usb_dev)
{
	int rc = 0;
	mutex_lock(&s2253_fw_lock);

	if (list_empty(&s2253_fw_list)) {
		rc = request_firmware_nowait(THIS_MODULE,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)
					     FW_ACTION_UEVENT,
#else
					     FW_ACTION_HOTPLUG,
#endif
					     firmware,
					     &usb_dev->dev,
#if defined(HAVE_REQUEST_FIRMWARE_GFP) || LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
					     GFP_KERNEL | GFP_DMA,
#endif
					     NULL, s2253_fw_complete);
		if (rc) {
			dprintk(1, "%s: request firmware %s returned %d\n",
				__func__, firmware, rc);
			rc = -EINVAL;
		}
	}
	if (rc == 0) {
		struct s2253_fw_device *dev;

		dev = kzalloc(sizeof(*dev), GFP_KERNEL);
		if (!dev)
			rc = -ENOMEM;
		else {
			dev->usb_dev = usb_dev;
			dev->removed = 0;
			list_add_tail(&dev->list, &s2253_fw_list);
		}
	}

	mutex_unlock(&s2253_fw_lock);
	return rc;
}

// while loading firmware, if the device is removed, stop
static void s2253_fw_device_remove(struct usb_device *usb_dev)
{
	struct s2253_fw_device *dev;
	mutex_lock(&s2253_fw_lock);
	list_for_each_entry(dev, &s2253_fw_list, list) {
		if (dev->usb_dev == usb_dev)
			dev->removed = 1;
	}
	mutex_unlock(&s2253_fw_lock);
}

#endif // !INCLUDE_S2253_FW


#define S2253_MAX_BOARDS 64
static struct s2253_board *boards[S2253_MAX_BOARDS] = {};

static void s2253_update_serial_number(struct s2253_board *brd, int new_serial)
{
	int rc;
	brd->info.serial_no = new_serial;
	rc = s2253_set_param_data(brd, S2253_MFG_INFO, 0, &brd->info, sizeof(brd->info));
}

static void s2253_remove_from_serial_list(struct s2253_board *brd)
{
	int i;
	for (i = 0; i < S2253_MAX_BOARDS; i++)
		if (boards[i] == brd)
			boards[i] = NULL;
}

/* Check each board as it is probed, to look for duplicate serial numbers.
 * The model 953 has one serial number and 4 devices on a single board.  If a
 * duplicate serial number is found, the original has the digit '1' appended,
 * and the duplicate appends next larger digit in the available range '2'-'9'.
 * The four boards are chain-loaded and will always appear in the same order.
 * The new serial number is recorded on the device, so that after a warm-reboot
 * the devices will keep the serial number when the driver is loaded.
 */
static void s2253_fix_duplicate_serial_numbers(struct s2253_board *brd)
{
	int i;
	int n = brd->info.serial_no * 10;
	int new_serial = n;

	for (i = 0; i < S2253_MAX_BOARDS; i++) {
		if (!boards[i])
			continue;
		if (boards[i]->info.serial_no == brd->info.serial_no) {
			// update original
			s2253_update_serial_number(boards[i], n + 1);
		}
		if (boards[i]->info.serial_no >= new_serial &&
		    boards[i]->info.serial_no <= n + 9) {
		    	new_serial = boards[i]->info.serial_no + 1;
		}
	}

	// found a duplicate?
	if (new_serial != n)
		s2253_update_serial_number(brd, new_serial);

	// find an empty slot and use it
	for (i = 0; i < S2253_MAX_BOARDS; i++) {
		if (!boards[i]) {
			boards[i] = brd;
			break;
		}
	}
}


static int s2253_probe(struct usb_interface *interface,
		       const struct usb_device_id *id)
{
	int rc = 0;
	struct usb_device *usb_dev;
	int interface_number;
	char *firmware = NULL;
	struct s2253_board *board = NULL;
	int i;

	usb_dev = usb_get_dev(interface_to_usbdev(interface));

	dprintk(100, "%s: probing %04x:%04x...\n", __func__,
		id->idVendor, id->idProduct);

#ifdef DM365_BOOT
	if (id->idVendor == USB_DM365_VENDOR_ID &&
	    id->idProduct == USB_DM365_PRODUCT_ID)
		firmware = USB_DM365_FIRMWARE;
#endif
	if (id->idVendor == USB_S2253_VENDOR_ID &&
	    id->idProduct == USB_S2253_BOOTUP_ID)
		firmware = USB_S2253_FIRMWARE;
	if (firmware) {
		return s2253_load_firmware(firmware, usb_dev);
	}

	/* done firmware loading */
	// must be 0x1943, 0x2253 now
        if (!(id->idVendor == USB_S2253_VENDOR_ID &&
	      id->idProduct == USB_S2253_PRODUCT_ID)) {
		dprintk(1, "unknown vendor, product ID %04x:%04x\n",
			id->idVendor, id->idProduct);
		return -EINVAL;
	}

	board = dev_get_drvdata(&usb_dev->dev);
	if (board == NULL || board->usb_dev != usb_dev) {
		__u16 fwver, blver;

		board = kzalloc(sizeof (struct s2253_board), GFP_KERNEL);
		dprintk(100, "%s: new board %p\n", __func__, board);
		if (!board) {
			dprintk(1, "Failed to allocate board struct\n");
			rc = -ENOMEM;
			goto fail;
		}
		board->ctrl_buf = kmalloc(CTRL_BUF_SIZE, GFP_KERNEL);
		if (!board->ctrl_buf) {
			dprintk(1, "Failed to allocate ctrl_buf\n");
			rc = -ENOMEM;
			goto fail;
		}
		board->usb_dev = usb_dev;
		rc = s2253_get_param_data(board, S2253_MFG_INFO, &board->info, sizeof(board->info));
		if (rc < 0) {
			dprintk(1, "Failed to get mfg info\n");
			goto fail;
		}
		le32_to_cpus(&board->info.model_no);
		le32_to_cpus(&board->info.serial_no);
		s2253_fix_duplicate_serial_numbers(board);
		fwver = s2253_get_param(board, S2253_FIRMWARE_VERSION);
		blver = s2253_get_param(board, S2253_BOOTLOADER_VERSION);
		dprintk(0, "driver %d.%d.%d, model %u, serial %u, fw %u, bl %x\n",
			(DRV_VERSION >> 16), (DRV_VERSION >> 8) & 0xff, DRV_VERSION & 0xff,
			board->info.model_no, board->info.serial_no, fwver, blver);
		if (fwver < FIRMWARE_VERSION_EXPECTED && fwver >= 3738) {
			dprintk(1, "expected fw %d, reloading fw\n", FIRMWARE_VERSION_EXPECTED);
			s2253_set_param(board, S2253_SYSTEM_REBOOT, 0);
			rc = -ENODEV;
			goto fail;
		}
		atomic_set(&board->refcount, 0);
		board->old_usb_drvdata = dev_get_drvdata(&usb_dev->dev);
		dev_set_drvdata(&usb_dev->dev, board);
		board->stream_a.stream.board = board;
		board->stream_b.stream.board = board;
		board->snd.board = board;
		board->gpio.board = board;
		board->stream_output.stream.board = board;
		board->overlay.board = board;
		board->osd_timezone = 15;
	}
	s2253_board_get(board, __func__);

	interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
	dprintk(100, "%s: interface number is %d, with %d endpoints\n",
		__func__, interface_number,
		interface->cur_altsetting->desc.bNumEndpoints);
	for (i = 0; i < interface->cur_altsetting->desc.bNumEndpoints; i++) {
		struct usb_endpoint_descriptor *ep =
			&interface->cur_altsetting->endpoint[i].desc;
		dprintk(1, "found endpoint %02x\n", ep->bEndpointAddress);
		switch (ep->bEndpointAddress) {
		case S2253_ENDPOINT_STREAM_A:
			board->stream_a.stream.endpoint = S2253_ENDPOINT_STREAM_A;
			board->stream_a.stream.interface = interface;
			s2253_video_init(&board->stream_a);
			break;
		case S2253_ENDPOINT_STREAM_B:
			board->stream_b.stream.endpoint = S2253_ENDPOINT_STREAM_B;
			board->stream_b.stream.interface = interface;
			s2253_video_init(&board->stream_b);
			break;
		case S2253_ENDPOINT_RECORD:
			board->snd.capture_endpoint = S2253_ENDPOINT_RECORD;
			board->snd.playback_endpoint = S2253_ENDPOINT_PLAYBACK;
			if (audio) s2253_snd_init(&board->snd);
			break;
		case S2253_ENDPOINT_GPIO: // gpio endpoint no longer exists
			board->gpio.endpoint = ep;
			s2253_gpio_chip_init(&board->gpio);
			break;
		case S2253_ENDPOINT_OVERLAY:
			board->overlay.endpoint = S2253_ENDPOINT_OVERLAY;
			board->overlay.interface = interface;
			s2253_init_stream(&board->overlay);
			break;
		case S2253_ENDPOINT_OUTPUT:
			board->stream_output.stream.endpoint = S2253_ENDPOINT_OUTPUT;
			board->stream_output.stream.interface = interface;
			s2253_video_init(&board->stream_output);
			break;
		}
	}
	if (!board->gpio.endpoint)
		s2253_gpio_chip_init(&board->gpio);

	return 0;
fail:
	if (board) {
		if (board->ctrl_buf)
			kfree(board->ctrl_buf);
		kfree(board);
	}
	return rc;
}

static void s2253_disconnect(struct usb_interface *interface)
{
	struct usb_device *usb_dev = usb_get_dev(interface_to_usbdev(interface));
	struct s2253_board *board = dev_get_drvdata(&usb_dev->dev);
	int i;

	if (!board || board->usb_dev != usb_dev) {
		s2253_fw_device_remove(usb_dev);
		return;	/* firmware loader doesn't have board */
	}

	for (i = 0; i < interface->cur_altsetting->desc.bNumEndpoints; i++) {
		struct usb_endpoint_descriptor *ep =
			&interface->cur_altsetting->endpoint[i].desc;
		switch (ep->bEndpointAddress) {
		case S2253_ENDPOINT_STREAM_A:
			s2253_video_fini(&board->stream_a);
			break;
		case S2253_ENDPOINT_STREAM_B:
			s2253_video_fini(&board->stream_b);
			break;
		case S2253_ENDPOINT_RECORD:
			s2253_snd_remove(&board->snd);
			break;
		case S2253_ENDPOINT_GPIO:
			s2253_gpio_chip_fini(&board->gpio);
			break;
		case S2253_ENDPOINT_OUTPUT:
			s2253_video_fini(&board->stream_output);
			break;
		case S2253_ENDPOINT_OVERLAY:
			s2253_uninit_stream(&board->overlay, 1, 0);
			break;
		}
	}
	if (!board->gpio.endpoint)
		s2253_gpio_chip_fini(&board->gpio);

	s2253_remove_from_serial_list(board);
	if (board->usb_dev) {
		// this must be done before returning from disconnect
		struct device *dev = &board->usb_dev->dev;
		board->usb_dev = NULL;
		dev_set_drvdata(dev, board->old_usb_drvdata);
	}
	if (board->ctrl_buf) {
		kfree(board->ctrl_buf);
		board->ctrl_buf = NULL;
	}
	s2253_board_put(board, __func__);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)
	// TODO: Why is this needed?  cache_firmware was never called.
	//       This may be removed altogether.
	/* uncache any existing firmware image */
	do {} while (0 == uncache_firmware(USB_S2253_FIRMWARE));
#endif
}

static int s2253_suspend(struct usb_interface *intf, pm_message_t message)
{
	struct usb_device *usb_dev = usb_get_dev(interface_to_usbdev(intf));
	struct s2253_board *board = dev_get_drvdata(&usb_dev->dev);

	dprintk(1, "suspending interface %u\n", intf->cur_altsetting->desc.bInterfaceNumber);
	if (!board || board->usb_dev != usb_dev)
		return 0;
	s2253_uninit_stream(&board->stream_a.stream, 1, 0);
	s2253_uninit_stream(&board->stream_b.stream, 1, 0);
	s2253_uninit_stream(&board->stream_output.stream, 1, 0);
	return 0;
}

static int s2253_resume(struct usb_interface *intf)
{
	struct usb_device *usb_dev = usb_get_dev(interface_to_usbdev(intf));

	dprintk(1, "resuming interface %u\n", intf->cur_altsetting->desc.bInterfaceNumber);
	usb_reset_configuration(usb_dev); // this should wake the device
	return 0;
}

static struct usb_driver s2253_driver = {
	.name = S2253_MODULE_NAME,
	.probe = s2253_probe,
	.disconnect = s2253_disconnect,
	.id_table = s2253_table,
	.suspend = s2253_suspend,
	.resume = s2253_resume,
};

static int __init usb_s2253_init(void)
{
	int rc;
	/* register this driver with the USB subsystem */
	rc = usb_register(&s2253_driver);
	if (rc)
		dprintk(1, "usb_register failed. Error number %d\n", rc);

	return rc;
}
module_init(usb_s2253_init);

static void __exit usb_s2253_exit(void)
{
	usb_deregister(&s2253_driver);
}
module_exit(usb_s2253_exit);


// kate: space-indent off; indent-width 8; mixedindent off; indent-mode cstyle;
