/*
 * 2253 kernel compatibility header
 * Copyright (c) 2013-2015 Sensoray Co., Inc.
 */


#ifndef S2253_COMPAT_H
#define S2253_COMPAT_H

// exclude some things in userspace
#ifdef __KERNEL__

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28)
/* from include/media/v4l2-drv.h */
/* Combine video_get_drvdata and video_devdata as this is
   used very often. */
static inline void *video_drvdata(struct file *file)
{
	void *tmp = video_devdata(file);
	return tmp ? video_get_drvdata(tmp) : tmp;
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)
static 
void v4l_bound_align_image(u32 *w, unsigned int wmin, unsigned int wmax,
			   unsigned int walign,
			   u32 *h, unsigned int hmin, unsigned int hmax,
			   unsigned int halign, unsigned int salign)
{
	*w &= ~((1 << walign) - 1);
	if (*w < wmin)
		*w = wmin;
	else if (*w > wmax)
		*w = wmax;
	*h &= ~((1 << halign) - 1);
	if (*h < hmin)
		*h = hmin;
	else if (*h > hmax)
		*h = hmax;
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && !defined(HAVE_VIDEO_DEVICE_NODE_NAME)
static inline const char *video_device_node_name(struct video_device *vdev)
{
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26)
        return dev_name(&vdev->dev);
#elif LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18)
	return kobject_name(&vdev->dev->kobj);
#else
	static char name[10];
	sprintf(name, "video%d", vdev->minor);
	return (const char *)name;
#endif
}
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
#define HAS_V4L2_CTRL_TYPE_STRING
#endif

#if 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

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24)
#define VIDEOBUF_NEEDS_INIT	STATE_NEEDS_INIT
#define VIDEOBUF_PREPARED	STATE_PREPARED
#define VIDEOBUF_QUEUED		STATE_QUEUED
#define VIDEOBUF_ACTIVE		STATE_ACTIVE
#define VIDEOBUF_DONE		STATE_DONE
#define VIDEOBUF_ERROR		STATE_ERROR
#define VIDEOBUF_IDLE		STATE_IDLE
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
static void videobuf_stop(struct videobuf_queue *q)
{
        if (q->streaming)
                videobuf_streamoff(q);
        if (q->reading)
                videobuf_read_stop(q);
}
#endif

#endif /* __KERNEL__ */

#ifndef DIV_ROUND_UP
/* from linux/kernel.h */
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
#endif



#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 17)

struct v4l2_tvnorm {
	char          *name;
	v4l2_std_id   id;

	void          *priv_data;
};

struct v4l2_ext_control {
	__u32 id;
	__u32 size;
	__u32 reserved2[1];
	union {
		__s32 value;
		__s64 value64;
		char *string;
	};
} __attribute__ ((packed));

struct v4l2_ext_controls {
	__u32 ctrl_class;
	__u32 count;
	__u32 error_idx;
	__u32 reserved[2];
	struct v4l2_ext_control *controls;
};

#define V4L2_CTRL_ID_MASK         (0x0fffffff)
#define V4L2_CTRL_ID2CLASS(id)    ((id) & 0x0fff0000UL)

enum vl42_ctrl_type2 {
	V4L2_CTRL_TYPE_CTRL_CLASS    = 6,
	V4L2_CTRL_TYPE_STRING        = 7,
	V4L2_CTRL_TYPE_BITMASK       = 8,
	V4L2_CTRL_TYPE_INTEGER_MENU = 9,
};


#define V4L2_CTRL_FLAG_READ_ONLY 	0x0004
/*  Query flag, to be ORed with the control ID */
#define V4L2_CTRL_FLAG_NEXT_CTRL	0x80000000

static u32 v4l2_ctrl_next(const u32 * const * ctrl_classes, u32 id)
{
	u32 ctrl_class = V4L2_CTRL_ID2CLASS(id);
	const u32 *pctrl;

	if (ctrl_classes == NULL)
		return 0;

	/* if no query is desired, then check if the ID is part of ctrl_classes */
	if ((id & V4L2_CTRL_FLAG_NEXT_CTRL) == 0) {
		/* find class */
		while (*ctrl_classes && V4L2_CTRL_ID2CLASS(**ctrl_classes) != ctrl_class)
			ctrl_classes++;
		if (*ctrl_classes == NULL)
			return 0;
		pctrl = *ctrl_classes;
		/* find control ID */
		while (*pctrl && *pctrl != id) pctrl++;
		return *pctrl ? id : 0;
	}
	id &= V4L2_CTRL_ID_MASK;
	id++;   /* select next control */
	/* find first class that matches (or is greater than) the class of
	   the ID */
	while (*ctrl_classes && V4L2_CTRL_ID2CLASS(**ctrl_classes) < ctrl_class)
		ctrl_classes++;
	/* no more classes */
	if (*ctrl_classes == NULL)
		return 0;
	pctrl = *ctrl_classes;
	/* find first ctrl within the class that is >= ID */
	while (*pctrl && *pctrl < id) pctrl++;
	if (*pctrl)
		return *pctrl;
	/* we are at the end of the controls of the current class. */
	/* continue with next class if available */
	ctrl_classes++;
	if (*ctrl_classes == NULL)
		return 0;
	return **ctrl_classes;
}


#define V4L2_CTRL_CLASS_USER		0x00980000	/* Old-style 'user' controls */
#define V4L2_CTRL_CLASS_MPEG		0x00990000	/* MPEG-compression controls */
#define V4L2_CID_USER_CLASS 		(V4L2_CTRL_CLASS_USER | 1)

#define V4L2_CID_MPEG_BASE 			(V4L2_CTRL_CLASS_MPEG | 0x900)
#define V4L2_CID_MPEG_CLASS 			(V4L2_CTRL_CLASS_MPEG | 1)
/*  MPEG streams, specific to multiplexed streams */

#define V4L2_CID_MPEG_STREAM_TYPE 		(V4L2_CID_MPEG_BASE+0)
enum v4l2_mpeg_stream_type {
	V4L2_MPEG_STREAM_TYPE_MPEG2_PS   = 0, /* MPEG-2 program stream */
	V4L2_MPEG_STREAM_TYPE_MPEG2_TS   = 1, /* MPEG-2 transport stream */
	V4L2_MPEG_STREAM_TYPE_MPEG1_SS   = 2, /* MPEG-1 system stream */
	V4L2_MPEG_STREAM_TYPE_MPEG2_DVD  = 3, /* MPEG-2 DVD-compatible stream */
	V4L2_MPEG_STREAM_TYPE_MPEG1_VCD  = 4, /* MPEG-1 VCD-compatible stream */
	V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD = 5, /* MPEG-2 SVCD-compatible stream */
};

#define V4L2_CID_MPEG_STREAM_VBI_FMT 		(V4L2_CID_MPEG_BASE+7)
enum v4l2_mpeg_stream_vbi_fmt {
	V4L2_MPEG_STREAM_VBI_FMT_NONE = 0,  /* No VBI in the MPEG stream */
	V4L2_MPEG_STREAM_VBI_FMT_IVTV = 1,  /* VBI in private packets, IVTV format */
};

#define V4L2_CID_MPEG_AUDIO_ENCODING 		(V4L2_CID_MPEG_BASE+101)
enum v4l2_mpeg_audio_encoding {
	V4L2_MPEG_AUDIO_ENCODING_LAYER_1 = 0,
	V4L2_MPEG_AUDIO_ENCODING_LAYER_2 = 1,
	V4L2_MPEG_AUDIO_ENCODING_LAYER_3 = 2,
	V4L2_MPEG_AUDIO_ENCODING_AAC     = 3,
	V4L2_MPEG_AUDIO_ENCODING_AC3     = 4,
};
#define V4L2_CID_MPEG_AUDIO_MODE 		(V4L2_CID_MPEG_BASE+105)
enum v4l2_mpeg_audio_mode {
	V4L2_MPEG_AUDIO_MODE_STEREO       = 0,
	V4L2_MPEG_AUDIO_MODE_JOINT_STEREO = 1,
	V4L2_MPEG_AUDIO_MODE_DUAL         = 2,
	V4L2_MPEG_AUDIO_MODE_MONO         = 3,
};
#define V4L2_CID_MPEG_AUDIO_AAC_BITRATE         (V4L2_CID_MPEG_BASE+110)

/*  MPEG video controls specific to multiplexed streams */
#define V4L2_CID_MPEG_VIDEO_ENCODING 		(V4L2_CID_MPEG_BASE+200)
enum v4l2_mpeg_video_encoding {
	V4L2_MPEG_VIDEO_ENCODING_MPEG_1     = 0,
	V4L2_MPEG_VIDEO_ENCODING_MPEG_2     = 1,
	V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC = 2,
};
#define V4L2_CID_MPEG_VIDEO_ASPECT 		(V4L2_CID_MPEG_BASE+201)
enum v4l2_mpeg_video_aspect {
	V4L2_MPEG_VIDEO_ASPECT_1x1     = 0,
	V4L2_MPEG_VIDEO_ASPECT_4x3     = 1,
	V4L2_MPEG_VIDEO_ASPECT_16x9    = 2,
	V4L2_MPEG_VIDEO_ASPECT_221x100 = 3,
};
#define V4L2_CID_MPEG_VIDEO_GOP_SIZE 		(V4L2_CID_MPEG_BASE+203)
#define V4L2_CID_MPEG_VIDEO_GOP_CLOSURE 	(V4L2_CID_MPEG_BASE+204)

#define V4L2_CID_MPEG_VIDEO_BITRATE_MODE 	(V4L2_CID_MPEG_BASE+206)
enum v4l2_mpeg_video_bitrate_mode {
	V4L2_MPEG_VIDEO_BITRATE_MODE_VBR = 0,
	V4L2_MPEG_VIDEO_BITRATE_MODE_CBR = 1,
};
#define V4L2_CID_MPEG_VIDEO_BITRATE 		(V4L2_CID_MPEG_BASE+207)

#endif


#ifndef V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP
#define V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP             (V4L2_CID_MPEG_BASE+350)
#endif

#ifndef V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP
#define V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP             (V4L2_CID_MPEG_BASE+351)
#endif

#ifndef V4L2_CID_MPEG_VIDEO_H264_I_PERIOD
#define V4L2_CID_MPEG_VIDEO_H264_I_PERIOD               (V4L2_CID_MPEG_BASE+358)
#endif

#ifndef V4L2_CID_MPEG_VIDEO_H264_LEVEL
#define V4L2_CID_MPEG_VIDEO_H264_LEVEL                  (V4L2_CID_MPEG_BASE+359)
enum v4l2_mpeg_video_h264_level {
        V4L2_MPEG_VIDEO_H264_LEVEL_1_0  = 0,
        V4L2_MPEG_VIDEO_H264_LEVEL_1B   = 1,
        V4L2_MPEG_VIDEO_H264_LEVEL_1_1  = 2,
        V4L2_MPEG_VIDEO_H264_LEVEL_1_2  = 3,
        V4L2_MPEG_VIDEO_H264_LEVEL_1_3  = 4,
        V4L2_MPEG_VIDEO_H264_LEVEL_2_0  = 5,
        V4L2_MPEG_VIDEO_H264_LEVEL_2_1  = 6,
        V4L2_MPEG_VIDEO_H264_LEVEL_2_2  = 7,
        V4L2_MPEG_VIDEO_H264_LEVEL_3_0  = 8,
        V4L2_MPEG_VIDEO_H264_LEVEL_3_1  = 9,
        V4L2_MPEG_VIDEO_H264_LEVEL_3_2  = 10,
        V4L2_MPEG_VIDEO_H264_LEVEL_4_0  = 11,
        V4L2_MPEG_VIDEO_H264_LEVEL_4_1  = 12,
        V4L2_MPEG_VIDEO_H264_LEVEL_4_2  = 13,
        V4L2_MPEG_VIDEO_H264_LEVEL_5_0  = 14,
        V4L2_MPEG_VIDEO_H264_LEVEL_5_1  = 15,
};
#endif

#ifndef V4L2_CID_MPEG_VIDEO_H264_PROFILE
#define V4L2_CID_MPEG_VIDEO_H264_PROFILE                (V4L2_CID_MPEG_BASE+363)
enum v4l2_mpeg_video_h264_profile {
        V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE                   = 0,
        V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE       = 1,
        V4L2_MPEG_VIDEO_H264_PROFILE_MAIN                       = 2,
        V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED                   = 3,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH                       = 4,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10                    = 5,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422                   = 6,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE        = 7,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA              = 8,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA             = 9,
        V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA             = 10,
        V4L2_MPEG_VIDEO_H264_PROFILE_CAVLC_444_INTRA            = 11,
        V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE          = 12,
        V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH              = 13,
        V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH_INTRA        = 14,
        V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH                = 15,
        V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH             = 16,
};
#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
/* Fill in a struct v4l2_queryctrl */
static int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 max, s32 step, s32 def)
{
	const char *name;

	//v4l2_ctrl_fill(qctrl->id, &name, &qctrl->type,
	//		&min, &max, &step, &def, &qctrl->flags);
	switch (qctrl->id) {
		case V4L2_CID_USER_CLASS: name = "User Controls"; break;
		case V4L2_CID_BRIGHTNESS: name = "Brightness"; break;
		case V4L2_CID_CONTRAST: name = "Contrast"; break;
		case V4L2_CID_SATURATION: name = "Saturation"; break;
		case V4L2_CID_HUE: name ="Hue"; break;
		case V4L2_CID_AUDIO_VOLUME: name = "Volume"; break;
		case V4L2_CID_AUDIO_BALANCE: name = "Balance"; break;
		case V4L2_CID_HFLIP: name = "Horizontal Flip"; break;
		case V4L2_CID_VFLIP: name = "Vertical Flip"; break;
		case V4L2_CID_HCENTER: name = "Horizontal Center"; break;
		case V4L2_CID_VCENTER: name = "Vertical Center"; break;

		case V4L2_CID_MPEG_CLASS: name = "Codec Controls"; break;
		case V4L2_CID_MPEG_STREAM_TYPE: name = "Stream Type"; break;
		case V4L2_CID_MPEG_STREAM_VBI_FMT: name = "Stream VBI Format"; break;
//		case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: name = "Audio Sampling Frequency"; break;
		case V4L2_CID_MPEG_AUDIO_ENCODING: name = "Audio Encoding"; break;
//		case V4L2_CID_MPEG_AUDIO_L2_BITRATE: name = "Audio Layer II Bitrate"; break;
		case V4L2_CID_MPEG_AUDIO_MODE: name = "Audio Stereo Mode"; break;
		case V4L2_CID_MPEG_AUDIO_AAC_BITRATE: name = "Audio AAC Bitrate"; break;
		case V4L2_CID_MPEG_VIDEO_ENCODING: name = "Video Encoding"; break;
		case V4L2_CID_MPEG_VIDEO_ASPECT: name = "Video Aspect"; break;
// 		case V4L2_CID_MPEG_VIDEO_B_FRAMES: name = "Video B Frames"; break;
		case V4L2_CID_MPEG_VIDEO_GOP_SIZE: name = "Video GOP Size"; break;
//		case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: name = "Video GOP Closure"; break;
//		case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: name = "Video Bitrate Mode"; break;
		case V4L2_CID_MPEG_VIDEO_BITRATE: name = "Video Bitrate"; break;
		case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: name = "H264 I-Frame QP Value"; break;
		case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: name = "H264 P-Frame QP Value"; break;
		case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: name = "H264 I-Frame Period"; break;
		case V4L2_CID_MPEG_VIDEO_H264_LEVEL: name = "H264 Level"; break;
		case V4L2_CID_MPEG_VIDEO_H264_PROFILE: name = "H264 Profile"; break;
		default: name = NULL; break;
	}

	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;
}

#endif

#ifndef list_first_entry
#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
static u32 v4l2_ctrl_next(const u32 *ctrl_classes[], u32 old_id)
{
	u32 id = 0;
	unsigned int i, j;
	if (ctrl_classes == NULL) return 0;
	if (!(old_id & V4L2_CTRL_FLAG_NEXT_CTRL))
		return old_id;
	old_id &= V4L2_CTRL_ID_MASK;
	for (i = 0; ctrl_classes[i]; i++) {
		for (j = 0; ctrl_classes[i][j]; j++) {
			u32 c = ctrl_classes[i][j];
			if (c > old_id && (c < id || id == 0))
				id = c;
		}
	}
	return id;
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) 
static int v4l2_ctrl_query_menu(struct v4l2_querymenu *qmenu, 
				struct v4l2_queryctrl *qctrl,
				const char * const *menu_items)
{
	//TODO: fill values
	return -EINVAL;
}
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
#include <media/v4l2-ctrls.h>
static int v4l2_ctrl_query_menu(struct v4l2_querymenu *qmenu, 
				struct v4l2_queryctrl *qctrl,
				const char * const *menu_items)
{
	int i;

	qmenu->reserved = 0;
	if (menu_items == NULL)
		menu_items = v4l2_ctrl_get_menu(qmenu->id);
	if (menu_items == NULL ||
	    (qctrl && (qmenu->index < qctrl->minimum || qmenu->index > qctrl->maximum)))
		return -EINVAL;
	for (i = 0; i < qmenu->index && menu_items[i]; i++) ;
	if (menu_items[i] == NULL || menu_items[i][0] == '\0')
		return -EINVAL;
	strscpy(qmenu->name, menu_items[qmenu->index], sizeof(qmenu->name));
	return 0;
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
static void v4l2_get_timestamp(struct timeval *tv)
{
	struct timespec ts;

	ktime_get_ts(&ts);
	tv->tv_sec = ts.tv_sec;
	tv->tv_usec = ts.tv_nsec / NSEC_PER_USEC;
}
#endif


#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
struct timeval {
	__kernel_old_time_t	tv_sec;		/* seconds */
	__kernel_suseconds_t	tv_usec;	/* microseconds */
};

static struct timeval ns_to_timeval(const s64 nsec)
{
	struct timespec64 ts = ns_to_timespec64(nsec);
	struct timeval tv;

	tv.tv_sec = ts.tv_sec;
	tv.tv_usec = (suseconds_t) ts.tv_nsec / 1000;

	return tv;
}

static s64 timeval_to_ns(struct timeval *tv)
{
	return (s64)tv->tv_sec * 1000000000 + tv->tv_usec * 1000;
}
#endif

#ifndef fallthrough
# define fallthrough                    do {} while (0)  /* fallthrough */
#endif

#endif /* S2253_COMPAT_H */
