18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * device driver for Conexant 2388x based TV cards
58c2ecf20Sopenharmony_ci * video4linux video interface
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * (c) 2003-04 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * (c) 2005-2006 Mauro Carvalho Chehab <mchehab@kernel.org>
108c2ecf20Sopenharmony_ci *	- Multituner support
118c2ecf20Sopenharmony_ci *	- video_ioctl2 conversion
128c2ecf20Sopenharmony_ci *	- PAL/M fixes
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include "cx88.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <linux/init.h>
188c2ecf20Sopenharmony_ci#include <linux/list.h>
198c2ecf20Sopenharmony_ci#include <linux/module.h>
208c2ecf20Sopenharmony_ci#include <linux/kmod.h>
218c2ecf20Sopenharmony_ci#include <linux/kernel.h>
228c2ecf20Sopenharmony_ci#include <linux/slab.h>
238c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
248c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
258c2ecf20Sopenharmony_ci#include <linux/delay.h>
268c2ecf20Sopenharmony_ci#include <linux/kthread.h>
278c2ecf20Sopenharmony_ci#include <asm/div64.h>
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#include <media/v4l2-common.h>
308c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
318c2ecf20Sopenharmony_ci#include <media/v4l2-event.h>
328c2ecf20Sopenharmony_ci#include <media/i2c/wm8775.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards");
358c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
368c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
378c2ecf20Sopenharmony_ciMODULE_VERSION(CX88_VERSION);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
428c2ecf20Sopenharmony_cistatic unsigned int vbi_nr[]   = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
438c2ecf20Sopenharmony_cistatic unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET };
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cimodule_param_array(video_nr, int, NULL, 0444);
468c2ecf20Sopenharmony_cimodule_param_array(vbi_nr,   int, NULL, 0444);
478c2ecf20Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ciMODULE_PARM_DESC(video_nr, "video device numbers");
508c2ecf20Sopenharmony_ciMODULE_PARM_DESC(vbi_nr, "vbi device numbers");
518c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "radio device numbers");
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic unsigned int video_debug;
548c2ecf20Sopenharmony_cimodule_param(video_debug, int, 0644);
558c2ecf20Sopenharmony_ciMODULE_PARM_DESC(video_debug, "enable debug messages [video]");
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistatic unsigned int irq_debug;
588c2ecf20Sopenharmony_cimodule_param(irq_debug, int, 0644);
598c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]");
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#define dprintk(level, fmt, arg...) do {			\
628c2ecf20Sopenharmony_ci	if (video_debug >= level)				\
638c2ecf20Sopenharmony_ci		printk(KERN_DEBUG pr_fmt("%s: video:" fmt),	\
648c2ecf20Sopenharmony_ci			__func__, ##arg);			\
658c2ecf20Sopenharmony_ci} while (0)
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------- */
688c2ecf20Sopenharmony_ci/* static data                                                         */
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic const struct cx8800_fmt formats[] = {
718c2ecf20Sopenharmony_ci	{
728c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_GREY,
738c2ecf20Sopenharmony_ci		.cxformat = ColorFormatY8,
748c2ecf20Sopenharmony_ci		.depth    = 8,
758c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
768c2ecf20Sopenharmony_ci	}, {
778c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_RGB555,
788c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB15,
798c2ecf20Sopenharmony_ci		.depth    = 16,
808c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
818c2ecf20Sopenharmony_ci	}, {
828c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_RGB555X,
838c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB15 | ColorFormatBSWAP,
848c2ecf20Sopenharmony_ci		.depth    = 16,
858c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
868c2ecf20Sopenharmony_ci	}, {
878c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_RGB565,
888c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB16,
898c2ecf20Sopenharmony_ci		.depth    = 16,
908c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
918c2ecf20Sopenharmony_ci	}, {
928c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_RGB565X,
938c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB16 | ColorFormatBSWAP,
948c2ecf20Sopenharmony_ci		.depth    = 16,
958c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
968c2ecf20Sopenharmony_ci	}, {
978c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_BGR24,
988c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB24,
998c2ecf20Sopenharmony_ci		.depth    = 24,
1008c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
1018c2ecf20Sopenharmony_ci	}, {
1028c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_BGR32,
1038c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB32,
1048c2ecf20Sopenharmony_ci		.depth    = 32,
1058c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
1068c2ecf20Sopenharmony_ci	}, {
1078c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_RGB32,
1088c2ecf20Sopenharmony_ci		.cxformat = ColorFormatRGB32 | ColorFormatBSWAP |
1098c2ecf20Sopenharmony_ci			    ColorFormatWSWAP,
1108c2ecf20Sopenharmony_ci		.depth    = 32,
1118c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
1128c2ecf20Sopenharmony_ci	}, {
1138c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_YUYV,
1148c2ecf20Sopenharmony_ci		.cxformat = ColorFormatYUY2,
1158c2ecf20Sopenharmony_ci		.depth    = 16,
1168c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
1178c2ecf20Sopenharmony_ci	}, {
1188c2ecf20Sopenharmony_ci		.fourcc   = V4L2_PIX_FMT_UYVY,
1198c2ecf20Sopenharmony_ci		.cxformat = ColorFormatYUY2 | ColorFormatBSWAP,
1208c2ecf20Sopenharmony_ci		.depth    = 16,
1218c2ecf20Sopenharmony_ci		.flags    = FORMAT_FLAGS_PACKED,
1228c2ecf20Sopenharmony_ci	},
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic const struct cx8800_fmt *format_by_fourcc(unsigned int fourcc)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	unsigned int i;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(formats); i++)
1308c2ecf20Sopenharmony_ci		if (formats[i].fourcc == fourcc)
1318c2ecf20Sopenharmony_ci			return formats + i;
1328c2ecf20Sopenharmony_ci	return NULL;
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------- */
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistruct cx88_ctrl {
1388c2ecf20Sopenharmony_ci	/* control information */
1398c2ecf20Sopenharmony_ci	u32 id;
1408c2ecf20Sopenharmony_ci	s32 minimum;
1418c2ecf20Sopenharmony_ci	s32 maximum;
1428c2ecf20Sopenharmony_ci	u32 step;
1438c2ecf20Sopenharmony_ci	s32 default_value;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	/* control register information */
1468c2ecf20Sopenharmony_ci	u32 off;
1478c2ecf20Sopenharmony_ci	u32 reg;
1488c2ecf20Sopenharmony_ci	u32 sreg;
1498c2ecf20Sopenharmony_ci	u32 mask;
1508c2ecf20Sopenharmony_ci	u32 shift;
1518c2ecf20Sopenharmony_ci};
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_cistatic const struct cx88_ctrl cx8800_vid_ctls[] = {
1548c2ecf20Sopenharmony_ci	/* --- video --- */
1558c2ecf20Sopenharmony_ci	{
1568c2ecf20Sopenharmony_ci		.id            = V4L2_CID_BRIGHTNESS,
1578c2ecf20Sopenharmony_ci		.minimum       = 0x00,
1588c2ecf20Sopenharmony_ci		.maximum       = 0xff,
1598c2ecf20Sopenharmony_ci		.step          = 1,
1608c2ecf20Sopenharmony_ci		.default_value = 0x7f,
1618c2ecf20Sopenharmony_ci		.off           = 128,
1628c2ecf20Sopenharmony_ci		.reg           = MO_CONTR_BRIGHT,
1638c2ecf20Sopenharmony_ci		.mask          = 0x00ff,
1648c2ecf20Sopenharmony_ci		.shift         = 0,
1658c2ecf20Sopenharmony_ci	}, {
1668c2ecf20Sopenharmony_ci		.id            = V4L2_CID_CONTRAST,
1678c2ecf20Sopenharmony_ci		.minimum       = 0,
1688c2ecf20Sopenharmony_ci		.maximum       = 0xff,
1698c2ecf20Sopenharmony_ci		.step          = 1,
1708c2ecf20Sopenharmony_ci		.default_value = 0x3f,
1718c2ecf20Sopenharmony_ci		.off           = 0,
1728c2ecf20Sopenharmony_ci		.reg           = MO_CONTR_BRIGHT,
1738c2ecf20Sopenharmony_ci		.mask          = 0xff00,
1748c2ecf20Sopenharmony_ci		.shift         = 8,
1758c2ecf20Sopenharmony_ci	}, {
1768c2ecf20Sopenharmony_ci		.id            = V4L2_CID_HUE,
1778c2ecf20Sopenharmony_ci		.minimum       = 0,
1788c2ecf20Sopenharmony_ci		.maximum       = 0xff,
1798c2ecf20Sopenharmony_ci		.step          = 1,
1808c2ecf20Sopenharmony_ci		.default_value = 0x7f,
1818c2ecf20Sopenharmony_ci		.off           = 128,
1828c2ecf20Sopenharmony_ci		.reg           = MO_HUE,
1838c2ecf20Sopenharmony_ci		.mask          = 0x00ff,
1848c2ecf20Sopenharmony_ci		.shift         = 0,
1858c2ecf20Sopenharmony_ci	}, {
1868c2ecf20Sopenharmony_ci		/* strictly, this only describes only U saturation.
1878c2ecf20Sopenharmony_ci		 * V saturation is handled specially through code.
1888c2ecf20Sopenharmony_ci		 */
1898c2ecf20Sopenharmony_ci		.id            = V4L2_CID_SATURATION,
1908c2ecf20Sopenharmony_ci		.minimum       = 0,
1918c2ecf20Sopenharmony_ci		.maximum       = 0xff,
1928c2ecf20Sopenharmony_ci		.step          = 1,
1938c2ecf20Sopenharmony_ci		.default_value = 0x7f,
1948c2ecf20Sopenharmony_ci		.off           = 0,
1958c2ecf20Sopenharmony_ci		.reg           = MO_UV_SATURATION,
1968c2ecf20Sopenharmony_ci		.mask          = 0x00ff,
1978c2ecf20Sopenharmony_ci		.shift         = 0,
1988c2ecf20Sopenharmony_ci	}, {
1998c2ecf20Sopenharmony_ci		.id            = V4L2_CID_SHARPNESS,
2008c2ecf20Sopenharmony_ci		.minimum       = 0,
2018c2ecf20Sopenharmony_ci		.maximum       = 4,
2028c2ecf20Sopenharmony_ci		.step          = 1,
2038c2ecf20Sopenharmony_ci		.default_value = 0x0,
2048c2ecf20Sopenharmony_ci		.off           = 0,
2058c2ecf20Sopenharmony_ci		/*
2068c2ecf20Sopenharmony_ci		 * NOTE: the value is converted and written to both even
2078c2ecf20Sopenharmony_ci		 * and odd registers in the code
2088c2ecf20Sopenharmony_ci		 */
2098c2ecf20Sopenharmony_ci		.reg           = MO_FILTER_ODD,
2108c2ecf20Sopenharmony_ci		.mask          = 7 << 7,
2118c2ecf20Sopenharmony_ci		.shift         = 7,
2128c2ecf20Sopenharmony_ci	}, {
2138c2ecf20Sopenharmony_ci		.id            = V4L2_CID_CHROMA_AGC,
2148c2ecf20Sopenharmony_ci		.minimum       = 0,
2158c2ecf20Sopenharmony_ci		.maximum       = 1,
2168c2ecf20Sopenharmony_ci		.default_value = 0x1,
2178c2ecf20Sopenharmony_ci		.reg           = MO_INPUT_FORMAT,
2188c2ecf20Sopenharmony_ci		.mask          = 1 << 10,
2198c2ecf20Sopenharmony_ci		.shift         = 10,
2208c2ecf20Sopenharmony_ci	}, {
2218c2ecf20Sopenharmony_ci		.id            = V4L2_CID_COLOR_KILLER,
2228c2ecf20Sopenharmony_ci		.minimum       = 0,
2238c2ecf20Sopenharmony_ci		.maximum       = 1,
2248c2ecf20Sopenharmony_ci		.default_value = 0x1,
2258c2ecf20Sopenharmony_ci		.reg           = MO_INPUT_FORMAT,
2268c2ecf20Sopenharmony_ci		.mask          = 1 << 9,
2278c2ecf20Sopenharmony_ci		.shift         = 9,
2288c2ecf20Sopenharmony_ci	}, {
2298c2ecf20Sopenharmony_ci		.id            = V4L2_CID_BAND_STOP_FILTER,
2308c2ecf20Sopenharmony_ci		.minimum       = 0,
2318c2ecf20Sopenharmony_ci		.maximum       = 1,
2328c2ecf20Sopenharmony_ci		.step          = 1,
2338c2ecf20Sopenharmony_ci		.default_value = 0x0,
2348c2ecf20Sopenharmony_ci		.off           = 0,
2358c2ecf20Sopenharmony_ci		.reg           = MO_HTOTAL,
2368c2ecf20Sopenharmony_ci		.mask          = 3 << 11,
2378c2ecf20Sopenharmony_ci		.shift         = 11,
2388c2ecf20Sopenharmony_ci	}
2398c2ecf20Sopenharmony_ci};
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cistatic const struct cx88_ctrl cx8800_aud_ctls[] = {
2428c2ecf20Sopenharmony_ci	{
2438c2ecf20Sopenharmony_ci		/* --- audio --- */
2448c2ecf20Sopenharmony_ci		.id            = V4L2_CID_AUDIO_MUTE,
2458c2ecf20Sopenharmony_ci		.minimum       = 0,
2468c2ecf20Sopenharmony_ci		.maximum       = 1,
2478c2ecf20Sopenharmony_ci		.default_value = 1,
2488c2ecf20Sopenharmony_ci		.reg           = AUD_VOL_CTL,
2498c2ecf20Sopenharmony_ci		.sreg          = SHADOW_AUD_VOL_CTL,
2508c2ecf20Sopenharmony_ci		.mask          = (1 << 6),
2518c2ecf20Sopenharmony_ci		.shift         = 6,
2528c2ecf20Sopenharmony_ci	}, {
2538c2ecf20Sopenharmony_ci		.id            = V4L2_CID_AUDIO_VOLUME,
2548c2ecf20Sopenharmony_ci		.minimum       = 0,
2558c2ecf20Sopenharmony_ci		.maximum       = 0x3f,
2568c2ecf20Sopenharmony_ci		.step          = 1,
2578c2ecf20Sopenharmony_ci		.default_value = 0x3f,
2588c2ecf20Sopenharmony_ci		.reg           = AUD_VOL_CTL,
2598c2ecf20Sopenharmony_ci		.sreg          = SHADOW_AUD_VOL_CTL,
2608c2ecf20Sopenharmony_ci		.mask          = 0x3f,
2618c2ecf20Sopenharmony_ci		.shift         = 0,
2628c2ecf20Sopenharmony_ci	}, {
2638c2ecf20Sopenharmony_ci		.id            = V4L2_CID_AUDIO_BALANCE,
2648c2ecf20Sopenharmony_ci		.minimum       = 0,
2658c2ecf20Sopenharmony_ci		.maximum       = 0x7f,
2668c2ecf20Sopenharmony_ci		.step          = 1,
2678c2ecf20Sopenharmony_ci		.default_value = 0x40,
2688c2ecf20Sopenharmony_ci		.reg           = AUD_BAL_CTL,
2698c2ecf20Sopenharmony_ci		.sreg          = SHADOW_AUD_BAL_CTL,
2708c2ecf20Sopenharmony_ci		.mask          = 0x7f,
2718c2ecf20Sopenharmony_ci		.shift         = 0,
2728c2ecf20Sopenharmony_ci	}
2738c2ecf20Sopenharmony_ci};
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cienum {
2768c2ecf20Sopenharmony_ci	CX8800_VID_CTLS = ARRAY_SIZE(cx8800_vid_ctls),
2778c2ecf20Sopenharmony_ci	CX8800_AUD_CTLS = ARRAY_SIZE(cx8800_aud_ctls),
2788c2ecf20Sopenharmony_ci};
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ciint cx88_video_mux(struct cx88_core *core, unsigned int input)
2838c2ecf20Sopenharmony_ci{
2848c2ecf20Sopenharmony_ci	/* struct cx88_core *core = dev->core; */
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	dprintk(1, "video_mux: %d [vmux=%d,gpio=0x%x,0x%x,0x%x,0x%x]\n",
2878c2ecf20Sopenharmony_ci		input, INPUT(input).vmux,
2888c2ecf20Sopenharmony_ci		INPUT(input).gpio0, INPUT(input).gpio1,
2898c2ecf20Sopenharmony_ci		INPUT(input).gpio2, INPUT(input).gpio3);
2908c2ecf20Sopenharmony_ci	core->input = input;
2918c2ecf20Sopenharmony_ci	cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input).vmux << 14);
2928c2ecf20Sopenharmony_ci	cx_write(MO_GP3_IO, INPUT(input).gpio3);
2938c2ecf20Sopenharmony_ci	cx_write(MO_GP0_IO, INPUT(input).gpio0);
2948c2ecf20Sopenharmony_ci	cx_write(MO_GP1_IO, INPUT(input).gpio1);
2958c2ecf20Sopenharmony_ci	cx_write(MO_GP2_IO, INPUT(input).gpio2);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	switch (INPUT(input).type) {
2988c2ecf20Sopenharmony_ci	case CX88_VMUX_SVIDEO:
2998c2ecf20Sopenharmony_ci		cx_set(MO_AFECFG_IO,    0x00000001);
3008c2ecf20Sopenharmony_ci		cx_set(MO_INPUT_FORMAT, 0x00010010);
3018c2ecf20Sopenharmony_ci		cx_set(MO_FILTER_EVEN,  0x00002020);
3028c2ecf20Sopenharmony_ci		cx_set(MO_FILTER_ODD,   0x00002020);
3038c2ecf20Sopenharmony_ci		break;
3048c2ecf20Sopenharmony_ci	default:
3058c2ecf20Sopenharmony_ci		cx_clear(MO_AFECFG_IO,    0x00000001);
3068c2ecf20Sopenharmony_ci		cx_clear(MO_INPUT_FORMAT, 0x00010010);
3078c2ecf20Sopenharmony_ci		cx_clear(MO_FILTER_EVEN,  0x00002020);
3088c2ecf20Sopenharmony_ci		cx_clear(MO_FILTER_ODD,   0x00002020);
3098c2ecf20Sopenharmony_ci		break;
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	/*
3138c2ecf20Sopenharmony_ci	 * if there are audioroutes defined, we have an external
3148c2ecf20Sopenharmony_ci	 * ADC to deal with audio
3158c2ecf20Sopenharmony_ci	 */
3168c2ecf20Sopenharmony_ci	if (INPUT(input).audioroute) {
3178c2ecf20Sopenharmony_ci		/*
3188c2ecf20Sopenharmony_ci		 * The wm8775 module has the "2" route hardwired into
3198c2ecf20Sopenharmony_ci		 * the initialization. Some boards may use different
3208c2ecf20Sopenharmony_ci		 * routes for different inputs. HVR-1300 surely does
3218c2ecf20Sopenharmony_ci		 */
3228c2ecf20Sopenharmony_ci		if (core->sd_wm8775) {
3238c2ecf20Sopenharmony_ci			call_all(core, audio, s_routing,
3248c2ecf20Sopenharmony_ci				 INPUT(input).audioroute, 0, 0);
3258c2ecf20Sopenharmony_ci		}
3268c2ecf20Sopenharmony_ci		/*
3278c2ecf20Sopenharmony_ci		 * cx2388's C-ADC is connected to the tuner only.
3288c2ecf20Sopenharmony_ci		 * When used with S-Video, that ADC is busy dealing with
3298c2ecf20Sopenharmony_ci		 * chroma, so an external must be used for baseband audio
3308c2ecf20Sopenharmony_ci		 */
3318c2ecf20Sopenharmony_ci		if (INPUT(input).type != CX88_VMUX_TELEVISION &&
3328c2ecf20Sopenharmony_ci		    INPUT(input).type != CX88_VMUX_CABLE) {
3338c2ecf20Sopenharmony_ci			/* "I2S ADC mode" */
3348c2ecf20Sopenharmony_ci			core->tvaudio = WW_I2SADC;
3358c2ecf20Sopenharmony_ci			cx88_set_tvaudio(core);
3368c2ecf20Sopenharmony_ci		} else {
3378c2ecf20Sopenharmony_ci			/* Normal mode */
3388c2ecf20Sopenharmony_ci			cx_write(AUD_I2SCNTL, 0x0);
3398c2ecf20Sopenharmony_ci			cx_clear(AUD_CTL, EN_I2SIN_ENABLE);
3408c2ecf20Sopenharmony_ci		}
3418c2ecf20Sopenharmony_ci	}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	return 0;
3448c2ecf20Sopenharmony_ci}
3458c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx88_video_mux);
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_cistatic int start_video_dma(struct cx8800_dev    *dev,
3508c2ecf20Sopenharmony_ci			   struct cx88_dmaqueue *q,
3518c2ecf20Sopenharmony_ci			   struct cx88_buffer   *buf)
3528c2ecf20Sopenharmony_ci{
3538c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci	/* setup fifo + format */
3568c2ecf20Sopenharmony_ci	cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21],
3578c2ecf20Sopenharmony_ci				buf->bpl, buf->risc.dma);
3588c2ecf20Sopenharmony_ci	cx88_set_scale(core, core->width, core->height, core->field);
3598c2ecf20Sopenharmony_ci	cx_write(MO_COLOR_CTRL, dev->fmt->cxformat | ColorFormatGamma);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/* reset counter */
3628c2ecf20Sopenharmony_ci	cx_write(MO_VIDY_GPCNTRL, GP_COUNT_CONTROL_RESET);
3638c2ecf20Sopenharmony_ci	q->count = 0;
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	/* enable irqs */
3668c2ecf20Sopenharmony_ci	cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT);
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	/*
3698c2ecf20Sopenharmony_ci	 * Enables corresponding bits at PCI_INT_STAT:
3708c2ecf20Sopenharmony_ci	 *	bits 0 to 4: video, audio, transport stream, VIP, Host
3718c2ecf20Sopenharmony_ci	 *	bit 7: timer
3728c2ecf20Sopenharmony_ci	 *	bits 8 and 9: DMA complete for: SRC, DST
3738c2ecf20Sopenharmony_ci	 *	bits 10 and 11: BERR signal asserted for RISC: RD, WR
3748c2ecf20Sopenharmony_ci	 *	bits 12 to 15: BERR signal asserted for: BRDG, SRC, DST, IPB
3758c2ecf20Sopenharmony_ci	 */
3768c2ecf20Sopenharmony_ci	cx_set(MO_VID_INTMSK, 0x0f0011);
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	/* enable capture */
3798c2ecf20Sopenharmony_ci	cx_set(VID_CAPTURE_CONTROL, 0x06);
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	/* start dma */
3828c2ecf20Sopenharmony_ci	cx_set(MO_DEV_CNTRL2, (1 << 5));
3838c2ecf20Sopenharmony_ci	cx_set(MO_VID_DMACNTRL, 0x11); /* Planar Y and packed FIFO and RISC enable */
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	return 0;
3868c2ecf20Sopenharmony_ci}
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_cistatic int __maybe_unused stop_video_dma(struct cx8800_dev    *dev)
3898c2ecf20Sopenharmony_ci{
3908c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	/* stop dma */
3938c2ecf20Sopenharmony_ci	cx_clear(MO_VID_DMACNTRL, 0x11);
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	/* disable capture */
3968c2ecf20Sopenharmony_ci	cx_clear(VID_CAPTURE_CONTROL, 0x06);
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	/* disable irqs */
3998c2ecf20Sopenharmony_ci	cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT);
4008c2ecf20Sopenharmony_ci	cx_clear(MO_VID_INTMSK, 0x0f0011);
4018c2ecf20Sopenharmony_ci	return 0;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic int __maybe_unused restart_video_queue(struct cx8800_dev *dev,
4058c2ecf20Sopenharmony_ci					      struct cx88_dmaqueue *q)
4068c2ecf20Sopenharmony_ci{
4078c2ecf20Sopenharmony_ci	struct cx88_buffer *buf;
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	if (!list_empty(&q->active)) {
4108c2ecf20Sopenharmony_ci		buf = list_entry(q->active.next, struct cx88_buffer, list);
4118c2ecf20Sopenharmony_ci		dprintk(2, "restart_queue [%p/%d]: restart dma\n",
4128c2ecf20Sopenharmony_ci			buf, buf->vb.vb2_buf.index);
4138c2ecf20Sopenharmony_ci		start_video_dma(dev, q, buf);
4148c2ecf20Sopenharmony_ci	}
4158c2ecf20Sopenharmony_ci	return 0;
4168c2ecf20Sopenharmony_ci}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_cistatic int queue_setup(struct vb2_queue *q,
4218c2ecf20Sopenharmony_ci		       unsigned int *num_buffers, unsigned int *num_planes,
4228c2ecf20Sopenharmony_ci		       unsigned int sizes[], struct device *alloc_devs[])
4238c2ecf20Sopenharmony_ci{
4248c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = q->drv_priv;
4258c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	*num_planes = 1;
4288c2ecf20Sopenharmony_ci	sizes[0] = (dev->fmt->depth * core->width * core->height) >> 3;
4298c2ecf20Sopenharmony_ci	return 0;
4308c2ecf20Sopenharmony_ci}
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_cistatic int buffer_prepare(struct vb2_buffer *vb)
4338c2ecf20Sopenharmony_ci{
4348c2ecf20Sopenharmony_ci	int ret;
4358c2ecf20Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
4368c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
4378c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
4388c2ecf20Sopenharmony_ci	struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
4398c2ecf20Sopenharmony_ci	struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	buf->bpl = core->width * dev->fmt->depth >> 3;
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_ci	if (vb2_plane_size(vb, 0) < core->height * buf->bpl)
4448c2ecf20Sopenharmony_ci		return -EINVAL;
4458c2ecf20Sopenharmony_ci	vb2_set_plane_payload(vb, 0, core->height * buf->bpl);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	switch (core->field) {
4488c2ecf20Sopenharmony_ci	case V4L2_FIELD_TOP:
4498c2ecf20Sopenharmony_ci		ret = cx88_risc_buffer(dev->pci, &buf->risc,
4508c2ecf20Sopenharmony_ci				       sgt->sgl, 0, UNSET,
4518c2ecf20Sopenharmony_ci				       buf->bpl, 0, core->height);
4528c2ecf20Sopenharmony_ci		break;
4538c2ecf20Sopenharmony_ci	case V4L2_FIELD_BOTTOM:
4548c2ecf20Sopenharmony_ci		ret = cx88_risc_buffer(dev->pci, &buf->risc,
4558c2ecf20Sopenharmony_ci				       sgt->sgl, UNSET, 0,
4568c2ecf20Sopenharmony_ci				       buf->bpl, 0, core->height);
4578c2ecf20Sopenharmony_ci		break;
4588c2ecf20Sopenharmony_ci	case V4L2_FIELD_SEQ_TB:
4598c2ecf20Sopenharmony_ci		ret = cx88_risc_buffer(dev->pci, &buf->risc,
4608c2ecf20Sopenharmony_ci				       sgt->sgl,
4618c2ecf20Sopenharmony_ci				       0, buf->bpl * (core->height >> 1),
4628c2ecf20Sopenharmony_ci				       buf->bpl, 0,
4638c2ecf20Sopenharmony_ci				       core->height >> 1);
4648c2ecf20Sopenharmony_ci		break;
4658c2ecf20Sopenharmony_ci	case V4L2_FIELD_SEQ_BT:
4668c2ecf20Sopenharmony_ci		ret = cx88_risc_buffer(dev->pci, &buf->risc,
4678c2ecf20Sopenharmony_ci				       sgt->sgl,
4688c2ecf20Sopenharmony_ci				       buf->bpl * (core->height >> 1), 0,
4698c2ecf20Sopenharmony_ci				       buf->bpl, 0,
4708c2ecf20Sopenharmony_ci				       core->height >> 1);
4718c2ecf20Sopenharmony_ci		break;
4728c2ecf20Sopenharmony_ci	case V4L2_FIELD_INTERLACED:
4738c2ecf20Sopenharmony_ci	default:
4748c2ecf20Sopenharmony_ci		ret = cx88_risc_buffer(dev->pci, &buf->risc,
4758c2ecf20Sopenharmony_ci				       sgt->sgl, 0, buf->bpl,
4768c2ecf20Sopenharmony_ci				       buf->bpl, buf->bpl,
4778c2ecf20Sopenharmony_ci				       core->height >> 1);
4788c2ecf20Sopenharmony_ci		break;
4798c2ecf20Sopenharmony_ci	}
4808c2ecf20Sopenharmony_ci	dprintk(2,
4818c2ecf20Sopenharmony_ci		"[%p/%d] %s - %dx%d %dbpp 0x%08x - dma=0x%08lx\n",
4828c2ecf20Sopenharmony_ci		buf, buf->vb.vb2_buf.index, __func__,
4838c2ecf20Sopenharmony_ci		core->width, core->height, dev->fmt->depth, dev->fmt->fourcc,
4848c2ecf20Sopenharmony_ci		(unsigned long)buf->risc.dma);
4858c2ecf20Sopenharmony_ci	return ret;
4868c2ecf20Sopenharmony_ci}
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_cistatic void buffer_finish(struct vb2_buffer *vb)
4898c2ecf20Sopenharmony_ci{
4908c2ecf20Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
4918c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
4928c2ecf20Sopenharmony_ci	struct cx88_buffer *buf = container_of(vbuf, struct cx88_buffer, vb);
4938c2ecf20Sopenharmony_ci	struct cx88_riscmem *risc = &buf->risc;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	if (risc->cpu)
4968c2ecf20Sopenharmony_ci		pci_free_consistent(dev->pci, risc->size, risc->cpu, risc->dma);
4978c2ecf20Sopenharmony_ci	memset(risc, 0, sizeof(*risc));
4988c2ecf20Sopenharmony_ci}
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cistatic void buffer_queue(struct vb2_buffer *vb)
5018c2ecf20Sopenharmony_ci{
5028c2ecf20Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
5038c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = vb->vb2_queue->drv_priv;
5048c2ecf20Sopenharmony_ci	struct cx88_buffer    *buf = container_of(vbuf, struct cx88_buffer, vb);
5058c2ecf20Sopenharmony_ci	struct cx88_buffer    *prev;
5068c2ecf20Sopenharmony_ci	struct cx88_dmaqueue  *q    = &dev->vidq;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	/* add jump to start */
5098c2ecf20Sopenharmony_ci	buf->risc.cpu[1] = cpu_to_le32(buf->risc.dma + 8);
5108c2ecf20Sopenharmony_ci	buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_CNT_INC);
5118c2ecf20Sopenharmony_ci	buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma + 8);
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	if (list_empty(&q->active)) {
5148c2ecf20Sopenharmony_ci		list_add_tail(&buf->list, &q->active);
5158c2ecf20Sopenharmony_ci		dprintk(2, "[%p/%d] buffer_queue - first active\n",
5168c2ecf20Sopenharmony_ci			buf, buf->vb.vb2_buf.index);
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	} else {
5198c2ecf20Sopenharmony_ci		buf->risc.cpu[0] |= cpu_to_le32(RISC_IRQ1);
5208c2ecf20Sopenharmony_ci		prev = list_entry(q->active.prev, struct cx88_buffer, list);
5218c2ecf20Sopenharmony_ci		list_add_tail(&buf->list, &q->active);
5228c2ecf20Sopenharmony_ci		prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
5238c2ecf20Sopenharmony_ci		dprintk(2, "[%p/%d] buffer_queue - append to active\n",
5248c2ecf20Sopenharmony_ci			buf, buf->vb.vb2_buf.index);
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci}
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_cistatic int start_streaming(struct vb2_queue *q, unsigned int count)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = q->drv_priv;
5318c2ecf20Sopenharmony_ci	struct cx88_dmaqueue *dmaq = &dev->vidq;
5328c2ecf20Sopenharmony_ci	struct cx88_buffer *buf = list_entry(dmaq->active.next,
5338c2ecf20Sopenharmony_ci			struct cx88_buffer, list);
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	start_video_dma(dev, dmaq, buf);
5368c2ecf20Sopenharmony_ci	return 0;
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_cistatic void stop_streaming(struct vb2_queue *q)
5408c2ecf20Sopenharmony_ci{
5418c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = q->drv_priv;
5428c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
5438c2ecf20Sopenharmony_ci	struct cx88_dmaqueue *dmaq = &dev->vidq;
5448c2ecf20Sopenharmony_ci	unsigned long flags;
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	cx_clear(MO_VID_DMACNTRL, 0x11);
5478c2ecf20Sopenharmony_ci	cx_clear(VID_CAPTURE_CONTROL, 0x06);
5488c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->slock, flags);
5498c2ecf20Sopenharmony_ci	while (!list_empty(&dmaq->active)) {
5508c2ecf20Sopenharmony_ci		struct cx88_buffer *buf = list_entry(dmaq->active.next,
5518c2ecf20Sopenharmony_ci			struct cx88_buffer, list);
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci		list_del(&buf->list);
5548c2ecf20Sopenharmony_ci		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
5558c2ecf20Sopenharmony_ci	}
5568c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->slock, flags);
5578c2ecf20Sopenharmony_ci}
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_cistatic const struct vb2_ops cx8800_video_qops = {
5608c2ecf20Sopenharmony_ci	.queue_setup    = queue_setup,
5618c2ecf20Sopenharmony_ci	.buf_prepare  = buffer_prepare,
5628c2ecf20Sopenharmony_ci	.buf_finish = buffer_finish,
5638c2ecf20Sopenharmony_ci	.buf_queue    = buffer_queue,
5648c2ecf20Sopenharmony_ci	.wait_prepare = vb2_ops_wait_prepare,
5658c2ecf20Sopenharmony_ci	.wait_finish = vb2_ops_wait_finish,
5668c2ecf20Sopenharmony_ci	.start_streaming = start_streaming,
5678c2ecf20Sopenharmony_ci	.stop_streaming = stop_streaming,
5688c2ecf20Sopenharmony_ci};
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_cistatic int radio_open(struct file *file)
5738c2ecf20Sopenharmony_ci{
5748c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
5758c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
5768c2ecf20Sopenharmony_ci	int ret = v4l2_fh_open(file);
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci	if (ret)
5798c2ecf20Sopenharmony_ci		return ret;
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	cx_write(MO_GP3_IO, core->board.radio.gpio3);
5828c2ecf20Sopenharmony_ci	cx_write(MO_GP0_IO, core->board.radio.gpio0);
5838c2ecf20Sopenharmony_ci	cx_write(MO_GP1_IO, core->board.radio.gpio1);
5848c2ecf20Sopenharmony_ci	cx_write(MO_GP2_IO, core->board.radio.gpio2);
5858c2ecf20Sopenharmony_ci	if (core->board.radio.audioroute) {
5868c2ecf20Sopenharmony_ci		if (core->sd_wm8775) {
5878c2ecf20Sopenharmony_ci			call_all(core, audio, s_routing,
5888c2ecf20Sopenharmony_ci				 core->board.radio.audioroute, 0, 0);
5898c2ecf20Sopenharmony_ci		}
5908c2ecf20Sopenharmony_ci		/* "I2S ADC mode" */
5918c2ecf20Sopenharmony_ci		core->tvaudio = WW_I2SADC;
5928c2ecf20Sopenharmony_ci		cx88_set_tvaudio(core);
5938c2ecf20Sopenharmony_ci	} else {
5948c2ecf20Sopenharmony_ci		/* FM Mode */
5958c2ecf20Sopenharmony_ci		core->tvaudio = WW_FM;
5968c2ecf20Sopenharmony_ci		cx88_set_tvaudio(core);
5978c2ecf20Sopenharmony_ci		cx88_set_stereo(core, V4L2_TUNER_MODE_STEREO, 1);
5988c2ecf20Sopenharmony_ci	}
5998c2ecf20Sopenharmony_ci	call_all(core, tuner, s_radio);
6008c2ecf20Sopenharmony_ci	return 0;
6018c2ecf20Sopenharmony_ci}
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
6048c2ecf20Sopenharmony_ci/* VIDEO CTRL IOCTLS                                                  */
6058c2ecf20Sopenharmony_ci
6068c2ecf20Sopenharmony_cistatic int cx8800_s_vid_ctrl(struct v4l2_ctrl *ctrl)
6078c2ecf20Sopenharmony_ci{
6088c2ecf20Sopenharmony_ci	struct cx88_core *core =
6098c2ecf20Sopenharmony_ci		container_of(ctrl->handler, struct cx88_core, video_hdl);
6108c2ecf20Sopenharmony_ci	const struct cx88_ctrl *cc = ctrl->priv;
6118c2ecf20Sopenharmony_ci	u32 value, mask;
6128c2ecf20Sopenharmony_ci
6138c2ecf20Sopenharmony_ci	mask = cc->mask;
6148c2ecf20Sopenharmony_ci	switch (ctrl->id) {
6158c2ecf20Sopenharmony_ci	case V4L2_CID_SATURATION:
6168c2ecf20Sopenharmony_ci		/* special v_sat handling */
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_ci		value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
6198c2ecf20Sopenharmony_ci
6208c2ecf20Sopenharmony_ci		if (core->tvnorm & V4L2_STD_SECAM) {
6218c2ecf20Sopenharmony_ci			/* For SECAM, both U and V sat should be equal */
6228c2ecf20Sopenharmony_ci			value = value << 8 | value;
6238c2ecf20Sopenharmony_ci		} else {
6248c2ecf20Sopenharmony_ci			/* Keeps U Saturation proportional to V Sat */
6258c2ecf20Sopenharmony_ci			value = (value * 0x5a) / 0x7f << 8 | value;
6268c2ecf20Sopenharmony_ci		}
6278c2ecf20Sopenharmony_ci		mask = 0xffff;
6288c2ecf20Sopenharmony_ci		break;
6298c2ecf20Sopenharmony_ci	case V4L2_CID_SHARPNESS:
6308c2ecf20Sopenharmony_ci		/* 0b000, 0b100, 0b101, 0b110, or 0b111 */
6318c2ecf20Sopenharmony_ci		value = (ctrl->val < 1 ? 0 : ((ctrl->val + 3) << 7));
6328c2ecf20Sopenharmony_ci		/* needs to be set for both fields */
6338c2ecf20Sopenharmony_ci		cx_andor(MO_FILTER_EVEN, mask, value);
6348c2ecf20Sopenharmony_ci		break;
6358c2ecf20Sopenharmony_ci	case V4L2_CID_CHROMA_AGC:
6368c2ecf20Sopenharmony_ci		value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
6378c2ecf20Sopenharmony_ci		break;
6388c2ecf20Sopenharmony_ci	default:
6398c2ecf20Sopenharmony_ci		value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
6408c2ecf20Sopenharmony_ci		break;
6418c2ecf20Sopenharmony_ci	}
6428c2ecf20Sopenharmony_ci	dprintk(1,
6438c2ecf20Sopenharmony_ci		"set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n",
6448c2ecf20Sopenharmony_ci		ctrl->id, ctrl->name, ctrl->val, cc->reg, value,
6458c2ecf20Sopenharmony_ci		mask, cc->sreg ? " [shadowed]" : "");
6468c2ecf20Sopenharmony_ci	if (cc->sreg)
6478c2ecf20Sopenharmony_ci		cx_sandor(cc->sreg, cc->reg, mask, value);
6488c2ecf20Sopenharmony_ci	else
6498c2ecf20Sopenharmony_ci		cx_andor(cc->reg, mask, value);
6508c2ecf20Sopenharmony_ci	return 0;
6518c2ecf20Sopenharmony_ci}
6528c2ecf20Sopenharmony_ci
6538c2ecf20Sopenharmony_cistatic int cx8800_s_aud_ctrl(struct v4l2_ctrl *ctrl)
6548c2ecf20Sopenharmony_ci{
6558c2ecf20Sopenharmony_ci	struct cx88_core *core =
6568c2ecf20Sopenharmony_ci		container_of(ctrl->handler, struct cx88_core, audio_hdl);
6578c2ecf20Sopenharmony_ci	const struct cx88_ctrl *cc = ctrl->priv;
6588c2ecf20Sopenharmony_ci	u32 value, mask;
6598c2ecf20Sopenharmony_ci
6608c2ecf20Sopenharmony_ci	/* Pass changes onto any WM8775 */
6618c2ecf20Sopenharmony_ci	if (core->sd_wm8775) {
6628c2ecf20Sopenharmony_ci		switch (ctrl->id) {
6638c2ecf20Sopenharmony_ci		case V4L2_CID_AUDIO_MUTE:
6648c2ecf20Sopenharmony_ci			wm8775_s_ctrl(core, ctrl->id, ctrl->val);
6658c2ecf20Sopenharmony_ci			break;
6668c2ecf20Sopenharmony_ci		case V4L2_CID_AUDIO_VOLUME:
6678c2ecf20Sopenharmony_ci			wm8775_s_ctrl(core, ctrl->id, (ctrl->val) ?
6688c2ecf20Sopenharmony_ci						(0x90 + ctrl->val) << 8 : 0);
6698c2ecf20Sopenharmony_ci			break;
6708c2ecf20Sopenharmony_ci		case V4L2_CID_AUDIO_BALANCE:
6718c2ecf20Sopenharmony_ci			wm8775_s_ctrl(core, ctrl->id, ctrl->val << 9);
6728c2ecf20Sopenharmony_ci			break;
6738c2ecf20Sopenharmony_ci		default:
6748c2ecf20Sopenharmony_ci			break;
6758c2ecf20Sopenharmony_ci		}
6768c2ecf20Sopenharmony_ci	}
6778c2ecf20Sopenharmony_ci
6788c2ecf20Sopenharmony_ci	mask = cc->mask;
6798c2ecf20Sopenharmony_ci	switch (ctrl->id) {
6808c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_BALANCE:
6818c2ecf20Sopenharmony_ci		value = (ctrl->val < 0x40) ?
6828c2ecf20Sopenharmony_ci			(0x7f - ctrl->val) : (ctrl->val - 0x40);
6838c2ecf20Sopenharmony_ci		break;
6848c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_VOLUME:
6858c2ecf20Sopenharmony_ci		value = 0x3f - (ctrl->val & 0x3f);
6868c2ecf20Sopenharmony_ci		break;
6878c2ecf20Sopenharmony_ci	default:
6888c2ecf20Sopenharmony_ci		value = ((ctrl->val - cc->off) << cc->shift) & cc->mask;
6898c2ecf20Sopenharmony_ci		break;
6908c2ecf20Sopenharmony_ci	}
6918c2ecf20Sopenharmony_ci	dprintk(1,
6928c2ecf20Sopenharmony_ci		"set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n",
6938c2ecf20Sopenharmony_ci		ctrl->id, ctrl->name, ctrl->val, cc->reg, value,
6948c2ecf20Sopenharmony_ci		mask, cc->sreg ? " [shadowed]" : "");
6958c2ecf20Sopenharmony_ci	if (cc->sreg)
6968c2ecf20Sopenharmony_ci		cx_sandor(cc->sreg, cc->reg, mask, value);
6978c2ecf20Sopenharmony_ci	else
6988c2ecf20Sopenharmony_ci		cx_andor(cc->reg, mask, value);
6998c2ecf20Sopenharmony_ci	return 0;
7008c2ecf20Sopenharmony_ci}
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------ */
7038c2ecf20Sopenharmony_ci/* VIDEO IOCTLS                                                       */
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_cistatic int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
7068c2ecf20Sopenharmony_ci				struct v4l2_format *f)
7078c2ecf20Sopenharmony_ci{
7088c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
7098c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
7108c2ecf20Sopenharmony_ci
7118c2ecf20Sopenharmony_ci	f->fmt.pix.width        = core->width;
7128c2ecf20Sopenharmony_ci	f->fmt.pix.height       = core->height;
7138c2ecf20Sopenharmony_ci	f->fmt.pix.field        = core->field;
7148c2ecf20Sopenharmony_ci	f->fmt.pix.pixelformat  = dev->fmt->fourcc;
7158c2ecf20Sopenharmony_ci	f->fmt.pix.bytesperline =
7168c2ecf20Sopenharmony_ci		(f->fmt.pix.width * dev->fmt->depth) >> 3;
7178c2ecf20Sopenharmony_ci	f->fmt.pix.sizeimage =
7188c2ecf20Sopenharmony_ci		f->fmt.pix.height * f->fmt.pix.bytesperline;
7198c2ecf20Sopenharmony_ci	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
7208c2ecf20Sopenharmony_ci	return 0;
7218c2ecf20Sopenharmony_ci}
7228c2ecf20Sopenharmony_ci
7238c2ecf20Sopenharmony_cistatic int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
7248c2ecf20Sopenharmony_ci				  struct v4l2_format *f)
7258c2ecf20Sopenharmony_ci{
7268c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
7278c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
7288c2ecf20Sopenharmony_ci	const struct cx8800_fmt *fmt;
7298c2ecf20Sopenharmony_ci	enum v4l2_field   field;
7308c2ecf20Sopenharmony_ci	unsigned int      maxw, maxh;
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_ci	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
7338c2ecf20Sopenharmony_ci	if (!fmt)
7348c2ecf20Sopenharmony_ci		return -EINVAL;
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	maxw = norm_maxw(core->tvnorm);
7378c2ecf20Sopenharmony_ci	maxh = norm_maxh(core->tvnorm);
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_ci	field = f->fmt.pix.field;
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	switch (field) {
7428c2ecf20Sopenharmony_ci	case V4L2_FIELD_TOP:
7438c2ecf20Sopenharmony_ci	case V4L2_FIELD_BOTTOM:
7448c2ecf20Sopenharmony_ci	case V4L2_FIELD_INTERLACED:
7458c2ecf20Sopenharmony_ci	case V4L2_FIELD_SEQ_BT:
7468c2ecf20Sopenharmony_ci	case V4L2_FIELD_SEQ_TB:
7478c2ecf20Sopenharmony_ci		break;
7488c2ecf20Sopenharmony_ci	default:
7498c2ecf20Sopenharmony_ci		field = (f->fmt.pix.height > maxh / 2)
7508c2ecf20Sopenharmony_ci			? V4L2_FIELD_INTERLACED
7518c2ecf20Sopenharmony_ci			: V4L2_FIELD_BOTTOM;
7528c2ecf20Sopenharmony_ci		break;
7538c2ecf20Sopenharmony_ci	}
7548c2ecf20Sopenharmony_ci	if (V4L2_FIELD_HAS_T_OR_B(field))
7558c2ecf20Sopenharmony_ci		maxh /= 2;
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
7588c2ecf20Sopenharmony_ci			      &f->fmt.pix.height, 32, maxh, 0, 0);
7598c2ecf20Sopenharmony_ci	f->fmt.pix.field = field;
7608c2ecf20Sopenharmony_ci	f->fmt.pix.bytesperline =
7618c2ecf20Sopenharmony_ci		(f->fmt.pix.width * fmt->depth) >> 3;
7628c2ecf20Sopenharmony_ci	f->fmt.pix.sizeimage =
7638c2ecf20Sopenharmony_ci		f->fmt.pix.height * f->fmt.pix.bytesperline;
7648c2ecf20Sopenharmony_ci	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
7658c2ecf20Sopenharmony_ci
7668c2ecf20Sopenharmony_ci	return 0;
7678c2ecf20Sopenharmony_ci}
7688c2ecf20Sopenharmony_ci
7698c2ecf20Sopenharmony_cistatic int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
7708c2ecf20Sopenharmony_ci				struct v4l2_format *f)
7718c2ecf20Sopenharmony_ci{
7728c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
7738c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
7748c2ecf20Sopenharmony_ci	int err = vidioc_try_fmt_vid_cap(file, priv, f);
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_ci	if (err != 0)
7778c2ecf20Sopenharmony_ci		return err;
7788c2ecf20Sopenharmony_ci	if (vb2_is_busy(&dev->vb2_vidq) || vb2_is_busy(&dev->vb2_vbiq))
7798c2ecf20Sopenharmony_ci		return -EBUSY;
7808c2ecf20Sopenharmony_ci	if (core->dvbdev && vb2_is_busy(&core->dvbdev->vb2_mpegq))
7818c2ecf20Sopenharmony_ci		return -EBUSY;
7828c2ecf20Sopenharmony_ci	dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
7838c2ecf20Sopenharmony_ci	core->width = f->fmt.pix.width;
7848c2ecf20Sopenharmony_ci	core->height = f->fmt.pix.height;
7858c2ecf20Sopenharmony_ci	core->field = f->fmt.pix.field;
7868c2ecf20Sopenharmony_ci	return 0;
7878c2ecf20Sopenharmony_ci}
7888c2ecf20Sopenharmony_ci
7898c2ecf20Sopenharmony_ciint cx88_querycap(struct file *file, struct cx88_core *core,
7908c2ecf20Sopenharmony_ci		  struct v4l2_capability *cap)
7918c2ecf20Sopenharmony_ci{
7928c2ecf20Sopenharmony_ci	strscpy(cap->card, core->board.name, sizeof(cap->card));
7938c2ecf20Sopenharmony_ci	cap->capabilities = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
7948c2ecf20Sopenharmony_ci			    V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE |
7958c2ecf20Sopenharmony_ci			    V4L2_CAP_DEVICE_CAPS;
7968c2ecf20Sopenharmony_ci	if (core->board.tuner_type != UNSET)
7978c2ecf20Sopenharmony_ci		cap->capabilities |= V4L2_CAP_TUNER;
7988c2ecf20Sopenharmony_ci	if (core->board.radio.type == CX88_RADIO)
7998c2ecf20Sopenharmony_ci		cap->capabilities |= V4L2_CAP_RADIO;
8008c2ecf20Sopenharmony_ci	return 0;
8018c2ecf20Sopenharmony_ci}
8028c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx88_querycap);
8038c2ecf20Sopenharmony_ci
8048c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void  *priv,
8058c2ecf20Sopenharmony_ci			   struct v4l2_capability *cap)
8068c2ecf20Sopenharmony_ci{
8078c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
8088c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
8098c2ecf20Sopenharmony_ci
8108c2ecf20Sopenharmony_ci	strscpy(cap->driver, "cx8800", sizeof(cap->driver));
8118c2ecf20Sopenharmony_ci	sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
8128c2ecf20Sopenharmony_ci	return cx88_querycap(file, core, cap);
8138c2ecf20Sopenharmony_ci}
8148c2ecf20Sopenharmony_ci
8158c2ecf20Sopenharmony_cistatic int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
8168c2ecf20Sopenharmony_ci				   struct v4l2_fmtdesc *f)
8178c2ecf20Sopenharmony_ci{
8188c2ecf20Sopenharmony_ci	if (unlikely(f->index >= ARRAY_SIZE(formats)))
8198c2ecf20Sopenharmony_ci		return -EINVAL;
8208c2ecf20Sopenharmony_ci
8218c2ecf20Sopenharmony_ci	f->pixelformat = formats[f->index].fourcc;
8228c2ecf20Sopenharmony_ci
8238c2ecf20Sopenharmony_ci	return 0;
8248c2ecf20Sopenharmony_ci}
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_cistatic int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm)
8278c2ecf20Sopenharmony_ci{
8288c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
8298c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_ci	*tvnorm = core->tvnorm;
8328c2ecf20Sopenharmony_ci	return 0;
8338c2ecf20Sopenharmony_ci}
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_cistatic int vidioc_s_std(struct file *file, void *priv, v4l2_std_id tvnorms)
8368c2ecf20Sopenharmony_ci{
8378c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
8388c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
8398c2ecf20Sopenharmony_ci
8408c2ecf20Sopenharmony_ci	return cx88_set_tvnorm(core, tvnorms);
8418c2ecf20Sopenharmony_ci}
8428c2ecf20Sopenharmony_ci
8438c2ecf20Sopenharmony_ci/* only one input in this sample driver */
8448c2ecf20Sopenharmony_ciint cx88_enum_input(struct cx88_core  *core, struct v4l2_input *i)
8458c2ecf20Sopenharmony_ci{
8468c2ecf20Sopenharmony_ci	static const char * const iname[] = {
8478c2ecf20Sopenharmony_ci		[CX88_VMUX_COMPOSITE1] = "Composite1",
8488c2ecf20Sopenharmony_ci		[CX88_VMUX_COMPOSITE2] = "Composite2",
8498c2ecf20Sopenharmony_ci		[CX88_VMUX_COMPOSITE3] = "Composite3",
8508c2ecf20Sopenharmony_ci		[CX88_VMUX_COMPOSITE4] = "Composite4",
8518c2ecf20Sopenharmony_ci		[CX88_VMUX_SVIDEO] = "S-Video",
8528c2ecf20Sopenharmony_ci		[CX88_VMUX_TELEVISION] = "Television",
8538c2ecf20Sopenharmony_ci		[CX88_VMUX_CABLE] = "Cable TV",
8548c2ecf20Sopenharmony_ci		[CX88_VMUX_DVB] = "DVB",
8558c2ecf20Sopenharmony_ci		[CX88_VMUX_DEBUG] = "for debug only",
8568c2ecf20Sopenharmony_ci	};
8578c2ecf20Sopenharmony_ci	unsigned int n = i->index;
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_ci	if (n >= 4)
8608c2ecf20Sopenharmony_ci		return -EINVAL;
8618c2ecf20Sopenharmony_ci	if (!INPUT(n).type)
8628c2ecf20Sopenharmony_ci		return -EINVAL;
8638c2ecf20Sopenharmony_ci	i->type  = V4L2_INPUT_TYPE_CAMERA;
8648c2ecf20Sopenharmony_ci	strscpy(i->name, iname[INPUT(n).type], sizeof(i->name));
8658c2ecf20Sopenharmony_ci	if ((INPUT(n).type == CX88_VMUX_TELEVISION) ||
8668c2ecf20Sopenharmony_ci	    (INPUT(n).type == CX88_VMUX_CABLE))
8678c2ecf20Sopenharmony_ci		i->type = V4L2_INPUT_TYPE_TUNER;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	i->std = CX88_NORMS;
8708c2ecf20Sopenharmony_ci	return 0;
8718c2ecf20Sopenharmony_ci}
8728c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx88_enum_input);
8738c2ecf20Sopenharmony_ci
8748c2ecf20Sopenharmony_cistatic int vidioc_enum_input(struct file *file, void *priv,
8758c2ecf20Sopenharmony_ci			     struct v4l2_input *i)
8768c2ecf20Sopenharmony_ci{
8778c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
8788c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_ci	return cx88_enum_input(core, i);
8818c2ecf20Sopenharmony_ci}
8828c2ecf20Sopenharmony_ci
8838c2ecf20Sopenharmony_cistatic int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
8848c2ecf20Sopenharmony_ci{
8858c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
8868c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
8878c2ecf20Sopenharmony_ci
8888c2ecf20Sopenharmony_ci	*i = core->input;
8898c2ecf20Sopenharmony_ci	return 0;
8908c2ecf20Sopenharmony_ci}
8918c2ecf20Sopenharmony_ci
8928c2ecf20Sopenharmony_cistatic int vidioc_s_input(struct file *file, void *priv, unsigned int i)
8938c2ecf20Sopenharmony_ci{
8948c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
8958c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_ci	if (i >= 4)
8988c2ecf20Sopenharmony_ci		return -EINVAL;
8998c2ecf20Sopenharmony_ci	if (!INPUT(i).type)
9008c2ecf20Sopenharmony_ci		return -EINVAL;
9018c2ecf20Sopenharmony_ci
9028c2ecf20Sopenharmony_ci	cx88_newstation(core);
9038c2ecf20Sopenharmony_ci	cx88_video_mux(core, i);
9048c2ecf20Sopenharmony_ci	return 0;
9058c2ecf20Sopenharmony_ci}
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv,
9088c2ecf20Sopenharmony_ci			  struct v4l2_tuner *t)
9098c2ecf20Sopenharmony_ci{
9108c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
9118c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
9128c2ecf20Sopenharmony_ci	u32 reg;
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_ci	if (unlikely(core->board.tuner_type == UNSET))
9158c2ecf20Sopenharmony_ci		return -EINVAL;
9168c2ecf20Sopenharmony_ci	if (t->index != 0)
9178c2ecf20Sopenharmony_ci		return -EINVAL;
9188c2ecf20Sopenharmony_ci
9198c2ecf20Sopenharmony_ci	strscpy(t->name, "Television", sizeof(t->name));
9208c2ecf20Sopenharmony_ci	t->capability = V4L2_TUNER_CAP_NORM;
9218c2ecf20Sopenharmony_ci	t->rangehigh  = 0xffffffffUL;
9228c2ecf20Sopenharmony_ci	call_all(core, tuner, g_tuner, t);
9238c2ecf20Sopenharmony_ci
9248c2ecf20Sopenharmony_ci	cx88_get_stereo(core, t);
9258c2ecf20Sopenharmony_ci	reg = cx_read(MO_DEVICE_STATUS);
9268c2ecf20Sopenharmony_ci	t->signal = (reg & (1 << 5)) ? 0xffff : 0x0000;
9278c2ecf20Sopenharmony_ci	return 0;
9288c2ecf20Sopenharmony_ci}
9298c2ecf20Sopenharmony_ci
9308c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv,
9318c2ecf20Sopenharmony_ci			  const struct v4l2_tuner *t)
9328c2ecf20Sopenharmony_ci{
9338c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
9348c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
9358c2ecf20Sopenharmony_ci
9368c2ecf20Sopenharmony_ci	if (core->board.tuner_type == UNSET)
9378c2ecf20Sopenharmony_ci		return -EINVAL;
9388c2ecf20Sopenharmony_ci	if (t->index != 0)
9398c2ecf20Sopenharmony_ci		return -EINVAL;
9408c2ecf20Sopenharmony_ci
9418c2ecf20Sopenharmony_ci	cx88_set_stereo(core, t->audmode, 1);
9428c2ecf20Sopenharmony_ci	return 0;
9438c2ecf20Sopenharmony_ci}
9448c2ecf20Sopenharmony_ci
9458c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv,
9468c2ecf20Sopenharmony_ci			      struct v4l2_frequency *f)
9478c2ecf20Sopenharmony_ci{
9488c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
9498c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
9508c2ecf20Sopenharmony_ci
9518c2ecf20Sopenharmony_ci	if (unlikely(core->board.tuner_type == UNSET))
9528c2ecf20Sopenharmony_ci		return -EINVAL;
9538c2ecf20Sopenharmony_ci	if (f->tuner)
9548c2ecf20Sopenharmony_ci		return -EINVAL;
9558c2ecf20Sopenharmony_ci
9568c2ecf20Sopenharmony_ci	f->frequency = core->freq;
9578c2ecf20Sopenharmony_ci
9588c2ecf20Sopenharmony_ci	call_all(core, tuner, g_frequency, f);
9598c2ecf20Sopenharmony_ci
9608c2ecf20Sopenharmony_ci	return 0;
9618c2ecf20Sopenharmony_ci}
9628c2ecf20Sopenharmony_ci
9638c2ecf20Sopenharmony_ciint cx88_set_freq(struct cx88_core  *core,
9648c2ecf20Sopenharmony_ci		  const struct v4l2_frequency *f)
9658c2ecf20Sopenharmony_ci{
9668c2ecf20Sopenharmony_ci	struct v4l2_frequency new_freq = *f;
9678c2ecf20Sopenharmony_ci
9688c2ecf20Sopenharmony_ci	if (unlikely(core->board.tuner_type == UNSET))
9698c2ecf20Sopenharmony_ci		return -EINVAL;
9708c2ecf20Sopenharmony_ci	if (unlikely(f->tuner != 0))
9718c2ecf20Sopenharmony_ci		return -EINVAL;
9728c2ecf20Sopenharmony_ci
9738c2ecf20Sopenharmony_ci	cx88_newstation(core);
9748c2ecf20Sopenharmony_ci	call_all(core, tuner, s_frequency, f);
9758c2ecf20Sopenharmony_ci	call_all(core, tuner, g_frequency, &new_freq);
9768c2ecf20Sopenharmony_ci	core->freq = new_freq.frequency;
9778c2ecf20Sopenharmony_ci
9788c2ecf20Sopenharmony_ci	/* When changing channels it is required to reset TVAUDIO */
9798c2ecf20Sopenharmony_ci	usleep_range(10000, 20000);
9808c2ecf20Sopenharmony_ci	cx88_set_tvaudio(core);
9818c2ecf20Sopenharmony_ci
9828c2ecf20Sopenharmony_ci	return 0;
9838c2ecf20Sopenharmony_ci}
9848c2ecf20Sopenharmony_ciEXPORT_SYMBOL(cx88_set_freq);
9858c2ecf20Sopenharmony_ci
9868c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv,
9878c2ecf20Sopenharmony_ci			      const struct v4l2_frequency *f)
9888c2ecf20Sopenharmony_ci{
9898c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
9908c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
9918c2ecf20Sopenharmony_ci
9928c2ecf20Sopenharmony_ci	return cx88_set_freq(core, f);
9938c2ecf20Sopenharmony_ci}
9948c2ecf20Sopenharmony_ci
9958c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
9968c2ecf20Sopenharmony_cistatic int vidioc_g_register(struct file *file, void *fh,
9978c2ecf20Sopenharmony_ci			     struct v4l2_dbg_register *reg)
9988c2ecf20Sopenharmony_ci{
9998c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
10008c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
10018c2ecf20Sopenharmony_ci
10028c2ecf20Sopenharmony_ci	/* cx2388x has a 24-bit register space */
10038c2ecf20Sopenharmony_ci	reg->val = cx_read(reg->reg & 0xfffffc);
10048c2ecf20Sopenharmony_ci	reg->size = 4;
10058c2ecf20Sopenharmony_ci	return 0;
10068c2ecf20Sopenharmony_ci}
10078c2ecf20Sopenharmony_ci
10088c2ecf20Sopenharmony_cistatic int vidioc_s_register(struct file *file, void *fh,
10098c2ecf20Sopenharmony_ci			     const struct v4l2_dbg_register *reg)
10108c2ecf20Sopenharmony_ci{
10118c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
10128c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
10138c2ecf20Sopenharmony_ci
10148c2ecf20Sopenharmony_ci	cx_write(reg->reg & 0xfffffc, reg->val);
10158c2ecf20Sopenharmony_ci	return 0;
10168c2ecf20Sopenharmony_ci}
10178c2ecf20Sopenharmony_ci#endif
10188c2ecf20Sopenharmony_ci
10198c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */
10208c2ecf20Sopenharmony_ci/* RADIO ESPECIFIC IOCTLS                                      */
10218c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */
10228c2ecf20Sopenharmony_ci
10238c2ecf20Sopenharmony_cistatic int radio_g_tuner(struct file *file, void *priv,
10248c2ecf20Sopenharmony_ci			 struct v4l2_tuner *t)
10258c2ecf20Sopenharmony_ci{
10268c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
10278c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
10288c2ecf20Sopenharmony_ci
10298c2ecf20Sopenharmony_ci	if (unlikely(t->index > 0))
10308c2ecf20Sopenharmony_ci		return -EINVAL;
10318c2ecf20Sopenharmony_ci
10328c2ecf20Sopenharmony_ci	strscpy(t->name, "Radio", sizeof(t->name));
10338c2ecf20Sopenharmony_ci
10348c2ecf20Sopenharmony_ci	call_all(core, tuner, g_tuner, t);
10358c2ecf20Sopenharmony_ci	return 0;
10368c2ecf20Sopenharmony_ci}
10378c2ecf20Sopenharmony_ci
10388c2ecf20Sopenharmony_cistatic int radio_s_tuner(struct file *file, void *priv,
10398c2ecf20Sopenharmony_ci			 const struct v4l2_tuner *t)
10408c2ecf20Sopenharmony_ci{
10418c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = video_drvdata(file);
10428c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
10438c2ecf20Sopenharmony_ci
10448c2ecf20Sopenharmony_ci	if (t->index != 0)
10458c2ecf20Sopenharmony_ci		return -EINVAL;
10468c2ecf20Sopenharmony_ci
10478c2ecf20Sopenharmony_ci	call_all(core, tuner, s_tuner, t);
10488c2ecf20Sopenharmony_ci	return 0;
10498c2ecf20Sopenharmony_ci}
10508c2ecf20Sopenharmony_ci
10518c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */
10528c2ecf20Sopenharmony_ci
10538c2ecf20Sopenharmony_cistatic const char *cx88_vid_irqs[32] = {
10548c2ecf20Sopenharmony_ci	"y_risci1", "u_risci1", "v_risci1", "vbi_risc1",
10558c2ecf20Sopenharmony_ci	"y_risci2", "u_risci2", "v_risci2", "vbi_risc2",
10568c2ecf20Sopenharmony_ci	"y_oflow",  "u_oflow",  "v_oflow",  "vbi_oflow",
10578c2ecf20Sopenharmony_ci	"y_sync",   "u_sync",   "v_sync",   "vbi_sync",
10588c2ecf20Sopenharmony_ci	"opc_err",  "par_err",  "rip_err",  "pci_abort",
10598c2ecf20Sopenharmony_ci};
10608c2ecf20Sopenharmony_ci
10618c2ecf20Sopenharmony_cistatic void cx8800_vid_irq(struct cx8800_dev *dev)
10628c2ecf20Sopenharmony_ci{
10638c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
10648c2ecf20Sopenharmony_ci	u32 status, mask, count;
10658c2ecf20Sopenharmony_ci
10668c2ecf20Sopenharmony_ci	status = cx_read(MO_VID_INTSTAT);
10678c2ecf20Sopenharmony_ci	mask   = cx_read(MO_VID_INTMSK);
10688c2ecf20Sopenharmony_ci	if (0 == (status & mask))
10698c2ecf20Sopenharmony_ci		return;
10708c2ecf20Sopenharmony_ci	cx_write(MO_VID_INTSTAT, status);
10718c2ecf20Sopenharmony_ci	if (irq_debug  ||  (status & mask & ~0xff))
10728c2ecf20Sopenharmony_ci		cx88_print_irqbits("irq vid",
10738c2ecf20Sopenharmony_ci				   cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs),
10748c2ecf20Sopenharmony_ci				   status, mask);
10758c2ecf20Sopenharmony_ci
10768c2ecf20Sopenharmony_ci	/* risc op code error */
10778c2ecf20Sopenharmony_ci	if (status & (1 << 16)) {
10788c2ecf20Sopenharmony_ci		pr_warn("video risc op code error\n");
10798c2ecf20Sopenharmony_ci		cx_clear(MO_VID_DMACNTRL, 0x11);
10808c2ecf20Sopenharmony_ci		cx_clear(VID_CAPTURE_CONTROL, 0x06);
10818c2ecf20Sopenharmony_ci		cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]);
10828c2ecf20Sopenharmony_ci	}
10838c2ecf20Sopenharmony_ci
10848c2ecf20Sopenharmony_ci	/* risc1 y */
10858c2ecf20Sopenharmony_ci	if (status & 0x01) {
10868c2ecf20Sopenharmony_ci		spin_lock(&dev->slock);
10878c2ecf20Sopenharmony_ci		count = cx_read(MO_VIDY_GPCNT);
10888c2ecf20Sopenharmony_ci		cx88_wakeup(core, &dev->vidq, count);
10898c2ecf20Sopenharmony_ci		spin_unlock(&dev->slock);
10908c2ecf20Sopenharmony_ci	}
10918c2ecf20Sopenharmony_ci
10928c2ecf20Sopenharmony_ci	/* risc1 vbi */
10938c2ecf20Sopenharmony_ci	if (status & 0x08) {
10948c2ecf20Sopenharmony_ci		spin_lock(&dev->slock);
10958c2ecf20Sopenharmony_ci		count = cx_read(MO_VBI_GPCNT);
10968c2ecf20Sopenharmony_ci		cx88_wakeup(core, &dev->vbiq, count);
10978c2ecf20Sopenharmony_ci		spin_unlock(&dev->slock);
10988c2ecf20Sopenharmony_ci	}
10998c2ecf20Sopenharmony_ci}
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_cistatic irqreturn_t cx8800_irq(int irq, void *dev_id)
11028c2ecf20Sopenharmony_ci{
11038c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = dev_id;
11048c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
11058c2ecf20Sopenharmony_ci	u32 status;
11068c2ecf20Sopenharmony_ci	int loop, handled = 0;
11078c2ecf20Sopenharmony_ci
11088c2ecf20Sopenharmony_ci	for (loop = 0; loop < 10; loop++) {
11098c2ecf20Sopenharmony_ci		status = cx_read(MO_PCI_INTSTAT) &
11108c2ecf20Sopenharmony_ci			(core->pci_irqmask | PCI_INT_VIDINT);
11118c2ecf20Sopenharmony_ci		if (status == 0)
11128c2ecf20Sopenharmony_ci			goto out;
11138c2ecf20Sopenharmony_ci		cx_write(MO_PCI_INTSTAT, status);
11148c2ecf20Sopenharmony_ci		handled = 1;
11158c2ecf20Sopenharmony_ci
11168c2ecf20Sopenharmony_ci		if (status & core->pci_irqmask)
11178c2ecf20Sopenharmony_ci			cx88_core_irq(core, status);
11188c2ecf20Sopenharmony_ci		if (status & PCI_INT_VIDINT)
11198c2ecf20Sopenharmony_ci			cx8800_vid_irq(dev);
11208c2ecf20Sopenharmony_ci	}
11218c2ecf20Sopenharmony_ci	if (loop == 10) {
11228c2ecf20Sopenharmony_ci		pr_warn("irq loop -- clearing mask\n");
11238c2ecf20Sopenharmony_ci		cx_write(MO_PCI_INTMSK, 0);
11248c2ecf20Sopenharmony_ci	}
11258c2ecf20Sopenharmony_ci
11268c2ecf20Sopenharmony_ci out:
11278c2ecf20Sopenharmony_ci	return IRQ_RETVAL(handled);
11288c2ecf20Sopenharmony_ci}
11298c2ecf20Sopenharmony_ci
11308c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */
11318c2ecf20Sopenharmony_ci/* exported stuff                                              */
11328c2ecf20Sopenharmony_ci
11338c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations video_fops = {
11348c2ecf20Sopenharmony_ci	.owner	       = THIS_MODULE,
11358c2ecf20Sopenharmony_ci	.open	       = v4l2_fh_open,
11368c2ecf20Sopenharmony_ci	.release       = vb2_fop_release,
11378c2ecf20Sopenharmony_ci	.read	       = vb2_fop_read,
11388c2ecf20Sopenharmony_ci	.poll          = vb2_fop_poll,
11398c2ecf20Sopenharmony_ci	.mmap	       = vb2_fop_mmap,
11408c2ecf20Sopenharmony_ci	.unlocked_ioctl = video_ioctl2,
11418c2ecf20Sopenharmony_ci};
11428c2ecf20Sopenharmony_ci
11438c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops video_ioctl_ops = {
11448c2ecf20Sopenharmony_ci	.vidioc_querycap      = vidioc_querycap,
11458c2ecf20Sopenharmony_ci	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
11468c2ecf20Sopenharmony_ci	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
11478c2ecf20Sopenharmony_ci	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
11488c2ecf20Sopenharmony_ci	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
11498c2ecf20Sopenharmony_ci	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
11508c2ecf20Sopenharmony_ci	.vidioc_querybuf      = vb2_ioctl_querybuf,
11518c2ecf20Sopenharmony_ci	.vidioc_qbuf          = vb2_ioctl_qbuf,
11528c2ecf20Sopenharmony_ci	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
11538c2ecf20Sopenharmony_ci	.vidioc_g_std         = vidioc_g_std,
11548c2ecf20Sopenharmony_ci	.vidioc_s_std         = vidioc_s_std,
11558c2ecf20Sopenharmony_ci	.vidioc_enum_input    = vidioc_enum_input,
11568c2ecf20Sopenharmony_ci	.vidioc_g_input       = vidioc_g_input,
11578c2ecf20Sopenharmony_ci	.vidioc_s_input       = vidioc_s_input,
11588c2ecf20Sopenharmony_ci	.vidioc_streamon      = vb2_ioctl_streamon,
11598c2ecf20Sopenharmony_ci	.vidioc_streamoff     = vb2_ioctl_streamoff,
11608c2ecf20Sopenharmony_ci	.vidioc_g_tuner       = vidioc_g_tuner,
11618c2ecf20Sopenharmony_ci	.vidioc_s_tuner       = vidioc_s_tuner,
11628c2ecf20Sopenharmony_ci	.vidioc_g_frequency   = vidioc_g_frequency,
11638c2ecf20Sopenharmony_ci	.vidioc_s_frequency   = vidioc_s_frequency,
11648c2ecf20Sopenharmony_ci	.vidioc_subscribe_event      = v4l2_ctrl_subscribe_event,
11658c2ecf20Sopenharmony_ci	.vidioc_unsubscribe_event    = v4l2_event_unsubscribe,
11668c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
11678c2ecf20Sopenharmony_ci	.vidioc_g_register    = vidioc_g_register,
11688c2ecf20Sopenharmony_ci	.vidioc_s_register    = vidioc_s_register,
11698c2ecf20Sopenharmony_ci#endif
11708c2ecf20Sopenharmony_ci};
11718c2ecf20Sopenharmony_ci
11728c2ecf20Sopenharmony_cistatic const struct video_device cx8800_video_template = {
11738c2ecf20Sopenharmony_ci	.name                 = "cx8800-video",
11748c2ecf20Sopenharmony_ci	.fops                 = &video_fops,
11758c2ecf20Sopenharmony_ci	.ioctl_ops	      = &video_ioctl_ops,
11768c2ecf20Sopenharmony_ci	.tvnorms              = CX88_NORMS,
11778c2ecf20Sopenharmony_ci};
11788c2ecf20Sopenharmony_ci
11798c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops vbi_ioctl_ops = {
11808c2ecf20Sopenharmony_ci	.vidioc_querycap      = vidioc_querycap,
11818c2ecf20Sopenharmony_ci	.vidioc_g_fmt_vbi_cap     = cx8800_vbi_fmt,
11828c2ecf20Sopenharmony_ci	.vidioc_try_fmt_vbi_cap   = cx8800_vbi_fmt,
11838c2ecf20Sopenharmony_ci	.vidioc_s_fmt_vbi_cap     = cx8800_vbi_fmt,
11848c2ecf20Sopenharmony_ci	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
11858c2ecf20Sopenharmony_ci	.vidioc_querybuf      = vb2_ioctl_querybuf,
11868c2ecf20Sopenharmony_ci	.vidioc_qbuf          = vb2_ioctl_qbuf,
11878c2ecf20Sopenharmony_ci	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
11888c2ecf20Sopenharmony_ci	.vidioc_g_std         = vidioc_g_std,
11898c2ecf20Sopenharmony_ci	.vidioc_s_std         = vidioc_s_std,
11908c2ecf20Sopenharmony_ci	.vidioc_enum_input    = vidioc_enum_input,
11918c2ecf20Sopenharmony_ci	.vidioc_g_input       = vidioc_g_input,
11928c2ecf20Sopenharmony_ci	.vidioc_s_input       = vidioc_s_input,
11938c2ecf20Sopenharmony_ci	.vidioc_streamon      = vb2_ioctl_streamon,
11948c2ecf20Sopenharmony_ci	.vidioc_streamoff     = vb2_ioctl_streamoff,
11958c2ecf20Sopenharmony_ci	.vidioc_g_tuner       = vidioc_g_tuner,
11968c2ecf20Sopenharmony_ci	.vidioc_s_tuner       = vidioc_s_tuner,
11978c2ecf20Sopenharmony_ci	.vidioc_g_frequency   = vidioc_g_frequency,
11988c2ecf20Sopenharmony_ci	.vidioc_s_frequency   = vidioc_s_frequency,
11998c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
12008c2ecf20Sopenharmony_ci	.vidioc_g_register    = vidioc_g_register,
12018c2ecf20Sopenharmony_ci	.vidioc_s_register    = vidioc_s_register,
12028c2ecf20Sopenharmony_ci#endif
12038c2ecf20Sopenharmony_ci};
12048c2ecf20Sopenharmony_ci
12058c2ecf20Sopenharmony_cistatic const struct video_device cx8800_vbi_template = {
12068c2ecf20Sopenharmony_ci	.name                 = "cx8800-vbi",
12078c2ecf20Sopenharmony_ci	.fops                 = &video_fops,
12088c2ecf20Sopenharmony_ci	.ioctl_ops	      = &vbi_ioctl_ops,
12098c2ecf20Sopenharmony_ci	.tvnorms              = CX88_NORMS,
12108c2ecf20Sopenharmony_ci};
12118c2ecf20Sopenharmony_ci
12128c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations radio_fops = {
12138c2ecf20Sopenharmony_ci	.owner         = THIS_MODULE,
12148c2ecf20Sopenharmony_ci	.open          = radio_open,
12158c2ecf20Sopenharmony_ci	.poll          = v4l2_ctrl_poll,
12168c2ecf20Sopenharmony_ci	.release       = v4l2_fh_release,
12178c2ecf20Sopenharmony_ci	.unlocked_ioctl = video_ioctl2,
12188c2ecf20Sopenharmony_ci};
12198c2ecf20Sopenharmony_ci
12208c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops radio_ioctl_ops = {
12218c2ecf20Sopenharmony_ci	.vidioc_querycap      = vidioc_querycap,
12228c2ecf20Sopenharmony_ci	.vidioc_g_tuner       = radio_g_tuner,
12238c2ecf20Sopenharmony_ci	.vidioc_s_tuner       = radio_s_tuner,
12248c2ecf20Sopenharmony_ci	.vidioc_g_frequency   = vidioc_g_frequency,
12258c2ecf20Sopenharmony_ci	.vidioc_s_frequency   = vidioc_s_frequency,
12268c2ecf20Sopenharmony_ci	.vidioc_subscribe_event      = v4l2_ctrl_subscribe_event,
12278c2ecf20Sopenharmony_ci	.vidioc_unsubscribe_event    = v4l2_event_unsubscribe,
12288c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
12298c2ecf20Sopenharmony_ci	.vidioc_g_register    = vidioc_g_register,
12308c2ecf20Sopenharmony_ci	.vidioc_s_register    = vidioc_s_register,
12318c2ecf20Sopenharmony_ci#endif
12328c2ecf20Sopenharmony_ci};
12338c2ecf20Sopenharmony_ci
12348c2ecf20Sopenharmony_cistatic const struct video_device cx8800_radio_template = {
12358c2ecf20Sopenharmony_ci	.name                 = "cx8800-radio",
12368c2ecf20Sopenharmony_ci	.fops                 = &radio_fops,
12378c2ecf20Sopenharmony_ci	.ioctl_ops	      = &radio_ioctl_ops,
12388c2ecf20Sopenharmony_ci};
12398c2ecf20Sopenharmony_ci
12408c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops cx8800_ctrl_vid_ops = {
12418c2ecf20Sopenharmony_ci	.s_ctrl = cx8800_s_vid_ctrl,
12428c2ecf20Sopenharmony_ci};
12438c2ecf20Sopenharmony_ci
12448c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops cx8800_ctrl_aud_ops = {
12458c2ecf20Sopenharmony_ci	.s_ctrl = cx8800_s_aud_ctrl,
12468c2ecf20Sopenharmony_ci};
12478c2ecf20Sopenharmony_ci
12488c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */
12498c2ecf20Sopenharmony_ci
12508c2ecf20Sopenharmony_cistatic void cx8800_unregister_video(struct cx8800_dev *dev)
12518c2ecf20Sopenharmony_ci{
12528c2ecf20Sopenharmony_ci	video_unregister_device(&dev->radio_dev);
12538c2ecf20Sopenharmony_ci	video_unregister_device(&dev->vbi_dev);
12548c2ecf20Sopenharmony_ci	video_unregister_device(&dev->video_dev);
12558c2ecf20Sopenharmony_ci}
12568c2ecf20Sopenharmony_ci
12578c2ecf20Sopenharmony_cistatic int cx8800_initdev(struct pci_dev *pci_dev,
12588c2ecf20Sopenharmony_ci			  const struct pci_device_id *pci_id)
12598c2ecf20Sopenharmony_ci{
12608c2ecf20Sopenharmony_ci	struct cx8800_dev *dev;
12618c2ecf20Sopenharmony_ci	struct cx88_core *core;
12628c2ecf20Sopenharmony_ci	struct vb2_queue *q;
12638c2ecf20Sopenharmony_ci	int err;
12648c2ecf20Sopenharmony_ci	int i;
12658c2ecf20Sopenharmony_ci
12668c2ecf20Sopenharmony_ci	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
12678c2ecf20Sopenharmony_ci	if (!dev)
12688c2ecf20Sopenharmony_ci		return -ENOMEM;
12698c2ecf20Sopenharmony_ci
12708c2ecf20Sopenharmony_ci	/* pci init */
12718c2ecf20Sopenharmony_ci	dev->pci = pci_dev;
12728c2ecf20Sopenharmony_ci	if (pci_enable_device(pci_dev)) {
12738c2ecf20Sopenharmony_ci		err = -EIO;
12748c2ecf20Sopenharmony_ci		goto fail_free;
12758c2ecf20Sopenharmony_ci	}
12768c2ecf20Sopenharmony_ci	core = cx88_core_get(dev->pci);
12778c2ecf20Sopenharmony_ci	if (!core) {
12788c2ecf20Sopenharmony_ci		err = -EINVAL;
12798c2ecf20Sopenharmony_ci		goto fail_disable;
12808c2ecf20Sopenharmony_ci	}
12818c2ecf20Sopenharmony_ci	dev->core = core;
12828c2ecf20Sopenharmony_ci
12838c2ecf20Sopenharmony_ci	/* print pci info */
12848c2ecf20Sopenharmony_ci	dev->pci_rev = pci_dev->revision;
12858c2ecf20Sopenharmony_ci	pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER,  &dev->pci_lat);
12868c2ecf20Sopenharmony_ci	pr_info("found at %s, rev: %d, irq: %d, latency: %d, mmio: 0x%llx\n",
12878c2ecf20Sopenharmony_ci		pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
12888c2ecf20Sopenharmony_ci		dev->pci_lat,
12898c2ecf20Sopenharmony_ci		(unsigned long long)pci_resource_start(pci_dev, 0));
12908c2ecf20Sopenharmony_ci
12918c2ecf20Sopenharmony_ci	pci_set_master(pci_dev);
12928c2ecf20Sopenharmony_ci	err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
12938c2ecf20Sopenharmony_ci	if (err) {
12948c2ecf20Sopenharmony_ci		pr_err("Oops: no 32bit PCI DMA ???\n");
12958c2ecf20Sopenharmony_ci		goto fail_core;
12968c2ecf20Sopenharmony_ci	}
12978c2ecf20Sopenharmony_ci
12988c2ecf20Sopenharmony_ci	/* initialize driver struct */
12998c2ecf20Sopenharmony_ci	spin_lock_init(&dev->slock);
13008c2ecf20Sopenharmony_ci
13018c2ecf20Sopenharmony_ci	/* init video dma queues */
13028c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&dev->vidq.active);
13038c2ecf20Sopenharmony_ci
13048c2ecf20Sopenharmony_ci	/* init vbi dma queues */
13058c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&dev->vbiq.active);
13068c2ecf20Sopenharmony_ci
13078c2ecf20Sopenharmony_ci	/* get irq */
13088c2ecf20Sopenharmony_ci	err = request_irq(pci_dev->irq, cx8800_irq,
13098c2ecf20Sopenharmony_ci			  IRQF_SHARED, core->name, dev);
13108c2ecf20Sopenharmony_ci	if (err < 0) {
13118c2ecf20Sopenharmony_ci		pr_err("can't get IRQ %d\n", pci_dev->irq);
13128c2ecf20Sopenharmony_ci		goto fail_core;
13138c2ecf20Sopenharmony_ci	}
13148c2ecf20Sopenharmony_ci	cx_set(MO_PCI_INTMSK, core->pci_irqmask);
13158c2ecf20Sopenharmony_ci
13168c2ecf20Sopenharmony_ci	for (i = 0; i < CX8800_AUD_CTLS; i++) {
13178c2ecf20Sopenharmony_ci		const struct cx88_ctrl *cc = &cx8800_aud_ctls[i];
13188c2ecf20Sopenharmony_ci		struct v4l2_ctrl *vc;
13198c2ecf20Sopenharmony_ci
13208c2ecf20Sopenharmony_ci		vc = v4l2_ctrl_new_std(&core->audio_hdl, &cx8800_ctrl_aud_ops,
13218c2ecf20Sopenharmony_ci				       cc->id, cc->minimum, cc->maximum,
13228c2ecf20Sopenharmony_ci				       cc->step, cc->default_value);
13238c2ecf20Sopenharmony_ci		if (!vc) {
13248c2ecf20Sopenharmony_ci			err = core->audio_hdl.error;
13258c2ecf20Sopenharmony_ci			goto fail_irq;
13268c2ecf20Sopenharmony_ci		}
13278c2ecf20Sopenharmony_ci		vc->priv = (void *)cc;
13288c2ecf20Sopenharmony_ci	}
13298c2ecf20Sopenharmony_ci
13308c2ecf20Sopenharmony_ci	for (i = 0; i < CX8800_VID_CTLS; i++) {
13318c2ecf20Sopenharmony_ci		const struct cx88_ctrl *cc = &cx8800_vid_ctls[i];
13328c2ecf20Sopenharmony_ci		struct v4l2_ctrl *vc;
13338c2ecf20Sopenharmony_ci
13348c2ecf20Sopenharmony_ci		vc = v4l2_ctrl_new_std(&core->video_hdl, &cx8800_ctrl_vid_ops,
13358c2ecf20Sopenharmony_ci				       cc->id, cc->minimum, cc->maximum,
13368c2ecf20Sopenharmony_ci				       cc->step, cc->default_value);
13378c2ecf20Sopenharmony_ci		if (!vc) {
13388c2ecf20Sopenharmony_ci			err = core->video_hdl.error;
13398c2ecf20Sopenharmony_ci			goto fail_irq;
13408c2ecf20Sopenharmony_ci		}
13418c2ecf20Sopenharmony_ci		vc->priv = (void *)cc;
13428c2ecf20Sopenharmony_ci		if (vc->id == V4L2_CID_CHROMA_AGC)
13438c2ecf20Sopenharmony_ci			core->chroma_agc = vc;
13448c2ecf20Sopenharmony_ci	}
13458c2ecf20Sopenharmony_ci	v4l2_ctrl_add_handler(&core->video_hdl, &core->audio_hdl, NULL, false);
13468c2ecf20Sopenharmony_ci
13478c2ecf20Sopenharmony_ci	/* load and configure helper modules */
13488c2ecf20Sopenharmony_ci
13498c2ecf20Sopenharmony_ci	if (core->board.audio_chip == CX88_AUDIO_WM8775) {
13508c2ecf20Sopenharmony_ci		struct i2c_board_info wm8775_info = {
13518c2ecf20Sopenharmony_ci			.type = "wm8775",
13528c2ecf20Sopenharmony_ci			.addr = 0x36 >> 1,
13538c2ecf20Sopenharmony_ci			.platform_data = &core->wm8775_data,
13548c2ecf20Sopenharmony_ci		};
13558c2ecf20Sopenharmony_ci		struct v4l2_subdev *sd;
13568c2ecf20Sopenharmony_ci
13578c2ecf20Sopenharmony_ci		if (core->boardnr == CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1)
13588c2ecf20Sopenharmony_ci			core->wm8775_data.is_nova_s = true;
13598c2ecf20Sopenharmony_ci		else
13608c2ecf20Sopenharmony_ci			core->wm8775_data.is_nova_s = false;
13618c2ecf20Sopenharmony_ci
13628c2ecf20Sopenharmony_ci		sd = v4l2_i2c_new_subdev_board(&core->v4l2_dev, &core->i2c_adap,
13638c2ecf20Sopenharmony_ci					       &wm8775_info, NULL);
13648c2ecf20Sopenharmony_ci		if (sd) {
13658c2ecf20Sopenharmony_ci			core->sd_wm8775 = sd;
13668c2ecf20Sopenharmony_ci			sd->grp_id = WM8775_GID;
13678c2ecf20Sopenharmony_ci		}
13688c2ecf20Sopenharmony_ci	}
13698c2ecf20Sopenharmony_ci
13708c2ecf20Sopenharmony_ci	if (core->board.audio_chip == CX88_AUDIO_TVAUDIO) {
13718c2ecf20Sopenharmony_ci		/*
13728c2ecf20Sopenharmony_ci		 * This probes for a tda9874 as is used on some
13738c2ecf20Sopenharmony_ci		 * Pixelview Ultra boards.
13748c2ecf20Sopenharmony_ci		 */
13758c2ecf20Sopenharmony_ci		v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap,
13768c2ecf20Sopenharmony_ci				    "tvaudio", 0, I2C_ADDRS(0xb0 >> 1));
13778c2ecf20Sopenharmony_ci	}
13788c2ecf20Sopenharmony_ci
13798c2ecf20Sopenharmony_ci	switch (core->boardnr) {
13808c2ecf20Sopenharmony_ci	case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD:
13818c2ecf20Sopenharmony_ci	case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: {
13828c2ecf20Sopenharmony_ci		static const struct i2c_board_info rtc_info = {
13838c2ecf20Sopenharmony_ci			I2C_BOARD_INFO("isl1208", 0x6f)
13848c2ecf20Sopenharmony_ci		};
13858c2ecf20Sopenharmony_ci
13868c2ecf20Sopenharmony_ci		request_module("rtc-isl1208");
13878c2ecf20Sopenharmony_ci		core->i2c_rtc = i2c_new_client_device(&core->i2c_adap, &rtc_info);
13888c2ecf20Sopenharmony_ci	}
13898c2ecf20Sopenharmony_ci		fallthrough;
13908c2ecf20Sopenharmony_ci	case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO:
13918c2ecf20Sopenharmony_ci		request_module("ir-kbd-i2c");
13928c2ecf20Sopenharmony_ci	}
13938c2ecf20Sopenharmony_ci
13948c2ecf20Sopenharmony_ci	/* Sets device info at pci_dev */
13958c2ecf20Sopenharmony_ci	pci_set_drvdata(pci_dev, dev);
13968c2ecf20Sopenharmony_ci
13978c2ecf20Sopenharmony_ci	dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);
13988c2ecf20Sopenharmony_ci
13998c2ecf20Sopenharmony_ci	/* Maintain a reference so cx88-blackbird can query the 8800 device. */
14008c2ecf20Sopenharmony_ci	core->v4ldev = dev;
14018c2ecf20Sopenharmony_ci
14028c2ecf20Sopenharmony_ci	/* initial device configuration */
14038c2ecf20Sopenharmony_ci	mutex_lock(&core->lock);
14048c2ecf20Sopenharmony_ci	cx88_set_tvnorm(core, V4L2_STD_NTSC_M);
14058c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_setup(&core->video_hdl);
14068c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_setup(&core->audio_hdl);
14078c2ecf20Sopenharmony_ci	cx88_video_mux(core, 0);
14088c2ecf20Sopenharmony_ci
14098c2ecf20Sopenharmony_ci	q = &dev->vb2_vidq;
14108c2ecf20Sopenharmony_ci	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
14118c2ecf20Sopenharmony_ci	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
14128c2ecf20Sopenharmony_ci	q->gfp_flags = GFP_DMA32;
14138c2ecf20Sopenharmony_ci	q->min_buffers_needed = 2;
14148c2ecf20Sopenharmony_ci	q->drv_priv = dev;
14158c2ecf20Sopenharmony_ci	q->buf_struct_size = sizeof(struct cx88_buffer);
14168c2ecf20Sopenharmony_ci	q->ops = &cx8800_video_qops;
14178c2ecf20Sopenharmony_ci	q->mem_ops = &vb2_dma_sg_memops;
14188c2ecf20Sopenharmony_ci	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
14198c2ecf20Sopenharmony_ci	q->lock = &core->lock;
14208c2ecf20Sopenharmony_ci	q->dev = &dev->pci->dev;
14218c2ecf20Sopenharmony_ci
14228c2ecf20Sopenharmony_ci	err = vb2_queue_init(q);
14238c2ecf20Sopenharmony_ci	if (err < 0)
14248c2ecf20Sopenharmony_ci		goto fail_unreg;
14258c2ecf20Sopenharmony_ci
14268c2ecf20Sopenharmony_ci	q = &dev->vb2_vbiq;
14278c2ecf20Sopenharmony_ci	q->type = V4L2_BUF_TYPE_VBI_CAPTURE;
14288c2ecf20Sopenharmony_ci	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
14298c2ecf20Sopenharmony_ci	q->gfp_flags = GFP_DMA32;
14308c2ecf20Sopenharmony_ci	q->min_buffers_needed = 2;
14318c2ecf20Sopenharmony_ci	q->drv_priv = dev;
14328c2ecf20Sopenharmony_ci	q->buf_struct_size = sizeof(struct cx88_buffer);
14338c2ecf20Sopenharmony_ci	q->ops = &cx8800_vbi_qops;
14348c2ecf20Sopenharmony_ci	q->mem_ops = &vb2_dma_sg_memops;
14358c2ecf20Sopenharmony_ci	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
14368c2ecf20Sopenharmony_ci	q->lock = &core->lock;
14378c2ecf20Sopenharmony_ci	q->dev = &dev->pci->dev;
14388c2ecf20Sopenharmony_ci
14398c2ecf20Sopenharmony_ci	err = vb2_queue_init(q);
14408c2ecf20Sopenharmony_ci	if (err < 0)
14418c2ecf20Sopenharmony_ci		goto fail_unreg;
14428c2ecf20Sopenharmony_ci
14438c2ecf20Sopenharmony_ci	/* register v4l devices */
14448c2ecf20Sopenharmony_ci	cx88_vdev_init(core, dev->pci, &dev->video_dev,
14458c2ecf20Sopenharmony_ci		       &cx8800_video_template, "video");
14468c2ecf20Sopenharmony_ci	video_set_drvdata(&dev->video_dev, dev);
14478c2ecf20Sopenharmony_ci	dev->video_dev.ctrl_handler = &core->video_hdl;
14488c2ecf20Sopenharmony_ci	dev->video_dev.queue = &dev->vb2_vidq;
14498c2ecf20Sopenharmony_ci	dev->video_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
14508c2ecf20Sopenharmony_ci				     V4L2_CAP_VIDEO_CAPTURE;
14518c2ecf20Sopenharmony_ci	if (core->board.tuner_type != UNSET)
14528c2ecf20Sopenharmony_ci		dev->video_dev.device_caps |= V4L2_CAP_TUNER;
14538c2ecf20Sopenharmony_ci	err = video_register_device(&dev->video_dev, VFL_TYPE_VIDEO,
14548c2ecf20Sopenharmony_ci				    video_nr[core->nr]);
14558c2ecf20Sopenharmony_ci	if (err < 0) {
14568c2ecf20Sopenharmony_ci		pr_err("can't register video device\n");
14578c2ecf20Sopenharmony_ci		goto fail_unreg;
14588c2ecf20Sopenharmony_ci	}
14598c2ecf20Sopenharmony_ci	pr_info("registered device %s [v4l2]\n",
14608c2ecf20Sopenharmony_ci		video_device_node_name(&dev->video_dev));
14618c2ecf20Sopenharmony_ci
14628c2ecf20Sopenharmony_ci	cx88_vdev_init(core, dev->pci, &dev->vbi_dev,
14638c2ecf20Sopenharmony_ci		       &cx8800_vbi_template, "vbi");
14648c2ecf20Sopenharmony_ci	video_set_drvdata(&dev->vbi_dev, dev);
14658c2ecf20Sopenharmony_ci	dev->vbi_dev.queue = &dev->vb2_vbiq;
14668c2ecf20Sopenharmony_ci	dev->vbi_dev.device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING |
14678c2ecf20Sopenharmony_ci				   V4L2_CAP_VBI_CAPTURE;
14688c2ecf20Sopenharmony_ci	if (core->board.tuner_type != UNSET)
14698c2ecf20Sopenharmony_ci		dev->vbi_dev.device_caps |= V4L2_CAP_TUNER;
14708c2ecf20Sopenharmony_ci	err = video_register_device(&dev->vbi_dev, VFL_TYPE_VBI,
14718c2ecf20Sopenharmony_ci				    vbi_nr[core->nr]);
14728c2ecf20Sopenharmony_ci	if (err < 0) {
14738c2ecf20Sopenharmony_ci		pr_err("can't register vbi device\n");
14748c2ecf20Sopenharmony_ci		goto fail_unreg;
14758c2ecf20Sopenharmony_ci	}
14768c2ecf20Sopenharmony_ci	pr_info("registered device %s\n",
14778c2ecf20Sopenharmony_ci		video_device_node_name(&dev->vbi_dev));
14788c2ecf20Sopenharmony_ci
14798c2ecf20Sopenharmony_ci	if (core->board.radio.type == CX88_RADIO) {
14808c2ecf20Sopenharmony_ci		cx88_vdev_init(core, dev->pci, &dev->radio_dev,
14818c2ecf20Sopenharmony_ci			       &cx8800_radio_template, "radio");
14828c2ecf20Sopenharmony_ci		video_set_drvdata(&dev->radio_dev, dev);
14838c2ecf20Sopenharmony_ci		dev->radio_dev.ctrl_handler = &core->audio_hdl;
14848c2ecf20Sopenharmony_ci		dev->radio_dev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER;
14858c2ecf20Sopenharmony_ci		err = video_register_device(&dev->radio_dev, VFL_TYPE_RADIO,
14868c2ecf20Sopenharmony_ci					    radio_nr[core->nr]);
14878c2ecf20Sopenharmony_ci		if (err < 0) {
14888c2ecf20Sopenharmony_ci			pr_err("can't register radio device\n");
14898c2ecf20Sopenharmony_ci			goto fail_unreg;
14908c2ecf20Sopenharmony_ci		}
14918c2ecf20Sopenharmony_ci		pr_info("registered device %s\n",
14928c2ecf20Sopenharmony_ci			video_device_node_name(&dev->radio_dev));
14938c2ecf20Sopenharmony_ci	}
14948c2ecf20Sopenharmony_ci
14958c2ecf20Sopenharmony_ci	/* start tvaudio thread */
14968c2ecf20Sopenharmony_ci	if (core->board.tuner_type != UNSET) {
14978c2ecf20Sopenharmony_ci		core->kthread = kthread_run(cx88_audio_thread,
14988c2ecf20Sopenharmony_ci					    core, "cx88 tvaudio");
14998c2ecf20Sopenharmony_ci		if (IS_ERR(core->kthread)) {
15008c2ecf20Sopenharmony_ci			err = PTR_ERR(core->kthread);
15018c2ecf20Sopenharmony_ci			pr_err("failed to create cx88 audio thread, err=%d\n",
15028c2ecf20Sopenharmony_ci			       err);
15038c2ecf20Sopenharmony_ci		}
15048c2ecf20Sopenharmony_ci	}
15058c2ecf20Sopenharmony_ci	mutex_unlock(&core->lock);
15068c2ecf20Sopenharmony_ci
15078c2ecf20Sopenharmony_ci	return 0;
15088c2ecf20Sopenharmony_ci
15098c2ecf20Sopenharmony_cifail_unreg:
15108c2ecf20Sopenharmony_ci	cx8800_unregister_video(dev);
15118c2ecf20Sopenharmony_ci	mutex_unlock(&core->lock);
15128c2ecf20Sopenharmony_cifail_irq:
15138c2ecf20Sopenharmony_ci	free_irq(pci_dev->irq, dev);
15148c2ecf20Sopenharmony_cifail_core:
15158c2ecf20Sopenharmony_ci	core->v4ldev = NULL;
15168c2ecf20Sopenharmony_ci	cx88_core_put(core, dev->pci);
15178c2ecf20Sopenharmony_cifail_disable:
15188c2ecf20Sopenharmony_ci	pci_disable_device(pci_dev);
15198c2ecf20Sopenharmony_cifail_free:
15208c2ecf20Sopenharmony_ci	kfree(dev);
15218c2ecf20Sopenharmony_ci	return err;
15228c2ecf20Sopenharmony_ci}
15238c2ecf20Sopenharmony_ci
15248c2ecf20Sopenharmony_cistatic void cx8800_finidev(struct pci_dev *pci_dev)
15258c2ecf20Sopenharmony_ci{
15268c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = pci_get_drvdata(pci_dev);
15278c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
15288c2ecf20Sopenharmony_ci
15298c2ecf20Sopenharmony_ci	/* stop thread */
15308c2ecf20Sopenharmony_ci	if (core->kthread) {
15318c2ecf20Sopenharmony_ci		kthread_stop(core->kthread);
15328c2ecf20Sopenharmony_ci		core->kthread = NULL;
15338c2ecf20Sopenharmony_ci	}
15348c2ecf20Sopenharmony_ci
15358c2ecf20Sopenharmony_ci	if (core->ir)
15368c2ecf20Sopenharmony_ci		cx88_ir_stop(core);
15378c2ecf20Sopenharmony_ci
15388c2ecf20Sopenharmony_ci	cx88_shutdown(core); /* FIXME */
15398c2ecf20Sopenharmony_ci
15408c2ecf20Sopenharmony_ci	/* unregister stuff */
15418c2ecf20Sopenharmony_ci
15428c2ecf20Sopenharmony_ci	free_irq(pci_dev->irq, dev);
15438c2ecf20Sopenharmony_ci	cx8800_unregister_video(dev);
15448c2ecf20Sopenharmony_ci	pci_disable_device(pci_dev);
15458c2ecf20Sopenharmony_ci
15468c2ecf20Sopenharmony_ci	core->v4ldev = NULL;
15478c2ecf20Sopenharmony_ci
15488c2ecf20Sopenharmony_ci	/* free memory */
15498c2ecf20Sopenharmony_ci	cx88_core_put(core, dev->pci);
15508c2ecf20Sopenharmony_ci	kfree(dev);
15518c2ecf20Sopenharmony_ci}
15528c2ecf20Sopenharmony_ci
15538c2ecf20Sopenharmony_cistatic int __maybe_unused cx8800_suspend(struct device *dev_d)
15548c2ecf20Sopenharmony_ci{
15558c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = dev_get_drvdata(dev_d);
15568c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
15578c2ecf20Sopenharmony_ci	unsigned long flags;
15588c2ecf20Sopenharmony_ci
15598c2ecf20Sopenharmony_ci	/* stop video+vbi capture */
15608c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->slock, flags);
15618c2ecf20Sopenharmony_ci	if (!list_empty(&dev->vidq.active)) {
15628c2ecf20Sopenharmony_ci		pr_info("suspend video\n");
15638c2ecf20Sopenharmony_ci		stop_video_dma(dev);
15648c2ecf20Sopenharmony_ci	}
15658c2ecf20Sopenharmony_ci	if (!list_empty(&dev->vbiq.active)) {
15668c2ecf20Sopenharmony_ci		pr_info("suspend vbi\n");
15678c2ecf20Sopenharmony_ci		cx8800_stop_vbi_dma(dev);
15688c2ecf20Sopenharmony_ci	}
15698c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->slock, flags);
15708c2ecf20Sopenharmony_ci
15718c2ecf20Sopenharmony_ci	if (core->ir)
15728c2ecf20Sopenharmony_ci		cx88_ir_stop(core);
15738c2ecf20Sopenharmony_ci	/* FIXME -- shutdown device */
15748c2ecf20Sopenharmony_ci	cx88_shutdown(core);
15758c2ecf20Sopenharmony_ci
15768c2ecf20Sopenharmony_ci	dev->state.disabled = 1;
15778c2ecf20Sopenharmony_ci	return 0;
15788c2ecf20Sopenharmony_ci}
15798c2ecf20Sopenharmony_ci
15808c2ecf20Sopenharmony_cistatic int __maybe_unused cx8800_resume(struct device *dev_d)
15818c2ecf20Sopenharmony_ci{
15828c2ecf20Sopenharmony_ci	struct cx8800_dev *dev = dev_get_drvdata(dev_d);
15838c2ecf20Sopenharmony_ci	struct cx88_core *core = dev->core;
15848c2ecf20Sopenharmony_ci	unsigned long flags;
15858c2ecf20Sopenharmony_ci
15868c2ecf20Sopenharmony_ci	dev->state.disabled = 0;
15878c2ecf20Sopenharmony_ci
15888c2ecf20Sopenharmony_ci	/* FIXME: re-initialize hardware */
15898c2ecf20Sopenharmony_ci	cx88_reset(core);
15908c2ecf20Sopenharmony_ci	if (core->ir)
15918c2ecf20Sopenharmony_ci		cx88_ir_start(core);
15928c2ecf20Sopenharmony_ci
15938c2ecf20Sopenharmony_ci	cx_set(MO_PCI_INTMSK, core->pci_irqmask);
15948c2ecf20Sopenharmony_ci
15958c2ecf20Sopenharmony_ci	/* restart video+vbi capture */
15968c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->slock, flags);
15978c2ecf20Sopenharmony_ci	if (!list_empty(&dev->vidq.active)) {
15988c2ecf20Sopenharmony_ci		pr_info("resume video\n");
15998c2ecf20Sopenharmony_ci		restart_video_queue(dev, &dev->vidq);
16008c2ecf20Sopenharmony_ci	}
16018c2ecf20Sopenharmony_ci	if (!list_empty(&dev->vbiq.active)) {
16028c2ecf20Sopenharmony_ci		pr_info("resume vbi\n");
16038c2ecf20Sopenharmony_ci		cx8800_restart_vbi_queue(dev, &dev->vbiq);
16048c2ecf20Sopenharmony_ci	}
16058c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->slock, flags);
16068c2ecf20Sopenharmony_ci
16078c2ecf20Sopenharmony_ci	return 0;
16088c2ecf20Sopenharmony_ci}
16098c2ecf20Sopenharmony_ci
16108c2ecf20Sopenharmony_ci/* ----------------------------------------------------------- */
16118c2ecf20Sopenharmony_ci
16128c2ecf20Sopenharmony_cistatic const struct pci_device_id cx8800_pci_tbl[] = {
16138c2ecf20Sopenharmony_ci	{
16148c2ecf20Sopenharmony_ci		.vendor       = 0x14f1,
16158c2ecf20Sopenharmony_ci		.device       = 0x8800,
16168c2ecf20Sopenharmony_ci		.subvendor    = PCI_ANY_ID,
16178c2ecf20Sopenharmony_ci		.subdevice    = PCI_ANY_ID,
16188c2ecf20Sopenharmony_ci	}, {
16198c2ecf20Sopenharmony_ci		/* --- end of list --- */
16208c2ecf20Sopenharmony_ci	}
16218c2ecf20Sopenharmony_ci};
16228c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cx8800_pci_tbl);
16238c2ecf20Sopenharmony_ci
16248c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume);
16258c2ecf20Sopenharmony_ci
16268c2ecf20Sopenharmony_cistatic struct pci_driver cx8800_pci_driver = {
16278c2ecf20Sopenharmony_ci	.name      = "cx8800",
16288c2ecf20Sopenharmony_ci	.id_table  = cx8800_pci_tbl,
16298c2ecf20Sopenharmony_ci	.probe     = cx8800_initdev,
16308c2ecf20Sopenharmony_ci	.remove    = cx8800_finidev,
16318c2ecf20Sopenharmony_ci	.driver.pm = &cx8800_pm_ops,
16328c2ecf20Sopenharmony_ci};
16338c2ecf20Sopenharmony_ci
16348c2ecf20Sopenharmony_cimodule_pci_driver(cx8800_pci_driver);
1635