18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// DVB USB compliant linux driver for Conexant USB reference design -
48c2ecf20Sopenharmony_ci// (analog part).
58c2ecf20Sopenharmony_ci//
68c2ecf20Sopenharmony_ci// Copyright (C) 2011, 2017, 2018
78c2ecf20Sopenharmony_ci//	Maciej S. Szmigiero (mail@maciej.szmigiero.name)
88c2ecf20Sopenharmony_ci//
98c2ecf20Sopenharmony_ci// In case there are new analog / DVB-T hybrid devices released in the market
108c2ecf20Sopenharmony_ci// using the same general design as Medion MD95700: a CX25840 video decoder
118c2ecf20Sopenharmony_ci// outputting a BT.656 stream to a USB bridge chip which then forwards it to
128c2ecf20Sopenharmony_ci// the host in isochronous USB packets this code should be made generic, with
138c2ecf20Sopenharmony_ci// board specific bits implemented via separate card structures.
148c2ecf20Sopenharmony_ci//
158c2ecf20Sopenharmony_ci// This is, however, unlikely as the Medion model was released
168c2ecf20Sopenharmony_ci// years ago (in 2005).
178c2ecf20Sopenharmony_ci//
188c2ecf20Sopenharmony_ci// TODO:
198c2ecf20Sopenharmony_ci//  * audio support,
208c2ecf20Sopenharmony_ci//  * finish radio support (requires audio of course),
218c2ecf20Sopenharmony_ci//  * VBI support,
228c2ecf20Sopenharmony_ci//  * controls support
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#include <linux/bitops.h>
258c2ecf20Sopenharmony_ci#include <linux/device.h>
268c2ecf20Sopenharmony_ci#include <linux/slab.h>
278c2ecf20Sopenharmony_ci#include <linux/string.h>
288c2ecf20Sopenharmony_ci#include <linux/ktime.h>
298c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
308c2ecf20Sopenharmony_ci#include <media/drv-intf/cx25840.h>
318c2ecf20Sopenharmony_ci#include <media/tuner.h>
328c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h>
338c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
348c2ecf20Sopenharmony_ci#include <media/v4l2-subdev.h>
358c2ecf20Sopenharmony_ci#include <media/videobuf2-vmalloc.h>
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#include "cxusb.h"
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic int cxusb_medion_v_queue_setup(struct vb2_queue *q,
408c2ecf20Sopenharmony_ci				      unsigned int *num_buffers,
418c2ecf20Sopenharmony_ci				      unsigned int *num_planes,
428c2ecf20Sopenharmony_ci				      unsigned int sizes[],
438c2ecf20Sopenharmony_ci				      struct device *alloc_devs[])
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
468c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
478c2ecf20Sopenharmony_ci	unsigned int size = cxdev->width * cxdev->height * 2;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	if (*num_planes > 0) {
508c2ecf20Sopenharmony_ci		if (*num_planes != 1)
518c2ecf20Sopenharmony_ci			return -EINVAL;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci		if (sizes[0] < size)
548c2ecf20Sopenharmony_ci			return -EINVAL;
558c2ecf20Sopenharmony_ci	} else {
568c2ecf20Sopenharmony_ci		*num_planes = 1;
578c2ecf20Sopenharmony_ci		sizes[0] = size;
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	return 0;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic int cxusb_medion_v_buf_init(struct vb2_buffer *vb)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
668c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "buffer init\n");
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	if (vb2_plane_size(vb, 0) < cxdev->width * cxdev->height * 2)
718c2ecf20Sopenharmony_ci		return -ENOMEM;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "buffer OK\n");
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	return 0;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic void cxusb_auxbuf_init(struct dvb_usb_device *dvbdev,
798c2ecf20Sopenharmony_ci			      struct cxusb_medion_auxbuf *auxbuf,
808c2ecf20Sopenharmony_ci			      u8 *buf, unsigned int len)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, AUXB, "initializing auxbuf of len %u\n", len);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	auxbuf->buf = buf;
858c2ecf20Sopenharmony_ci	auxbuf->len = len;
868c2ecf20Sopenharmony_ci	auxbuf->paylen = 0;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic void cxusb_auxbuf_head_trim(struct dvb_usb_device *dvbdev,
908c2ecf20Sopenharmony_ci				   struct cxusb_medion_auxbuf *auxbuf,
918c2ecf20Sopenharmony_ci				   unsigned int pos)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	if (pos == 0)
948c2ecf20Sopenharmony_ci		return;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (WARN_ON(pos > auxbuf->paylen))
978c2ecf20Sopenharmony_ci		return;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, AUXB,
1008c2ecf20Sopenharmony_ci		      "trimming auxbuf len by %u to %u\n",
1018c2ecf20Sopenharmony_ci		      pos, auxbuf->paylen - pos);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	memmove(auxbuf->buf, auxbuf->buf + pos, auxbuf->paylen - pos);
1048c2ecf20Sopenharmony_ci	auxbuf->paylen -= pos;
1058c2ecf20Sopenharmony_ci}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistatic unsigned int cxusb_auxbuf_paylen(struct cxusb_medion_auxbuf *auxbuf)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	return auxbuf->paylen;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic bool cxusb_auxbuf_make_space(struct dvb_usb_device *dvbdev,
1138c2ecf20Sopenharmony_ci				    struct cxusb_medion_auxbuf *auxbuf,
1148c2ecf20Sopenharmony_ci				    unsigned int howmuch)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	unsigned int freespace;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (WARN_ON(howmuch >= auxbuf->len))
1198c2ecf20Sopenharmony_ci		howmuch = auxbuf->len - 1;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	freespace = auxbuf->len - cxusb_auxbuf_paylen(auxbuf);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, AUXB, "freespace is %u\n", freespace);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	if (freespace >= howmuch)
1268c2ecf20Sopenharmony_ci		return true;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	howmuch -= freespace;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, AUXB, "will overwrite %u bytes of buffer\n",
1318c2ecf20Sopenharmony_ci		      howmuch);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	cxusb_auxbuf_head_trim(dvbdev, auxbuf, howmuch);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	return false;
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci/* returns false if some data was overwritten */
1398c2ecf20Sopenharmony_cistatic bool cxusb_auxbuf_append_urb(struct dvb_usb_device *dvbdev,
1408c2ecf20Sopenharmony_ci				    struct cxusb_medion_auxbuf *auxbuf,
1418c2ecf20Sopenharmony_ci				    struct urb *urb)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	unsigned long len;
1448c2ecf20Sopenharmony_ci	int i;
1458c2ecf20Sopenharmony_ci	bool ret;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	for (i = 0, len = 0; i < urb->number_of_packets; i++)
1488c2ecf20Sopenharmony_ci		len += urb->iso_frame_desc[i].actual_length;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	ret = cxusb_auxbuf_make_space(dvbdev, auxbuf, len);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	for (i = 0; i < urb->number_of_packets; i++) {
1538c2ecf20Sopenharmony_ci		unsigned int to_copy;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci		to_copy = urb->iso_frame_desc[i].actual_length;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		memcpy(auxbuf->buf + auxbuf->paylen, urb->transfer_buffer +
1588c2ecf20Sopenharmony_ci		       urb->iso_frame_desc[i].offset, to_copy);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci		auxbuf->paylen += to_copy;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	return ret;
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic bool cxusb_auxbuf_copy(struct cxusb_medion_auxbuf *auxbuf,
1678c2ecf20Sopenharmony_ci			      unsigned int pos, unsigned char *dest,
1688c2ecf20Sopenharmony_ci			      unsigned int len)
1698c2ecf20Sopenharmony_ci{
1708c2ecf20Sopenharmony_ci	if (pos + len > auxbuf->paylen)
1718c2ecf20Sopenharmony_ci		return false;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	memcpy(dest, auxbuf->buf + pos, len);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	return true;
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic bool cxusb_medion_cf_refc_fld_chg(struct dvb_usb_device *dvbdev,
1798c2ecf20Sopenharmony_ci					 struct cxusb_bt656_params *bt656,
1808c2ecf20Sopenharmony_ci					 bool firstfield,
1818c2ecf20Sopenharmony_ci					 unsigned int maxlines,
1828c2ecf20Sopenharmony_ci					 unsigned int maxlinesamples,
1838c2ecf20Sopenharmony_ci					 unsigned char buf[4])
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	bool firstfield_code = (buf[3] & CXUSB_BT656_FIELD_MASK) ==
1868c2ecf20Sopenharmony_ci		CXUSB_BT656_FIELD_1;
1878c2ecf20Sopenharmony_ci	unsigned int remlines;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (bt656->line == 0 || firstfield == firstfield_code)
1908c2ecf20Sopenharmony_ci		return false;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	if (bt656->fmode == LINE_SAMPLES) {
1938c2ecf20Sopenharmony_ci		unsigned int remsamples = maxlinesamples -
1948c2ecf20Sopenharmony_ci			bt656->linesamples;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656,
1978c2ecf20Sopenharmony_ci			      "field %c after line %u field change\n",
1988c2ecf20Sopenharmony_ci			      firstfield ? '1' : '2', bt656->line);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		if (bt656->buf && remsamples > 0) {
2018c2ecf20Sopenharmony_ci			memset(bt656->buf, 0, remsamples);
2028c2ecf20Sopenharmony_ci			bt656->buf += remsamples;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci			cxusb_vprintk(dvbdev, BT656,
2058c2ecf20Sopenharmony_ci				      "field %c line %u %u samples still remaining (of %u)\n",
2068c2ecf20Sopenharmony_ci				      firstfield ? '1' : '2',
2078c2ecf20Sopenharmony_ci				      bt656->line, remsamples,
2088c2ecf20Sopenharmony_ci				      maxlinesamples);
2098c2ecf20Sopenharmony_ci		}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci		bt656->line++;
2128c2ecf20Sopenharmony_ci	}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	remlines = maxlines - bt656->line;
2158c2ecf20Sopenharmony_ci	if (bt656->buf && remlines > 0) {
2168c2ecf20Sopenharmony_ci		memset(bt656->buf, 0, remlines * maxlinesamples);
2178c2ecf20Sopenharmony_ci		bt656->buf += remlines * maxlinesamples;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656,
2208c2ecf20Sopenharmony_ci			      "field %c %u lines still remaining (of %u)\n",
2218c2ecf20Sopenharmony_ci			      firstfield ? '1' : '2', remlines,
2228c2ecf20Sopenharmony_ci			      maxlines);
2238c2ecf20Sopenharmony_ci	}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	return true;
2268c2ecf20Sopenharmony_ci}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_cistatic void cxusb_medion_cf_refc_start_sch(struct dvb_usb_device *dvbdev,
2298c2ecf20Sopenharmony_ci					   struct cxusb_bt656_params *bt656,
2308c2ecf20Sopenharmony_ci					   bool firstfield,
2318c2ecf20Sopenharmony_ci					   unsigned char buf[4])
2328c2ecf20Sopenharmony_ci{
2338c2ecf20Sopenharmony_ci	bool firstfield_code = (buf[3] & CXUSB_BT656_FIELD_MASK) ==
2348c2ecf20Sopenharmony_ci		CXUSB_BT656_FIELD_1;
2358c2ecf20Sopenharmony_ci	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
2368c2ecf20Sopenharmony_ci		CXUSB_BT656_SEAV_SAV;
2378c2ecf20Sopenharmony_ci	bool vbi_code = (buf[3] & CXUSB_BT656_VBI_MASK) ==
2388c2ecf20Sopenharmony_ci		CXUSB_BT656_VBI_ON;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	if (!sav_code || firstfield != firstfield_code)
2418c2ecf20Sopenharmony_ci		return;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	if (!vbi_code) {
2448c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656, "line start @ pos %u\n",
2458c2ecf20Sopenharmony_ci			      bt656->pos);
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci		bt656->linesamples = 0;
2488c2ecf20Sopenharmony_ci		bt656->fmode = LINE_SAMPLES;
2498c2ecf20Sopenharmony_ci	} else {
2508c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656, "VBI start @ pos %u\n",
2518c2ecf20Sopenharmony_ci			      bt656->pos);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci		bt656->fmode = VBI_SAMPLES;
2548c2ecf20Sopenharmony_ci	}
2558c2ecf20Sopenharmony_ci}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_cistatic void cxusb_medion_cf_refc_line_smpl(struct dvb_usb_device *dvbdev,
2588c2ecf20Sopenharmony_ci					   struct cxusb_bt656_params *bt656,
2598c2ecf20Sopenharmony_ci					   bool firstfield,
2608c2ecf20Sopenharmony_ci					   unsigned int maxlinesamples,
2618c2ecf20Sopenharmony_ci					   unsigned char buf[4])
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
2648c2ecf20Sopenharmony_ci		CXUSB_BT656_SEAV_SAV;
2658c2ecf20Sopenharmony_ci	unsigned int remsamples;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	if (sav_code)
2688c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656,
2698c2ecf20Sopenharmony_ci			      "SAV in line samples @ line %u, pos %u\n",
2708c2ecf20Sopenharmony_ci			      bt656->line, bt656->pos);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	remsamples = maxlinesamples - bt656->linesamples;
2738c2ecf20Sopenharmony_ci	if (bt656->buf && remsamples > 0) {
2748c2ecf20Sopenharmony_ci		memset(bt656->buf, 0, remsamples);
2758c2ecf20Sopenharmony_ci		bt656->buf += remsamples;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656,
2788c2ecf20Sopenharmony_ci			      "field %c line %u %u samples still remaining (of %u)\n",
2798c2ecf20Sopenharmony_ci			      firstfield ? '1' : '2', bt656->line, remsamples,
2808c2ecf20Sopenharmony_ci			      maxlinesamples);
2818c2ecf20Sopenharmony_ci	}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	bt656->fmode = START_SEARCH;
2848c2ecf20Sopenharmony_ci	bt656->line++;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cistatic void cxusb_medion_cf_refc_vbi_smpl(struct dvb_usb_device *dvbdev,
2888c2ecf20Sopenharmony_ci					  struct cxusb_bt656_params *bt656,
2898c2ecf20Sopenharmony_ci					  unsigned char buf[4])
2908c2ecf20Sopenharmony_ci{
2918c2ecf20Sopenharmony_ci	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
2928c2ecf20Sopenharmony_ci		CXUSB_BT656_SEAV_SAV;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	if (sav_code)
2958c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656, "SAV in VBI samples @ pos %u\n",
2968c2ecf20Sopenharmony_ci			      bt656->pos);
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	bt656->fmode = START_SEARCH;
2998c2ecf20Sopenharmony_ci}
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci/* returns whether the whole 4-byte code should be skipped in the buffer */
3028c2ecf20Sopenharmony_cistatic bool cxusb_medion_cf_ref_code(struct dvb_usb_device *dvbdev,
3038c2ecf20Sopenharmony_ci				     struct cxusb_bt656_params *bt656,
3048c2ecf20Sopenharmony_ci				     bool firstfield,
3058c2ecf20Sopenharmony_ci				     unsigned int maxlines,
3068c2ecf20Sopenharmony_ci				     unsigned int maxlinesamples,
3078c2ecf20Sopenharmony_ci				     unsigned char buf[4])
3088c2ecf20Sopenharmony_ci{
3098c2ecf20Sopenharmony_ci	if (bt656->fmode == START_SEARCH) {
3108c2ecf20Sopenharmony_ci		cxusb_medion_cf_refc_start_sch(dvbdev, bt656, firstfield, buf);
3118c2ecf20Sopenharmony_ci	} else if (bt656->fmode == LINE_SAMPLES) {
3128c2ecf20Sopenharmony_ci		cxusb_medion_cf_refc_line_smpl(dvbdev, bt656, firstfield,
3138c2ecf20Sopenharmony_ci					       maxlinesamples, buf);
3148c2ecf20Sopenharmony_ci		return false;
3158c2ecf20Sopenharmony_ci	} else if (bt656->fmode == VBI_SAMPLES) {
3168c2ecf20Sopenharmony_ci		cxusb_medion_cf_refc_vbi_smpl(dvbdev, bt656, buf);
3178c2ecf20Sopenharmony_ci		return false;
3188c2ecf20Sopenharmony_ci	}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	return true;
3218c2ecf20Sopenharmony_ci}
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_cistatic bool cxusb_medion_cs_start_sch(struct dvb_usb_device *dvbdev,
3248c2ecf20Sopenharmony_ci				      struct cxusb_medion_auxbuf *auxbuf,
3258c2ecf20Sopenharmony_ci				      struct cxusb_bt656_params *bt656,
3268c2ecf20Sopenharmony_ci				      unsigned int maxlinesamples)
3278c2ecf20Sopenharmony_ci{
3288c2ecf20Sopenharmony_ci	unsigned char buf[64];
3298c2ecf20Sopenharmony_ci	unsigned int idx;
3308c2ecf20Sopenharmony_ci	unsigned int tocheck = clamp_t(size_t, maxlinesamples / 4, 3,
3318c2ecf20Sopenharmony_ci				       sizeof(buf));
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1, buf, tocheck))
3348c2ecf20Sopenharmony_ci		return false;
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_ci	for (idx = 0; idx <= tocheck - 3; idx++)
3378c2ecf20Sopenharmony_ci		if (memcmp(buf + idx, CXUSB_BT656_PREAMBLE, 3) == 0) {
3388c2ecf20Sopenharmony_ci			bt656->pos += (1 + idx);
3398c2ecf20Sopenharmony_ci			return true;
3408c2ecf20Sopenharmony_ci		}
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, BT656, "line %u early start, pos %u\n",
3438c2ecf20Sopenharmony_ci		      bt656->line, bt656->pos);
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	bt656->linesamples = 0;
3468c2ecf20Sopenharmony_ci	bt656->fmode = LINE_SAMPLES;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	return true;
3498c2ecf20Sopenharmony_ci}
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_cistatic void cxusb_medion_cs_line_smpl(struct cxusb_bt656_params *bt656,
3528c2ecf20Sopenharmony_ci				      unsigned int maxlinesamples,
3538c2ecf20Sopenharmony_ci				      unsigned char val)
3548c2ecf20Sopenharmony_ci{
3558c2ecf20Sopenharmony_ci	if (bt656->buf)
3568c2ecf20Sopenharmony_ci		*(bt656->buf++) = val;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	bt656->linesamples++;
3598c2ecf20Sopenharmony_ci	bt656->pos++;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	if (bt656->linesamples >= maxlinesamples) {
3628c2ecf20Sopenharmony_ci		bt656->fmode = START_SEARCH;
3638c2ecf20Sopenharmony_ci		bt656->line++;
3648c2ecf20Sopenharmony_ci	}
3658c2ecf20Sopenharmony_ci}
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_cistatic bool cxusb_medion_copy_samples(struct dvb_usb_device *dvbdev,
3688c2ecf20Sopenharmony_ci				      struct cxusb_medion_auxbuf *auxbuf,
3698c2ecf20Sopenharmony_ci				      struct cxusb_bt656_params *bt656,
3708c2ecf20Sopenharmony_ci				      unsigned int maxlinesamples,
3718c2ecf20Sopenharmony_ci				      unsigned char val)
3728c2ecf20Sopenharmony_ci{
3738c2ecf20Sopenharmony_ci	if (bt656->fmode == START_SEARCH && bt656->line > 0)
3748c2ecf20Sopenharmony_ci		return cxusb_medion_cs_start_sch(dvbdev, auxbuf, bt656,
3758c2ecf20Sopenharmony_ci						 maxlinesamples);
3768c2ecf20Sopenharmony_ci	else if (bt656->fmode == LINE_SAMPLES)
3778c2ecf20Sopenharmony_ci		cxusb_medion_cs_line_smpl(bt656, maxlinesamples, val);
3788c2ecf20Sopenharmony_ci	else /* TODO: copy VBI samples */
3798c2ecf20Sopenharmony_ci		bt656->pos++;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	return true;
3828c2ecf20Sopenharmony_ci}
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_cistatic bool cxusb_medion_copy_field(struct dvb_usb_device *dvbdev,
3858c2ecf20Sopenharmony_ci				    struct cxusb_medion_auxbuf *auxbuf,
3868c2ecf20Sopenharmony_ci				    struct cxusb_bt656_params *bt656,
3878c2ecf20Sopenharmony_ci				    bool firstfield,
3888c2ecf20Sopenharmony_ci				    unsigned int maxlines,
3898c2ecf20Sopenharmony_ci				    unsigned int maxlinesmpls)
3908c2ecf20Sopenharmony_ci{
3918c2ecf20Sopenharmony_ci	while (bt656->line < maxlines) {
3928c2ecf20Sopenharmony_ci		unsigned char val;
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci		if (!cxusb_auxbuf_copy(auxbuf, bt656->pos, &val, 1))
3958c2ecf20Sopenharmony_ci			break;
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci		if (val == CXUSB_BT656_PREAMBLE[0]) {
3988c2ecf20Sopenharmony_ci			unsigned char buf[4];
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci			buf[0] = val;
4018c2ecf20Sopenharmony_ci			if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1,
4028c2ecf20Sopenharmony_ci					       buf + 1, 3))
4038c2ecf20Sopenharmony_ci				break;
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci			if (buf[1] == CXUSB_BT656_PREAMBLE[1] &&
4068c2ecf20Sopenharmony_ci			    buf[2] == CXUSB_BT656_PREAMBLE[2]) {
4078c2ecf20Sopenharmony_ci				/*
4088c2ecf20Sopenharmony_ci				 * is this a field change?
4098c2ecf20Sopenharmony_ci				 * if so, terminate copying the current field
4108c2ecf20Sopenharmony_ci				 */
4118c2ecf20Sopenharmony_ci				if (cxusb_medion_cf_refc_fld_chg(dvbdev,
4128c2ecf20Sopenharmony_ci								 bt656,
4138c2ecf20Sopenharmony_ci								 firstfield,
4148c2ecf20Sopenharmony_ci								 maxlines,
4158c2ecf20Sopenharmony_ci								 maxlinesmpls,
4168c2ecf20Sopenharmony_ci								 buf))
4178c2ecf20Sopenharmony_ci					return true;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci				if (cxusb_medion_cf_ref_code(dvbdev, bt656,
4208c2ecf20Sopenharmony_ci							     firstfield,
4218c2ecf20Sopenharmony_ci							     maxlines,
4228c2ecf20Sopenharmony_ci							     maxlinesmpls,
4238c2ecf20Sopenharmony_ci							     buf))
4248c2ecf20Sopenharmony_ci					bt656->pos += 4;
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci				continue;
4278c2ecf20Sopenharmony_ci			}
4288c2ecf20Sopenharmony_ci		}
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci		if (!cxusb_medion_copy_samples(dvbdev, auxbuf, bt656,
4318c2ecf20Sopenharmony_ci					       maxlinesmpls, val))
4328c2ecf20Sopenharmony_ci			break;
4338c2ecf20Sopenharmony_ci	}
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	if (bt656->line < maxlines) {
4368c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, BT656,
4378c2ecf20Sopenharmony_ci			      "end of buffer pos = %u, line = %u\n",
4388c2ecf20Sopenharmony_ci			      bt656->pos, bt656->line);
4398c2ecf20Sopenharmony_ci		return false;
4408c2ecf20Sopenharmony_ci	}
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	return true;
4438c2ecf20Sopenharmony_ci}
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_cistatic bool cxusb_medion_v_process_auxbuf(struct cxusb_medion_dev *cxdev,
4468c2ecf20Sopenharmony_ci					  bool reset)
4478c2ecf20Sopenharmony_ci{
4488c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
4498c2ecf20Sopenharmony_ci	struct cxusb_bt656_params *bt656 = &cxdev->bt656;
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_ci	/*
4528c2ecf20Sopenharmony_ci	 * if this is a new frame
4538c2ecf20Sopenharmony_ci	 * fetch a buffer from list
4548c2ecf20Sopenharmony_ci	 */
4558c2ecf20Sopenharmony_ci	if (bt656->mode == NEW_FRAME) {
4568c2ecf20Sopenharmony_ci		if (!list_empty(&cxdev->buflist)) {
4578c2ecf20Sopenharmony_ci			cxdev->vbuf =
4588c2ecf20Sopenharmony_ci				list_first_entry(&cxdev->buflist,
4598c2ecf20Sopenharmony_ci						 struct cxusb_medion_vbuffer,
4608c2ecf20Sopenharmony_ci						 list);
4618c2ecf20Sopenharmony_ci			list_del(&cxdev->vbuf->list);
4628c2ecf20Sopenharmony_ci		} else {
4638c2ecf20Sopenharmony_ci			dev_warn(&dvbdev->udev->dev, "no free buffers\n");
4648c2ecf20Sopenharmony_ci		}
4658c2ecf20Sopenharmony_ci	}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	if (bt656->mode == NEW_FRAME || reset) {
4688c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, URB, "will copy field 1\n");
4698c2ecf20Sopenharmony_ci		bt656->pos = 0;
4708c2ecf20Sopenharmony_ci		bt656->mode = FIRST_FIELD;
4718c2ecf20Sopenharmony_ci		bt656->fmode = START_SEARCH;
4728c2ecf20Sopenharmony_ci		bt656->line = 0;
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci		if (cxdev->vbuf) {
4758c2ecf20Sopenharmony_ci			cxdev->vbuf->vb2.vb2_buf.timestamp = ktime_get_ns();
4768c2ecf20Sopenharmony_ci			bt656->buf = vb2_plane_vaddr(&cxdev->vbuf->vb2.vb2_buf,
4778c2ecf20Sopenharmony_ci						     0);
4788c2ecf20Sopenharmony_ci		}
4798c2ecf20Sopenharmony_ci	}
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	if (bt656->mode == FIRST_FIELD) {
4828c2ecf20Sopenharmony_ci		if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
4838c2ecf20Sopenharmony_ci					     true, cxdev->height / 2,
4848c2ecf20Sopenharmony_ci					     cxdev->width * 2))
4858c2ecf20Sopenharmony_ci			return false;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci		/*
4888c2ecf20Sopenharmony_ci		 * do not trim buffer there in case
4898c2ecf20Sopenharmony_ci		 * we need to reset the search later
4908c2ecf20Sopenharmony_ci		 */
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, URB, "will copy field 2\n");
4938c2ecf20Sopenharmony_ci		bt656->mode = SECOND_FIELD;
4948c2ecf20Sopenharmony_ci		bt656->fmode = START_SEARCH;
4958c2ecf20Sopenharmony_ci		bt656->line = 0;
4968c2ecf20Sopenharmony_ci	}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci	if (bt656->mode == SECOND_FIELD) {
4998c2ecf20Sopenharmony_ci		if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
5008c2ecf20Sopenharmony_ci					     false, cxdev->height / 2,
5018c2ecf20Sopenharmony_ci					     cxdev->width * 2))
5028c2ecf20Sopenharmony_ci			return false;
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci		cxusb_auxbuf_head_trim(dvbdev, &cxdev->auxbuf, bt656->pos);
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci		bt656->mode = NEW_FRAME;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci		if (cxdev->vbuf) {
5098c2ecf20Sopenharmony_ci			vb2_set_plane_payload(&cxdev->vbuf->vb2.vb2_buf, 0,
5108c2ecf20Sopenharmony_ci					      cxdev->width * cxdev->height * 2);
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci			cxdev->vbuf->vb2.field = cxdev->field_order;
5138c2ecf20Sopenharmony_ci			cxdev->vbuf->vb2.sequence = cxdev->vbuf_sequence++;
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci			vb2_buffer_done(&cxdev->vbuf->vb2.vb2_buf,
5168c2ecf20Sopenharmony_ci					VB2_BUF_STATE_DONE);
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci			cxdev->vbuf = NULL;
5198c2ecf20Sopenharmony_ci			cxdev->bt656.buf = NULL;
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_ci			cxusb_vprintk(dvbdev, URB, "frame done\n");
5228c2ecf20Sopenharmony_ci		} else {
5238c2ecf20Sopenharmony_ci			cxusb_vprintk(dvbdev, URB, "frame skipped\n");
5248c2ecf20Sopenharmony_ci			cxdev->vbuf_sequence++;
5258c2ecf20Sopenharmony_ci		}
5268c2ecf20Sopenharmony_ci	}
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	return true;
5298c2ecf20Sopenharmony_ci}
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_cistatic bool cxusb_medion_v_complete_handle_urb(struct cxusb_medion_dev *cxdev,
5328c2ecf20Sopenharmony_ci					       bool *auxbuf_reset)
5338c2ecf20Sopenharmony_ci{
5348c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
5358c2ecf20Sopenharmony_ci	unsigned int urbn;
5368c2ecf20Sopenharmony_ci	struct urb *urb;
5378c2ecf20Sopenharmony_ci	int ret;
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	*auxbuf_reset = false;
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	urbn = cxdev->nexturb;
5428c2ecf20Sopenharmony_ci	if (!test_bit(urbn, &cxdev->urbcomplete))
5438c2ecf20Sopenharmony_ci		return false;
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci	clear_bit(urbn, &cxdev->urbcomplete);
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ci	do {
5488c2ecf20Sopenharmony_ci		cxdev->nexturb++;
5498c2ecf20Sopenharmony_ci		cxdev->nexturb %= CXUSB_VIDEO_URBS;
5508c2ecf20Sopenharmony_ci		urb = cxdev->streamurbs[cxdev->nexturb];
5518c2ecf20Sopenharmony_ci	} while (!urb);
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	urb = cxdev->streamurbs[urbn];
5548c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, URB, "URB %u status = %d\n", urbn, urb->status);
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	if (urb->status == 0 || urb->status == -EXDEV) {
5578c2ecf20Sopenharmony_ci		int i;
5588c2ecf20Sopenharmony_ci		unsigned long len;
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci		for (i = 0, len = 0; i < urb->number_of_packets; i++)
5618c2ecf20Sopenharmony_ci			len += urb->iso_frame_desc[i].actual_length;
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, URB, "URB %u data len = %lu\n", urbn,
5648c2ecf20Sopenharmony_ci			      len);
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci		if (len > 0) {
5678c2ecf20Sopenharmony_ci			cxusb_vprintk(dvbdev, URB, "appending URB\n");
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci			/*
5708c2ecf20Sopenharmony_ci			 * append new data to auxbuf while
5718c2ecf20Sopenharmony_ci			 * overwriting old data if necessary
5728c2ecf20Sopenharmony_ci			 *
5738c2ecf20Sopenharmony_ci			 * if any overwrite happens then we can no
5748c2ecf20Sopenharmony_ci			 * longer rely on consistency of the whole
5758c2ecf20Sopenharmony_ci			 * data so let's start again the current
5768c2ecf20Sopenharmony_ci			 * auxbuf frame assembling process from
5778c2ecf20Sopenharmony_ci			 * the beginning
5788c2ecf20Sopenharmony_ci			 */
5798c2ecf20Sopenharmony_ci			*auxbuf_reset =
5808c2ecf20Sopenharmony_ci				!cxusb_auxbuf_append_urb(dvbdev,
5818c2ecf20Sopenharmony_ci							 &cxdev->auxbuf,
5828c2ecf20Sopenharmony_ci							 urb);
5838c2ecf20Sopenharmony_ci		}
5848c2ecf20Sopenharmony_ci	}
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, URB, "URB %u resubmit\n", urbn);
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci	ret = usb_submit_urb(urb, GFP_KERNEL);
5898c2ecf20Sopenharmony_ci	if (ret != 0)
5908c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
5918c2ecf20Sopenharmony_ci			"unable to resubmit URB %u (%d), you'll have to restart streaming\n",
5928c2ecf20Sopenharmony_ci			urbn, ret);
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	/* next URB is complete already? reschedule us then to handle it */
5958c2ecf20Sopenharmony_ci	return test_bit(cxdev->nexturb, &cxdev->urbcomplete);
5968c2ecf20Sopenharmony_ci}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_cistatic void cxusb_medion_v_complete_work(struct work_struct *work)
5998c2ecf20Sopenharmony_ci{
6008c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = container_of(work,
6018c2ecf20Sopenharmony_ci						      struct cxusb_medion_dev,
6028c2ecf20Sopenharmony_ci						      urbwork);
6038c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
6048c2ecf20Sopenharmony_ci	bool auxbuf_reset;
6058c2ecf20Sopenharmony_ci	bool reschedule;
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	mutex_lock(cxdev->videodev->lock);
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, URB, "worker called, stop_streaming = %d\n",
6108c2ecf20Sopenharmony_ci		      (int)cxdev->stop_streaming);
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci	if (cxdev->stop_streaming)
6138c2ecf20Sopenharmony_ci		goto unlock;
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	reschedule = cxusb_medion_v_complete_handle_urb(cxdev, &auxbuf_reset);
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_ci	if (cxusb_medion_v_process_auxbuf(cxdev, auxbuf_reset))
6188c2ecf20Sopenharmony_ci		/* reschedule us until auxbuf no longer can produce any frame */
6198c2ecf20Sopenharmony_ci		reschedule = true;
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ci	if (reschedule) {
6228c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, URB, "rescheduling worker\n");
6238c2ecf20Sopenharmony_ci		schedule_work(&cxdev->urbwork);
6248c2ecf20Sopenharmony_ci	}
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ciunlock:
6278c2ecf20Sopenharmony_ci	mutex_unlock(cxdev->videodev->lock);
6288c2ecf20Sopenharmony_ci}
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_cistatic void cxusb_medion_v_complete(struct urb *u)
6318c2ecf20Sopenharmony_ci{
6328c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = u->context;
6338c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
6348c2ecf20Sopenharmony_ci	unsigned int i;
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
6378c2ecf20Sopenharmony_ci		if (cxdev->streamurbs[i] == u)
6388c2ecf20Sopenharmony_ci			break;
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci	if (i >= CXUSB_VIDEO_URBS) {
6418c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
6428c2ecf20Sopenharmony_ci			"complete on unknown URB\n");
6438c2ecf20Sopenharmony_ci		return;
6448c2ecf20Sopenharmony_ci	}
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, URB, "URB %u complete\n", i);
6478c2ecf20Sopenharmony_ci
6488c2ecf20Sopenharmony_ci	set_bit(i, &cxdev->urbcomplete);
6498c2ecf20Sopenharmony_ci	schedule_work(&cxdev->urbwork);
6508c2ecf20Sopenharmony_ci}
6518c2ecf20Sopenharmony_ci
6528c2ecf20Sopenharmony_cistatic void cxusb_medion_urbs_free(struct cxusb_medion_dev *cxdev)
6538c2ecf20Sopenharmony_ci{
6548c2ecf20Sopenharmony_ci	unsigned int i;
6558c2ecf20Sopenharmony_ci
6568c2ecf20Sopenharmony_ci	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
6578c2ecf20Sopenharmony_ci		if (cxdev->streamurbs[i]) {
6588c2ecf20Sopenharmony_ci			kfree(cxdev->streamurbs[i]->transfer_buffer);
6598c2ecf20Sopenharmony_ci			usb_free_urb(cxdev->streamurbs[i]);
6608c2ecf20Sopenharmony_ci			cxdev->streamurbs[i] = NULL;
6618c2ecf20Sopenharmony_ci		}
6628c2ecf20Sopenharmony_ci}
6638c2ecf20Sopenharmony_ci
6648c2ecf20Sopenharmony_cistatic void cxusb_medion_return_buffers(struct cxusb_medion_dev *cxdev,
6658c2ecf20Sopenharmony_ci					bool requeue)
6668c2ecf20Sopenharmony_ci{
6678c2ecf20Sopenharmony_ci	struct cxusb_medion_vbuffer *vbuf, *vbuf_tmp;
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ci	list_for_each_entry_safe(vbuf, vbuf_tmp, &cxdev->buflist,
6708c2ecf20Sopenharmony_ci				 list) {
6718c2ecf20Sopenharmony_ci		list_del(&vbuf->list);
6728c2ecf20Sopenharmony_ci		vb2_buffer_done(&vbuf->vb2.vb2_buf,
6738c2ecf20Sopenharmony_ci				requeue ? VB2_BUF_STATE_QUEUED :
6748c2ecf20Sopenharmony_ci				VB2_BUF_STATE_ERROR);
6758c2ecf20Sopenharmony_ci	}
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_ci	if (cxdev->vbuf) {
6788c2ecf20Sopenharmony_ci		vb2_buffer_done(&cxdev->vbuf->vb2.vb2_buf,
6798c2ecf20Sopenharmony_ci				requeue ? VB2_BUF_STATE_QUEUED :
6808c2ecf20Sopenharmony_ci				VB2_BUF_STATE_ERROR);
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_ci		cxdev->vbuf = NULL;
6838c2ecf20Sopenharmony_ci		cxdev->bt656.buf = NULL;
6848c2ecf20Sopenharmony_ci	}
6858c2ecf20Sopenharmony_ci}
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_cistatic int cxusb_medion_v_ss_auxbuf_alloc(struct cxusb_medion_dev *cxdev,
6888c2ecf20Sopenharmony_ci					  int *npackets)
6898c2ecf20Sopenharmony_ci{
6908c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
6918c2ecf20Sopenharmony_ci	u8 *buf;
6928c2ecf20Sopenharmony_ci	unsigned int framelen, urblen, auxbuflen;
6938c2ecf20Sopenharmony_ci
6948c2ecf20Sopenharmony_ci	framelen = (cxdev->width * 2 + 4 + 4) *
6958c2ecf20Sopenharmony_ci		(cxdev->height + 50 /* VBI lines */);
6968c2ecf20Sopenharmony_ci
6978c2ecf20Sopenharmony_ci	/*
6988c2ecf20Sopenharmony_ci	 * try to fit a whole frame into each URB, as long as doing so
6998c2ecf20Sopenharmony_ci	 * does not require very high order memory allocations
7008c2ecf20Sopenharmony_ci	 */
7018c2ecf20Sopenharmony_ci	BUILD_BUG_ON(CXUSB_VIDEO_URB_MAX_SIZE / CXUSB_VIDEO_PKT_SIZE >
7028c2ecf20Sopenharmony_ci		     CXUSB_VIDEO_MAX_FRAME_PKTS);
7038c2ecf20Sopenharmony_ci	*npackets = min_t(int, (framelen + CXUSB_VIDEO_PKT_SIZE - 1) /
7048c2ecf20Sopenharmony_ci			  CXUSB_VIDEO_PKT_SIZE,
7058c2ecf20Sopenharmony_ci			  CXUSB_VIDEO_URB_MAX_SIZE / CXUSB_VIDEO_PKT_SIZE);
7068c2ecf20Sopenharmony_ci	urblen = *npackets * CXUSB_VIDEO_PKT_SIZE;
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, URB,
7098c2ecf20Sopenharmony_ci		      "each URB will have %d packets for total of %u bytes (%u x %u @ %u)\n",
7108c2ecf20Sopenharmony_ci		      *npackets, urblen, (unsigned int)cxdev->width,
7118c2ecf20Sopenharmony_ci		      (unsigned int)cxdev->height, framelen);
7128c2ecf20Sopenharmony_ci
7138c2ecf20Sopenharmony_ci	auxbuflen = framelen + urblen;
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	buf = vmalloc(auxbuflen);
7168c2ecf20Sopenharmony_ci	if (!buf)
7178c2ecf20Sopenharmony_ci		return -ENOMEM;
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_ci	cxusb_auxbuf_init(dvbdev, &cxdev->auxbuf, buf, auxbuflen);
7208c2ecf20Sopenharmony_ci
7218c2ecf20Sopenharmony_ci	return 0;
7228c2ecf20Sopenharmony_ci}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_cistatic u32 cxusb_medion_norm2field_order(v4l2_std_id norm)
7258c2ecf20Sopenharmony_ci{
7268c2ecf20Sopenharmony_ci	bool is625 = norm & V4L2_STD_625_50;
7278c2ecf20Sopenharmony_ci	bool is525 = norm & V4L2_STD_525_60;
7288c2ecf20Sopenharmony_ci
7298c2ecf20Sopenharmony_ci	if (!is625 && !is525)
7308c2ecf20Sopenharmony_ci		return V4L2_FIELD_NONE;
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_ci	if (is625 && is525)
7338c2ecf20Sopenharmony_ci		return V4L2_FIELD_NONE;
7348c2ecf20Sopenharmony_ci
7358c2ecf20Sopenharmony_ci	if (is625)
7368c2ecf20Sopenharmony_ci		return V4L2_FIELD_SEQ_TB;
7378c2ecf20Sopenharmony_ci	else /* is525 */
7388c2ecf20Sopenharmony_ci		return V4L2_FIELD_SEQ_BT;
7398c2ecf20Sopenharmony_ci}
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_cistatic u32 cxusb_medion_field_order(struct cxusb_medion_dev *cxdev)
7428c2ecf20Sopenharmony_ci{
7438c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
7448c2ecf20Sopenharmony_ci	u32 field;
7458c2ecf20Sopenharmony_ci	int ret;
7468c2ecf20Sopenharmony_ci	v4l2_std_id norm;
7478c2ecf20Sopenharmony_ci
7488c2ecf20Sopenharmony_ci	/* TV tuner is PAL-only so it is always TB */
7498c2ecf20Sopenharmony_ci	if (cxdev->input == 0)
7508c2ecf20Sopenharmony_ci		return V4L2_FIELD_SEQ_TB;
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_ci	field = cxusb_medion_norm2field_order(cxdev->norm);
7538c2ecf20Sopenharmony_ci	if (field != V4L2_FIELD_NONE)
7548c2ecf20Sopenharmony_ci		return field;
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, g_std, &norm);
7578c2ecf20Sopenharmony_ci	if (ret != 0) {
7588c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, OPS,
7598c2ecf20Sopenharmony_ci			      "cannot get current standard for input %u\n",
7608c2ecf20Sopenharmony_ci			      (unsigned int)cxdev->input);
7618c2ecf20Sopenharmony_ci	} else {
7628c2ecf20Sopenharmony_ci		field = cxusb_medion_norm2field_order(norm);
7638c2ecf20Sopenharmony_ci		if (field != V4L2_FIELD_NONE)
7648c2ecf20Sopenharmony_ci			return field;
7658c2ecf20Sopenharmony_ci	}
7668c2ecf20Sopenharmony_ci
7678c2ecf20Sopenharmony_ci	dev_warn(&dvbdev->udev->dev,
7688c2ecf20Sopenharmony_ci		 "cannot determine field order for the current standard setup and received signal, using TB\n");
7698c2ecf20Sopenharmony_ci	return V4L2_FIELD_SEQ_TB;
7708c2ecf20Sopenharmony_ci}
7718c2ecf20Sopenharmony_ci
7728c2ecf20Sopenharmony_cistatic int cxusb_medion_v_start_streaming(struct vb2_queue *q,
7738c2ecf20Sopenharmony_ci					  unsigned int count)
7748c2ecf20Sopenharmony_ci{
7758c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
7768c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
7778c2ecf20Sopenharmony_ci	u8 streamon_params[2] = { 0x03, 0x00 };
7788c2ecf20Sopenharmony_ci	int npackets, i;
7798c2ecf20Sopenharmony_ci	int ret;
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "should start streaming\n");
7828c2ecf20Sopenharmony_ci
7838c2ecf20Sopenharmony_ci	if (cxdev->stop_streaming) {
7848c2ecf20Sopenharmony_ci		/* stream is being stopped */
7858c2ecf20Sopenharmony_ci		ret = -EBUSY;
7868c2ecf20Sopenharmony_ci		goto ret_retbufs;
7878c2ecf20Sopenharmony_ci	}
7888c2ecf20Sopenharmony_ci
7898c2ecf20Sopenharmony_ci	cxdev->field_order = cxusb_medion_field_order(cxdev);
7908c2ecf20Sopenharmony_ci
7918c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 1);
7928c2ecf20Sopenharmony_ci	if (ret != 0) {
7938c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
7948c2ecf20Sopenharmony_ci			"unable to start stream (%d)\n", ret);
7958c2ecf20Sopenharmony_ci		goto ret_retbufs;
7968c2ecf20Sopenharmony_ci	}
7978c2ecf20Sopenharmony_ci
7988c2ecf20Sopenharmony_ci	ret = cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, streamon_params, 2,
7998c2ecf20Sopenharmony_ci			     NULL, 0);
8008c2ecf20Sopenharmony_ci	if (ret != 0) {
8018c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
8028c2ecf20Sopenharmony_ci			"unable to start streaming (%d)\n", ret);
8038c2ecf20Sopenharmony_ci		goto ret_unstream_cx;
8048c2ecf20Sopenharmony_ci	}
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ci	ret = cxusb_medion_v_ss_auxbuf_alloc(cxdev, &npackets);
8078c2ecf20Sopenharmony_ci	if (ret != 0)
8088c2ecf20Sopenharmony_ci		goto ret_unstream_md;
8098c2ecf20Sopenharmony_ci
8108c2ecf20Sopenharmony_ci	for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
8118c2ecf20Sopenharmony_ci		int framen;
8128c2ecf20Sopenharmony_ci		u8 *streambuf;
8138c2ecf20Sopenharmony_ci		struct urb *surb;
8148c2ecf20Sopenharmony_ci
8158c2ecf20Sopenharmony_ci		/*
8168c2ecf20Sopenharmony_ci		 * TODO: change this to an array of single pages to avoid
8178c2ecf20Sopenharmony_ci		 * doing a large continuous allocation when (if)
8188c2ecf20Sopenharmony_ci		 * s-g isochronous USB transfers are supported
8198c2ecf20Sopenharmony_ci		 */
8208c2ecf20Sopenharmony_ci		streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
8218c2ecf20Sopenharmony_ci				    GFP_KERNEL);
8228c2ecf20Sopenharmony_ci		if (!streambuf) {
8238c2ecf20Sopenharmony_ci			if (i < 2) {
8248c2ecf20Sopenharmony_ci				ret = -ENOMEM;
8258c2ecf20Sopenharmony_ci				goto ret_freeab;
8268c2ecf20Sopenharmony_ci			}
8278c2ecf20Sopenharmony_ci			break;
8288c2ecf20Sopenharmony_ci		}
8298c2ecf20Sopenharmony_ci
8308c2ecf20Sopenharmony_ci		surb = usb_alloc_urb(npackets, GFP_KERNEL);
8318c2ecf20Sopenharmony_ci		if (!surb) {
8328c2ecf20Sopenharmony_ci			kfree(streambuf);
8338c2ecf20Sopenharmony_ci			ret = -ENOMEM;
8348c2ecf20Sopenharmony_ci			goto ret_freeu;
8358c2ecf20Sopenharmony_ci		}
8368c2ecf20Sopenharmony_ci
8378c2ecf20Sopenharmony_ci		cxdev->streamurbs[i] = surb;
8388c2ecf20Sopenharmony_ci		surb->dev = dvbdev->udev;
8398c2ecf20Sopenharmony_ci		surb->context = dvbdev;
8408c2ecf20Sopenharmony_ci		surb->pipe = usb_rcvisocpipe(dvbdev->udev, 2);
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci		surb->interval = 1;
8438c2ecf20Sopenharmony_ci		surb->transfer_flags = URB_ISO_ASAP;
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci		surb->transfer_buffer = streambuf;
8468c2ecf20Sopenharmony_ci
8478c2ecf20Sopenharmony_ci		surb->complete = cxusb_medion_v_complete;
8488c2ecf20Sopenharmony_ci		surb->number_of_packets = npackets;
8498c2ecf20Sopenharmony_ci		surb->transfer_buffer_length = npackets * CXUSB_VIDEO_PKT_SIZE;
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_ci		for (framen = 0; framen < npackets; framen++) {
8528c2ecf20Sopenharmony_ci			surb->iso_frame_desc[framen].offset =
8538c2ecf20Sopenharmony_ci				CXUSB_VIDEO_PKT_SIZE * framen;
8548c2ecf20Sopenharmony_ci
8558c2ecf20Sopenharmony_ci			surb->iso_frame_desc[framen].length =
8568c2ecf20Sopenharmony_ci				CXUSB_VIDEO_PKT_SIZE;
8578c2ecf20Sopenharmony_ci		}
8588c2ecf20Sopenharmony_ci	}
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci	cxdev->urbcomplete = 0;
8618c2ecf20Sopenharmony_ci	cxdev->nexturb = 0;
8628c2ecf20Sopenharmony_ci	cxdev->vbuf_sequence = 0;
8638c2ecf20Sopenharmony_ci
8648c2ecf20Sopenharmony_ci	cxdev->vbuf = NULL;
8658c2ecf20Sopenharmony_ci	cxdev->bt656.mode = NEW_FRAME;
8668c2ecf20Sopenharmony_ci	cxdev->bt656.buf = NULL;
8678c2ecf20Sopenharmony_ci
8688c2ecf20Sopenharmony_ci	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
8698c2ecf20Sopenharmony_ci		if (cxdev->streamurbs[i]) {
8708c2ecf20Sopenharmony_ci			ret = usb_submit_urb(cxdev->streamurbs[i],
8718c2ecf20Sopenharmony_ci					     GFP_KERNEL);
8728c2ecf20Sopenharmony_ci			if (ret != 0)
8738c2ecf20Sopenharmony_ci				dev_err(&dvbdev->udev->dev,
8748c2ecf20Sopenharmony_ci					"URB %d submission failed (%d)\n", i,
8758c2ecf20Sopenharmony_ci					ret);
8768c2ecf20Sopenharmony_ci		}
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_ci	return 0;
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_ciret_freeu:
8818c2ecf20Sopenharmony_ci	cxusb_medion_urbs_free(cxdev);
8828c2ecf20Sopenharmony_ci
8838c2ecf20Sopenharmony_ciret_freeab:
8848c2ecf20Sopenharmony_ci	vfree(cxdev->auxbuf.buf);
8858c2ecf20Sopenharmony_ci
8868c2ecf20Sopenharmony_ciret_unstream_md:
8878c2ecf20Sopenharmony_ci	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
8888c2ecf20Sopenharmony_ci
8898c2ecf20Sopenharmony_ciret_unstream_cx:
8908c2ecf20Sopenharmony_ci	v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
8918c2ecf20Sopenharmony_ci
8928c2ecf20Sopenharmony_ciret_retbufs:
8938c2ecf20Sopenharmony_ci	cxusb_medion_return_buffers(cxdev, true);
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci	return ret;
8968c2ecf20Sopenharmony_ci}
8978c2ecf20Sopenharmony_ci
8988c2ecf20Sopenharmony_cistatic void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
8998c2ecf20Sopenharmony_ci{
9008c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
9018c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
9028c2ecf20Sopenharmony_ci	int ret;
9038c2ecf20Sopenharmony_ci	unsigned int i;
9048c2ecf20Sopenharmony_ci
9058c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci	if (WARN_ON(cxdev->stop_streaming))
9088c2ecf20Sopenharmony_ci		return;
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_ci	cxdev->stop_streaming = true;
9118c2ecf20Sopenharmony_ci
9128c2ecf20Sopenharmony_ci	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
9158c2ecf20Sopenharmony_ci	if (ret != 0)
9168c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev, "unable to stop stream (%d)\n",
9178c2ecf20Sopenharmony_ci			ret);
9188c2ecf20Sopenharmony_ci
9198c2ecf20Sopenharmony_ci	/* let URB completion run */
9208c2ecf20Sopenharmony_ci	mutex_unlock(cxdev->videodev->lock);
9218c2ecf20Sopenharmony_ci
9228c2ecf20Sopenharmony_ci	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
9238c2ecf20Sopenharmony_ci		if (cxdev->streamurbs[i])
9248c2ecf20Sopenharmony_ci			usb_kill_urb(cxdev->streamurbs[i]);
9258c2ecf20Sopenharmony_ci
9268c2ecf20Sopenharmony_ci	flush_work(&cxdev->urbwork);
9278c2ecf20Sopenharmony_ci
9288c2ecf20Sopenharmony_ci	mutex_lock(cxdev->videodev->lock);
9298c2ecf20Sopenharmony_ci
9308c2ecf20Sopenharmony_ci	/* free transfer buffer and URB */
9318c2ecf20Sopenharmony_ci	vfree(cxdev->auxbuf.buf);
9328c2ecf20Sopenharmony_ci
9338c2ecf20Sopenharmony_ci	cxusb_medion_urbs_free(cxdev);
9348c2ecf20Sopenharmony_ci
9358c2ecf20Sopenharmony_ci	cxusb_medion_return_buffers(cxdev, false);
9368c2ecf20Sopenharmony_ci
9378c2ecf20Sopenharmony_ci	cxdev->stop_streaming = false;
9388c2ecf20Sopenharmony_ci}
9398c2ecf20Sopenharmony_ci
9408c2ecf20Sopenharmony_cistatic void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
9418c2ecf20Sopenharmony_ci{
9428c2ecf20Sopenharmony_ci	struct vb2_v4l2_buffer *v4l2buf = to_vb2_v4l2_buffer(vb);
9438c2ecf20Sopenharmony_ci	struct cxusb_medion_vbuffer *vbuf =
9448c2ecf20Sopenharmony_ci		container_of(v4l2buf, struct cxusb_medion_vbuffer, vb2);
9458c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
9468c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
9478c2ecf20Sopenharmony_ci
9488c2ecf20Sopenharmony_ci	/* cxusb_vprintk(dvbdev, OPS, "mmmm.. a fresh buffer...\n"); */
9498c2ecf20Sopenharmony_ci
9508c2ecf20Sopenharmony_ci	list_add_tail(&vbuf->list, &cxdev->buflist);
9518c2ecf20Sopenharmony_ci}
9528c2ecf20Sopenharmony_ci
9538c2ecf20Sopenharmony_cistatic const struct vb2_ops cxdev_video_qops = {
9548c2ecf20Sopenharmony_ci	.queue_setup = cxusb_medion_v_queue_setup,
9558c2ecf20Sopenharmony_ci	.buf_init = cxusb_medion_v_buf_init,
9568c2ecf20Sopenharmony_ci	.start_streaming = cxusb_medion_v_start_streaming,
9578c2ecf20Sopenharmony_ci	.stop_streaming = cxusb_medion_v_stop_streaming,
9588c2ecf20Sopenharmony_ci	.buf_queue = cxusub_medion_v_buf_queue,
9598c2ecf20Sopenharmony_ci	.wait_prepare = vb2_ops_wait_prepare,
9608c2ecf20Sopenharmony_ci	.wait_finish = vb2_ops_wait_finish
9618c2ecf20Sopenharmony_ci};
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_cistatic const __u32 videocaps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
9648c2ecf20Sopenharmony_ci	V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
9658c2ecf20Sopenharmony_cistatic const __u32 radiocaps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_cistatic int cxusb_medion_v_querycap(struct file *file, void *fh,
9688c2ecf20Sopenharmony_ci				   struct v4l2_capability *cap)
9698c2ecf20Sopenharmony_ci{
9708c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
9718c2ecf20Sopenharmony_ci
9728c2ecf20Sopenharmony_ci	strscpy(cap->driver, dvbdev->udev->dev.driver->name,
9738c2ecf20Sopenharmony_ci		sizeof(cap->driver));
9748c2ecf20Sopenharmony_ci	strscpy(cap->card, "Medion 95700", sizeof(cap->card));
9758c2ecf20Sopenharmony_ci	usb_make_path(dvbdev->udev, cap->bus_info, sizeof(cap->bus_info));
9768c2ecf20Sopenharmony_ci
9778c2ecf20Sopenharmony_ci	cap->capabilities = videocaps | radiocaps | V4L2_CAP_DEVICE_CAPS;
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ci	return 0;
9808c2ecf20Sopenharmony_ci}
9818c2ecf20Sopenharmony_ci
9828c2ecf20Sopenharmony_cistatic int cxusb_medion_v_enum_fmt_vid_cap(struct file *file, void *fh,
9838c2ecf20Sopenharmony_ci					   struct v4l2_fmtdesc *f)
9848c2ecf20Sopenharmony_ci{
9858c2ecf20Sopenharmony_ci	if (f->index != 0)
9868c2ecf20Sopenharmony_ci		return -EINVAL;
9878c2ecf20Sopenharmony_ci
9888c2ecf20Sopenharmony_ci	f->pixelformat = V4L2_PIX_FMT_UYVY;
9898c2ecf20Sopenharmony_ci
9908c2ecf20Sopenharmony_ci	return 0;
9918c2ecf20Sopenharmony_ci}
9928c2ecf20Sopenharmony_ci
9938c2ecf20Sopenharmony_cistatic int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
9948c2ecf20Sopenharmony_ci				      struct v4l2_format *f)
9958c2ecf20Sopenharmony_ci{
9968c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
9978c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
9988c2ecf20Sopenharmony_ci
9998c2ecf20Sopenharmony_ci	f->fmt.pix.width = cxdev->width;
10008c2ecf20Sopenharmony_ci	f->fmt.pix.height = cxdev->height;
10018c2ecf20Sopenharmony_ci	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
10028c2ecf20Sopenharmony_ci	f->fmt.pix.field = vb2_start_streaming_called(&cxdev->videoqueue) ?
10038c2ecf20Sopenharmony_ci		cxdev->field_order : cxusb_medion_field_order(cxdev);
10048c2ecf20Sopenharmony_ci	f->fmt.pix.bytesperline = cxdev->width * 2;
10058c2ecf20Sopenharmony_ci	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
10068c2ecf20Sopenharmony_ci	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
10078c2ecf20Sopenharmony_ci
10088c2ecf20Sopenharmony_ci	return 0;
10098c2ecf20Sopenharmony_ci}
10108c2ecf20Sopenharmony_ci
10118c2ecf20Sopenharmony_cistatic int cxusb_medion_try_s_fmt_vid_cap(struct file *file,
10128c2ecf20Sopenharmony_ci					  struct v4l2_format *f,
10138c2ecf20Sopenharmony_ci					  bool isset)
10148c2ecf20Sopenharmony_ci{
10158c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
10168c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
10178c2ecf20Sopenharmony_ci	struct v4l2_subdev_format subfmt;
10188c2ecf20Sopenharmony_ci	u32 field;
10198c2ecf20Sopenharmony_ci	int ret;
10208c2ecf20Sopenharmony_ci
10218c2ecf20Sopenharmony_ci	if (isset && vb2_is_busy(&cxdev->videoqueue))
10228c2ecf20Sopenharmony_ci		return -EBUSY;
10238c2ecf20Sopenharmony_ci
10248c2ecf20Sopenharmony_ci	field = vb2_start_streaming_called(&cxdev->videoqueue) ?
10258c2ecf20Sopenharmony_ci		cxdev->field_order : cxusb_medion_field_order(cxdev);
10268c2ecf20Sopenharmony_ci
10278c2ecf20Sopenharmony_ci	memset(&subfmt, 0, sizeof(subfmt));
10288c2ecf20Sopenharmony_ci	subfmt.which = isset ? V4L2_SUBDEV_FORMAT_ACTIVE :
10298c2ecf20Sopenharmony_ci		V4L2_SUBDEV_FORMAT_TRY;
10308c2ecf20Sopenharmony_ci	subfmt.format.width = f->fmt.pix.width & ~1;
10318c2ecf20Sopenharmony_ci	subfmt.format.height = f->fmt.pix.height & ~1;
10328c2ecf20Sopenharmony_ci	subfmt.format.code = MEDIA_BUS_FMT_FIXED;
10338c2ecf20Sopenharmony_ci	subfmt.format.field = field;
10348c2ecf20Sopenharmony_ci	subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
10358c2ecf20Sopenharmony_ci
10368c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
10378c2ecf20Sopenharmony_ci	if (ret != 0)
10388c2ecf20Sopenharmony_ci		return ret;
10398c2ecf20Sopenharmony_ci
10408c2ecf20Sopenharmony_ci	f->fmt.pix.width = subfmt.format.width;
10418c2ecf20Sopenharmony_ci	f->fmt.pix.height = subfmt.format.height;
10428c2ecf20Sopenharmony_ci	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
10438c2ecf20Sopenharmony_ci	f->fmt.pix.field = field;
10448c2ecf20Sopenharmony_ci	f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
10458c2ecf20Sopenharmony_ci	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
10468c2ecf20Sopenharmony_ci	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
10478c2ecf20Sopenharmony_ci
10488c2ecf20Sopenharmony_ci	if (isset) {
10498c2ecf20Sopenharmony_ci		cxdev->width = f->fmt.pix.width;
10508c2ecf20Sopenharmony_ci		cxdev->height = f->fmt.pix.height;
10518c2ecf20Sopenharmony_ci	}
10528c2ecf20Sopenharmony_ci
10538c2ecf20Sopenharmony_ci	return 0;
10548c2ecf20Sopenharmony_ci}
10558c2ecf20Sopenharmony_ci
10568c2ecf20Sopenharmony_cistatic int cxusb_medion_try_fmt_vid_cap(struct file *file, void *fh,
10578c2ecf20Sopenharmony_ci					struct v4l2_format *f)
10588c2ecf20Sopenharmony_ci{
10598c2ecf20Sopenharmony_ci	return cxusb_medion_try_s_fmt_vid_cap(file, f, false);
10608c2ecf20Sopenharmony_ci}
10618c2ecf20Sopenharmony_ci
10628c2ecf20Sopenharmony_cistatic int cxusb_medion_s_fmt_vid_cap(struct file *file, void *fh,
10638c2ecf20Sopenharmony_ci				      struct v4l2_format *f)
10648c2ecf20Sopenharmony_ci{
10658c2ecf20Sopenharmony_ci	return cxusb_medion_try_s_fmt_vid_cap(file, f, true);
10668c2ecf20Sopenharmony_ci}
10678c2ecf20Sopenharmony_ci
10688c2ecf20Sopenharmony_cistatic const struct {
10698c2ecf20Sopenharmony_ci	struct v4l2_input input;
10708c2ecf20Sopenharmony_ci	u32 inputcfg;
10718c2ecf20Sopenharmony_ci} cxusb_medion_inputs[] = {
10728c2ecf20Sopenharmony_ci	{ .input = { .name = "TV tuner", .type = V4L2_INPUT_TYPE_TUNER,
10738c2ecf20Sopenharmony_ci		     .tuner = 0, .std = V4L2_STD_PAL },
10748c2ecf20Sopenharmony_ci	  .inputcfg = CX25840_COMPOSITE2, },
10758c2ecf20Sopenharmony_ci
10768c2ecf20Sopenharmony_ci	{  .input = { .name = "Composite", .type = V4L2_INPUT_TYPE_CAMERA,
10778c2ecf20Sopenharmony_ci		     .std = V4L2_STD_ALL },
10788c2ecf20Sopenharmony_ci	   .inputcfg = CX25840_COMPOSITE1, },
10798c2ecf20Sopenharmony_ci
10808c2ecf20Sopenharmony_ci	{  .input = { .name = "S-Video", .type = V4L2_INPUT_TYPE_CAMERA,
10818c2ecf20Sopenharmony_ci		      .std = V4L2_STD_ALL },
10828c2ecf20Sopenharmony_ci	   .inputcfg = CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }
10838c2ecf20Sopenharmony_ci};
10848c2ecf20Sopenharmony_ci
10858c2ecf20Sopenharmony_ci#define CXUSB_INPUT_CNT ARRAY_SIZE(cxusb_medion_inputs)
10868c2ecf20Sopenharmony_ci
10878c2ecf20Sopenharmony_cistatic int cxusb_medion_enum_input(struct file *file, void *fh,
10888c2ecf20Sopenharmony_ci				   struct v4l2_input *inp)
10898c2ecf20Sopenharmony_ci{
10908c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
10918c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
10928c2ecf20Sopenharmony_ci	u32 index = inp->index;
10938c2ecf20Sopenharmony_ci
10948c2ecf20Sopenharmony_ci	if (index >= CXUSB_INPUT_CNT)
10958c2ecf20Sopenharmony_ci		return -EINVAL;
10968c2ecf20Sopenharmony_ci
10978c2ecf20Sopenharmony_ci	*inp = cxusb_medion_inputs[index].input;
10988c2ecf20Sopenharmony_ci	inp->index = index;
10998c2ecf20Sopenharmony_ci	inp->capabilities |= V4L2_IN_CAP_STD;
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_ci	if (index == cxdev->input) {
11028c2ecf20Sopenharmony_ci		int ret;
11038c2ecf20Sopenharmony_ci		u32 status = 0;
11048c2ecf20Sopenharmony_ci
11058c2ecf20Sopenharmony_ci		ret = v4l2_subdev_call(cxdev->cx25840, video, g_input_status,
11068c2ecf20Sopenharmony_ci				       &status);
11078c2ecf20Sopenharmony_ci		if (ret != 0)
11088c2ecf20Sopenharmony_ci			dev_warn(&dvbdev->udev->dev,
11098c2ecf20Sopenharmony_ci				 "cx25840 input status query failed (%d)\n",
11108c2ecf20Sopenharmony_ci				 ret);
11118c2ecf20Sopenharmony_ci		else
11128c2ecf20Sopenharmony_ci			inp->status = status;
11138c2ecf20Sopenharmony_ci	}
11148c2ecf20Sopenharmony_ci
11158c2ecf20Sopenharmony_ci	return 0;
11168c2ecf20Sopenharmony_ci}
11178c2ecf20Sopenharmony_ci
11188c2ecf20Sopenharmony_cistatic int cxusb_medion_g_input(struct file *file, void *fh,
11198c2ecf20Sopenharmony_ci				unsigned int *i)
11208c2ecf20Sopenharmony_ci{
11218c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
11228c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
11238c2ecf20Sopenharmony_ci
11248c2ecf20Sopenharmony_ci	*i = cxdev->input;
11258c2ecf20Sopenharmony_ci
11268c2ecf20Sopenharmony_ci	return 0;
11278c2ecf20Sopenharmony_ci}
11288c2ecf20Sopenharmony_ci
11298c2ecf20Sopenharmony_cistatic int cxusb_medion_set_norm(struct cxusb_medion_dev *cxdev,
11308c2ecf20Sopenharmony_ci				 v4l2_std_id norm)
11318c2ecf20Sopenharmony_ci{
11328c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
11338c2ecf20Sopenharmony_ci	int ret;
11348c2ecf20Sopenharmony_ci
11358c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS,
11368c2ecf20Sopenharmony_ci		      "trying to set standard for input %u to %lx\n",
11378c2ecf20Sopenharmony_ci		      (unsigned int)cxdev->input,
11388c2ecf20Sopenharmony_ci		      (unsigned long)norm);
11398c2ecf20Sopenharmony_ci
11408c2ecf20Sopenharmony_ci	/* no autodetection support */
11418c2ecf20Sopenharmony_ci	if (norm == V4L2_STD_UNKNOWN)
11428c2ecf20Sopenharmony_ci		return -EINVAL;
11438c2ecf20Sopenharmony_ci
11448c2ecf20Sopenharmony_ci	/* on composite or S-Video any std is acceptable */
11458c2ecf20Sopenharmony_ci	if (cxdev->input != 0) {
11468c2ecf20Sopenharmony_ci		ret = v4l2_subdev_call(cxdev->cx25840, video, s_std, norm);
11478c2ecf20Sopenharmony_ci		if (ret)
11488c2ecf20Sopenharmony_ci			return ret;
11498c2ecf20Sopenharmony_ci
11508c2ecf20Sopenharmony_ci		goto ret_savenorm;
11518c2ecf20Sopenharmony_ci	}
11528c2ecf20Sopenharmony_ci
11538c2ecf20Sopenharmony_ci	/* TV tuner is only able to demodulate PAL */
11548c2ecf20Sopenharmony_ci	if ((norm & ~V4L2_STD_PAL) != 0)
11558c2ecf20Sopenharmony_ci		return -EINVAL;
11568c2ecf20Sopenharmony_ci
11578c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tda9887, video, s_std, norm);
11588c2ecf20Sopenharmony_ci	if (ret != 0) {
11598c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
11608c2ecf20Sopenharmony_ci			"tda9887 norm setup failed (%d)\n",
11618c2ecf20Sopenharmony_ci			ret);
11628c2ecf20Sopenharmony_ci		return ret;
11638c2ecf20Sopenharmony_ci	}
11648c2ecf20Sopenharmony_ci
11658c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tuner, video, s_std, norm);
11668c2ecf20Sopenharmony_ci	if (ret != 0) {
11678c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
11688c2ecf20Sopenharmony_ci			"tuner norm setup failed (%d)\n",
11698c2ecf20Sopenharmony_ci			ret);
11708c2ecf20Sopenharmony_ci		return ret;
11718c2ecf20Sopenharmony_ci	}
11728c2ecf20Sopenharmony_ci
11738c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, s_std, norm);
11748c2ecf20Sopenharmony_ci	if (ret != 0) {
11758c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
11768c2ecf20Sopenharmony_ci			"cx25840 norm setup failed (%d)\n",
11778c2ecf20Sopenharmony_ci			ret);
11788c2ecf20Sopenharmony_ci		return ret;
11798c2ecf20Sopenharmony_ci	}
11808c2ecf20Sopenharmony_ci
11818c2ecf20Sopenharmony_ciret_savenorm:
11828c2ecf20Sopenharmony_ci	cxdev->norm = norm;
11838c2ecf20Sopenharmony_ci
11848c2ecf20Sopenharmony_ci	return 0;
11858c2ecf20Sopenharmony_ci}
11868c2ecf20Sopenharmony_ci
11878c2ecf20Sopenharmony_cistatic int cxusb_medion_s_input(struct file *file, void *fh,
11888c2ecf20Sopenharmony_ci				unsigned int i)
11898c2ecf20Sopenharmony_ci{
11908c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
11918c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
11928c2ecf20Sopenharmony_ci	int ret;
11938c2ecf20Sopenharmony_ci	v4l2_std_id norm;
11948c2ecf20Sopenharmony_ci
11958c2ecf20Sopenharmony_ci	if (i >= CXUSB_INPUT_CNT)
11968c2ecf20Sopenharmony_ci		return -EINVAL;
11978c2ecf20Sopenharmony_ci
11988c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
11998c2ecf20Sopenharmony_ci			       cxusb_medion_inputs[i].inputcfg, 0, 0);
12008c2ecf20Sopenharmony_ci	if (ret != 0)
12018c2ecf20Sopenharmony_ci		return ret;
12028c2ecf20Sopenharmony_ci
12038c2ecf20Sopenharmony_ci	cxdev->input = i;
12048c2ecf20Sopenharmony_ci	cxdev->videodev->tvnorms = cxusb_medion_inputs[i].input.std;
12058c2ecf20Sopenharmony_ci
12068c2ecf20Sopenharmony_ci	norm = cxdev->norm & cxusb_medion_inputs[i].input.std;
12078c2ecf20Sopenharmony_ci	if (norm == 0)
12088c2ecf20Sopenharmony_ci		norm = cxusb_medion_inputs[i].input.std;
12098c2ecf20Sopenharmony_ci
12108c2ecf20Sopenharmony_ci	cxusb_medion_set_norm(cxdev, norm);
12118c2ecf20Sopenharmony_ci
12128c2ecf20Sopenharmony_ci	return 0;
12138c2ecf20Sopenharmony_ci}
12148c2ecf20Sopenharmony_ci
12158c2ecf20Sopenharmony_cistatic int cxusb_medion_g_tuner(struct file *file, void *fh,
12168c2ecf20Sopenharmony_ci				struct v4l2_tuner *tuner)
12178c2ecf20Sopenharmony_ci{
12188c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
12198c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
12208c2ecf20Sopenharmony_ci	struct video_device *vdev = video_devdata(file);
12218c2ecf20Sopenharmony_ci	int ret;
12228c2ecf20Sopenharmony_ci
12238c2ecf20Sopenharmony_ci	if (tuner->index != 0)
12248c2ecf20Sopenharmony_ci		return -EINVAL;
12258c2ecf20Sopenharmony_ci
12268c2ecf20Sopenharmony_ci	if (vdev->vfl_type == VFL_TYPE_VIDEO)
12278c2ecf20Sopenharmony_ci		tuner->type = V4L2_TUNER_ANALOG_TV;
12288c2ecf20Sopenharmony_ci	else
12298c2ecf20Sopenharmony_ci		tuner->type = V4L2_TUNER_RADIO;
12308c2ecf20Sopenharmony_ci
12318c2ecf20Sopenharmony_ci	tuner->capability = 0;
12328c2ecf20Sopenharmony_ci	tuner->afc = 0;
12338c2ecf20Sopenharmony_ci
12348c2ecf20Sopenharmony_ci	/*
12358c2ecf20Sopenharmony_ci	 * fills:
12368c2ecf20Sopenharmony_ci	 * always: capability (static), rangelow (static), rangehigh (static)
12378c2ecf20Sopenharmony_ci	 * radio mode: afc (may fail silently), rxsubchans (static), audmode
12388c2ecf20Sopenharmony_ci	 */
12398c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tda9887, tuner, g_tuner, tuner);
12408c2ecf20Sopenharmony_ci	if (ret != 0)
12418c2ecf20Sopenharmony_ci		return ret;
12428c2ecf20Sopenharmony_ci
12438c2ecf20Sopenharmony_ci	/*
12448c2ecf20Sopenharmony_ci	 * fills:
12458c2ecf20Sopenharmony_ci	 * always: capability (static), rangelow (static), rangehigh (static)
12468c2ecf20Sopenharmony_ci	 * radio mode: rxsubchans (always stereo), audmode,
12478c2ecf20Sopenharmony_ci	 * signal (might be wrong)
12488c2ecf20Sopenharmony_ci	 */
12498c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tuner, tuner, g_tuner, tuner);
12508c2ecf20Sopenharmony_ci	if (ret != 0)
12518c2ecf20Sopenharmony_ci		return ret;
12528c2ecf20Sopenharmony_ci
12538c2ecf20Sopenharmony_ci	tuner->signal = 0;
12548c2ecf20Sopenharmony_ci
12558c2ecf20Sopenharmony_ci	/*
12568c2ecf20Sopenharmony_ci	 * fills: TV mode: capability, rxsubchans, audmode, signal
12578c2ecf20Sopenharmony_ci	 */
12588c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, tuner, g_tuner, tuner);
12598c2ecf20Sopenharmony_ci	if (ret != 0)
12608c2ecf20Sopenharmony_ci		return ret;
12618c2ecf20Sopenharmony_ci
12628c2ecf20Sopenharmony_ci	if (vdev->vfl_type == VFL_TYPE_VIDEO)
12638c2ecf20Sopenharmony_ci		strscpy(tuner->name, "TV tuner", sizeof(tuner->name));
12648c2ecf20Sopenharmony_ci	else
12658c2ecf20Sopenharmony_ci		strscpy(tuner->name, "Radio tuner", sizeof(tuner->name));
12668c2ecf20Sopenharmony_ci
12678c2ecf20Sopenharmony_ci	memset(tuner->reserved, 0, sizeof(tuner->reserved));
12688c2ecf20Sopenharmony_ci
12698c2ecf20Sopenharmony_ci	return 0;
12708c2ecf20Sopenharmony_ci}
12718c2ecf20Sopenharmony_ci
12728c2ecf20Sopenharmony_cistatic int cxusb_medion_s_tuner(struct file *file, void *fh,
12738c2ecf20Sopenharmony_ci				const struct v4l2_tuner *tuner)
12748c2ecf20Sopenharmony_ci{
12758c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
12768c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
12778c2ecf20Sopenharmony_ci	struct video_device *vdev = video_devdata(file);
12788c2ecf20Sopenharmony_ci	int ret;
12798c2ecf20Sopenharmony_ci
12808c2ecf20Sopenharmony_ci	if (tuner->index != 0)
12818c2ecf20Sopenharmony_ci		return -EINVAL;
12828c2ecf20Sopenharmony_ci
12838c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tda9887, tuner, s_tuner, tuner);
12848c2ecf20Sopenharmony_ci	if (ret != 0)
12858c2ecf20Sopenharmony_ci		return ret;
12868c2ecf20Sopenharmony_ci
12878c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tuner, tuner, s_tuner, tuner);
12888c2ecf20Sopenharmony_ci	if (ret != 0)
12898c2ecf20Sopenharmony_ci		return ret;
12908c2ecf20Sopenharmony_ci
12918c2ecf20Sopenharmony_ci	/*
12928c2ecf20Sopenharmony_ci	 * make sure that cx25840 is in a correct TV / radio mode,
12938c2ecf20Sopenharmony_ci	 * since calls above may have changed it for tuner / IF demod
12948c2ecf20Sopenharmony_ci	 */
12958c2ecf20Sopenharmony_ci	if (vdev->vfl_type == VFL_TYPE_VIDEO)
12968c2ecf20Sopenharmony_ci		v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
12978c2ecf20Sopenharmony_ci	else
12988c2ecf20Sopenharmony_ci		v4l2_subdev_call(cxdev->cx25840, tuner, s_radio);
12998c2ecf20Sopenharmony_ci
13008c2ecf20Sopenharmony_ci	return v4l2_subdev_call(cxdev->cx25840, tuner, s_tuner, tuner);
13018c2ecf20Sopenharmony_ci}
13028c2ecf20Sopenharmony_ci
13038c2ecf20Sopenharmony_cistatic int cxusb_medion_g_frequency(struct file *file, void *fh,
13048c2ecf20Sopenharmony_ci				    struct v4l2_frequency *freq)
13058c2ecf20Sopenharmony_ci{
13068c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
13078c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
13088c2ecf20Sopenharmony_ci
13098c2ecf20Sopenharmony_ci	if (freq->tuner != 0)
13108c2ecf20Sopenharmony_ci		return -EINVAL;
13118c2ecf20Sopenharmony_ci
13128c2ecf20Sopenharmony_ci	return v4l2_subdev_call(cxdev->tuner, tuner, g_frequency, freq);
13138c2ecf20Sopenharmony_ci}
13148c2ecf20Sopenharmony_ci
13158c2ecf20Sopenharmony_cistatic int cxusb_medion_s_frequency(struct file *file, void *fh,
13168c2ecf20Sopenharmony_ci				    const struct v4l2_frequency *freq)
13178c2ecf20Sopenharmony_ci{
13188c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
13198c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
13208c2ecf20Sopenharmony_ci	struct video_device *vdev = video_devdata(file);
13218c2ecf20Sopenharmony_ci	int ret;
13228c2ecf20Sopenharmony_ci
13238c2ecf20Sopenharmony_ci	if (freq->tuner != 0)
13248c2ecf20Sopenharmony_ci		return -EINVAL;
13258c2ecf20Sopenharmony_ci
13268c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tda9887, tuner, s_frequency, freq);
13278c2ecf20Sopenharmony_ci	if (ret != 0)
13288c2ecf20Sopenharmony_ci		return ret;
13298c2ecf20Sopenharmony_ci
13308c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->tuner, tuner, s_frequency, freq);
13318c2ecf20Sopenharmony_ci	if (ret != 0)
13328c2ecf20Sopenharmony_ci		return ret;
13338c2ecf20Sopenharmony_ci
13348c2ecf20Sopenharmony_ci	/*
13358c2ecf20Sopenharmony_ci	 * make sure that cx25840 is in a correct TV / radio mode,
13368c2ecf20Sopenharmony_ci	 * since calls above may have changed it for tuner / IF demod
13378c2ecf20Sopenharmony_ci	 */
13388c2ecf20Sopenharmony_ci	if (vdev->vfl_type == VFL_TYPE_VIDEO)
13398c2ecf20Sopenharmony_ci		v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
13408c2ecf20Sopenharmony_ci	else
13418c2ecf20Sopenharmony_ci		v4l2_subdev_call(cxdev->cx25840, tuner, s_radio);
13428c2ecf20Sopenharmony_ci
13438c2ecf20Sopenharmony_ci	return v4l2_subdev_call(cxdev->cx25840, tuner, s_frequency, freq);
13448c2ecf20Sopenharmony_ci}
13458c2ecf20Sopenharmony_ci
13468c2ecf20Sopenharmony_cistatic int cxusb_medion_g_std(struct file *file, void *fh,
13478c2ecf20Sopenharmony_ci			      v4l2_std_id *norm)
13488c2ecf20Sopenharmony_ci{
13498c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
13508c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
13518c2ecf20Sopenharmony_ci
13528c2ecf20Sopenharmony_ci	*norm = cxdev->norm;
13538c2ecf20Sopenharmony_ci
13548c2ecf20Sopenharmony_ci	if (*norm == V4L2_STD_UNKNOWN)
13558c2ecf20Sopenharmony_ci		return -ENODATA;
13568c2ecf20Sopenharmony_ci
13578c2ecf20Sopenharmony_ci	return 0;
13588c2ecf20Sopenharmony_ci}
13598c2ecf20Sopenharmony_ci
13608c2ecf20Sopenharmony_cistatic int cxusb_medion_s_std(struct file *file, void *fh,
13618c2ecf20Sopenharmony_ci			      v4l2_std_id norm)
13628c2ecf20Sopenharmony_ci{
13638c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
13648c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
13658c2ecf20Sopenharmony_ci
13668c2ecf20Sopenharmony_ci	return cxusb_medion_set_norm(cxdev, norm);
13678c2ecf20Sopenharmony_ci}
13688c2ecf20Sopenharmony_ci
13698c2ecf20Sopenharmony_cistatic int cxusb_medion_querystd(struct file *file, void *fh,
13708c2ecf20Sopenharmony_ci				 v4l2_std_id *norm)
13718c2ecf20Sopenharmony_ci{
13728c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
13738c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
13748c2ecf20Sopenharmony_ci	v4l2_std_id norm_mask;
13758c2ecf20Sopenharmony_ci	int ret;
13768c2ecf20Sopenharmony_ci
13778c2ecf20Sopenharmony_ci	/*
13788c2ecf20Sopenharmony_ci	 * make sure we don't have improper std bits set for the TV tuner
13798c2ecf20Sopenharmony_ci	 * (could happen when no signal was present yet after reset)
13808c2ecf20Sopenharmony_ci	 */
13818c2ecf20Sopenharmony_ci	if (cxdev->input == 0)
13828c2ecf20Sopenharmony_ci		norm_mask = V4L2_STD_PAL;
13838c2ecf20Sopenharmony_ci	else
13848c2ecf20Sopenharmony_ci		norm_mask = V4L2_STD_ALL;
13858c2ecf20Sopenharmony_ci
13868c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, querystd, norm);
13878c2ecf20Sopenharmony_ci	if (ret != 0) {
13888c2ecf20Sopenharmony_ci		cxusb_vprintk(dvbdev, OPS,
13898c2ecf20Sopenharmony_ci			      "cannot get detected standard for input %u\n",
13908c2ecf20Sopenharmony_ci			      (unsigned int)cxdev->input);
13918c2ecf20Sopenharmony_ci		return ret;
13928c2ecf20Sopenharmony_ci	}
13938c2ecf20Sopenharmony_ci
13948c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "input %u detected standard is %lx\n",
13958c2ecf20Sopenharmony_ci		      (unsigned int)cxdev->input, (unsigned long)*norm);
13968c2ecf20Sopenharmony_ci	*norm &= norm_mask;
13978c2ecf20Sopenharmony_ci
13988c2ecf20Sopenharmony_ci	return 0;
13998c2ecf20Sopenharmony_ci}
14008c2ecf20Sopenharmony_ci
14018c2ecf20Sopenharmony_cistatic int cxusb_medion_log_status(struct file *file, void *fh)
14028c2ecf20Sopenharmony_ci{
14038c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(file);
14048c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
14058c2ecf20Sopenharmony_ci
14068c2ecf20Sopenharmony_ci	v4l2_device_call_all(&cxdev->v4l2dev, 0, core, log_status);
14078c2ecf20Sopenharmony_ci
14088c2ecf20Sopenharmony_ci	return 0;
14098c2ecf20Sopenharmony_ci}
14108c2ecf20Sopenharmony_ci
14118c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops cxusb_video_ioctl = {
14128c2ecf20Sopenharmony_ci	.vidioc_querycap = cxusb_medion_v_querycap,
14138c2ecf20Sopenharmony_ci	.vidioc_enum_fmt_vid_cap = cxusb_medion_v_enum_fmt_vid_cap,
14148c2ecf20Sopenharmony_ci	.vidioc_g_fmt_vid_cap = cxusb_medion_g_fmt_vid_cap,
14158c2ecf20Sopenharmony_ci	.vidioc_s_fmt_vid_cap = cxusb_medion_s_fmt_vid_cap,
14168c2ecf20Sopenharmony_ci	.vidioc_try_fmt_vid_cap = cxusb_medion_try_fmt_vid_cap,
14178c2ecf20Sopenharmony_ci	.vidioc_enum_input = cxusb_medion_enum_input,
14188c2ecf20Sopenharmony_ci	.vidioc_g_input = cxusb_medion_g_input,
14198c2ecf20Sopenharmony_ci	.vidioc_s_input = cxusb_medion_s_input,
14208c2ecf20Sopenharmony_ci	.vidioc_g_tuner = cxusb_medion_g_tuner,
14218c2ecf20Sopenharmony_ci	.vidioc_s_tuner = cxusb_medion_s_tuner,
14228c2ecf20Sopenharmony_ci	.vidioc_g_frequency = cxusb_medion_g_frequency,
14238c2ecf20Sopenharmony_ci	.vidioc_s_frequency = cxusb_medion_s_frequency,
14248c2ecf20Sopenharmony_ci	.vidioc_g_std = cxusb_medion_g_std,
14258c2ecf20Sopenharmony_ci	.vidioc_s_std = cxusb_medion_s_std,
14268c2ecf20Sopenharmony_ci	.vidioc_querystd = cxusb_medion_querystd,
14278c2ecf20Sopenharmony_ci	.vidioc_log_status = cxusb_medion_log_status,
14288c2ecf20Sopenharmony_ci	.vidioc_reqbufs = vb2_ioctl_reqbufs,
14298c2ecf20Sopenharmony_ci	.vidioc_querybuf = vb2_ioctl_querybuf,
14308c2ecf20Sopenharmony_ci	.vidioc_qbuf = vb2_ioctl_qbuf,
14318c2ecf20Sopenharmony_ci	.vidioc_dqbuf = vb2_ioctl_dqbuf,
14328c2ecf20Sopenharmony_ci	.vidioc_create_bufs = vb2_ioctl_create_bufs,
14338c2ecf20Sopenharmony_ci	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
14348c2ecf20Sopenharmony_ci	.vidioc_streamon = vb2_ioctl_streamon,
14358c2ecf20Sopenharmony_ci	.vidioc_streamoff = vb2_ioctl_streamoff
14368c2ecf20Sopenharmony_ci};
14378c2ecf20Sopenharmony_ci
14388c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops cxusb_radio_ioctl = {
14398c2ecf20Sopenharmony_ci	.vidioc_querycap = cxusb_medion_v_querycap,
14408c2ecf20Sopenharmony_ci	.vidioc_g_tuner = cxusb_medion_g_tuner,
14418c2ecf20Sopenharmony_ci	.vidioc_s_tuner = cxusb_medion_s_tuner,
14428c2ecf20Sopenharmony_ci	.vidioc_g_frequency = cxusb_medion_g_frequency,
14438c2ecf20Sopenharmony_ci	.vidioc_s_frequency = cxusb_medion_s_frequency,
14448c2ecf20Sopenharmony_ci	.vidioc_log_status = cxusb_medion_log_status
14458c2ecf20Sopenharmony_ci};
14468c2ecf20Sopenharmony_ci
14478c2ecf20Sopenharmony_ci/*
14488c2ecf20Sopenharmony_ci * in principle, this should be const, but s_io_pin_config is declared
14498c2ecf20Sopenharmony_ci * to take non-const, and gcc complains
14508c2ecf20Sopenharmony_ci */
14518c2ecf20Sopenharmony_cistatic struct v4l2_subdev_io_pin_config cxusub_medion_pin_config[] = {
14528c2ecf20Sopenharmony_ci	{ .pin = CX25840_PIN_DVALID_PRGM0, .function = CX25840_PAD_DEFAULT,
14538c2ecf20Sopenharmony_ci	  .strength = CX25840_PIN_DRIVE_MEDIUM },
14548c2ecf20Sopenharmony_ci	{ .pin = CX25840_PIN_PLL_CLK_PRGM7, .function = CX25840_PAD_AUX_PLL },
14558c2ecf20Sopenharmony_ci	{ .pin = CX25840_PIN_HRESET_PRGM2, .function = CX25840_PAD_ACTIVE,
14568c2ecf20Sopenharmony_ci	  .strength = CX25840_PIN_DRIVE_MEDIUM }
14578c2ecf20Sopenharmony_ci};
14588c2ecf20Sopenharmony_ci
14598c2ecf20Sopenharmony_ciint cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
14608c2ecf20Sopenharmony_ci{
14618c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
14628c2ecf20Sopenharmony_ci	u8 tuner_analog_msg_data[] = { 0x9c, 0x60, 0x85, 0x54 };
14638c2ecf20Sopenharmony_ci	struct i2c_msg tuner_analog_msg = { .addr = 0x61, .flags = 0,
14648c2ecf20Sopenharmony_ci					    .buf = tuner_analog_msg_data,
14658c2ecf20Sopenharmony_ci					    .len =
14668c2ecf20Sopenharmony_ci					    sizeof(tuner_analog_msg_data) };
14678c2ecf20Sopenharmony_ci	struct v4l2_subdev_format subfmt;
14688c2ecf20Sopenharmony_ci	int ret;
14698c2ecf20Sopenharmony_ci
14708c2ecf20Sopenharmony_ci	/* switch tuner to analog mode so IF demod will become accessible */
14718c2ecf20Sopenharmony_ci	ret = i2c_transfer(&dvbdev->i2c_adap, &tuner_analog_msg, 1);
14728c2ecf20Sopenharmony_ci	if (ret != 1)
14738c2ecf20Sopenharmony_ci		dev_warn(&dvbdev->udev->dev,
14748c2ecf20Sopenharmony_ci			 "tuner analog switch failed (%d)\n", ret);
14758c2ecf20Sopenharmony_ci
14768c2ecf20Sopenharmony_ci	/*
14778c2ecf20Sopenharmony_ci	 * cx25840 might have lost power during mode switching so we need
14788c2ecf20Sopenharmony_ci	 * to set it again
14798c2ecf20Sopenharmony_ci	 */
14808c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, core, reset, 0);
14818c2ecf20Sopenharmony_ci	if (ret != 0)
14828c2ecf20Sopenharmony_ci		dev_warn(&dvbdev->udev->dev,
14838c2ecf20Sopenharmony_ci			 "cx25840 reset failed (%d)\n", ret);
14848c2ecf20Sopenharmony_ci
14858c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
14868c2ecf20Sopenharmony_ci			       CX25840_COMPOSITE1, 0, 0);
14878c2ecf20Sopenharmony_ci	if (ret != 0)
14888c2ecf20Sopenharmony_ci		dev_warn(&dvbdev->udev->dev,
14898c2ecf20Sopenharmony_ci			 "cx25840 initial input setting failed (%d)\n", ret);
14908c2ecf20Sopenharmony_ci
14918c2ecf20Sopenharmony_ci	/* composite */
14928c2ecf20Sopenharmony_ci	cxdev->input = 1;
14938c2ecf20Sopenharmony_ci	cxdev->videodev->tvnorms = V4L2_STD_ALL;
14948c2ecf20Sopenharmony_ci	cxdev->norm = V4L2_STD_PAL;
14958c2ecf20Sopenharmony_ci
14968c2ecf20Sopenharmony_ci	/* TODO: setup audio samples insertion */
14978c2ecf20Sopenharmony_ci
14988c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, core, s_io_pin_config,
14998c2ecf20Sopenharmony_ci			       ARRAY_SIZE(cxusub_medion_pin_config),
15008c2ecf20Sopenharmony_ci			       cxusub_medion_pin_config);
15018c2ecf20Sopenharmony_ci	if (ret != 0)
15028c2ecf20Sopenharmony_ci		dev_warn(&dvbdev->udev->dev,
15038c2ecf20Sopenharmony_ci			 "cx25840 pin config failed (%d)\n", ret);
15048c2ecf20Sopenharmony_ci
15058c2ecf20Sopenharmony_ci	/* make sure that we aren't in radio mode */
15068c2ecf20Sopenharmony_ci	v4l2_subdev_call(cxdev->tda9887, video, s_std, cxdev->norm);
15078c2ecf20Sopenharmony_ci	v4l2_subdev_call(cxdev->tuner, video, s_std, cxdev->norm);
15088c2ecf20Sopenharmony_ci	v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
15098c2ecf20Sopenharmony_ci
15108c2ecf20Sopenharmony_ci	memset(&subfmt, 0, sizeof(subfmt));
15118c2ecf20Sopenharmony_ci	subfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
15128c2ecf20Sopenharmony_ci	subfmt.format.width = cxdev->width;
15138c2ecf20Sopenharmony_ci	subfmt.format.height = cxdev->height;
15148c2ecf20Sopenharmony_ci	subfmt.format.code = MEDIA_BUS_FMT_FIXED;
15158c2ecf20Sopenharmony_ci	subfmt.format.field = V4L2_FIELD_SEQ_TB;
15168c2ecf20Sopenharmony_ci	subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
15178c2ecf20Sopenharmony_ci
15188c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
15198c2ecf20Sopenharmony_ci	if (ret != 0)
15208c2ecf20Sopenharmony_ci		dev_warn(&dvbdev->udev->dev,
15218c2ecf20Sopenharmony_ci			 "cx25840 format set failed (%d)\n", ret);
15228c2ecf20Sopenharmony_ci
15238c2ecf20Sopenharmony_ci	if (ret == 0) {
15248c2ecf20Sopenharmony_ci		cxdev->width = subfmt.format.width;
15258c2ecf20Sopenharmony_ci		cxdev->height = subfmt.format.height;
15268c2ecf20Sopenharmony_ci	}
15278c2ecf20Sopenharmony_ci
15288c2ecf20Sopenharmony_ci	return 0;
15298c2ecf20Sopenharmony_ci}
15308c2ecf20Sopenharmony_ci
15318c2ecf20Sopenharmony_cistatic int cxusb_videoradio_open(struct file *f)
15328c2ecf20Sopenharmony_ci{
15338c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(f);
15348c2ecf20Sopenharmony_ci	int ret;
15358c2ecf20Sopenharmony_ci
15368c2ecf20Sopenharmony_ci	/*
15378c2ecf20Sopenharmony_ci	 * no locking needed since this call only modifies analog
15388c2ecf20Sopenharmony_ci	 * state if there are no other analog handles currenly
15398c2ecf20Sopenharmony_ci	 * opened so ops done via them cannot create a conflict
15408c2ecf20Sopenharmony_ci	 */
15418c2ecf20Sopenharmony_ci	ret = cxusb_medion_get(dvbdev, CXUSB_OPEN_ANALOG);
15428c2ecf20Sopenharmony_ci	if (ret != 0)
15438c2ecf20Sopenharmony_ci		return ret;
15448c2ecf20Sopenharmony_ci
15458c2ecf20Sopenharmony_ci	ret = v4l2_fh_open(f);
15468c2ecf20Sopenharmony_ci	if (ret != 0)
15478c2ecf20Sopenharmony_ci		goto ret_release;
15488c2ecf20Sopenharmony_ci
15498c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "got open\n");
15508c2ecf20Sopenharmony_ci
15518c2ecf20Sopenharmony_ci	return 0;
15528c2ecf20Sopenharmony_ci
15538c2ecf20Sopenharmony_ciret_release:
15548c2ecf20Sopenharmony_ci	cxusb_medion_put(dvbdev);
15558c2ecf20Sopenharmony_ci
15568c2ecf20Sopenharmony_ci	return ret;
15578c2ecf20Sopenharmony_ci}
15588c2ecf20Sopenharmony_ci
15598c2ecf20Sopenharmony_cistatic int cxusb_videoradio_release(struct file *f)
15608c2ecf20Sopenharmony_ci{
15618c2ecf20Sopenharmony_ci	struct video_device *vdev = video_devdata(f);
15628c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_drvdata(f);
15638c2ecf20Sopenharmony_ci	int ret;
15648c2ecf20Sopenharmony_ci
15658c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "got release\n");
15668c2ecf20Sopenharmony_ci
15678c2ecf20Sopenharmony_ci	if (vdev->vfl_type == VFL_TYPE_VIDEO)
15688c2ecf20Sopenharmony_ci		ret = vb2_fop_release(f);
15698c2ecf20Sopenharmony_ci	else
15708c2ecf20Sopenharmony_ci		ret = v4l2_fh_release(f);
15718c2ecf20Sopenharmony_ci
15728c2ecf20Sopenharmony_ci	cxusb_medion_put(dvbdev);
15738c2ecf20Sopenharmony_ci
15748c2ecf20Sopenharmony_ci	return ret;
15758c2ecf20Sopenharmony_ci}
15768c2ecf20Sopenharmony_ci
15778c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations cxusb_video_fops = {
15788c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
15798c2ecf20Sopenharmony_ci	.read = vb2_fop_read,
15808c2ecf20Sopenharmony_ci	.poll = vb2_fop_poll,
15818c2ecf20Sopenharmony_ci	.unlocked_ioctl = video_ioctl2,
15828c2ecf20Sopenharmony_ci	.mmap = vb2_fop_mmap,
15838c2ecf20Sopenharmony_ci	.open = cxusb_videoradio_open,
15848c2ecf20Sopenharmony_ci	.release = cxusb_videoradio_release
15858c2ecf20Sopenharmony_ci};
15868c2ecf20Sopenharmony_ci
15878c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations cxusb_radio_fops = {
15888c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
15898c2ecf20Sopenharmony_ci	.unlocked_ioctl = video_ioctl2,
15908c2ecf20Sopenharmony_ci	.open = cxusb_videoradio_open,
15918c2ecf20Sopenharmony_ci	.release = cxusb_videoradio_release
15928c2ecf20Sopenharmony_ci};
15938c2ecf20Sopenharmony_ci
15948c2ecf20Sopenharmony_cistatic void cxusb_medion_v4l2_release(struct v4l2_device *v4l2_dev)
15958c2ecf20Sopenharmony_ci{
15968c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev =
15978c2ecf20Sopenharmony_ci		container_of(v4l2_dev, struct cxusb_medion_dev, v4l2dev);
15988c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
15998c2ecf20Sopenharmony_ci
16008c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "v4l2 device release\n");
16018c2ecf20Sopenharmony_ci
16028c2ecf20Sopenharmony_ci	v4l2_device_unregister(&cxdev->v4l2dev);
16038c2ecf20Sopenharmony_ci
16048c2ecf20Sopenharmony_ci	mutex_destroy(&cxdev->dev_lock);
16058c2ecf20Sopenharmony_ci
16068c2ecf20Sopenharmony_ci	while (completion_done(&cxdev->v4l2_release))
16078c2ecf20Sopenharmony_ci		schedule();
16088c2ecf20Sopenharmony_ci
16098c2ecf20Sopenharmony_ci	complete(&cxdev->v4l2_release);
16108c2ecf20Sopenharmony_ci}
16118c2ecf20Sopenharmony_ci
16128c2ecf20Sopenharmony_cistatic void cxusb_medion_videodev_release(struct video_device *vdev)
16138c2ecf20Sopenharmony_ci{
16148c2ecf20Sopenharmony_ci	struct dvb_usb_device *dvbdev = video_get_drvdata(vdev);
16158c2ecf20Sopenharmony_ci
16168c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "video device release\n");
16178c2ecf20Sopenharmony_ci
16188c2ecf20Sopenharmony_ci	video_device_release(vdev);
16198c2ecf20Sopenharmony_ci}
16208c2ecf20Sopenharmony_ci
16218c2ecf20Sopenharmony_cistatic int cxusb_medion_register_analog_video(struct dvb_usb_device *dvbdev)
16228c2ecf20Sopenharmony_ci{
16238c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
16248c2ecf20Sopenharmony_ci	int ret;
16258c2ecf20Sopenharmony_ci
16268c2ecf20Sopenharmony_ci	cxdev->videoqueue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
16278c2ecf20Sopenharmony_ci	cxdev->videoqueue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ |
16288c2ecf20Sopenharmony_ci		VB2_DMABUF;
16298c2ecf20Sopenharmony_ci	cxdev->videoqueue.ops = &cxdev_video_qops;
16308c2ecf20Sopenharmony_ci	cxdev->videoqueue.mem_ops = &vb2_vmalloc_memops;
16318c2ecf20Sopenharmony_ci	cxdev->videoqueue.drv_priv = dvbdev;
16328c2ecf20Sopenharmony_ci	cxdev->videoqueue.buf_struct_size =
16338c2ecf20Sopenharmony_ci		sizeof(struct cxusb_medion_vbuffer);
16348c2ecf20Sopenharmony_ci	cxdev->videoqueue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
16358c2ecf20Sopenharmony_ci	cxdev->videoqueue.min_buffers_needed = 6;
16368c2ecf20Sopenharmony_ci	cxdev->videoqueue.lock = &cxdev->dev_lock;
16378c2ecf20Sopenharmony_ci
16388c2ecf20Sopenharmony_ci	ret = vb2_queue_init(&cxdev->videoqueue);
16398c2ecf20Sopenharmony_ci	if (ret) {
16408c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
16418c2ecf20Sopenharmony_ci			"video queue init failed, ret = %d\n", ret);
16428c2ecf20Sopenharmony_ci		return ret;
16438c2ecf20Sopenharmony_ci	}
16448c2ecf20Sopenharmony_ci
16458c2ecf20Sopenharmony_ci	cxdev->videodev = video_device_alloc();
16468c2ecf20Sopenharmony_ci	if (!cxdev->videodev) {
16478c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev, "video device alloc failed\n");
16488c2ecf20Sopenharmony_ci		return -ENOMEM;
16498c2ecf20Sopenharmony_ci	}
16508c2ecf20Sopenharmony_ci
16518c2ecf20Sopenharmony_ci	cxdev->videodev->device_caps = videocaps;
16528c2ecf20Sopenharmony_ci	cxdev->videodev->fops = &cxusb_video_fops;
16538c2ecf20Sopenharmony_ci	cxdev->videodev->v4l2_dev = &cxdev->v4l2dev;
16548c2ecf20Sopenharmony_ci	cxdev->videodev->queue = &cxdev->videoqueue;
16558c2ecf20Sopenharmony_ci	strscpy(cxdev->videodev->name, "cxusb", sizeof(cxdev->videodev->name));
16568c2ecf20Sopenharmony_ci	cxdev->videodev->vfl_dir = VFL_DIR_RX;
16578c2ecf20Sopenharmony_ci	cxdev->videodev->ioctl_ops = &cxusb_video_ioctl;
16588c2ecf20Sopenharmony_ci	cxdev->videodev->tvnorms = V4L2_STD_ALL;
16598c2ecf20Sopenharmony_ci	cxdev->videodev->release = cxusb_medion_videodev_release;
16608c2ecf20Sopenharmony_ci	cxdev->videodev->lock = &cxdev->dev_lock;
16618c2ecf20Sopenharmony_ci	video_set_drvdata(cxdev->videodev, dvbdev);
16628c2ecf20Sopenharmony_ci
16638c2ecf20Sopenharmony_ci	ret = video_register_device(cxdev->videodev, VFL_TYPE_VIDEO, -1);
16648c2ecf20Sopenharmony_ci	if (ret) {
16658c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
16668c2ecf20Sopenharmony_ci			"video device register failed, ret = %d\n", ret);
16678c2ecf20Sopenharmony_ci		goto ret_vrelease;
16688c2ecf20Sopenharmony_ci	}
16698c2ecf20Sopenharmony_ci
16708c2ecf20Sopenharmony_ci	return 0;
16718c2ecf20Sopenharmony_ci
16728c2ecf20Sopenharmony_ciret_vrelease:
16738c2ecf20Sopenharmony_ci	video_device_release(cxdev->videodev);
16748c2ecf20Sopenharmony_ci	return ret;
16758c2ecf20Sopenharmony_ci}
16768c2ecf20Sopenharmony_ci
16778c2ecf20Sopenharmony_cistatic int cxusb_medion_register_analog_radio(struct dvb_usb_device *dvbdev)
16788c2ecf20Sopenharmony_ci{
16798c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
16808c2ecf20Sopenharmony_ci	int ret;
16818c2ecf20Sopenharmony_ci
16828c2ecf20Sopenharmony_ci	cxdev->radiodev = video_device_alloc();
16838c2ecf20Sopenharmony_ci	if (!cxdev->radiodev) {
16848c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev, "radio device alloc failed\n");
16858c2ecf20Sopenharmony_ci		return -ENOMEM;
16868c2ecf20Sopenharmony_ci	}
16878c2ecf20Sopenharmony_ci
16888c2ecf20Sopenharmony_ci	cxdev->radiodev->device_caps = radiocaps;
16898c2ecf20Sopenharmony_ci	cxdev->radiodev->fops = &cxusb_radio_fops;
16908c2ecf20Sopenharmony_ci	cxdev->radiodev->v4l2_dev = &cxdev->v4l2dev;
16918c2ecf20Sopenharmony_ci	strscpy(cxdev->radiodev->name, "cxusb", sizeof(cxdev->radiodev->name));
16928c2ecf20Sopenharmony_ci	cxdev->radiodev->vfl_dir = VFL_DIR_RX;
16938c2ecf20Sopenharmony_ci	cxdev->radiodev->ioctl_ops = &cxusb_radio_ioctl;
16948c2ecf20Sopenharmony_ci	cxdev->radiodev->release = video_device_release;
16958c2ecf20Sopenharmony_ci	cxdev->radiodev->lock = &cxdev->dev_lock;
16968c2ecf20Sopenharmony_ci	video_set_drvdata(cxdev->radiodev, dvbdev);
16978c2ecf20Sopenharmony_ci
16988c2ecf20Sopenharmony_ci	ret = video_register_device(cxdev->radiodev, VFL_TYPE_RADIO, -1);
16998c2ecf20Sopenharmony_ci	if (ret) {
17008c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
17018c2ecf20Sopenharmony_ci			"radio device register failed, ret = %d\n", ret);
17028c2ecf20Sopenharmony_ci		video_device_release(cxdev->radiodev);
17038c2ecf20Sopenharmony_ci		return ret;
17048c2ecf20Sopenharmony_ci	}
17058c2ecf20Sopenharmony_ci
17068c2ecf20Sopenharmony_ci	return 0;
17078c2ecf20Sopenharmony_ci}
17088c2ecf20Sopenharmony_ci
17098c2ecf20Sopenharmony_cistatic int cxusb_medion_register_analog_subdevs(struct dvb_usb_device *dvbdev)
17108c2ecf20Sopenharmony_ci{
17118c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
17128c2ecf20Sopenharmony_ci	int ret;
17138c2ecf20Sopenharmony_ci	struct tuner_setup tun_setup;
17148c2ecf20Sopenharmony_ci
17158c2ecf20Sopenharmony_ci	/* attach cx25840 capture chip */
17168c2ecf20Sopenharmony_ci	cxdev->cx25840 = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
17178c2ecf20Sopenharmony_ci					     &dvbdev->i2c_adap,
17188c2ecf20Sopenharmony_ci					     "cx25840", 0x44, NULL);
17198c2ecf20Sopenharmony_ci	if (!cxdev->cx25840) {
17208c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev, "cx25840 not found\n");
17218c2ecf20Sopenharmony_ci		return -ENODEV;
17228c2ecf20Sopenharmony_ci	}
17238c2ecf20Sopenharmony_ci
17248c2ecf20Sopenharmony_ci	/*
17258c2ecf20Sopenharmony_ci	 * Initialize cx25840 chip by calling its subdevice init core op.
17268c2ecf20Sopenharmony_ci	 *
17278c2ecf20Sopenharmony_ci	 * This switches it into the generic mode that disables some of
17288c2ecf20Sopenharmony_ci	 * ivtv-related hacks in the cx25840 driver while allowing setting
17298c2ecf20Sopenharmony_ci	 * of the chip video output configuration (passed in the call below
17308c2ecf20Sopenharmony_ci	 * as the last argument).
17318c2ecf20Sopenharmony_ci	 */
17328c2ecf20Sopenharmony_ci	ret = v4l2_subdev_call(cxdev->cx25840, core, init,
17338c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_FMT_BT656 |
17348c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_RES_8BIT |
17358c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_VBIRAW_DISABLED |
17368c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_ANCDATA_DISABLED |
17378c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_ACTIVE_COMPOSITE |
17388c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_VALID_ANDACTIVE |
17398c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_HRESETW_NORMAL |
17408c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_CLKGATE_NONE |
17418c2ecf20Sopenharmony_ci			       CX25840_VCONFIG_DCMODE_DWORDS);
17428c2ecf20Sopenharmony_ci	if (ret != 0) {
17438c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
17448c2ecf20Sopenharmony_ci			"cx25840 init failed (%d)\n", ret);
17458c2ecf20Sopenharmony_ci		return ret;
17468c2ecf20Sopenharmony_ci	}
17478c2ecf20Sopenharmony_ci
17488c2ecf20Sopenharmony_ci	/* attach analog tuner */
17498c2ecf20Sopenharmony_ci	cxdev->tuner = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
17508c2ecf20Sopenharmony_ci					   &dvbdev->i2c_adap,
17518c2ecf20Sopenharmony_ci					   "tuner", 0x61, NULL);
17528c2ecf20Sopenharmony_ci	if (!cxdev->tuner) {
17538c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev, "tuner not found\n");
17548c2ecf20Sopenharmony_ci		return -ENODEV;
17558c2ecf20Sopenharmony_ci	}
17568c2ecf20Sopenharmony_ci
17578c2ecf20Sopenharmony_ci	/* configure it */
17588c2ecf20Sopenharmony_ci	memset(&tun_setup, 0, sizeof(tun_setup));
17598c2ecf20Sopenharmony_ci	tun_setup.addr = 0x61;
17608c2ecf20Sopenharmony_ci	tun_setup.type = TUNER_PHILIPS_FMD1216ME_MK3;
17618c2ecf20Sopenharmony_ci	tun_setup.mode_mask = T_RADIO | T_ANALOG_TV;
17628c2ecf20Sopenharmony_ci	v4l2_subdev_call(cxdev->tuner, tuner, s_type_addr, &tun_setup);
17638c2ecf20Sopenharmony_ci
17648c2ecf20Sopenharmony_ci	/* attach IF demod */
17658c2ecf20Sopenharmony_ci	cxdev->tda9887 = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
17668c2ecf20Sopenharmony_ci					     &dvbdev->i2c_adap,
17678c2ecf20Sopenharmony_ci					     "tuner", 0x43, NULL);
17688c2ecf20Sopenharmony_ci	if (!cxdev->tda9887) {
17698c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev, "tda9887 not found\n");
17708c2ecf20Sopenharmony_ci		return -ENODEV;
17718c2ecf20Sopenharmony_ci	}
17728c2ecf20Sopenharmony_ci
17738c2ecf20Sopenharmony_ci	return 0;
17748c2ecf20Sopenharmony_ci}
17758c2ecf20Sopenharmony_ci
17768c2ecf20Sopenharmony_ciint cxusb_medion_register_analog(struct dvb_usb_device *dvbdev)
17778c2ecf20Sopenharmony_ci{
17788c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
17798c2ecf20Sopenharmony_ci	int ret;
17808c2ecf20Sopenharmony_ci
17818c2ecf20Sopenharmony_ci	mutex_init(&cxdev->dev_lock);
17828c2ecf20Sopenharmony_ci
17838c2ecf20Sopenharmony_ci	init_completion(&cxdev->v4l2_release);
17848c2ecf20Sopenharmony_ci
17858c2ecf20Sopenharmony_ci	cxdev->v4l2dev.release = cxusb_medion_v4l2_release;
17868c2ecf20Sopenharmony_ci
17878c2ecf20Sopenharmony_ci	ret = v4l2_device_register(&dvbdev->udev->dev, &cxdev->v4l2dev);
17888c2ecf20Sopenharmony_ci	if (ret != 0) {
17898c2ecf20Sopenharmony_ci		dev_err(&dvbdev->udev->dev,
17908c2ecf20Sopenharmony_ci			"V4L2 device registration failed, ret = %d\n", ret);
17918c2ecf20Sopenharmony_ci		mutex_destroy(&cxdev->dev_lock);
17928c2ecf20Sopenharmony_ci		return ret;
17938c2ecf20Sopenharmony_ci	}
17948c2ecf20Sopenharmony_ci
17958c2ecf20Sopenharmony_ci	ret = cxusb_medion_register_analog_subdevs(dvbdev);
17968c2ecf20Sopenharmony_ci	if (ret)
17978c2ecf20Sopenharmony_ci		goto ret_unregister;
17988c2ecf20Sopenharmony_ci
17998c2ecf20Sopenharmony_ci	INIT_WORK(&cxdev->urbwork, cxusb_medion_v_complete_work);
18008c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&cxdev->buflist);
18018c2ecf20Sopenharmony_ci
18028c2ecf20Sopenharmony_ci	cxdev->width = 320;
18038c2ecf20Sopenharmony_ci	cxdev->height = 240;
18048c2ecf20Sopenharmony_ci
18058c2ecf20Sopenharmony_ci	ret = cxusb_medion_register_analog_video(dvbdev);
18068c2ecf20Sopenharmony_ci	if (ret)
18078c2ecf20Sopenharmony_ci		goto ret_unregister;
18088c2ecf20Sopenharmony_ci
18098c2ecf20Sopenharmony_ci	ret = cxusb_medion_register_analog_radio(dvbdev);
18108c2ecf20Sopenharmony_ci	if (ret)
18118c2ecf20Sopenharmony_ci		goto ret_vunreg;
18128c2ecf20Sopenharmony_ci
18138c2ecf20Sopenharmony_ci	return 0;
18148c2ecf20Sopenharmony_ci
18158c2ecf20Sopenharmony_ciret_vunreg:
18168c2ecf20Sopenharmony_ci	vb2_video_unregister_device(cxdev->videodev);
18178c2ecf20Sopenharmony_ci
18188c2ecf20Sopenharmony_ciret_unregister:
18198c2ecf20Sopenharmony_ci	v4l2_device_put(&cxdev->v4l2dev);
18208c2ecf20Sopenharmony_ci	wait_for_completion(&cxdev->v4l2_release);
18218c2ecf20Sopenharmony_ci
18228c2ecf20Sopenharmony_ci	return ret;
18238c2ecf20Sopenharmony_ci}
18248c2ecf20Sopenharmony_ci
18258c2ecf20Sopenharmony_civoid cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev)
18268c2ecf20Sopenharmony_ci{
18278c2ecf20Sopenharmony_ci	struct cxusb_medion_dev *cxdev = dvbdev->priv;
18288c2ecf20Sopenharmony_ci
18298c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "unregistering analog\n");
18308c2ecf20Sopenharmony_ci
18318c2ecf20Sopenharmony_ci	video_unregister_device(cxdev->radiodev);
18328c2ecf20Sopenharmony_ci	vb2_video_unregister_device(cxdev->videodev);
18338c2ecf20Sopenharmony_ci
18348c2ecf20Sopenharmony_ci	v4l2_device_put(&cxdev->v4l2dev);
18358c2ecf20Sopenharmony_ci	wait_for_completion(&cxdev->v4l2_release);
18368c2ecf20Sopenharmony_ci
18378c2ecf20Sopenharmony_ci	cxusb_vprintk(dvbdev, OPS, "analog unregistered\n");
18388c2ecf20Sopenharmony_ci}
1839