162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  tw68 functions to handle video data
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Much of this code is derived from the cx88 and sa7134 drivers, which
662306a36Sopenharmony_ci *  were in turn derived from the bt87x driver.  The original work was by
762306a36Sopenharmony_ci *  Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab,
862306a36Sopenharmony_ci *  Hans Verkuil, Andy Walls and many others.  Their work is gratefully
962306a36Sopenharmony_ci *  acknowledged.  Full credit goes to them - any problems within this code
1062306a36Sopenharmony_ci *  are mine.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci *  Copyright (C) 2009  William M. Brack
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci *  Refactored and updated to the latest v4l core frameworks:
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci *  Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <media/v4l2-common.h>
2162306a36Sopenharmony_ci#include <media/v4l2-event.h>
2262306a36Sopenharmony_ci#include <media/videobuf2-dma-sg.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "tw68.h"
2562306a36Sopenharmony_ci#include "tw68-reg.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
2862306a36Sopenharmony_ci/* data structs for video                                             */
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * FIXME -
3162306a36Sopenharmony_ci * Note that the saa7134 has formats, e.g. YUV420, which are classified
3262306a36Sopenharmony_ci * as "planar".  These affect overlay mode, and are flagged with a field
3362306a36Sopenharmony_ci * ".planar" in the format.  Do we need to implement this in this driver?
3462306a36Sopenharmony_ci */
3562306a36Sopenharmony_cistatic const struct tw68_format formats[] = {
3662306a36Sopenharmony_ci	{
3762306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_RGB555,
3862306a36Sopenharmony_ci		.depth		= 16,
3962306a36Sopenharmony_ci		.twformat	= ColorFormatRGB15,
4062306a36Sopenharmony_ci	}, {
4162306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_RGB555X,
4262306a36Sopenharmony_ci		.depth		= 16,
4362306a36Sopenharmony_ci		.twformat	= ColorFormatRGB15 | ColorFormatBSWAP,
4462306a36Sopenharmony_ci	}, {
4562306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_RGB565,
4662306a36Sopenharmony_ci		.depth		= 16,
4762306a36Sopenharmony_ci		.twformat	= ColorFormatRGB16,
4862306a36Sopenharmony_ci	}, {
4962306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_RGB565X,
5062306a36Sopenharmony_ci		.depth		= 16,
5162306a36Sopenharmony_ci		.twformat	= ColorFormatRGB16 | ColorFormatBSWAP,
5262306a36Sopenharmony_ci	}, {
5362306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_BGR24,
5462306a36Sopenharmony_ci		.depth		= 24,
5562306a36Sopenharmony_ci		.twformat	= ColorFormatRGB24,
5662306a36Sopenharmony_ci	}, {
5762306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_RGB24,
5862306a36Sopenharmony_ci		.depth		= 24,
5962306a36Sopenharmony_ci		.twformat	= ColorFormatRGB24 | ColorFormatBSWAP,
6062306a36Sopenharmony_ci	}, {
6162306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_BGR32,
6262306a36Sopenharmony_ci		.depth		= 32,
6362306a36Sopenharmony_ci		.twformat	= ColorFormatRGB32,
6462306a36Sopenharmony_ci	}, {
6562306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_RGB32,
6662306a36Sopenharmony_ci		.depth		= 32,
6762306a36Sopenharmony_ci		.twformat	= ColorFormatRGB32 | ColorFormatBSWAP |
6862306a36Sopenharmony_ci				  ColorFormatWSWAP,
6962306a36Sopenharmony_ci	}, {
7062306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_YUYV,
7162306a36Sopenharmony_ci		.depth		= 16,
7262306a36Sopenharmony_ci		.twformat	= ColorFormatYUY2,
7362306a36Sopenharmony_ci	}, {
7462306a36Sopenharmony_ci		.fourcc		= V4L2_PIX_FMT_UYVY,
7562306a36Sopenharmony_ci		.depth		= 16,
7662306a36Sopenharmony_ci		.twformat	= ColorFormatYUY2 | ColorFormatBSWAP,
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci#define FORMATS ARRAY_SIZE(formats)
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci#define NORM_625_50			\
8262306a36Sopenharmony_ci		.h_delay	= 3,	\
8362306a36Sopenharmony_ci		.h_delay0	= 133,	\
8462306a36Sopenharmony_ci		.h_start	= 0,	\
8562306a36Sopenharmony_ci		.h_stop		= 719,	\
8662306a36Sopenharmony_ci		.v_delay	= 24,	\
8762306a36Sopenharmony_ci		.vbi_v_start_0	= 7,	\
8862306a36Sopenharmony_ci		.vbi_v_stop_0	= 22,	\
8962306a36Sopenharmony_ci		.video_v_start	= 24,	\
9062306a36Sopenharmony_ci		.video_v_stop	= 311,	\
9162306a36Sopenharmony_ci		.vbi_v_start_1	= 319
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#define NORM_525_60			\
9462306a36Sopenharmony_ci		.h_delay	= 8,	\
9562306a36Sopenharmony_ci		.h_delay0	= 138,	\
9662306a36Sopenharmony_ci		.h_start	= 0,	\
9762306a36Sopenharmony_ci		.h_stop		= 719,	\
9862306a36Sopenharmony_ci		.v_delay	= 22,	\
9962306a36Sopenharmony_ci		.vbi_v_start_0	= 10,	\
10062306a36Sopenharmony_ci		.vbi_v_stop_0	= 21,	\
10162306a36Sopenharmony_ci		.video_v_start	= 22,	\
10262306a36Sopenharmony_ci		.video_v_stop	= 262,	\
10362306a36Sopenharmony_ci		.vbi_v_start_1	= 273
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/*
10662306a36Sopenharmony_ci * The following table is searched by tw68_s_std, first for a specific
10762306a36Sopenharmony_ci * match, then for an entry which contains the desired id.  The table
10862306a36Sopenharmony_ci * entries should therefore be ordered in ascending order of specificity.
10962306a36Sopenharmony_ci */
11062306a36Sopenharmony_cistatic const struct tw68_tvnorm tvnorms[] = {
11162306a36Sopenharmony_ci	{
11262306a36Sopenharmony_ci		.name		= "PAL", /* autodetect */
11362306a36Sopenharmony_ci		.id		= V4L2_STD_PAL,
11462306a36Sopenharmony_ci		NORM_625_50,
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		.sync_control	= 0x18,
11762306a36Sopenharmony_ci		.luma_control	= 0x40,
11862306a36Sopenharmony_ci		.chroma_ctrl1	= 0x81,
11962306a36Sopenharmony_ci		.chroma_gain	= 0x2a,
12062306a36Sopenharmony_ci		.chroma_ctrl2	= 0x06,
12162306a36Sopenharmony_ci		.vgate_misc	= 0x1c,
12262306a36Sopenharmony_ci		.format		= VideoFormatPALBDGHI,
12362306a36Sopenharmony_ci	}, {
12462306a36Sopenharmony_ci		.name		= "NTSC",
12562306a36Sopenharmony_ci		.id		= V4L2_STD_NTSC,
12662306a36Sopenharmony_ci		NORM_525_60,
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		.sync_control	= 0x59,
12962306a36Sopenharmony_ci		.luma_control	= 0x40,
13062306a36Sopenharmony_ci		.chroma_ctrl1	= 0x89,
13162306a36Sopenharmony_ci		.chroma_gain	= 0x2a,
13262306a36Sopenharmony_ci		.chroma_ctrl2	= 0x0e,
13362306a36Sopenharmony_ci		.vgate_misc	= 0x18,
13462306a36Sopenharmony_ci		.format		= VideoFormatNTSC,
13562306a36Sopenharmony_ci	}, {
13662306a36Sopenharmony_ci		.name		= "SECAM",
13762306a36Sopenharmony_ci		.id		= V4L2_STD_SECAM,
13862306a36Sopenharmony_ci		NORM_625_50,
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci		.sync_control	= 0x18,
14162306a36Sopenharmony_ci		.luma_control	= 0x1b,
14262306a36Sopenharmony_ci		.chroma_ctrl1	= 0xd1,
14362306a36Sopenharmony_ci		.chroma_gain	= 0x80,
14462306a36Sopenharmony_ci		.chroma_ctrl2	= 0x00,
14562306a36Sopenharmony_ci		.vgate_misc	= 0x1c,
14662306a36Sopenharmony_ci		.format		= VideoFormatSECAM,
14762306a36Sopenharmony_ci	}, {
14862306a36Sopenharmony_ci		.name		= "PAL-M",
14962306a36Sopenharmony_ci		.id		= V4L2_STD_PAL_M,
15062306a36Sopenharmony_ci		NORM_525_60,
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci		.sync_control	= 0x59,
15362306a36Sopenharmony_ci		.luma_control	= 0x40,
15462306a36Sopenharmony_ci		.chroma_ctrl1	= 0xb9,
15562306a36Sopenharmony_ci		.chroma_gain	= 0x2a,
15662306a36Sopenharmony_ci		.chroma_ctrl2	= 0x0e,
15762306a36Sopenharmony_ci		.vgate_misc	= 0x18,
15862306a36Sopenharmony_ci		.format		= VideoFormatPALM,
15962306a36Sopenharmony_ci	}, {
16062306a36Sopenharmony_ci		.name		= "PAL-Nc",
16162306a36Sopenharmony_ci		.id		= V4L2_STD_PAL_Nc,
16262306a36Sopenharmony_ci		NORM_625_50,
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		.sync_control	= 0x18,
16562306a36Sopenharmony_ci		.luma_control	= 0x40,
16662306a36Sopenharmony_ci		.chroma_ctrl1	= 0xa1,
16762306a36Sopenharmony_ci		.chroma_gain	= 0x2a,
16862306a36Sopenharmony_ci		.chroma_ctrl2	= 0x06,
16962306a36Sopenharmony_ci		.vgate_misc	= 0x1c,
17062306a36Sopenharmony_ci		.format		= VideoFormatPALNC,
17162306a36Sopenharmony_ci	}, {
17262306a36Sopenharmony_ci		.name		= "PAL-60",
17362306a36Sopenharmony_ci		.id		= V4L2_STD_PAL_60,
17462306a36Sopenharmony_ci		.h_delay	= 186,
17562306a36Sopenharmony_ci		.h_start	= 0,
17662306a36Sopenharmony_ci		.h_stop		= 719,
17762306a36Sopenharmony_ci		.v_delay	= 26,
17862306a36Sopenharmony_ci		.video_v_start	= 23,
17962306a36Sopenharmony_ci		.video_v_stop	= 262,
18062306a36Sopenharmony_ci		.vbi_v_start_0	= 10,
18162306a36Sopenharmony_ci		.vbi_v_stop_0	= 21,
18262306a36Sopenharmony_ci		.vbi_v_start_1	= 273,
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		.sync_control	= 0x18,
18562306a36Sopenharmony_ci		.luma_control	= 0x40,
18662306a36Sopenharmony_ci		.chroma_ctrl1	= 0x81,
18762306a36Sopenharmony_ci		.chroma_gain	= 0x2a,
18862306a36Sopenharmony_ci		.chroma_ctrl2	= 0x06,
18962306a36Sopenharmony_ci		.vgate_misc	= 0x1c,
19062306a36Sopenharmony_ci		.format		= VideoFormatPAL60,
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci};
19362306a36Sopenharmony_ci#define TVNORMS ARRAY_SIZE(tvnorms)
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic const struct tw68_format *format_by_fourcc(unsigned int fourcc)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	unsigned int i;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	for (i = 0; i < FORMATS; i++)
20062306a36Sopenharmony_ci		if (formats[i].fourcc == fourcc)
20162306a36Sopenharmony_ci			return formats+i;
20262306a36Sopenharmony_ci	return NULL;
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
20762306a36Sopenharmony_ci/*
20862306a36Sopenharmony_ci * Note that the cropping rectangles are described in terms of a single
20962306a36Sopenharmony_ci * frame, i.e. line positions are only 1/2 the interlaced equivalent
21062306a36Sopenharmony_ci */
21162306a36Sopenharmony_cistatic void set_tvnorm(struct tw68_dev *dev, const struct tw68_tvnorm *norm)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	if (norm != dev->tvnorm) {
21462306a36Sopenharmony_ci		dev->width = 720;
21562306a36Sopenharmony_ci		dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576;
21662306a36Sopenharmony_ci		dev->tvnorm = norm;
21762306a36Sopenharmony_ci		tw68_set_tvnorm_hw(dev);
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci/*
22262306a36Sopenharmony_ci * tw68_set_scale
22362306a36Sopenharmony_ci *
22462306a36Sopenharmony_ci * Scaling and Cropping for video decoding
22562306a36Sopenharmony_ci *
22662306a36Sopenharmony_ci * We are working with 3 values for horizontal and vertical - scale,
22762306a36Sopenharmony_ci * delay and active.
22862306a36Sopenharmony_ci *
22962306a36Sopenharmony_ci * HACTIVE represent the actual number of pixels in the "usable" image,
23062306a36Sopenharmony_ci * before scaling.  HDELAY represents the number of pixels skipped
23162306a36Sopenharmony_ci * between the start of the horizontal sync and the start of the image.
23262306a36Sopenharmony_ci * HSCALE is calculated using the formula
23362306a36Sopenharmony_ci *	HSCALE = (HACTIVE / (#pixels desired)) * 256
23462306a36Sopenharmony_ci *
23562306a36Sopenharmony_ci * The vertical registers are similar, except based upon the total number
23662306a36Sopenharmony_ci * of lines in the image, and the first line of the image (i.e. ignoring
23762306a36Sopenharmony_ci * vertical sync and VBI).
23862306a36Sopenharmony_ci *
23962306a36Sopenharmony_ci * Note that the number of bytes reaching the FIFO (and hence needing
24062306a36Sopenharmony_ci * to be processed by the DMAP program) is completely dependent upon
24162306a36Sopenharmony_ci * these values, especially HSCALE.
24262306a36Sopenharmony_ci *
24362306a36Sopenharmony_ci * Parameters:
24462306a36Sopenharmony_ci *	@dev		pointer to the device structure, needed for
24562306a36Sopenharmony_ci *			getting current norm (as well as debug print)
24662306a36Sopenharmony_ci *	@width		actual image width (from user buffer)
24762306a36Sopenharmony_ci *	@height		actual image height
24862306a36Sopenharmony_ci *	@field		indicates Top, Bottom or Interlaced
24962306a36Sopenharmony_ci */
25062306a36Sopenharmony_cistatic int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
25162306a36Sopenharmony_ci			  unsigned int height, enum v4l2_field field)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	const struct tw68_tvnorm *norm = dev->tvnorm;
25462306a36Sopenharmony_ci	/* set individually for debugging clarity */
25562306a36Sopenharmony_ci	int hactive, hdelay, hscale;
25662306a36Sopenharmony_ci	int vactive, vdelay, vscale;
25762306a36Sopenharmony_ci	int comb;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	if (V4L2_FIELD_HAS_BOTH(field))	/* if field is interlaced */
26062306a36Sopenharmony_ci		height /= 2;		/* we must set for 1-frame */
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	pr_debug("%s: width=%d, height=%d, both=%d\n"
26362306a36Sopenharmony_ci		 "  tvnorm h_delay=%d, h_start=%d, h_stop=%d, v_delay=%d, v_start=%d, v_stop=%d\n",
26462306a36Sopenharmony_ci		__func__, width, height, V4L2_FIELD_HAS_BOTH(field),
26562306a36Sopenharmony_ci		norm->h_delay, norm->h_start, norm->h_stop,
26662306a36Sopenharmony_ci		norm->v_delay, norm->video_v_start,
26762306a36Sopenharmony_ci		norm->video_v_stop);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	switch (dev->vdecoder) {
27062306a36Sopenharmony_ci	case TW6800:
27162306a36Sopenharmony_ci		hdelay = norm->h_delay0;
27262306a36Sopenharmony_ci		break;
27362306a36Sopenharmony_ci	default:
27462306a36Sopenharmony_ci		hdelay = norm->h_delay;
27562306a36Sopenharmony_ci		break;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	hdelay += norm->h_start;
27962306a36Sopenharmony_ci	hactive = norm->h_stop - norm->h_start + 1;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	hscale = (hactive * 256) / (width);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	vdelay = norm->v_delay;
28462306a36Sopenharmony_ci	vactive = ((norm->id & V4L2_STD_525_60) ? 524 : 624) / 2 - norm->video_v_start;
28562306a36Sopenharmony_ci	vscale = (vactive * 256) / height;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	pr_debug("%s: %dx%d [%s%s,%s]\n", __func__,
28862306a36Sopenharmony_ci		width, height,
28962306a36Sopenharmony_ci		V4L2_FIELD_HAS_TOP(field)    ? "T" : "",
29062306a36Sopenharmony_ci		V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "",
29162306a36Sopenharmony_ci		v4l2_norm_to_name(dev->tvnorm->id));
29262306a36Sopenharmony_ci	pr_debug("%s: hactive=%d, hdelay=%d, hscale=%d; vactive=%d, vdelay=%d, vscale=%d\n",
29362306a36Sopenharmony_ci		 __func__,
29462306a36Sopenharmony_ci		hactive, hdelay, hscale, vactive, vdelay, vscale);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	comb =	((vdelay & 0x300)  >> 2) |
29762306a36Sopenharmony_ci		((vactive & 0x300) >> 4) |
29862306a36Sopenharmony_ci		((hdelay & 0x300)  >> 6) |
29962306a36Sopenharmony_ci		((hactive & 0x300) >> 8);
30062306a36Sopenharmony_ci	pr_debug("%s: setting CROP_HI=%02x, VDELAY_LO=%02x, VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n",
30162306a36Sopenharmony_ci		__func__, comb, vdelay, vactive, hdelay, hactive);
30262306a36Sopenharmony_ci	tw_writeb(TW68_CROP_HI, comb);
30362306a36Sopenharmony_ci	tw_writeb(TW68_VDELAY_LO, vdelay & 0xff);
30462306a36Sopenharmony_ci	tw_writeb(TW68_VACTIVE_LO, vactive & 0xff);
30562306a36Sopenharmony_ci	tw_writeb(TW68_HDELAY_LO, hdelay & 0xff);
30662306a36Sopenharmony_ci	tw_writeb(TW68_HACTIVE_LO, hactive & 0xff);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8);
30962306a36Sopenharmony_ci	pr_debug("%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, HSCALE_LO=%02x\n",
31062306a36Sopenharmony_ci		 __func__, comb, vscale, hscale);
31162306a36Sopenharmony_ci	tw_writeb(TW68_SCALE_HI, comb);
31262306a36Sopenharmony_ci	tw_writeb(TW68_VSCALE_LO, vscale);
31362306a36Sopenharmony_ci	tw_writeb(TW68_HSCALE_LO, hscale);
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	return 0;
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ciint tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf)
32162306a36Sopenharmony_ci{
32262306a36Sopenharmony_ci	/* Set cropping and scaling */
32362306a36Sopenharmony_ci	tw68_set_scale(dev, dev->width, dev->height, dev->field);
32462306a36Sopenharmony_ci	/*
32562306a36Sopenharmony_ci	 *  Set start address for RISC program.  Note that if the DMAP
32662306a36Sopenharmony_ci	 *  processor is currently running, it must be stopped before
32762306a36Sopenharmony_ci	 *  a new address can be set.
32862306a36Sopenharmony_ci	 */
32962306a36Sopenharmony_ci	tw_clearl(TW68_DMAC, TW68_DMAP_EN);
33062306a36Sopenharmony_ci	tw_writel(TW68_DMAP_SA, buf->dma);
33162306a36Sopenharmony_ci	/* Clear any pending interrupts */
33262306a36Sopenharmony_ci	tw_writel(TW68_INTSTAT, dev->board_virqmask);
33362306a36Sopenharmony_ci	/* Enable the risc engine and the fifo */
33462306a36Sopenharmony_ci	tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat |
33562306a36Sopenharmony_ci		ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
33662306a36Sopenharmony_ci	dev->pci_irqmask |= dev->board_virqmask;
33762306a36Sopenharmony_ci	tw_setl(TW68_INTMASK, dev->pci_irqmask);
33862306a36Sopenharmony_ci	return 0;
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci/* calc max # of buffers from size (must not exceed the 4MB virtual
34462306a36Sopenharmony_ci * address space per DMA channel) */
34562306a36Sopenharmony_cistatic int tw68_buffer_count(unsigned int size, unsigned int count)
34662306a36Sopenharmony_ci{
34762306a36Sopenharmony_ci	unsigned int maxcount;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	maxcount = (4 * 1024 * 1024) / roundup(size, PAGE_SIZE);
35062306a36Sopenharmony_ci	if (count > maxcount)
35162306a36Sopenharmony_ci		count = maxcount;
35262306a36Sopenharmony_ci	return count;
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci/* ------------------------------------------------------------- */
35662306a36Sopenharmony_ci/* vb2 queue operations                                          */
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_cistatic int tw68_queue_setup(struct vb2_queue *q,
35962306a36Sopenharmony_ci			   unsigned int *num_buffers, unsigned int *num_planes,
36062306a36Sopenharmony_ci			   unsigned int sizes[], struct device *alloc_devs[])
36162306a36Sopenharmony_ci{
36262306a36Sopenharmony_ci	struct tw68_dev *dev = vb2_get_drv_priv(q);
36362306a36Sopenharmony_ci	unsigned tot_bufs = q->num_buffers + *num_buffers;
36462306a36Sopenharmony_ci	unsigned size = (dev->fmt->depth * dev->width * dev->height) >> 3;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	if (tot_bufs < 2)
36762306a36Sopenharmony_ci		tot_bufs = 2;
36862306a36Sopenharmony_ci	tot_bufs = tw68_buffer_count(size, tot_bufs);
36962306a36Sopenharmony_ci	*num_buffers = tot_bufs - q->num_buffers;
37062306a36Sopenharmony_ci	/*
37162306a36Sopenharmony_ci	 * We allow create_bufs, but only if the sizeimage is >= as the
37262306a36Sopenharmony_ci	 * current sizeimage. The tw68_buffer_count calculation becomes quite
37362306a36Sopenharmony_ci	 * difficult otherwise.
37462306a36Sopenharmony_ci	 */
37562306a36Sopenharmony_ci	if (*num_planes)
37662306a36Sopenharmony_ci		return sizes[0] < size ? -EINVAL : 0;
37762306a36Sopenharmony_ci	*num_planes = 1;
37862306a36Sopenharmony_ci	sizes[0] = size;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	return 0;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci/*
38462306a36Sopenharmony_ci * The risc program for each buffers works as follows: it starts with a simple
38562306a36Sopenharmony_ci * 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the
38662306a36Sopenharmony_ci * buffer follows and at the end we have a JUMP back to the start + 8 (skipping
38762306a36Sopenharmony_ci * the initial JUMP).
38862306a36Sopenharmony_ci *
38962306a36Sopenharmony_ci * This is the program of the first buffer to be queued if the active list is
39062306a36Sopenharmony_ci * empty and it just keeps DMAing this buffer without generating any interrupts.
39162306a36Sopenharmony_ci *
39262306a36Sopenharmony_ci * If a new buffer is added then the initial JUMP in the program generates an
39362306a36Sopenharmony_ci * interrupt as well which signals that the previous buffer has been DMAed
39462306a36Sopenharmony_ci * successfully and that it can be returned to userspace.
39562306a36Sopenharmony_ci *
39662306a36Sopenharmony_ci * It also sets the final jump of the previous buffer to the start of the new
39762306a36Sopenharmony_ci * buffer, thus chaining the new buffer into the DMA chain. This is a single
39862306a36Sopenharmony_ci * atomic u32 write, so there is no race condition.
39962306a36Sopenharmony_ci *
40062306a36Sopenharmony_ci * The end-result of all this that you only get an interrupt when a buffer
40162306a36Sopenharmony_ci * is ready, so the control flow is very easy.
40262306a36Sopenharmony_ci */
40362306a36Sopenharmony_cistatic void tw68_buf_queue(struct vb2_buffer *vb)
40462306a36Sopenharmony_ci{
40562306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
40662306a36Sopenharmony_ci	struct vb2_queue *vq = vb->vb2_queue;
40762306a36Sopenharmony_ci	struct tw68_dev *dev = vb2_get_drv_priv(vq);
40862306a36Sopenharmony_ci	struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb);
40962306a36Sopenharmony_ci	struct tw68_buf *prev;
41062306a36Sopenharmony_ci	unsigned long flags;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	spin_lock_irqsave(&dev->slock, flags);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	/* append a 'JUMP to start of buffer' to the buffer risc program */
41562306a36Sopenharmony_ci	buf->jmp[0] = cpu_to_le32(RISC_JUMP);
41662306a36Sopenharmony_ci	buf->jmp[1] = cpu_to_le32(buf->dma + 8);
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	if (!list_empty(&dev->active)) {
41962306a36Sopenharmony_ci		prev = list_entry(dev->active.prev, struct tw68_buf, list);
42062306a36Sopenharmony_ci		buf->cpu[0] |= cpu_to_le32(RISC_INT_BIT);
42162306a36Sopenharmony_ci		prev->jmp[1] = cpu_to_le32(buf->dma);
42262306a36Sopenharmony_ci	}
42362306a36Sopenharmony_ci	list_add_tail(&buf->list, &dev->active);
42462306a36Sopenharmony_ci	spin_unlock_irqrestore(&dev->slock, flags);
42562306a36Sopenharmony_ci}
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci/*
42862306a36Sopenharmony_ci * buffer_prepare
42962306a36Sopenharmony_ci *
43062306a36Sopenharmony_ci * Set the ancillary information into the buffer structure.  This
43162306a36Sopenharmony_ci * includes generating the necessary risc program if it hasn't already
43262306a36Sopenharmony_ci * been done for the current buffer format.
43362306a36Sopenharmony_ci * The structure fh contains the details of the format requested by the
43462306a36Sopenharmony_ci * user - type, width, height and #fields.  This is compared with the
43562306a36Sopenharmony_ci * last format set for the current buffer.  If they differ, the risc
43662306a36Sopenharmony_ci * code (which controls the filling of the buffer) is (re-)generated.
43762306a36Sopenharmony_ci */
43862306a36Sopenharmony_cistatic int tw68_buf_prepare(struct vb2_buffer *vb)
43962306a36Sopenharmony_ci{
44062306a36Sopenharmony_ci	int ret;
44162306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
44262306a36Sopenharmony_ci	struct vb2_queue *vq = vb->vb2_queue;
44362306a36Sopenharmony_ci	struct tw68_dev *dev = vb2_get_drv_priv(vq);
44462306a36Sopenharmony_ci	struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb);
44562306a36Sopenharmony_ci	struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
44662306a36Sopenharmony_ci	unsigned size, bpl;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	size = (dev->width * dev->height * dev->fmt->depth) >> 3;
44962306a36Sopenharmony_ci	if (vb2_plane_size(vb, 0) < size)
45062306a36Sopenharmony_ci		return -EINVAL;
45162306a36Sopenharmony_ci	vb2_set_plane_payload(vb, 0, size);
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	bpl = (dev->width * dev->fmt->depth) >> 3;
45462306a36Sopenharmony_ci	switch (dev->field) {
45562306a36Sopenharmony_ci	case V4L2_FIELD_TOP:
45662306a36Sopenharmony_ci		ret = tw68_risc_buffer(dev->pci, buf, dma->sgl,
45762306a36Sopenharmony_ci				 0, UNSET, bpl, 0, dev->height);
45862306a36Sopenharmony_ci		break;
45962306a36Sopenharmony_ci	case V4L2_FIELD_BOTTOM:
46062306a36Sopenharmony_ci		ret = tw68_risc_buffer(dev->pci, buf, dma->sgl,
46162306a36Sopenharmony_ci				 UNSET, 0, bpl, 0, dev->height);
46262306a36Sopenharmony_ci		break;
46362306a36Sopenharmony_ci	case V4L2_FIELD_SEQ_TB:
46462306a36Sopenharmony_ci		ret = tw68_risc_buffer(dev->pci, buf, dma->sgl,
46562306a36Sopenharmony_ci				 0, bpl * (dev->height >> 1),
46662306a36Sopenharmony_ci				 bpl, 0, dev->height >> 1);
46762306a36Sopenharmony_ci		break;
46862306a36Sopenharmony_ci	case V4L2_FIELD_SEQ_BT:
46962306a36Sopenharmony_ci		ret = tw68_risc_buffer(dev->pci, buf, dma->sgl,
47062306a36Sopenharmony_ci				 bpl * (dev->height >> 1), 0,
47162306a36Sopenharmony_ci				 bpl, 0, dev->height >> 1);
47262306a36Sopenharmony_ci		break;
47362306a36Sopenharmony_ci	case V4L2_FIELD_INTERLACED:
47462306a36Sopenharmony_ci	default:
47562306a36Sopenharmony_ci		ret = tw68_risc_buffer(dev->pci, buf, dma->sgl,
47662306a36Sopenharmony_ci				 0, bpl, bpl, bpl, dev->height >> 1);
47762306a36Sopenharmony_ci		break;
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci	return ret;
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic void tw68_buf_finish(struct vb2_buffer *vb)
48362306a36Sopenharmony_ci{
48462306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
48562306a36Sopenharmony_ci	struct vb2_queue *vq = vb->vb2_queue;
48662306a36Sopenharmony_ci	struct tw68_dev *dev = vb2_get_drv_priv(vq);
48762306a36Sopenharmony_ci	struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb);
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	if (buf->cpu)
49062306a36Sopenharmony_ci		dma_free_coherent(&dev->pci->dev, buf->size, buf->cpu, buf->dma);
49162306a36Sopenharmony_ci}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_cistatic int tw68_start_streaming(struct vb2_queue *q, unsigned int count)
49462306a36Sopenharmony_ci{
49562306a36Sopenharmony_ci	struct tw68_dev *dev = vb2_get_drv_priv(q);
49662306a36Sopenharmony_ci	struct tw68_buf *buf =
49762306a36Sopenharmony_ci		container_of(dev->active.next, struct tw68_buf, list);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	dev->seqnr = 0;
50062306a36Sopenharmony_ci	tw68_video_start_dma(dev, buf);
50162306a36Sopenharmony_ci	return 0;
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_cistatic void tw68_stop_streaming(struct vb2_queue *q)
50562306a36Sopenharmony_ci{
50662306a36Sopenharmony_ci	struct tw68_dev *dev = vb2_get_drv_priv(q);
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	/* Stop risc & fifo */
50962306a36Sopenharmony_ci	tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
51062306a36Sopenharmony_ci	while (!list_empty(&dev->active)) {
51162306a36Sopenharmony_ci		struct tw68_buf *buf =
51262306a36Sopenharmony_ci			container_of(dev->active.next, struct tw68_buf, list);
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci		list_del(&buf->list);
51562306a36Sopenharmony_ci		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
51662306a36Sopenharmony_ci	}
51762306a36Sopenharmony_ci}
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_cistatic const struct vb2_ops tw68_video_qops = {
52062306a36Sopenharmony_ci	.queue_setup	= tw68_queue_setup,
52162306a36Sopenharmony_ci	.buf_queue	= tw68_buf_queue,
52262306a36Sopenharmony_ci	.buf_prepare	= tw68_buf_prepare,
52362306a36Sopenharmony_ci	.buf_finish	= tw68_buf_finish,
52462306a36Sopenharmony_ci	.start_streaming = tw68_start_streaming,
52562306a36Sopenharmony_ci	.stop_streaming = tw68_stop_streaming,
52662306a36Sopenharmony_ci	.wait_prepare	= vb2_ops_wait_prepare,
52762306a36Sopenharmony_ci	.wait_finish	= vb2_ops_wait_finish,
52862306a36Sopenharmony_ci};
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_cistatic int tw68_s_ctrl(struct v4l2_ctrl *ctrl)
53362306a36Sopenharmony_ci{
53462306a36Sopenharmony_ci	struct tw68_dev *dev =
53562306a36Sopenharmony_ci		container_of(ctrl->handler, struct tw68_dev, hdl);
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	switch (ctrl->id) {
53862306a36Sopenharmony_ci	case V4L2_CID_BRIGHTNESS:
53962306a36Sopenharmony_ci		tw_writeb(TW68_BRIGHT, ctrl->val);
54062306a36Sopenharmony_ci		break;
54162306a36Sopenharmony_ci	case V4L2_CID_HUE:
54262306a36Sopenharmony_ci		tw_writeb(TW68_HUE, ctrl->val);
54362306a36Sopenharmony_ci		break;
54462306a36Sopenharmony_ci	case V4L2_CID_CONTRAST:
54562306a36Sopenharmony_ci		tw_writeb(TW68_CONTRAST, ctrl->val);
54662306a36Sopenharmony_ci		break;
54762306a36Sopenharmony_ci	case V4L2_CID_SATURATION:
54862306a36Sopenharmony_ci		tw_writeb(TW68_SAT_U, ctrl->val);
54962306a36Sopenharmony_ci		tw_writeb(TW68_SAT_V, ctrl->val);
55062306a36Sopenharmony_ci		break;
55162306a36Sopenharmony_ci	case V4L2_CID_COLOR_KILLER:
55262306a36Sopenharmony_ci		if (ctrl->val)
55362306a36Sopenharmony_ci			tw_andorb(TW68_MISC2, 0xe0, 0xe0);
55462306a36Sopenharmony_ci		else
55562306a36Sopenharmony_ci			tw_andorb(TW68_MISC2, 0xe0, 0x00);
55662306a36Sopenharmony_ci		break;
55762306a36Sopenharmony_ci	case V4L2_CID_CHROMA_AGC:
55862306a36Sopenharmony_ci		if (ctrl->val)
55962306a36Sopenharmony_ci			tw_andorb(TW68_LOOP, 0x30, 0x20);
56062306a36Sopenharmony_ci		else
56162306a36Sopenharmony_ci			tw_andorb(TW68_LOOP, 0x30, 0x00);
56262306a36Sopenharmony_ci		break;
56362306a36Sopenharmony_ci	}
56462306a36Sopenharmony_ci	return 0;
56562306a36Sopenharmony_ci}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_ci/*
57062306a36Sopenharmony_ci * Note that this routine returns what is stored in the fh structure, and
57162306a36Sopenharmony_ci * does not interrogate any of the device registers.
57262306a36Sopenharmony_ci */
57362306a36Sopenharmony_cistatic int tw68_g_fmt_vid_cap(struct file *file, void *priv,
57462306a36Sopenharmony_ci				struct v4l2_format *f)
57562306a36Sopenharmony_ci{
57662306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	f->fmt.pix.width        = dev->width;
57962306a36Sopenharmony_ci	f->fmt.pix.height       = dev->height;
58062306a36Sopenharmony_ci	f->fmt.pix.field        = dev->field;
58162306a36Sopenharmony_ci	f->fmt.pix.pixelformat  = dev->fmt->fourcc;
58262306a36Sopenharmony_ci	f->fmt.pix.bytesperline =
58362306a36Sopenharmony_ci		(f->fmt.pix.width * (dev->fmt->depth)) >> 3;
58462306a36Sopenharmony_ci	f->fmt.pix.sizeimage =
58562306a36Sopenharmony_ci		f->fmt.pix.height * f->fmt.pix.bytesperline;
58662306a36Sopenharmony_ci	f->fmt.pix.colorspace	= V4L2_COLORSPACE_SMPTE170M;
58762306a36Sopenharmony_ci	return 0;
58862306a36Sopenharmony_ci}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_cistatic int tw68_try_fmt_vid_cap(struct file *file, void *priv,
59162306a36Sopenharmony_ci						struct v4l2_format *f)
59262306a36Sopenharmony_ci{
59362306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
59462306a36Sopenharmony_ci	const struct tw68_format *fmt;
59562306a36Sopenharmony_ci	enum v4l2_field field;
59662306a36Sopenharmony_ci	unsigned int maxh;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
59962306a36Sopenharmony_ci	if (NULL == fmt)
60062306a36Sopenharmony_ci		return -EINVAL;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	field = f->fmt.pix.field;
60362306a36Sopenharmony_ci	maxh  = (dev->tvnorm->id & V4L2_STD_525_60) ? 480 : 576;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	switch (field) {
60662306a36Sopenharmony_ci	case V4L2_FIELD_TOP:
60762306a36Sopenharmony_ci	case V4L2_FIELD_BOTTOM:
60862306a36Sopenharmony_ci		break;
60962306a36Sopenharmony_ci	case V4L2_FIELD_INTERLACED:
61062306a36Sopenharmony_ci	case V4L2_FIELD_SEQ_BT:
61162306a36Sopenharmony_ci	case V4L2_FIELD_SEQ_TB:
61262306a36Sopenharmony_ci		maxh = maxh * 2;
61362306a36Sopenharmony_ci		break;
61462306a36Sopenharmony_ci	default:
61562306a36Sopenharmony_ci		field = (f->fmt.pix.height > maxh / 2)
61662306a36Sopenharmony_ci			? V4L2_FIELD_INTERLACED
61762306a36Sopenharmony_ci			: V4L2_FIELD_BOTTOM;
61862306a36Sopenharmony_ci		break;
61962306a36Sopenharmony_ci	}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	f->fmt.pix.field = field;
62262306a36Sopenharmony_ci	if (f->fmt.pix.width  < 48)
62362306a36Sopenharmony_ci		f->fmt.pix.width  = 48;
62462306a36Sopenharmony_ci	if (f->fmt.pix.height < 32)
62562306a36Sopenharmony_ci		f->fmt.pix.height = 32;
62662306a36Sopenharmony_ci	if (f->fmt.pix.width > 720)
62762306a36Sopenharmony_ci		f->fmt.pix.width = 720;
62862306a36Sopenharmony_ci	if (f->fmt.pix.height > maxh)
62962306a36Sopenharmony_ci		f->fmt.pix.height = maxh;
63062306a36Sopenharmony_ci	f->fmt.pix.width &= ~0x03;
63162306a36Sopenharmony_ci	f->fmt.pix.bytesperline =
63262306a36Sopenharmony_ci		(f->fmt.pix.width * (fmt->depth)) >> 3;
63362306a36Sopenharmony_ci	f->fmt.pix.sizeimage =
63462306a36Sopenharmony_ci		f->fmt.pix.height * f->fmt.pix.bytesperline;
63562306a36Sopenharmony_ci	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
63662306a36Sopenharmony_ci	return 0;
63762306a36Sopenharmony_ci}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci/*
64062306a36Sopenharmony_ci * Note that tw68_s_fmt_vid_cap sets the information into the fh structure,
64162306a36Sopenharmony_ci * and it will be used for all future new buffers.  However, there could be
64262306a36Sopenharmony_ci * some number of buffers on the "active" chain which will be filled before
64362306a36Sopenharmony_ci * the change takes place.
64462306a36Sopenharmony_ci */
64562306a36Sopenharmony_cistatic int tw68_s_fmt_vid_cap(struct file *file, void *priv,
64662306a36Sopenharmony_ci					struct v4l2_format *f)
64762306a36Sopenharmony_ci{
64862306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
64962306a36Sopenharmony_ci	int err;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	err = tw68_try_fmt_vid_cap(file, priv, f);
65262306a36Sopenharmony_ci	if (0 != err)
65362306a36Sopenharmony_ci		return err;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
65662306a36Sopenharmony_ci	dev->width = f->fmt.pix.width;
65762306a36Sopenharmony_ci	dev->height = f->fmt.pix.height;
65862306a36Sopenharmony_ci	dev->field = f->fmt.pix.field;
65962306a36Sopenharmony_ci	return 0;
66062306a36Sopenharmony_ci}
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_cistatic int tw68_enum_input(struct file *file, void *priv,
66362306a36Sopenharmony_ci					struct v4l2_input *i)
66462306a36Sopenharmony_ci{
66562306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
66662306a36Sopenharmony_ci	unsigned int n;
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	n = i->index;
66962306a36Sopenharmony_ci	if (n >= TW68_INPUT_MAX)
67062306a36Sopenharmony_ci		return -EINVAL;
67162306a36Sopenharmony_ci	i->index = n;
67262306a36Sopenharmony_ci	i->type = V4L2_INPUT_TYPE_CAMERA;
67362306a36Sopenharmony_ci	snprintf(i->name, sizeof(i->name), "Composite %d", n);
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	/* If the query is for the current input, get live data */
67662306a36Sopenharmony_ci	if (n == dev->input) {
67762306a36Sopenharmony_ci		int v1 = tw_readb(TW68_STATUS1);
67862306a36Sopenharmony_ci		int v2 = tw_readb(TW68_MVSN);
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci		if (0 != (v1 & (1 << 7)))
68162306a36Sopenharmony_ci			i->status |= V4L2_IN_ST_NO_SYNC;
68262306a36Sopenharmony_ci		if (0 != (v1 & (1 << 6)))
68362306a36Sopenharmony_ci			i->status |= V4L2_IN_ST_NO_H_LOCK;
68462306a36Sopenharmony_ci		if (0 != (v1 & (1 << 2)))
68562306a36Sopenharmony_ci			i->status |= V4L2_IN_ST_NO_SIGNAL;
68662306a36Sopenharmony_ci		if (0 != (v1 & 1 << 1))
68762306a36Sopenharmony_ci			i->status |= V4L2_IN_ST_NO_COLOR;
68862306a36Sopenharmony_ci		if (0 != (v2 & (1 << 2)))
68962306a36Sopenharmony_ci			i->status |= V4L2_IN_ST_MACROVISION;
69062306a36Sopenharmony_ci	}
69162306a36Sopenharmony_ci	i->std = video_devdata(file)->tvnorms;
69262306a36Sopenharmony_ci	return 0;
69362306a36Sopenharmony_ci}
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_cistatic int tw68_g_input(struct file *file, void *priv, unsigned int *i)
69662306a36Sopenharmony_ci{
69762306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_ci	*i = dev->input;
70062306a36Sopenharmony_ci	return 0;
70162306a36Sopenharmony_ci}
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_cistatic int tw68_s_input(struct file *file, void *priv, unsigned int i)
70462306a36Sopenharmony_ci{
70562306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	if (i >= TW68_INPUT_MAX)
70862306a36Sopenharmony_ci		return -EINVAL;
70962306a36Sopenharmony_ci	dev->input = i;
71062306a36Sopenharmony_ci	tw_andorb(TW68_INFORM, 0x03 << 2, dev->input << 2);
71162306a36Sopenharmony_ci	return 0;
71262306a36Sopenharmony_ci}
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_cistatic int tw68_querycap(struct file *file, void  *priv,
71562306a36Sopenharmony_ci					struct v4l2_capability *cap)
71662306a36Sopenharmony_ci{
71762306a36Sopenharmony_ci	strscpy(cap->driver, "tw68", sizeof(cap->driver));
71862306a36Sopenharmony_ci	strscpy(cap->card, "Techwell Capture Card",
71962306a36Sopenharmony_ci		sizeof(cap->card));
72062306a36Sopenharmony_ci	return 0;
72162306a36Sopenharmony_ci}
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_cistatic int tw68_s_std(struct file *file, void *priv, v4l2_std_id id)
72462306a36Sopenharmony_ci{
72562306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
72662306a36Sopenharmony_ci	unsigned int i;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	if (vb2_is_busy(&dev->vidq))
72962306a36Sopenharmony_ci		return -EBUSY;
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci	/* Look for match on complete norm id (may have mult bits) */
73262306a36Sopenharmony_ci	for (i = 0; i < TVNORMS; i++) {
73362306a36Sopenharmony_ci		if (id == tvnorms[i].id)
73462306a36Sopenharmony_ci			break;
73562306a36Sopenharmony_ci	}
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	/* If no exact match, look for norm which contains this one */
73862306a36Sopenharmony_ci	if (i == TVNORMS) {
73962306a36Sopenharmony_ci		for (i = 0; i < TVNORMS; i++)
74062306a36Sopenharmony_ci			if (id & tvnorms[i].id)
74162306a36Sopenharmony_ci				break;
74262306a36Sopenharmony_ci	}
74362306a36Sopenharmony_ci	/* If still not matched, give up */
74462306a36Sopenharmony_ci	if (i == TVNORMS)
74562306a36Sopenharmony_ci		return -EINVAL;
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	set_tvnorm(dev, &tvnorms[i]);	/* do the actual setting */
74862306a36Sopenharmony_ci	return 0;
74962306a36Sopenharmony_ci}
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_cistatic int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id)
75262306a36Sopenharmony_ci{
75362306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci	*id = dev->tvnorm->id;
75662306a36Sopenharmony_ci	return 0;
75762306a36Sopenharmony_ci}
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_cistatic int tw68_enum_fmt_vid_cap(struct file *file, void  *priv,
76062306a36Sopenharmony_ci					struct v4l2_fmtdesc *f)
76162306a36Sopenharmony_ci{
76262306a36Sopenharmony_ci	if (f->index >= FORMATS)
76362306a36Sopenharmony_ci		return -EINVAL;
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	f->pixelformat = formats[f->index].fourcc;
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	return 0;
76862306a36Sopenharmony_ci}
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci/*
77162306a36Sopenharmony_ci * Used strictly for internal development and debugging, this routine
77262306a36Sopenharmony_ci * prints out the current register contents for the tw68xx device.
77362306a36Sopenharmony_ci */
77462306a36Sopenharmony_cistatic void tw68_dump_regs(struct tw68_dev *dev)
77562306a36Sopenharmony_ci{
77662306a36Sopenharmony_ci	unsigned char line[80];
77762306a36Sopenharmony_ci	int i, j, k;
77862306a36Sopenharmony_ci	unsigned char *cptr;
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	pr_info("Full dump of TW68 registers:\n");
78162306a36Sopenharmony_ci	/* First we do the PCI regs, 8 4-byte regs per line */
78262306a36Sopenharmony_ci	for (i = 0; i < 0x100; i += 32) {
78362306a36Sopenharmony_ci		cptr = line;
78462306a36Sopenharmony_ci		cptr += sprintf(cptr, "%03x  ", i);
78562306a36Sopenharmony_ci		/* j steps through the next 4 words */
78662306a36Sopenharmony_ci		for (j = i; j < i + 16; j += 4)
78762306a36Sopenharmony_ci			cptr += sprintf(cptr, "%08x ", tw_readl(j));
78862306a36Sopenharmony_ci		*cptr++ = ' ';
78962306a36Sopenharmony_ci		for (; j < i + 32; j += 4)
79062306a36Sopenharmony_ci			cptr += sprintf(cptr, "%08x ", tw_readl(j));
79162306a36Sopenharmony_ci		*cptr++ = '\n';
79262306a36Sopenharmony_ci		*cptr = 0;
79362306a36Sopenharmony_ci		pr_info("%s", line);
79462306a36Sopenharmony_ci	}
79562306a36Sopenharmony_ci	/* Next the control regs, which are single-byte, address mod 4 */
79662306a36Sopenharmony_ci	while (i < 0x400) {
79762306a36Sopenharmony_ci		cptr = line;
79862306a36Sopenharmony_ci		cptr += sprintf(cptr, "%03x ", i);
79962306a36Sopenharmony_ci		/* Print out 4 groups of 4 bytes */
80062306a36Sopenharmony_ci		for (j = 0; j < 4; j++) {
80162306a36Sopenharmony_ci			for (k = 0; k < 4; k++) {
80262306a36Sopenharmony_ci				cptr += sprintf(cptr, "%02x ",
80362306a36Sopenharmony_ci					tw_readb(i));
80462306a36Sopenharmony_ci				i += 4;
80562306a36Sopenharmony_ci			}
80662306a36Sopenharmony_ci			*cptr++ = ' ';
80762306a36Sopenharmony_ci		}
80862306a36Sopenharmony_ci		*cptr++ = '\n';
80962306a36Sopenharmony_ci		*cptr = 0;
81062306a36Sopenharmony_ci		pr_info("%s", line);
81162306a36Sopenharmony_ci	}
81262306a36Sopenharmony_ci}
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_cistatic int vidioc_log_status(struct file *file, void *priv)
81562306a36Sopenharmony_ci{
81662306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci	tw68_dump_regs(dev);
81962306a36Sopenharmony_ci	return v4l2_ctrl_log_status(file, priv);
82062306a36Sopenharmony_ci}
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
82362306a36Sopenharmony_cistatic int vidioc_g_register(struct file *file, void *priv,
82462306a36Sopenharmony_ci			      struct v4l2_dbg_register *reg)
82562306a36Sopenharmony_ci{
82662306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	if (reg->size == 1)
82962306a36Sopenharmony_ci		reg->val = tw_readb(reg->reg);
83062306a36Sopenharmony_ci	else
83162306a36Sopenharmony_ci		reg->val = tw_readl(reg->reg);
83262306a36Sopenharmony_ci	return 0;
83362306a36Sopenharmony_ci}
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_cistatic int vidioc_s_register(struct file *file, void *priv,
83662306a36Sopenharmony_ci				const struct v4l2_dbg_register *reg)
83762306a36Sopenharmony_ci{
83862306a36Sopenharmony_ci	struct tw68_dev *dev = video_drvdata(file);
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	if (reg->size == 1)
84162306a36Sopenharmony_ci		tw_writeb(reg->reg, reg->val);
84262306a36Sopenharmony_ci	else
84362306a36Sopenharmony_ci		tw_writel(reg->reg & 0xffff, reg->val);
84462306a36Sopenharmony_ci	return 0;
84562306a36Sopenharmony_ci}
84662306a36Sopenharmony_ci#endif
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops tw68_ctrl_ops = {
84962306a36Sopenharmony_ci	.s_ctrl = tw68_s_ctrl,
85062306a36Sopenharmony_ci};
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_cistatic const struct v4l2_file_operations video_fops = {
85362306a36Sopenharmony_ci	.owner			= THIS_MODULE,
85462306a36Sopenharmony_ci	.open			= v4l2_fh_open,
85562306a36Sopenharmony_ci	.release		= vb2_fop_release,
85662306a36Sopenharmony_ci	.read			= vb2_fop_read,
85762306a36Sopenharmony_ci	.poll			= vb2_fop_poll,
85862306a36Sopenharmony_ci	.mmap			= vb2_fop_mmap,
85962306a36Sopenharmony_ci	.unlocked_ioctl		= video_ioctl2,
86062306a36Sopenharmony_ci};
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops video_ioctl_ops = {
86362306a36Sopenharmony_ci	.vidioc_querycap		= tw68_querycap,
86462306a36Sopenharmony_ci	.vidioc_enum_fmt_vid_cap	= tw68_enum_fmt_vid_cap,
86562306a36Sopenharmony_ci	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
86662306a36Sopenharmony_ci	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
86762306a36Sopenharmony_ci	.vidioc_querybuf		= vb2_ioctl_querybuf,
86862306a36Sopenharmony_ci	.vidioc_qbuf			= vb2_ioctl_qbuf,
86962306a36Sopenharmony_ci	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
87062306a36Sopenharmony_ci	.vidioc_s_std			= tw68_s_std,
87162306a36Sopenharmony_ci	.vidioc_g_std			= tw68_g_std,
87262306a36Sopenharmony_ci	.vidioc_enum_input		= tw68_enum_input,
87362306a36Sopenharmony_ci	.vidioc_g_input			= tw68_g_input,
87462306a36Sopenharmony_ci	.vidioc_s_input			= tw68_s_input,
87562306a36Sopenharmony_ci	.vidioc_streamon		= vb2_ioctl_streamon,
87662306a36Sopenharmony_ci	.vidioc_streamoff		= vb2_ioctl_streamoff,
87762306a36Sopenharmony_ci	.vidioc_g_fmt_vid_cap		= tw68_g_fmt_vid_cap,
87862306a36Sopenharmony_ci	.vidioc_try_fmt_vid_cap		= tw68_try_fmt_vid_cap,
87962306a36Sopenharmony_ci	.vidioc_s_fmt_vid_cap		= tw68_s_fmt_vid_cap,
88062306a36Sopenharmony_ci	.vidioc_log_status		= vidioc_log_status,
88162306a36Sopenharmony_ci	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
88262306a36Sopenharmony_ci	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
88362306a36Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
88462306a36Sopenharmony_ci	.vidioc_g_register              = vidioc_g_register,
88562306a36Sopenharmony_ci	.vidioc_s_register              = vidioc_s_register,
88662306a36Sopenharmony_ci#endif
88762306a36Sopenharmony_ci};
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_cistatic const struct video_device tw68_video_template = {
89062306a36Sopenharmony_ci	.name			= "tw68_video",
89162306a36Sopenharmony_ci	.fops			= &video_fops,
89262306a36Sopenharmony_ci	.ioctl_ops		= &video_ioctl_ops,
89362306a36Sopenharmony_ci	.release		= video_device_release_empty,
89462306a36Sopenharmony_ci	.tvnorms		= TW68_NORMS,
89562306a36Sopenharmony_ci	.device_caps		= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
89662306a36Sopenharmony_ci				  V4L2_CAP_STREAMING,
89762306a36Sopenharmony_ci};
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci/* ------------------------------------------------------------------ */
90062306a36Sopenharmony_ci/* exported stuff                                                     */
90162306a36Sopenharmony_civoid tw68_set_tvnorm_hw(struct tw68_dev *dev)
90262306a36Sopenharmony_ci{
90362306a36Sopenharmony_ci	tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format);
90462306a36Sopenharmony_ci}
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_ciint tw68_video_init1(struct tw68_dev *dev)
90762306a36Sopenharmony_ci{
90862306a36Sopenharmony_ci	struct v4l2_ctrl_handler *hdl = &dev->hdl;
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	v4l2_ctrl_handler_init(hdl, 6);
91162306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
91262306a36Sopenharmony_ci			V4L2_CID_BRIGHTNESS, -128, 127, 1, 20);
91362306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
91462306a36Sopenharmony_ci			V4L2_CID_CONTRAST, 0, 255, 1, 100);
91562306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
91662306a36Sopenharmony_ci			V4L2_CID_SATURATION, 0, 255, 1, 128);
91762306a36Sopenharmony_ci	/* NTSC only */
91862306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
91962306a36Sopenharmony_ci			V4L2_CID_HUE, -128, 127, 1, 0);
92062306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
92162306a36Sopenharmony_ci			V4L2_CID_COLOR_KILLER, 0, 1, 1, 0);
92262306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
92362306a36Sopenharmony_ci			V4L2_CID_CHROMA_AGC, 0, 1, 1, 1);
92462306a36Sopenharmony_ci	if (hdl->error) {
92562306a36Sopenharmony_ci		v4l2_ctrl_handler_free(hdl);
92662306a36Sopenharmony_ci		return hdl->error;
92762306a36Sopenharmony_ci	}
92862306a36Sopenharmony_ci	dev->v4l2_dev.ctrl_handler = hdl;
92962306a36Sopenharmony_ci	v4l2_ctrl_handler_setup(hdl);
93062306a36Sopenharmony_ci	return 0;
93162306a36Sopenharmony_ci}
93262306a36Sopenharmony_ci
93362306a36Sopenharmony_ciint tw68_video_init2(struct tw68_dev *dev, int video_nr)
93462306a36Sopenharmony_ci{
93562306a36Sopenharmony_ci	int ret;
93662306a36Sopenharmony_ci
93762306a36Sopenharmony_ci	set_tvnorm(dev, &tvnorms[0]);
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_ci	dev->fmt      = format_by_fourcc(V4L2_PIX_FMT_BGR24);
94062306a36Sopenharmony_ci	dev->width    = 720;
94162306a36Sopenharmony_ci	dev->height   = 576;
94262306a36Sopenharmony_ci	dev->field    = V4L2_FIELD_INTERLACED;
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci	INIT_LIST_HEAD(&dev->active);
94562306a36Sopenharmony_ci	dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
94662306a36Sopenharmony_ci	dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
94762306a36Sopenharmony_ci	dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;
94862306a36Sopenharmony_ci	dev->vidq.ops = &tw68_video_qops;
94962306a36Sopenharmony_ci	dev->vidq.mem_ops = &vb2_dma_sg_memops;
95062306a36Sopenharmony_ci	dev->vidq.drv_priv = dev;
95162306a36Sopenharmony_ci	dev->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM;
95262306a36Sopenharmony_ci	dev->vidq.buf_struct_size = sizeof(struct tw68_buf);
95362306a36Sopenharmony_ci	dev->vidq.lock = &dev->lock;
95462306a36Sopenharmony_ci	dev->vidq.min_buffers_needed = 2;
95562306a36Sopenharmony_ci	dev->vidq.dev = &dev->pci->dev;
95662306a36Sopenharmony_ci	ret = vb2_queue_init(&dev->vidq);
95762306a36Sopenharmony_ci	if (ret)
95862306a36Sopenharmony_ci		return ret;
95962306a36Sopenharmony_ci	dev->vdev = tw68_video_template;
96062306a36Sopenharmony_ci	dev->vdev.v4l2_dev = &dev->v4l2_dev;
96162306a36Sopenharmony_ci	dev->vdev.lock = &dev->lock;
96262306a36Sopenharmony_ci	dev->vdev.queue = &dev->vidq;
96362306a36Sopenharmony_ci	video_set_drvdata(&dev->vdev, dev);
96462306a36Sopenharmony_ci	return video_register_device(&dev->vdev, VFL_TYPE_VIDEO, video_nr);
96562306a36Sopenharmony_ci}
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci/*
96862306a36Sopenharmony_ci * tw68_irq_video_done
96962306a36Sopenharmony_ci */
97062306a36Sopenharmony_civoid tw68_irq_video_done(struct tw68_dev *dev, unsigned long status)
97162306a36Sopenharmony_ci{
97262306a36Sopenharmony_ci	__u32 reg;
97362306a36Sopenharmony_ci
97462306a36Sopenharmony_ci	/* reset interrupts handled by this routine */
97562306a36Sopenharmony_ci	tw_writel(TW68_INTSTAT, status);
97662306a36Sopenharmony_ci	/*
97762306a36Sopenharmony_ci	 * Check most likely first
97862306a36Sopenharmony_ci	 *
97962306a36Sopenharmony_ci	 * DMAPI shows we have reached the end of the risc code
98062306a36Sopenharmony_ci	 * for the current buffer.
98162306a36Sopenharmony_ci	 */
98262306a36Sopenharmony_ci	if (status & TW68_DMAPI) {
98362306a36Sopenharmony_ci		struct tw68_buf *buf;
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci		spin_lock(&dev->slock);
98662306a36Sopenharmony_ci		buf = list_entry(dev->active.next, struct tw68_buf, list);
98762306a36Sopenharmony_ci		list_del(&buf->list);
98862306a36Sopenharmony_ci		spin_unlock(&dev->slock);
98962306a36Sopenharmony_ci		buf->vb.vb2_buf.timestamp = ktime_get_ns();
99062306a36Sopenharmony_ci		buf->vb.field = dev->field;
99162306a36Sopenharmony_ci		buf->vb.sequence = dev->seqnr++;
99262306a36Sopenharmony_ci		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
99362306a36Sopenharmony_ci		status &= ~(TW68_DMAPI);
99462306a36Sopenharmony_ci		if (0 == status)
99562306a36Sopenharmony_ci			return;
99662306a36Sopenharmony_ci	}
99762306a36Sopenharmony_ci	if (status & (TW68_VLOCK | TW68_HLOCK))
99862306a36Sopenharmony_ci		dev_dbg(&dev->pci->dev, "Lost sync\n");
99962306a36Sopenharmony_ci	if (status & TW68_PABORT)
100062306a36Sopenharmony_ci		dev_err(&dev->pci->dev, "PABORT interrupt\n");
100162306a36Sopenharmony_ci	if (status & TW68_DMAPERR)
100262306a36Sopenharmony_ci		dev_err(&dev->pci->dev, "DMAPERR interrupt\n");
100362306a36Sopenharmony_ci	/*
100462306a36Sopenharmony_ci	 * On TW6800, FDMIS is apparently generated if video input is switched
100562306a36Sopenharmony_ci	 * during operation.  Therefore, it is not enabled for that chip.
100662306a36Sopenharmony_ci	 */
100762306a36Sopenharmony_ci	if (status & TW68_FDMIS)
100862306a36Sopenharmony_ci		dev_dbg(&dev->pci->dev, "FDMIS interrupt\n");
100962306a36Sopenharmony_ci	if (status & TW68_FFOF) {
101062306a36Sopenharmony_ci		/* probably a logic error */
101162306a36Sopenharmony_ci		reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN;
101262306a36Sopenharmony_ci		tw_clearl(TW68_DMAC, TW68_FIFO_EN);
101362306a36Sopenharmony_ci		dev_dbg(&dev->pci->dev, "FFOF interrupt\n");
101462306a36Sopenharmony_ci		tw_setl(TW68_DMAC, reg);
101562306a36Sopenharmony_ci	}
101662306a36Sopenharmony_ci	if (status & TW68_FFERR)
101762306a36Sopenharmony_ci		dev_dbg(&dev->pci->dev, "FFERR interrupt\n");
101862306a36Sopenharmony_ci}
1019