162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Xilinx Test Pattern Generator
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2013-2015 Ideas on Board
662306a36Sopenharmony_ci * Copyright (C) 2013-2015 Xilinx, Inc.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Contacts: Hyun Kwon <hyun.kwon@xilinx.com>
962306a36Sopenharmony_ci *           Laurent Pinchart <laurent.pinchart@ideasonboard.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/of.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/xilinx-v4l2-controls.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <media/v4l2-async.h>
2062306a36Sopenharmony_ci#include <media/v4l2-ctrls.h>
2162306a36Sopenharmony_ci#include <media/v4l2-subdev.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "xilinx-vip.h"
2462306a36Sopenharmony_ci#include "xilinx-vtc.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define XTPG_CTRL_STATUS_SLAVE_ERROR		(1 << 16)
2762306a36Sopenharmony_ci#define XTPG_CTRL_IRQ_SLAVE_ERROR		(1 << 16)
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL			0x0100
3062306a36Sopenharmony_ci#define XTPG_PATTERN_MASK			(0xf << 0)
3162306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_CROSS_HAIRS	(1 << 4)
3262306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_MOVING_BOX		(1 << 5)
3362306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_COLOR_MASK_SHIFT	6
3462306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_COLOR_MASK_MASK	(0xf << 6)
3562306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_STUCK_PIXEL	(1 << 9)
3662306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_NOISE		(1 << 10)
3762306a36Sopenharmony_ci#define XTPG_PATTERN_CONTROL_MOTION		(1 << 12)
3862306a36Sopenharmony_ci#define XTPG_MOTION_SPEED			0x0104
3962306a36Sopenharmony_ci#define XTPG_CROSS_HAIRS			0x0108
4062306a36Sopenharmony_ci#define XTPG_CROSS_HAIRS_ROW_SHIFT		0
4162306a36Sopenharmony_ci#define XTPG_CROSS_HAIRS_ROW_MASK		(0xfff << 0)
4262306a36Sopenharmony_ci#define XTPG_CROSS_HAIRS_COLUMN_SHIFT		16
4362306a36Sopenharmony_ci#define XTPG_CROSS_HAIRS_COLUMN_MASK		(0xfff << 16)
4462306a36Sopenharmony_ci#define XTPG_ZPLATE_HOR_CONTROL			0x010c
4562306a36Sopenharmony_ci#define XTPG_ZPLATE_VER_CONTROL			0x0110
4662306a36Sopenharmony_ci#define XTPG_ZPLATE_START_SHIFT			0
4762306a36Sopenharmony_ci#define XTPG_ZPLATE_START_MASK			(0xffff << 0)
4862306a36Sopenharmony_ci#define XTPG_ZPLATE_SPEED_SHIFT			16
4962306a36Sopenharmony_ci#define XTPG_ZPLATE_SPEED_MASK			(0xffff << 16)
5062306a36Sopenharmony_ci#define XTPG_BOX_SIZE				0x0114
5162306a36Sopenharmony_ci#define XTPG_BOX_COLOR				0x0118
5262306a36Sopenharmony_ci#define XTPG_STUCK_PIXEL_THRESH			0x011c
5362306a36Sopenharmony_ci#define XTPG_NOISE_GAIN				0x0120
5462306a36Sopenharmony_ci#define XTPG_BAYER_PHASE			0x0124
5562306a36Sopenharmony_ci#define XTPG_BAYER_PHASE_RGGB			0
5662306a36Sopenharmony_ci#define XTPG_BAYER_PHASE_GRBG			1
5762306a36Sopenharmony_ci#define XTPG_BAYER_PHASE_GBRG			2
5862306a36Sopenharmony_ci#define XTPG_BAYER_PHASE_BGGR			3
5962306a36Sopenharmony_ci#define XTPG_BAYER_PHASE_OFF			4
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci/*
6262306a36Sopenharmony_ci * The minimum blanking value is one clock cycle for the front porch, one clock
6362306a36Sopenharmony_ci * cycle for the sync pulse and one clock cycle for the back porch.
6462306a36Sopenharmony_ci */
6562306a36Sopenharmony_ci#define XTPG_MIN_HBLANK			3
6662306a36Sopenharmony_ci#define XTPG_MAX_HBLANK			(XVTC_MAX_HSIZE - XVIP_MIN_WIDTH)
6762306a36Sopenharmony_ci#define XTPG_MIN_VBLANK			3
6862306a36Sopenharmony_ci#define XTPG_MAX_VBLANK			(XVTC_MAX_VSIZE - XVIP_MIN_HEIGHT)
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/**
7162306a36Sopenharmony_ci * struct xtpg_device - Xilinx Test Pattern Generator device structure
7262306a36Sopenharmony_ci * @xvip: Xilinx Video IP device
7362306a36Sopenharmony_ci * @pads: media pads
7462306a36Sopenharmony_ci * @npads: number of pads (1 or 2)
7562306a36Sopenharmony_ci * @has_input: whether an input is connected to the sink pad
7662306a36Sopenharmony_ci * @formats: active V4L2 media bus format for each pad
7762306a36Sopenharmony_ci * @default_format: default V4L2 media bus format
7862306a36Sopenharmony_ci * @vip_format: format information corresponding to the active format
7962306a36Sopenharmony_ci * @bayer: boolean flag if TPG is set to any bayer format
8062306a36Sopenharmony_ci * @ctrl_handler: control handler
8162306a36Sopenharmony_ci * @hblank: horizontal blanking control
8262306a36Sopenharmony_ci * @vblank: vertical blanking control
8362306a36Sopenharmony_ci * @pattern: test pattern control
8462306a36Sopenharmony_ci * @streaming: is the video stream active
8562306a36Sopenharmony_ci * @vtc: video timing controller
8662306a36Sopenharmony_ci * @vtmux_gpio: video timing mux GPIO
8762306a36Sopenharmony_ci */
8862306a36Sopenharmony_cistruct xtpg_device {
8962306a36Sopenharmony_ci	struct xvip_device xvip;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	struct media_pad pads[2];
9262306a36Sopenharmony_ci	unsigned int npads;
9362306a36Sopenharmony_ci	bool has_input;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	struct v4l2_mbus_framefmt formats[2];
9662306a36Sopenharmony_ci	struct v4l2_mbus_framefmt default_format;
9762306a36Sopenharmony_ci	const struct xvip_video_format *vip_format;
9862306a36Sopenharmony_ci	bool bayer;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	struct v4l2_ctrl_handler ctrl_handler;
10162306a36Sopenharmony_ci	struct v4l2_ctrl *hblank;
10262306a36Sopenharmony_ci	struct v4l2_ctrl *vblank;
10362306a36Sopenharmony_ci	struct v4l2_ctrl *pattern;
10462306a36Sopenharmony_ci	bool streaming;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	struct xvtc_device *vtc;
10762306a36Sopenharmony_ci	struct gpio_desc *vtmux_gpio;
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic inline struct xtpg_device *to_tpg(struct v4l2_subdev *subdev)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	return container_of(subdev, struct xtpg_device, xvip.subdev);
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic u32 xtpg_get_bayer_phase(unsigned int code)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	switch (code) {
11862306a36Sopenharmony_ci	case MEDIA_BUS_FMT_SRGGB8_1X8:
11962306a36Sopenharmony_ci		return XTPG_BAYER_PHASE_RGGB;
12062306a36Sopenharmony_ci	case MEDIA_BUS_FMT_SGRBG8_1X8:
12162306a36Sopenharmony_ci		return XTPG_BAYER_PHASE_GRBG;
12262306a36Sopenharmony_ci	case MEDIA_BUS_FMT_SGBRG8_1X8:
12362306a36Sopenharmony_ci		return XTPG_BAYER_PHASE_GBRG;
12462306a36Sopenharmony_ci	case MEDIA_BUS_FMT_SBGGR8_1X8:
12562306a36Sopenharmony_ci		return XTPG_BAYER_PHASE_BGGR;
12662306a36Sopenharmony_ci	default:
12762306a36Sopenharmony_ci		return XTPG_BAYER_PHASE_OFF;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic void __xtpg_update_pattern_control(struct xtpg_device *xtpg,
13262306a36Sopenharmony_ci					  bool passthrough, bool pattern)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	u32 pattern_mask = (1 << (xtpg->pattern->maximum + 1)) - 1;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/*
13762306a36Sopenharmony_ci	 * If the TPG has no sink pad or no input connected to its sink pad
13862306a36Sopenharmony_ci	 * passthrough mode can't be enabled.
13962306a36Sopenharmony_ci	 */
14062306a36Sopenharmony_ci	if (xtpg->npads == 1 || !xtpg->has_input)
14162306a36Sopenharmony_ci		passthrough = false;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	/* If passthrough mode is allowed unmask bit 0. */
14462306a36Sopenharmony_ci	if (passthrough)
14562306a36Sopenharmony_ci		pattern_mask &= ~1;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* If test pattern mode is allowed unmask all other bits. */
14862306a36Sopenharmony_ci	if (pattern)
14962306a36Sopenharmony_ci		pattern_mask &= 1;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	__v4l2_ctrl_modify_range(xtpg->pattern, 0, xtpg->pattern->maximum,
15262306a36Sopenharmony_ci				 pattern_mask, pattern ? 9 : 0);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic void xtpg_update_pattern_control(struct xtpg_device *xtpg,
15662306a36Sopenharmony_ci					bool passthrough, bool pattern)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	mutex_lock(xtpg->ctrl_handler.lock);
15962306a36Sopenharmony_ci	__xtpg_update_pattern_control(xtpg, passthrough, pattern);
16062306a36Sopenharmony_ci	mutex_unlock(xtpg->ctrl_handler.lock);
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
16462306a36Sopenharmony_ci * V4L2 Subdevice Video Operations
16562306a36Sopenharmony_ci */
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int xtpg_s_stream(struct v4l2_subdev *subdev, int enable)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct xtpg_device *xtpg = to_tpg(subdev);
17062306a36Sopenharmony_ci	unsigned int width = xtpg->formats[0].width;
17162306a36Sopenharmony_ci	unsigned int height = xtpg->formats[0].height;
17262306a36Sopenharmony_ci	bool passthrough;
17362306a36Sopenharmony_ci	u32 bayer_phase;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	if (!enable) {
17662306a36Sopenharmony_ci		xvip_stop(&xtpg->xvip);
17762306a36Sopenharmony_ci		if (xtpg->vtc)
17862306a36Sopenharmony_ci			xvtc_generator_stop(xtpg->vtc);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci		xtpg_update_pattern_control(xtpg, true, true);
18162306a36Sopenharmony_ci		xtpg->streaming = false;
18262306a36Sopenharmony_ci		return 0;
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	xvip_set_frame_size(&xtpg->xvip, &xtpg->formats[0]);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	if (xtpg->vtc) {
18862306a36Sopenharmony_ci		struct xvtc_config config = {
18962306a36Sopenharmony_ci			.hblank_start = width,
19062306a36Sopenharmony_ci			.hsync_start = width + 1,
19162306a36Sopenharmony_ci			.vblank_start = height,
19262306a36Sopenharmony_ci			.vsync_start = height + 1,
19362306a36Sopenharmony_ci		};
19462306a36Sopenharmony_ci		unsigned int htotal;
19562306a36Sopenharmony_ci		unsigned int vtotal;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		htotal = min_t(unsigned int, XVTC_MAX_HSIZE,
19862306a36Sopenharmony_ci			       v4l2_ctrl_g_ctrl(xtpg->hblank) + width);
19962306a36Sopenharmony_ci		vtotal = min_t(unsigned int, XVTC_MAX_VSIZE,
20062306a36Sopenharmony_ci			       v4l2_ctrl_g_ctrl(xtpg->vblank) + height);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci		config.hsync_end = htotal - 1;
20362306a36Sopenharmony_ci		config.hsize = htotal;
20462306a36Sopenharmony_ci		config.vsync_end = vtotal - 1;
20562306a36Sopenharmony_ci		config.vsize = vtotal;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		xvtc_generator_start(xtpg->vtc, &config);
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	/*
21162306a36Sopenharmony_ci	 * Configure the bayer phase and video timing mux based on the
21262306a36Sopenharmony_ci	 * operation mode (passthrough or test pattern generation). The test
21362306a36Sopenharmony_ci	 * pattern can be modified by the control set handler, we thus need to
21462306a36Sopenharmony_ci	 * take the control lock here to avoid races.
21562306a36Sopenharmony_ci	 */
21662306a36Sopenharmony_ci	mutex_lock(xtpg->ctrl_handler.lock);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	xvip_clr_and_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
21962306a36Sopenharmony_ci			 XTPG_PATTERN_MASK, xtpg->pattern->cur.val);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/*
22262306a36Sopenharmony_ci	 * Switching between passthrough and test pattern generation modes isn't
22362306a36Sopenharmony_ci	 * allowed during streaming, update the control range accordingly.
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	passthrough = xtpg->pattern->cur.val == 0;
22662306a36Sopenharmony_ci	__xtpg_update_pattern_control(xtpg, passthrough, !passthrough);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	xtpg->streaming = true;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	mutex_unlock(xtpg->ctrl_handler.lock);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	/*
23362306a36Sopenharmony_ci	 * For TPG v5.0, the bayer phase needs to be off for the pass through
23462306a36Sopenharmony_ci	 * mode, otherwise the external input would be subsampled.
23562306a36Sopenharmony_ci	 */
23662306a36Sopenharmony_ci	bayer_phase = passthrough ? XTPG_BAYER_PHASE_OFF
23762306a36Sopenharmony_ci		    : xtpg_get_bayer_phase(xtpg->formats[0].code);
23862306a36Sopenharmony_ci	xvip_write(&xtpg->xvip, XTPG_BAYER_PHASE, bayer_phase);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	if (xtpg->vtmux_gpio)
24162306a36Sopenharmony_ci		gpiod_set_value_cansleep(xtpg->vtmux_gpio, !passthrough);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	xvip_start(&xtpg->xvip);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return 0;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
24962306a36Sopenharmony_ci * V4L2 Subdevice Pad Operations
25062306a36Sopenharmony_ci */
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic struct v4l2_mbus_framefmt *
25362306a36Sopenharmony_ci__xtpg_get_pad_format(struct xtpg_device *xtpg,
25462306a36Sopenharmony_ci		      struct v4l2_subdev_state *sd_state,
25562306a36Sopenharmony_ci		      unsigned int pad, u32 which)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	switch (which) {
25862306a36Sopenharmony_ci	case V4L2_SUBDEV_FORMAT_TRY:
25962306a36Sopenharmony_ci		return v4l2_subdev_get_try_format(&xtpg->xvip.subdev,
26062306a36Sopenharmony_ci						  sd_state, pad);
26162306a36Sopenharmony_ci	case V4L2_SUBDEV_FORMAT_ACTIVE:
26262306a36Sopenharmony_ci		return &xtpg->formats[pad];
26362306a36Sopenharmony_ci	default:
26462306a36Sopenharmony_ci		return NULL;
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic int xtpg_get_format(struct v4l2_subdev *subdev,
26962306a36Sopenharmony_ci			   struct v4l2_subdev_state *sd_state,
27062306a36Sopenharmony_ci			   struct v4l2_subdev_format *fmt)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct xtpg_device *xtpg = to_tpg(subdev);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	fmt->format = *__xtpg_get_pad_format(xtpg, sd_state, fmt->pad,
27562306a36Sopenharmony_ci					     fmt->which);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	return 0;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int xtpg_set_format(struct v4l2_subdev *subdev,
28162306a36Sopenharmony_ci			   struct v4l2_subdev_state *sd_state,
28262306a36Sopenharmony_ci			   struct v4l2_subdev_format *fmt)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	struct xtpg_device *xtpg = to_tpg(subdev);
28562306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *__format;
28662306a36Sopenharmony_ci	u32 bayer_phase;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	__format = __xtpg_get_pad_format(xtpg, sd_state, fmt->pad, fmt->which);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	/* In two pads mode the source pad format is always identical to the
29162306a36Sopenharmony_ci	 * sink pad format.
29262306a36Sopenharmony_ci	 */
29362306a36Sopenharmony_ci	if (xtpg->npads == 2 && fmt->pad == 1) {
29462306a36Sopenharmony_ci		fmt->format = *__format;
29562306a36Sopenharmony_ci		return 0;
29662306a36Sopenharmony_ci	}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/* Bayer phase is configurable at runtime */
29962306a36Sopenharmony_ci	if (xtpg->bayer) {
30062306a36Sopenharmony_ci		bayer_phase = xtpg_get_bayer_phase(fmt->format.code);
30162306a36Sopenharmony_ci		if (bayer_phase != XTPG_BAYER_PHASE_OFF)
30262306a36Sopenharmony_ci			__format->code = fmt->format.code;
30362306a36Sopenharmony_ci	}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	xvip_set_format_size(__format, fmt);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	fmt->format = *__format;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	/* Propagate the format to the source pad. */
31062306a36Sopenharmony_ci	if (xtpg->npads == 2) {
31162306a36Sopenharmony_ci		__format = __xtpg_get_pad_format(xtpg, sd_state, 1,
31262306a36Sopenharmony_ci						 fmt->which);
31362306a36Sopenharmony_ci		*__format = fmt->format;
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	return 0;
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
32062306a36Sopenharmony_ci * V4L2 Subdevice Operations
32162306a36Sopenharmony_ci */
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_cistatic int xtpg_enum_frame_size(struct v4l2_subdev *subdev,
32462306a36Sopenharmony_ci				struct v4l2_subdev_state *sd_state,
32562306a36Sopenharmony_ci				struct v4l2_subdev_frame_size_enum *fse)
32662306a36Sopenharmony_ci{
32762306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *format;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	format = v4l2_subdev_get_try_format(subdev, sd_state, fse->pad);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	if (fse->index || fse->code != format->code)
33262306a36Sopenharmony_ci		return -EINVAL;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	/* Min / max values for pad 0 is always fixed in both one and two pads
33562306a36Sopenharmony_ci	 * modes. In two pads mode, the source pad(= 1) size is identical to
33662306a36Sopenharmony_ci	 * the sink pad size */
33762306a36Sopenharmony_ci	if (fse->pad == 0) {
33862306a36Sopenharmony_ci		fse->min_width = XVIP_MIN_WIDTH;
33962306a36Sopenharmony_ci		fse->max_width = XVIP_MAX_WIDTH;
34062306a36Sopenharmony_ci		fse->min_height = XVIP_MIN_HEIGHT;
34162306a36Sopenharmony_ci		fse->max_height = XVIP_MAX_HEIGHT;
34262306a36Sopenharmony_ci	} else {
34362306a36Sopenharmony_ci		fse->min_width = format->width;
34462306a36Sopenharmony_ci		fse->max_width = format->width;
34562306a36Sopenharmony_ci		fse->min_height = format->height;
34662306a36Sopenharmony_ci		fse->max_height = format->height;
34762306a36Sopenharmony_ci	}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	return 0;
35062306a36Sopenharmony_ci}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cistatic int xtpg_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	struct xtpg_device *xtpg = to_tpg(subdev);
35562306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *format;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	format = v4l2_subdev_get_try_format(subdev, fh->state, 0);
35862306a36Sopenharmony_ci	*format = xtpg->default_format;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (xtpg->npads == 2) {
36162306a36Sopenharmony_ci		format = v4l2_subdev_get_try_format(subdev, fh->state, 1);
36262306a36Sopenharmony_ci		*format = xtpg->default_format;
36362306a36Sopenharmony_ci	}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return 0;
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistatic int xtpg_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	return 0;
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic int xtpg_s_ctrl(struct v4l2_ctrl *ctrl)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	struct xtpg_device *xtpg = container_of(ctrl->handler,
37662306a36Sopenharmony_ci						struct xtpg_device,
37762306a36Sopenharmony_ci						ctrl_handler);
37862306a36Sopenharmony_ci	switch (ctrl->id) {
37962306a36Sopenharmony_ci	case V4L2_CID_TEST_PATTERN:
38062306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
38162306a36Sopenharmony_ci				 XTPG_PATTERN_MASK, ctrl->val);
38262306a36Sopenharmony_ci		return 0;
38362306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_CROSS_HAIRS:
38462306a36Sopenharmony_ci		xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
38562306a36Sopenharmony_ci				XTPG_PATTERN_CONTROL_CROSS_HAIRS, ctrl->val);
38662306a36Sopenharmony_ci		return 0;
38762306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_MOVING_BOX:
38862306a36Sopenharmony_ci		xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
38962306a36Sopenharmony_ci				XTPG_PATTERN_CONTROL_MOVING_BOX, ctrl->val);
39062306a36Sopenharmony_ci		return 0;
39162306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_COLOR_MASK:
39262306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
39362306a36Sopenharmony_ci				 XTPG_PATTERN_CONTROL_COLOR_MASK_MASK,
39462306a36Sopenharmony_ci				 ctrl->val <<
39562306a36Sopenharmony_ci				 XTPG_PATTERN_CONTROL_COLOR_MASK_SHIFT);
39662306a36Sopenharmony_ci		return 0;
39762306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_STUCK_PIXEL:
39862306a36Sopenharmony_ci		xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
39962306a36Sopenharmony_ci				XTPG_PATTERN_CONTROL_STUCK_PIXEL, ctrl->val);
40062306a36Sopenharmony_ci		return 0;
40162306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_NOISE:
40262306a36Sopenharmony_ci		xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
40362306a36Sopenharmony_ci				XTPG_PATTERN_CONTROL_NOISE, ctrl->val);
40462306a36Sopenharmony_ci		return 0;
40562306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_MOTION:
40662306a36Sopenharmony_ci		xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL,
40762306a36Sopenharmony_ci				XTPG_PATTERN_CONTROL_MOTION, ctrl->val);
40862306a36Sopenharmony_ci		return 0;
40962306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_MOTION_SPEED:
41062306a36Sopenharmony_ci		xvip_write(&xtpg->xvip, XTPG_MOTION_SPEED, ctrl->val);
41162306a36Sopenharmony_ci		return 0;
41262306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_CROSS_HAIR_ROW:
41362306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_CROSS_HAIRS,
41462306a36Sopenharmony_ci				 XTPG_CROSS_HAIRS_ROW_MASK,
41562306a36Sopenharmony_ci				 ctrl->val << XTPG_CROSS_HAIRS_ROW_SHIFT);
41662306a36Sopenharmony_ci		return 0;
41762306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_CROSS_HAIR_COLUMN:
41862306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_CROSS_HAIRS,
41962306a36Sopenharmony_ci				 XTPG_CROSS_HAIRS_COLUMN_MASK,
42062306a36Sopenharmony_ci				 ctrl->val << XTPG_CROSS_HAIRS_COLUMN_SHIFT);
42162306a36Sopenharmony_ci		return 0;
42262306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_ZPLATE_HOR_START:
42362306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_HOR_CONTROL,
42462306a36Sopenharmony_ci				 XTPG_ZPLATE_START_MASK,
42562306a36Sopenharmony_ci				 ctrl->val << XTPG_ZPLATE_START_SHIFT);
42662306a36Sopenharmony_ci		return 0;
42762306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_ZPLATE_HOR_SPEED:
42862306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_HOR_CONTROL,
42962306a36Sopenharmony_ci				 XTPG_ZPLATE_SPEED_MASK,
43062306a36Sopenharmony_ci				 ctrl->val << XTPG_ZPLATE_SPEED_SHIFT);
43162306a36Sopenharmony_ci		return 0;
43262306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_ZPLATE_VER_START:
43362306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_VER_CONTROL,
43462306a36Sopenharmony_ci				 XTPG_ZPLATE_START_MASK,
43562306a36Sopenharmony_ci				 ctrl->val << XTPG_ZPLATE_START_SHIFT);
43662306a36Sopenharmony_ci		return 0;
43762306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_ZPLATE_VER_SPEED:
43862306a36Sopenharmony_ci		xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_VER_CONTROL,
43962306a36Sopenharmony_ci				 XTPG_ZPLATE_SPEED_MASK,
44062306a36Sopenharmony_ci				 ctrl->val << XTPG_ZPLATE_SPEED_SHIFT);
44162306a36Sopenharmony_ci		return 0;
44262306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_BOX_SIZE:
44362306a36Sopenharmony_ci		xvip_write(&xtpg->xvip, XTPG_BOX_SIZE, ctrl->val);
44462306a36Sopenharmony_ci		return 0;
44562306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_BOX_COLOR:
44662306a36Sopenharmony_ci		xvip_write(&xtpg->xvip, XTPG_BOX_COLOR, ctrl->val);
44762306a36Sopenharmony_ci		return 0;
44862306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_STUCK_PIXEL_THRESH:
44962306a36Sopenharmony_ci		xvip_write(&xtpg->xvip, XTPG_STUCK_PIXEL_THRESH, ctrl->val);
45062306a36Sopenharmony_ci		return 0;
45162306a36Sopenharmony_ci	case V4L2_CID_XILINX_TPG_NOISE_GAIN:
45262306a36Sopenharmony_ci		xvip_write(&xtpg->xvip, XTPG_NOISE_GAIN, ctrl->val);
45362306a36Sopenharmony_ci		return 0;
45462306a36Sopenharmony_ci	}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	return 0;
45762306a36Sopenharmony_ci}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops xtpg_ctrl_ops = {
46062306a36Sopenharmony_ci	.s_ctrl	= xtpg_s_ctrl,
46162306a36Sopenharmony_ci};
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_cistatic const struct v4l2_subdev_core_ops xtpg_core_ops = {
46462306a36Sopenharmony_ci};
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_cistatic const struct v4l2_subdev_video_ops xtpg_video_ops = {
46762306a36Sopenharmony_ci	.s_stream = xtpg_s_stream,
46862306a36Sopenharmony_ci};
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_cistatic const struct v4l2_subdev_pad_ops xtpg_pad_ops = {
47162306a36Sopenharmony_ci	.enum_mbus_code		= xvip_enum_mbus_code,
47262306a36Sopenharmony_ci	.enum_frame_size	= xtpg_enum_frame_size,
47362306a36Sopenharmony_ci	.get_fmt		= xtpg_get_format,
47462306a36Sopenharmony_ci	.set_fmt		= xtpg_set_format,
47562306a36Sopenharmony_ci};
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_cistatic const struct v4l2_subdev_ops xtpg_ops = {
47862306a36Sopenharmony_ci	.core   = &xtpg_core_ops,
47962306a36Sopenharmony_ci	.video  = &xtpg_video_ops,
48062306a36Sopenharmony_ci	.pad    = &xtpg_pad_ops,
48162306a36Sopenharmony_ci};
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic const struct v4l2_subdev_internal_ops xtpg_internal_ops = {
48462306a36Sopenharmony_ci	.open	= xtpg_open,
48562306a36Sopenharmony_ci	.close	= xtpg_close,
48662306a36Sopenharmony_ci};
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci/*
48962306a36Sopenharmony_ci * Control Config
49062306a36Sopenharmony_ci */
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_cistatic const char *const xtpg_pattern_strings[] = {
49362306a36Sopenharmony_ci	"Passthrough",
49462306a36Sopenharmony_ci	"Horizontal Ramp",
49562306a36Sopenharmony_ci	"Vertical Ramp",
49662306a36Sopenharmony_ci	"Temporal Ramp",
49762306a36Sopenharmony_ci	"Solid Red",
49862306a36Sopenharmony_ci	"Solid Green",
49962306a36Sopenharmony_ci	"Solid Blue",
50062306a36Sopenharmony_ci	"Solid Black",
50162306a36Sopenharmony_ci	"Solid White",
50262306a36Sopenharmony_ci	"Color Bars",
50362306a36Sopenharmony_ci	"Zone Plate",
50462306a36Sopenharmony_ci	"Tartan Color Bars",
50562306a36Sopenharmony_ci	"Cross Hatch",
50662306a36Sopenharmony_ci	"None",
50762306a36Sopenharmony_ci	"Vertical/Horizontal Ramps",
50862306a36Sopenharmony_ci	"Black/White Checker Board",
50962306a36Sopenharmony_ci};
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_cistatic struct v4l2_ctrl_config xtpg_ctrls[] = {
51262306a36Sopenharmony_ci	{
51362306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
51462306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_CROSS_HAIRS,
51562306a36Sopenharmony_ci		.name	= "Test Pattern: Cross Hairs",
51662306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_BOOLEAN,
51762306a36Sopenharmony_ci		.min	= false,
51862306a36Sopenharmony_ci		.max	= true,
51962306a36Sopenharmony_ci		.step	= 1,
52062306a36Sopenharmony_ci		.def	= 0,
52162306a36Sopenharmony_ci	}, {
52262306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
52362306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_MOVING_BOX,
52462306a36Sopenharmony_ci		.name	= "Test Pattern: Moving Box",
52562306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_BOOLEAN,
52662306a36Sopenharmony_ci		.min	= false,
52762306a36Sopenharmony_ci		.max	= true,
52862306a36Sopenharmony_ci		.step	= 1,
52962306a36Sopenharmony_ci		.def	= 0,
53062306a36Sopenharmony_ci	}, {
53162306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
53262306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_COLOR_MASK,
53362306a36Sopenharmony_ci		.name	= "Test Pattern: Color Mask",
53462306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_BITMASK,
53562306a36Sopenharmony_ci		.min	= 0,
53662306a36Sopenharmony_ci		.max	= 0xf,
53762306a36Sopenharmony_ci		.def	= 0,
53862306a36Sopenharmony_ci	}, {
53962306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
54062306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_STUCK_PIXEL,
54162306a36Sopenharmony_ci		.name	= "Test Pattern: Stuck Pixel",
54262306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_BOOLEAN,
54362306a36Sopenharmony_ci		.min	= false,
54462306a36Sopenharmony_ci		.max	= true,
54562306a36Sopenharmony_ci		.step	= 1,
54662306a36Sopenharmony_ci		.def	= 0,
54762306a36Sopenharmony_ci	}, {
54862306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
54962306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_NOISE,
55062306a36Sopenharmony_ci		.name	= "Test Pattern: Noise",
55162306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_BOOLEAN,
55262306a36Sopenharmony_ci		.min	= false,
55362306a36Sopenharmony_ci		.max	= true,
55462306a36Sopenharmony_ci		.step	= 1,
55562306a36Sopenharmony_ci		.def	= 0,
55662306a36Sopenharmony_ci	}, {
55762306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
55862306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_MOTION,
55962306a36Sopenharmony_ci		.name	= "Test Pattern: Motion",
56062306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_BOOLEAN,
56162306a36Sopenharmony_ci		.min	= false,
56262306a36Sopenharmony_ci		.max	= true,
56362306a36Sopenharmony_ci		.step	= 1,
56462306a36Sopenharmony_ci		.def	= 0,
56562306a36Sopenharmony_ci	}, {
56662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
56762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_MOTION_SPEED,
56862306a36Sopenharmony_ci		.name	= "Test Pattern: Motion Speed",
56962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
57062306a36Sopenharmony_ci		.min	= 0,
57162306a36Sopenharmony_ci		.max	= (1 << 8) - 1,
57262306a36Sopenharmony_ci		.step	= 1,
57362306a36Sopenharmony_ci		.def	= 4,
57462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
57562306a36Sopenharmony_ci	}, {
57662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
57762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_CROSS_HAIR_ROW,
57862306a36Sopenharmony_ci		.name	= "Test Pattern: Cross Hairs Row",
57962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
58062306a36Sopenharmony_ci		.min	= 0,
58162306a36Sopenharmony_ci		.max	= (1 << 12) - 1,
58262306a36Sopenharmony_ci		.step	= 1,
58362306a36Sopenharmony_ci		.def	= 0x64,
58462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
58562306a36Sopenharmony_ci	}, {
58662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
58762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_CROSS_HAIR_COLUMN,
58862306a36Sopenharmony_ci		.name	= "Test Pattern: Cross Hairs Column",
58962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
59062306a36Sopenharmony_ci		.min	= 0,
59162306a36Sopenharmony_ci		.max	= (1 << 12) - 1,
59262306a36Sopenharmony_ci		.step	= 1,
59362306a36Sopenharmony_ci		.def	= 0x64,
59462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
59562306a36Sopenharmony_ci	}, {
59662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
59762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_ZPLATE_HOR_START,
59862306a36Sopenharmony_ci		.name	= "Test Pattern: Zplate Horizontal Start Pos",
59962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
60062306a36Sopenharmony_ci		.min	= 0,
60162306a36Sopenharmony_ci		.max	= (1 << 16) - 1,
60262306a36Sopenharmony_ci		.step	= 1,
60362306a36Sopenharmony_ci		.def	= 0x1e,
60462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
60562306a36Sopenharmony_ci	}, {
60662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
60762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_ZPLATE_HOR_SPEED,
60862306a36Sopenharmony_ci		.name	= "Test Pattern: Zplate Horizontal Speed",
60962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
61062306a36Sopenharmony_ci		.min	= 0,
61162306a36Sopenharmony_ci		.max	= (1 << 16) - 1,
61262306a36Sopenharmony_ci		.step	= 1,
61362306a36Sopenharmony_ci		.def	= 0,
61462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
61562306a36Sopenharmony_ci	}, {
61662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
61762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_ZPLATE_VER_START,
61862306a36Sopenharmony_ci		.name	= "Test Pattern: Zplate Vertical Start Pos",
61962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
62062306a36Sopenharmony_ci		.min	= 0,
62162306a36Sopenharmony_ci		.max	= (1 << 16) - 1,
62262306a36Sopenharmony_ci		.step	= 1,
62362306a36Sopenharmony_ci		.def	= 1,
62462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
62562306a36Sopenharmony_ci	}, {
62662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
62762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_ZPLATE_VER_SPEED,
62862306a36Sopenharmony_ci		.name	= "Test Pattern: Zplate Vertical Speed",
62962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
63062306a36Sopenharmony_ci		.min	= 0,
63162306a36Sopenharmony_ci		.max	= (1 << 16) - 1,
63262306a36Sopenharmony_ci		.step	= 1,
63362306a36Sopenharmony_ci		.def	= 0,
63462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
63562306a36Sopenharmony_ci	}, {
63662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
63762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_BOX_SIZE,
63862306a36Sopenharmony_ci		.name	= "Test Pattern: Box Size",
63962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
64062306a36Sopenharmony_ci		.min	= 0,
64162306a36Sopenharmony_ci		.max	= (1 << 12) - 1,
64262306a36Sopenharmony_ci		.step	= 1,
64362306a36Sopenharmony_ci		.def	= 0x32,
64462306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
64562306a36Sopenharmony_ci	}, {
64662306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
64762306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_BOX_COLOR,
64862306a36Sopenharmony_ci		.name	= "Test Pattern: Box Color(RGB)",
64962306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
65062306a36Sopenharmony_ci		.min	= 0,
65162306a36Sopenharmony_ci		.max	= (1 << 24) - 1,
65262306a36Sopenharmony_ci		.step	= 1,
65362306a36Sopenharmony_ci		.def	= 0,
65462306a36Sopenharmony_ci	}, {
65562306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
65662306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_STUCK_PIXEL_THRESH,
65762306a36Sopenharmony_ci		.name	= "Test Pattern: Stuck Pixel threshold",
65862306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
65962306a36Sopenharmony_ci		.min	= 0,
66062306a36Sopenharmony_ci		.max	= (1 << 16) - 1,
66162306a36Sopenharmony_ci		.step	= 1,
66262306a36Sopenharmony_ci		.def	= 0,
66362306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
66462306a36Sopenharmony_ci	}, {
66562306a36Sopenharmony_ci		.ops	= &xtpg_ctrl_ops,
66662306a36Sopenharmony_ci		.id	= V4L2_CID_XILINX_TPG_NOISE_GAIN,
66762306a36Sopenharmony_ci		.name	= "Test Pattern: Noise Gain",
66862306a36Sopenharmony_ci		.type	= V4L2_CTRL_TYPE_INTEGER,
66962306a36Sopenharmony_ci		.min	= 0,
67062306a36Sopenharmony_ci		.max	= (1 << 8) - 1,
67162306a36Sopenharmony_ci		.step	= 1,
67262306a36Sopenharmony_ci		.def	= 0,
67362306a36Sopenharmony_ci		.flags	= V4L2_CTRL_FLAG_SLIDER,
67462306a36Sopenharmony_ci	},
67562306a36Sopenharmony_ci};
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
67862306a36Sopenharmony_ci * Media Operations
67962306a36Sopenharmony_ci */
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cistatic const struct media_entity_operations xtpg_media_ops = {
68262306a36Sopenharmony_ci	.link_validate = v4l2_subdev_link_validate,
68362306a36Sopenharmony_ci};
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
68662306a36Sopenharmony_ci * Power Management
68762306a36Sopenharmony_ci */
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_cistatic int __maybe_unused xtpg_pm_suspend(struct device *dev)
69062306a36Sopenharmony_ci{
69162306a36Sopenharmony_ci	struct xtpg_device *xtpg = dev_get_drvdata(dev);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	xvip_suspend(&xtpg->xvip);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	return 0;
69662306a36Sopenharmony_ci}
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_cistatic int __maybe_unused xtpg_pm_resume(struct device *dev)
69962306a36Sopenharmony_ci{
70062306a36Sopenharmony_ci	struct xtpg_device *xtpg = dev_get_drvdata(dev);
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	xvip_resume(&xtpg->xvip);
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	return 0;
70562306a36Sopenharmony_ci}
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
70862306a36Sopenharmony_ci * Platform Device Driver
70962306a36Sopenharmony_ci */
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_cistatic int xtpg_parse_of(struct xtpg_device *xtpg)
71262306a36Sopenharmony_ci{
71362306a36Sopenharmony_ci	struct device *dev = xtpg->xvip.dev;
71462306a36Sopenharmony_ci	struct device_node *node = xtpg->xvip.dev->of_node;
71562306a36Sopenharmony_ci	struct device_node *ports;
71662306a36Sopenharmony_ci	struct device_node *port;
71762306a36Sopenharmony_ci	unsigned int nports = 0;
71862306a36Sopenharmony_ci	bool has_endpoint = false;
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci	ports = of_get_child_by_name(node, "ports");
72162306a36Sopenharmony_ci	if (ports == NULL)
72262306a36Sopenharmony_ci		ports = node;
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci	for_each_child_of_node(ports, port) {
72562306a36Sopenharmony_ci		const struct xvip_video_format *format;
72662306a36Sopenharmony_ci		struct device_node *endpoint;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci		if (!of_node_name_eq(port, "port"))
72962306a36Sopenharmony_ci			continue;
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci		format = xvip_of_get_format(port);
73262306a36Sopenharmony_ci		if (IS_ERR(format)) {
73362306a36Sopenharmony_ci			dev_err(dev, "invalid format in DT");
73462306a36Sopenharmony_ci			of_node_put(port);
73562306a36Sopenharmony_ci			return PTR_ERR(format);
73662306a36Sopenharmony_ci		}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci		/* Get and check the format description */
73962306a36Sopenharmony_ci		if (!xtpg->vip_format) {
74062306a36Sopenharmony_ci			xtpg->vip_format = format;
74162306a36Sopenharmony_ci		} else if (xtpg->vip_format != format) {
74262306a36Sopenharmony_ci			dev_err(dev, "in/out format mismatch in DT");
74362306a36Sopenharmony_ci			of_node_put(port);
74462306a36Sopenharmony_ci			return -EINVAL;
74562306a36Sopenharmony_ci		}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci		if (nports == 0) {
74862306a36Sopenharmony_ci			endpoint = of_get_next_child(port, NULL);
74962306a36Sopenharmony_ci			if (endpoint)
75062306a36Sopenharmony_ci				has_endpoint = true;
75162306a36Sopenharmony_ci			of_node_put(endpoint);
75262306a36Sopenharmony_ci		}
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci		/* Count the number of ports. */
75562306a36Sopenharmony_ci		nports++;
75662306a36Sopenharmony_ci	}
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	if (nports != 1 && nports != 2) {
75962306a36Sopenharmony_ci		dev_err(dev, "invalid number of ports %u\n", nports);
76062306a36Sopenharmony_ci		return -EINVAL;
76162306a36Sopenharmony_ci	}
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	xtpg->npads = nports;
76462306a36Sopenharmony_ci	if (nports == 2 && has_endpoint)
76562306a36Sopenharmony_ci		xtpg->has_input = true;
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	return 0;
76862306a36Sopenharmony_ci}
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_cistatic int xtpg_probe(struct platform_device *pdev)
77162306a36Sopenharmony_ci{
77262306a36Sopenharmony_ci	struct v4l2_subdev *subdev;
77362306a36Sopenharmony_ci	struct xtpg_device *xtpg;
77462306a36Sopenharmony_ci	u32 i, bayer_phase;
77562306a36Sopenharmony_ci	int ret;
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	xtpg = devm_kzalloc(&pdev->dev, sizeof(*xtpg), GFP_KERNEL);
77862306a36Sopenharmony_ci	if (!xtpg)
77962306a36Sopenharmony_ci		return -ENOMEM;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	xtpg->xvip.dev = &pdev->dev;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	ret = xtpg_parse_of(xtpg);
78462306a36Sopenharmony_ci	if (ret < 0)
78562306a36Sopenharmony_ci		return ret;
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci	ret = xvip_init_resources(&xtpg->xvip);
78862306a36Sopenharmony_ci	if (ret < 0)
78962306a36Sopenharmony_ci		return ret;
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	xtpg->vtmux_gpio = devm_gpiod_get_optional(&pdev->dev, "timing",
79262306a36Sopenharmony_ci						   GPIOD_OUT_HIGH);
79362306a36Sopenharmony_ci	if (IS_ERR(xtpg->vtmux_gpio)) {
79462306a36Sopenharmony_ci		ret = PTR_ERR(xtpg->vtmux_gpio);
79562306a36Sopenharmony_ci		goto error_resource;
79662306a36Sopenharmony_ci	}
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	xtpg->vtc = xvtc_of_get(pdev->dev.of_node);
79962306a36Sopenharmony_ci	if (IS_ERR(xtpg->vtc)) {
80062306a36Sopenharmony_ci		ret = PTR_ERR(xtpg->vtc);
80162306a36Sopenharmony_ci		goto error_resource;
80262306a36Sopenharmony_ci	}
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	/* Reset and initialize the core */
80562306a36Sopenharmony_ci	xvip_reset(&xtpg->xvip);
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci	/* Initialize V4L2 subdevice and media entity. Pad numbers depend on the
80862306a36Sopenharmony_ci	 * number of pads.
80962306a36Sopenharmony_ci	 */
81062306a36Sopenharmony_ci	if (xtpg->npads == 2) {
81162306a36Sopenharmony_ci		xtpg->pads[0].flags = MEDIA_PAD_FL_SINK;
81262306a36Sopenharmony_ci		xtpg->pads[1].flags = MEDIA_PAD_FL_SOURCE;
81362306a36Sopenharmony_ci	} else {
81462306a36Sopenharmony_ci		xtpg->pads[0].flags = MEDIA_PAD_FL_SOURCE;
81562306a36Sopenharmony_ci	}
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci	/* Initialize the default format */
81862306a36Sopenharmony_ci	xtpg->default_format.code = xtpg->vip_format->code;
81962306a36Sopenharmony_ci	xtpg->default_format.field = V4L2_FIELD_NONE;
82062306a36Sopenharmony_ci	xtpg->default_format.colorspace = V4L2_COLORSPACE_SRGB;
82162306a36Sopenharmony_ci	xvip_get_frame_size(&xtpg->xvip, &xtpg->default_format);
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	bayer_phase = xtpg_get_bayer_phase(xtpg->vip_format->code);
82462306a36Sopenharmony_ci	if (bayer_phase != XTPG_BAYER_PHASE_OFF)
82562306a36Sopenharmony_ci		xtpg->bayer = true;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	xtpg->formats[0] = xtpg->default_format;
82862306a36Sopenharmony_ci	if (xtpg->npads == 2)
82962306a36Sopenharmony_ci		xtpg->formats[1] = xtpg->default_format;
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci	/* Initialize V4L2 subdevice and media entity */
83262306a36Sopenharmony_ci	subdev = &xtpg->xvip.subdev;
83362306a36Sopenharmony_ci	v4l2_subdev_init(subdev, &xtpg_ops);
83462306a36Sopenharmony_ci	subdev->dev = &pdev->dev;
83562306a36Sopenharmony_ci	subdev->internal_ops = &xtpg_internal_ops;
83662306a36Sopenharmony_ci	strscpy(subdev->name, dev_name(&pdev->dev), sizeof(subdev->name));
83762306a36Sopenharmony_ci	v4l2_set_subdevdata(subdev, xtpg);
83862306a36Sopenharmony_ci	subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
83962306a36Sopenharmony_ci	subdev->entity.ops = &xtpg_media_ops;
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci	ret = media_entity_pads_init(&subdev->entity, xtpg->npads, xtpg->pads);
84262306a36Sopenharmony_ci	if (ret < 0)
84362306a36Sopenharmony_ci		goto error;
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_ci	v4l2_ctrl_handler_init(&xtpg->ctrl_handler, 3 + ARRAY_SIZE(xtpg_ctrls));
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_ci	xtpg->vblank = v4l2_ctrl_new_std(&xtpg->ctrl_handler, &xtpg_ctrl_ops,
84862306a36Sopenharmony_ci					 V4L2_CID_VBLANK, XTPG_MIN_VBLANK,
84962306a36Sopenharmony_ci					 XTPG_MAX_VBLANK, 1, 100);
85062306a36Sopenharmony_ci	xtpg->hblank = v4l2_ctrl_new_std(&xtpg->ctrl_handler, &xtpg_ctrl_ops,
85162306a36Sopenharmony_ci					 V4L2_CID_HBLANK, XTPG_MIN_HBLANK,
85262306a36Sopenharmony_ci					 XTPG_MAX_HBLANK, 1, 100);
85362306a36Sopenharmony_ci	xtpg->pattern = v4l2_ctrl_new_std_menu_items(&xtpg->ctrl_handler,
85462306a36Sopenharmony_ci					&xtpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
85562306a36Sopenharmony_ci					ARRAY_SIZE(xtpg_pattern_strings) - 1,
85662306a36Sopenharmony_ci					1, 9, xtpg_pattern_strings);
85762306a36Sopenharmony_ci
85862306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(xtpg_ctrls); i++)
85962306a36Sopenharmony_ci		v4l2_ctrl_new_custom(&xtpg->ctrl_handler, &xtpg_ctrls[i], NULL);
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	if (xtpg->ctrl_handler.error) {
86262306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to add controls\n");
86362306a36Sopenharmony_ci		ret = xtpg->ctrl_handler.error;
86462306a36Sopenharmony_ci		goto error;
86562306a36Sopenharmony_ci	}
86662306a36Sopenharmony_ci	subdev->ctrl_handler = &xtpg->ctrl_handler;
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	xtpg_update_pattern_control(xtpg, true, true);
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci	ret = v4l2_ctrl_handler_setup(&xtpg->ctrl_handler);
87162306a36Sopenharmony_ci	if (ret < 0) {
87262306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to set controls\n");
87362306a36Sopenharmony_ci		goto error;
87462306a36Sopenharmony_ci	}
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_ci	platform_set_drvdata(pdev, xtpg);
87762306a36Sopenharmony_ci
87862306a36Sopenharmony_ci	xvip_print_version(&xtpg->xvip);
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci	ret = v4l2_async_register_subdev(subdev);
88162306a36Sopenharmony_ci	if (ret < 0) {
88262306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to register subdev\n");
88362306a36Sopenharmony_ci		goto error;
88462306a36Sopenharmony_ci	}
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci	return 0;
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_cierror:
88962306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&xtpg->ctrl_handler);
89062306a36Sopenharmony_ci	media_entity_cleanup(&subdev->entity);
89162306a36Sopenharmony_ci	xvtc_put(xtpg->vtc);
89262306a36Sopenharmony_cierror_resource:
89362306a36Sopenharmony_ci	xvip_cleanup_resources(&xtpg->xvip);
89462306a36Sopenharmony_ci	return ret;
89562306a36Sopenharmony_ci}
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_cistatic void xtpg_remove(struct platform_device *pdev)
89862306a36Sopenharmony_ci{
89962306a36Sopenharmony_ci	struct xtpg_device *xtpg = platform_get_drvdata(pdev);
90062306a36Sopenharmony_ci	struct v4l2_subdev *subdev = &xtpg->xvip.subdev;
90162306a36Sopenharmony_ci
90262306a36Sopenharmony_ci	v4l2_async_unregister_subdev(subdev);
90362306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&xtpg->ctrl_handler);
90462306a36Sopenharmony_ci	media_entity_cleanup(&subdev->entity);
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_ci	xvip_cleanup_resources(&xtpg->xvip);
90762306a36Sopenharmony_ci}
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(xtpg_pm_ops, xtpg_pm_suspend, xtpg_pm_resume);
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_cistatic const struct of_device_id xtpg_of_id_table[] = {
91262306a36Sopenharmony_ci	{ .compatible = "xlnx,v-tpg-5.0" },
91362306a36Sopenharmony_ci	{ }
91462306a36Sopenharmony_ci};
91562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, xtpg_of_id_table);
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_cistatic struct platform_driver xtpg_driver = {
91862306a36Sopenharmony_ci	.driver = {
91962306a36Sopenharmony_ci		.name		= "xilinx-tpg",
92062306a36Sopenharmony_ci		.pm		= &xtpg_pm_ops,
92162306a36Sopenharmony_ci		.of_match_table	= xtpg_of_id_table,
92262306a36Sopenharmony_ci	},
92362306a36Sopenharmony_ci	.probe			= xtpg_probe,
92462306a36Sopenharmony_ci	.remove_new		= xtpg_remove,
92562306a36Sopenharmony_ci};
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_cimodule_platform_driver(xtpg_driver);
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ciMODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
93062306a36Sopenharmony_ciMODULE_DESCRIPTION("Xilinx Test Pattern Generator Driver");
93162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
932