public inbox for linux-media@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v8 0/3] media: qcom: camss: Add camss TPG support for multiple targets
@ 2026-01-13  9:03 Wenmeng Liu
  2026-01-13  9:03 ` [PATCH v8 1/3] media: qcom: camss: Add common TPG support Wenmeng Liu
                   ` (2 more replies)
  0 siblings, 3 replies; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-13  9:03 UTC (permalink / raw)
  To: Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm, Wenmeng Liu

This series adds driver changes to bring up the TPG interfaces
in LeMans, Monaco, Hamoa.

We have tested this on LeMans EVK board and qcs8300-ride board and
Hamoa EVK board with 'Test Pattern Generator'.
Unlike CSID TPG, this TPG can be seen as a combination of CSIPHY
and sensor.

Tested with following commands:
- media-ctl --reset
- media-ctl -V '"msm_tpg0":0[fmt:SRGGB10/4608x2592 field:none]'
- media-ctl -V '"msm_csid0":0[fmt:SRGGB10/4608x2592 field:none]'
- media-ctl -V '"msm_vfe0_rdi0":0[fmt:SRGGB10/4608x2592 field:none]'
- media-ctl -l '"msm_tpg0":1->"msm_csid0":0[1]'
- media-ctl -l '"msm_csid0":1->"msm_vfe0_rdi0":0[1]'
- v4l2-ctl -d /dev/v4l-subdev1 -c test_pattern=9
- yavta -B capture-mplane -n 5 -f SRGGB10P -s 4608x2592 /dev/video2
  --capture=7

Changes in v8:
- Fix error bit operation. -- Bryan
- Add tpg link check for tpg enable/disable in csid node stream on.
- Link to v7: https://lore.kernel.org/r/20251226-camss_tpg-v7-0-ccb536734805@oss.qualcomm.com

Changes in V7:
- Add TPG support for Hamoa
- Add differentiation of register bitfields based on hardware version number.
- Fix the null pointer issue when TPG clock is 0.
- Correct the clock dependency for TPG.
- Link to V6: https://lore.kernel.org/all/20251114-camss_tpg-v6-0-38d3d9fbe339@oss.qualcomm.com/

Changes in V6:
- Addressed comments from Bryan and Konrad.
- Add exception handling for the streamon format.
- Link to V5: https://lore.kernel.org/all/20251017-camss_tpg-v5-0-cafe3ad42163@oss.qualcomm.com/

Changes in V5:
- Modify the commit message and change the chip names to LeMans and Monaco.
- Add the header file to resolve the compilation error.
- Remove the definition where tpg_num is 0.
- Link to v4: https://lore.kernel.org/all/20250925-camss_tpg-v4-0-d2eb099902c8@oss.qualcomm.com/

Changes in V4:
- Rebase changes
- Use GENMASK to define bit fields and avoid using tabs. Use FIELD_PREP and FIELD_GET uniformly to access bit fields.
- Link to V3: https://lore.kernel.org/all/20250822-camss_tpg-v3-0-c7833a5f10d0@quicinc.com/

Changes in V3:
- Change the payload mode string
- Change the method for setting the TPG clock rate
- Remove the TPG IRQ
- Format correction
- Remove unused variables
- Merge functions and eliminate redundancy
- Modify the register write method
- Change TPG matching method to use grp_id
- Encapsulate magic numbers as macros
- Link to V2: https://lore.kernel.org/all/20250717-lemans_tpg-v2-0-a2538659349c@quicinc.com/

Changes in V2:
- rebase tpg changes based on new versions of sa8775p and qcs8300 camss patches
- Link to V1: https://lore.kernel.org/all/20250211-sa8775p_tpg-v1-0-3f76c5f8431f@quicinc.com/

---
Wenmeng Liu (3):
      media: qcom: camss: Add common TPG support
      media: qcom: camss: Add link support for TPG
      media: qcom: camss: tpg: Add TPG support for multiple targets

 drivers/media/platform/qcom/camss/Makefile         |   2 +
 drivers/media/platform/qcom/camss/camss-csid-680.c |  14 +
 .../media/platform/qcom/camss/camss-csid-gen3.c    |  14 +
 drivers/media/platform/qcom/camss/camss-csid.c     |  45 +-
 drivers/media/platform/qcom/camss/camss-csid.h     |   1 +
 drivers/media/platform/qcom/camss/camss-csiphy.c   |   1 +
 drivers/media/platform/qcom/camss/camss-csiphy.h   |   2 +
 drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 257 ++++++++
 drivers/media/platform/qcom/camss/camss-tpg.c      | 710 +++++++++++++++++++++
 drivers/media/platform/qcom/camss/camss-tpg.h      | 127 ++++
 drivers/media/platform/qcom/camss/camss.c          | 183 ++++++
 drivers/media/platform/qcom/camss/camss.h          |   5 +
 12 files changed, 1347 insertions(+), 14 deletions(-)
---
base-commit: f417b7ffcbef7d76b0d8860518f50dae0e7e5eda
change-id: 20251226-camss_tpg-b23a398bb65a

Best regards,
-- 
Wenmeng <wenmeng.liu@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-13  9:03 [PATCH v8 0/3] media: qcom: camss: Add camss TPG support for multiple targets Wenmeng Liu
@ 2026-01-13  9:03 ` Wenmeng Liu
  2026-01-13 16:27   ` Vladimir Zapolskiy
  2026-01-13  9:03 ` [PATCH v8 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
  2026-01-13  9:03 ` [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets Wenmeng Liu
  2 siblings, 1 reply; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-13  9:03 UTC (permalink / raw)
  To: Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm, Wenmeng Liu

Introduce a new common Test Pattern Generator (TPG) implementation for
Qualcomm CAMSS. This module provides a generic interface for pattern
generation that can be reused by multiple platforms.

Unlike CSID-integrated TPG, this TPG acts as a standalone block
that emulates both CSIPHY and sensor behavior, enabling flexible test
patterns without external hardware.

Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
---
 drivers/media/platform/qcom/camss/Makefile    |   1 +
 drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++++++++++++++++++
 drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
 drivers/media/platform/qcom/camss/camss.h     |   5 +
 4 files changed, 843 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -27,5 +27,6 @@ qcom-camss-objs += \
 		camss-vfe.o \
 		camss-video.o \
 		camss-format.o \
+		camss-tpg.o \
 
 obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/media/platform/qcom/camss/camss-tpg.c
new file mode 100644
index 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-tpg.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *
+ * Qualcomm MSM Camera Subsystem - TPG Module
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <media/media-entity.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "camss-tpg.h"
+#include "camss.h"
+
+const char * const testgen_payload_modes[] = {
+	"Disabled",
+	"Incrementing",
+	"Alternating 0x55/0xAA",
+	"Reserved",
+	"Reserved",
+	"Pseudo-random Data",
+	"User Specified",
+	"Reserved",
+	"Reserved",
+	"Color bars",
+	"Reserved"
+};
+
+static const struct tpg_format_info formats_gen1[] = {
+	{
+		MEDIA_BUS_FMT_SBGGR8_1X8,
+		DATA_TYPE_RAW_8BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+		8,
+	},
+	{
+		MEDIA_BUS_FMT_SGBRG8_1X8,
+		DATA_TYPE_RAW_8BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+		8,
+	},
+	{
+		MEDIA_BUS_FMT_SGRBG8_1X8,
+		DATA_TYPE_RAW_8BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+		8,
+	},
+	{
+		MEDIA_BUS_FMT_SRGGB8_1X8,
+		DATA_TYPE_RAW_8BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+		8,
+	},
+	{
+		MEDIA_BUS_FMT_SBGGR10_1X10,
+		DATA_TYPE_RAW_10BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+		10,
+	},
+	{
+		MEDIA_BUS_FMT_SGBRG10_1X10,
+		DATA_TYPE_RAW_10BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+		10,
+	},
+	{
+		MEDIA_BUS_FMT_SGRBG10_1X10,
+		DATA_TYPE_RAW_10BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+		10,
+	},
+	{
+		MEDIA_BUS_FMT_SRGGB10_1X10,
+		DATA_TYPE_RAW_10BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+		10,
+	},
+	{
+		MEDIA_BUS_FMT_SBGGR12_1X12,
+		DATA_TYPE_RAW_12BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+		12,
+	},
+	{
+		MEDIA_BUS_FMT_SGBRG12_1X12,
+		DATA_TYPE_RAW_12BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+		12,
+	},
+	{
+		MEDIA_BUS_FMT_SGRBG12_1X12,
+		DATA_TYPE_RAW_12BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+		12,
+	},
+	{
+		MEDIA_BUS_FMT_SRGGB12_1X12,
+		DATA_TYPE_RAW_12BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+		12,
+	},
+	{
+		MEDIA_BUS_FMT_Y8_1X8,
+		DATA_TYPE_RAW_8BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+		8,
+	},
+	{
+		MEDIA_BUS_FMT_Y10_1X10,
+		DATA_TYPE_RAW_10BIT,
+		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+		10,
+	},
+};
+
+const struct tpg_formats tpg_formats_gen1 = {
+	.nformats = ARRAY_SIZE(formats_gen1),
+	.formats = formats_gen1
+};
+
+const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
+						const struct tpg_format_info *formats,
+						unsigned int nformats,
+						u32 code)
+{
+	struct device *dev = tpg->camss->dev;
+	size_t i;
+
+	for (i = 0; i < nformats; i++)
+		if (code == formats[i].code)
+			return &formats[i];
+
+	dev_warn(dev, "Unknown pixel format code=0x%08x\n", code);
+
+	return ERR_PTR(-EINVAL);
+}
+
+/*
+ * tpg_set_clock_rates - set clock rates on tpg module
+ * @tpg: tpg device
+ */
+static int tpg_set_clock_rates(struct tpg_device *tpg)
+{
+	struct device *dev = tpg->camss->dev;
+	int ret;
+	int i;
+
+	for (i = 0; i < tpg->nclocks; i++) {
+		struct camss_clock *clock = &tpg->clock[i];
+		long round_rate;
+
+		if (clock->freq) {
+			round_rate = clk_round_rate(clock->clk, clock->freq[0]);
+			if (round_rate < 0) {
+				dev_err(dev, "clk round rate failed: %ld\n",
+					round_rate);
+				return -EINVAL;
+			}
+
+			ret = clk_set_rate(clock->clk, round_rate);
+			if (ret < 0) {
+				dev_err(dev, "clk set rate failed: %d\n", ret);
+				return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * tpg_set_power - Power on/off tpg module
+ * @sd: tpg V4L2 subdevice
+ * @on: Requested power state
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int tpg_set_power(struct v4l2_subdev *sd, int on)
+{
+	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
+	struct device *dev = tpg->camss->dev;
+
+	if (on) {
+		int ret;
+
+		ret = pm_runtime_resume_and_get(dev);
+		if (ret < 0)
+			return ret;
+
+		ret = tpg_set_clock_rates(tpg);
+		if (ret < 0) {
+			pm_runtime_put_sync(dev);
+			return ret;
+		}
+
+		ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
+		if (ret < 0) {
+			pm_runtime_put_sync(dev);
+			return ret;
+		}
+
+		tpg->res->hw_ops->reset(tpg);
+
+		tpg->res->hw_ops->hw_version(tpg);
+	} else {
+		camss_disable_clocks(tpg->nclocks, tpg->clock);
+
+		pm_runtime_put_sync(dev);
+	}
+
+	return 0;
+}
+
+/*
+ * tpg_set_stream - Enable/disable streaming on tpg module
+ * @sd: tpg V4L2 subdevice
+ * @enable: Requested streaming state
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	if (enable) {
+		ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
+		if (ret < 0) {
+			dev_err(tpg->camss->dev,
+				"could not sync v4l2 controls: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = tpg->res->hw_ops->configure_stream(tpg, enable);
+
+	return ret;
+}
+
+/*
+ * __tpg_get_format - Get pointer to format structure
+ * @tpg: tpg device
+ * @cfg: V4L2 subdev pad configuration
+ * @pad: pad from which format is requested
+ * @which: TRY or ACTIVE format
+ *
+ * Return pointer to TRY or ACTIVE format structure
+ */
+static struct v4l2_mbus_framefmt *
+__tpg_get_format(struct tpg_device *tpg,
+		 struct v4l2_subdev_state *sd_state,
+		 unsigned int pad,
+		 enum v4l2_subdev_format_whence which)
+{
+	if (which == V4L2_SUBDEV_FORMAT_TRY)
+		return v4l2_subdev_state_get_format(sd_state,
+						    pad);
+
+	return &tpg->fmt[pad];
+}
+
+/*
+ * tpg_try_format - Handle try format by pad subdev method
+ * @tpg: tpg device
+ * @cfg: V4L2 subdev pad configuration
+ * @pad: pad on which format is requested
+ * @fmt: pointer to v4l2 format structure
+ * @which: wanted subdev format
+ */
+static void tpg_try_format(struct tpg_device *tpg,
+			   struct v4l2_subdev_state *sd_state,
+			   unsigned int pad,
+			   struct v4l2_mbus_framefmt *fmt,
+			   enum v4l2_subdev_format_whence which)
+{
+	unsigned int i;
+
+	switch (pad) {
+	case MSM_TPG_PAD_SINK:
+		for (i = 0; i < tpg->res->formats->nformats; i++)
+			if (tpg->res->formats->formats[i].code == fmt->code)
+				break;
+
+		/* If not found, use SBGGR8 as default */
+		if (i >= tpg->res->formats->nformats)
+			fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
+
+		fmt->width = clamp_t(u32, fmt->width, 1, 8191);
+		fmt->height = clamp_t(u32, fmt->height, 1, 8191);
+
+		fmt->field = V4L2_FIELD_NONE;
+		fmt->colorspace = V4L2_COLORSPACE_SRGB;
+
+		break;
+	case MSM_TPG_PAD_SRC:
+		*fmt = *__tpg_get_format(tpg, sd_state,
+					 MSM_TPG_PAD_SINK,
+					 which);
+
+		break;
+	}
+}
+
+/*
+ * tpg_enum_mbus_code - Handle format enumeration
+ * @sd: tpg V4L2 subdevice
+ * @cfg: V4L2 subdev pad configuration
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ * return -EINVAL or zero on success
+ */
+static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *sd_state,
+			      struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
+	struct v4l2_mbus_framefmt *format;
+
+	if (code->pad == MSM_TPG_PAD_SINK) {
+		if (code->index >= tpg->res->formats->nformats)
+			return -EINVAL;
+
+		code->code = tpg->res->formats->formats[code->index].code;
+	} else {
+		if (code->index > 0)
+			return -EINVAL;
+
+		format = __tpg_get_format(tpg, sd_state,
+					  MSM_TPG_PAD_SINK,
+					  code->which);
+
+		code->code = format->code;
+	}
+
+	return 0;
+}
+
+/*
+ * tpg_enum_frame_size - Handle frame size enumeration
+ * @sd: tpg V4L2 subdevice
+ * @cfg: V4L2 subdev pad configuration
+ * @fse: pointer to v4l2_subdev_frame_size_enum structure
+ * return -EINVAL or zero on success
+ */
+static int tpg_enum_frame_size(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *sd_state,
+			       struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
+	struct v4l2_mbus_framefmt format;
+
+	if (fse->index != 0)
+		return -EINVAL;
+
+	format.code = fse->code;
+	format.width = 1;
+	format.height = 1;
+	tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
+	fse->min_width = format.width;
+	fse->min_height = format.height;
+
+	if (format.code != fse->code)
+		return -EINVAL;
+
+	format.code = fse->code;
+	format.width = -1;
+	format.height = -1;
+	tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
+	fse->max_width = format.width;
+	fse->max_height = format.height;
+
+	return 0;
+}
+
+/*
+ * tpg_get_format - Handle get format by pads subdev method
+ * @sd: tpg V4L2 subdevice
+ * @cfg: V4L2 subdev pad configuration
+ * @fmt: pointer to v4l2 subdev format structure
+ *
+ * Return -EINVAL or zero on success
+ */
+static int tpg_get_format(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *sd_state,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
+	struct v4l2_mbus_framefmt *format;
+
+	format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
+	if (!format)
+		return -EINVAL;
+
+	fmt->format = *format;
+
+	return 0;
+}
+
+/*
+ * tpg_set_format - Handle set format by pads subdev method
+ * @sd: tpg V4L2 subdevice
+ * @cfg: V4L2 subdev pad configuration
+ * @fmt: pointer to v4l2 subdev format structure
+ *
+ * Return -EINVAL or zero on success
+ */
+static int tpg_set_format(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *sd_state,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
+	struct v4l2_mbus_framefmt *format;
+
+	format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
+	if (!format)
+		return -EINVAL;
+
+	tpg_try_format(tpg, sd_state, fmt->pad, &fmt->format,
+		       fmt->which);
+	*format = fmt->format;
+
+	if (fmt->pad == MSM_TPG_PAD_SINK) {
+		format = __tpg_get_format(tpg, sd_state,
+					  MSM_TPG_PAD_SRC,
+					  fmt->which);
+
+		*format = fmt->format;
+		tpg_try_format(tpg, sd_state, MSM_TPG_PAD_SRC,
+			       format,
+			       fmt->which);
+	}
+	return 0;
+}
+
+/*
+ * tpg_init_formats - Initialize formats on all pads
+ * @sd: tpg V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values.
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int tpg_init_formats(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_fh *fh)
+{
+	struct v4l2_subdev_format format = {
+		.pad = MSM_TPG_PAD_SINK,
+		.which = fh ? V4L2_SUBDEV_FORMAT_TRY :
+			      V4L2_SUBDEV_FORMAT_ACTIVE,
+		.format = {
+			.code = MEDIA_BUS_FMT_SBGGR8_1X8,
+			.width = 1920,
+			.height = 1080
+		}
+	};
+
+	return tpg_set_format(sd, fh ? fh->state : NULL, &format);
+}
+
+/*
+ * tpg_set_test_pattern - Set test generator's pattern mode
+ * @tpg: TPG device
+ * @value: desired test pattern mode
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int tpg_set_test_pattern(struct tpg_device *tpg, s32 value)
+{
+	return tpg->res->hw_ops->configure_testgen_pattern(tpg, value);
+}
+
+/*
+ * tpg_s_ctrl - Handle set control subdev method
+ * @ctrl: pointer to v4l2 control structure
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct tpg_device *tpg = container_of(ctrl->handler,
+					      struct tpg_device, ctrls);
+	int ret = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		ret = tpg_set_test_pattern(tpg, ctrl->val);
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
+	.s_ctrl = tpg_s_ctrl,
+};
+
+/*
+ * msm_tpg_subdev_init - Initialize tpg device structure and resources
+ * @tpg: tpg device
+ * @res: tpg module resources table
+ * @id: tpg module id
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int msm_tpg_subdev_init(struct camss *camss,
+			struct tpg_device *tpg,
+			const struct camss_subdev_resources *res, u8 id)
+{
+	struct platform_device *pdev;
+	struct device *dev;
+	int i, j;
+
+	dev  = camss->dev;
+	pdev = to_platform_device(dev);
+
+	tpg->camss = camss;
+	tpg->id = id;
+	tpg->res = &res->tpg;
+	tpg->res->hw_ops->subdev_init(tpg);
+
+	tpg->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]);
+	if (IS_ERR(tpg->base))
+		return PTR_ERR(tpg->base);
+
+	tpg->nclocks = 0;
+	while (res->clock[tpg->nclocks])
+		tpg->nclocks++;
+
+	if (tpg->nclocks) {
+		tpg->clock = devm_kcalloc(dev,
+					  tpg->nclocks, sizeof(*tpg->clock),
+					  GFP_KERNEL);
+		if (!tpg->clock)
+			return -ENOMEM;
+
+		for (i = 0; i < tpg->nclocks; i++) {
+			struct camss_clock *clock = &tpg->clock[i];
+
+			clock->clk = devm_clk_get(dev, res->clock[i]);
+			if (IS_ERR(clock->clk))
+				return PTR_ERR(clock->clk);
+
+			clock->name = res->clock[i];
+
+			clock->nfreqs = 0;
+			while (res->clock_rate[i][clock->nfreqs])
+				clock->nfreqs++;
+
+			if (!clock->nfreqs) {
+				clock->freq = NULL;
+				continue;
+			}
+
+			clock->freq = devm_kcalloc(dev,
+						   clock->nfreqs,
+						   sizeof(*clock->freq),
+						   GFP_KERNEL);
+			if (!clock->freq)
+				return -ENOMEM;
+
+			for (j = 0; j < clock->nfreqs; j++)
+				clock->freq[j] = res->clock_rate[i][j];
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * tpg_link_setup - Setup tpg connections
+ * @entity: Pointer to media entity structure
+ * @local: Pointer to local pad
+ * @remote: Pointer to remote pad
+ * @flags: Link flags
+ *
+ * Return 0 on success
+ */
+static int tpg_link_setup(struct media_entity *entity,
+			  const struct media_pad *local,
+			  const struct media_pad *remote, u32 flags)
+{
+	if (flags & MEDIA_LNK_FL_ENABLED)
+		if (media_pad_remote_pad_first(local))
+			return -EBUSY;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops tpg_core_ops = {
+	.s_power = tpg_set_power,
+};
+
+static const struct v4l2_subdev_video_ops tpg_video_ops = {
+	.s_stream = tpg_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
+	.enum_mbus_code = tpg_enum_mbus_code,
+	.enum_frame_size = tpg_enum_frame_size,
+	.get_fmt = tpg_get_format,
+	.set_fmt = tpg_set_format,
+};
+
+static const struct v4l2_subdev_ops tpg_v4l2_ops = {
+	.core = &tpg_core_ops,
+	.video = &tpg_video_ops,
+	.pad = &tpg_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
+	.open = tpg_init_formats,
+};
+
+static const struct media_entity_operations tpg_media_ops = {
+	.link_setup = tpg_link_setup,
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * msm_tpg_register_entity - Register subdev node for tpg module
+ * @tpg: tpg device
+ * @v4l2_dev: V4L2 device
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int msm_tpg_register_entity(struct tpg_device *tpg,
+			    struct v4l2_device *v4l2_dev)
+{
+	struct v4l2_subdev *sd = &tpg->subdev;
+	struct media_pad *pads = tpg->pads;
+	struct device *dev = tpg->camss->dev;
+	int ret;
+
+	v4l2_subdev_init(sd, &tpg_v4l2_ops);
+	sd->internal_ops = &tpg_v4l2_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+		     V4L2_SUBDEV_FL_HAS_EVENTS;
+	snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
+		 MSM_TPG_NAME, tpg->id);
+	sd->grp_id = TPG_GUP_ID;
+	v4l2_set_subdevdata(sd, tpg);
+
+	ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
+	if (ret < 0) {
+		dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
+		return ret;
+	}
+
+	tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
+							 &tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
+							 tpg->testgen.nmodes, 0, 0,
+							 tpg->testgen.modes);
+
+	if (tpg->ctrls.error) {
+		dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
+		ret = tpg->ctrls.error;
+		goto free_ctrl;
+	}
+
+	tpg->subdev.ctrl_handler = &tpg->ctrls;
+
+	ret = tpg_init_formats(sd, NULL);
+	if (ret < 0) {
+		dev_err(dev, "Failed to init format: %d\n", ret);
+		goto free_ctrl;
+	}
+
+	pads[MSM_TPG_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	pads[MSM_TPG_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+
+	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+	sd->entity.ops = &tpg_media_ops;
+	ret = media_entity_pads_init(&sd->entity, MSM_TPG_PADS_NUM, pads);
+	if (ret < 0) {
+		dev_err(dev, "Failed to init media entity: %d\n", ret);
+		goto free_ctrl;
+	}
+
+	ret = v4l2_device_register_subdev(v4l2_dev, sd);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register subdev: %d\n", ret);
+		media_entity_cleanup(&sd->entity);
+		goto free_ctrl;
+	}
+
+	return 0;
+
+free_ctrl:
+	v4l2_ctrl_handler_free(&tpg->ctrls);
+
+	return ret;
+}
+
+/*
+ * msm_tpg_unregister_entity - Unregister tpg module subdev node
+ * @tpg: tpg device
+ */
+void msm_tpg_unregister_entity(struct tpg_device *tpg)
+{
+	v4l2_device_unregister_subdev(&tpg->subdev);
+	media_entity_cleanup(&tpg->subdev.entity);
+	v4l2_ctrl_handler_free(&tpg->ctrls);
+}
diff --git a/drivers/media/platform/qcom/camss/camss-tpg.h b/drivers/media/platform/qcom/camss/camss-tpg.h
new file mode 100644
index 0000000000000000000000000000000000000000..1a16addac19418f2f11d0b8abb1c865c99888bde
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-tpg.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * camss-tpg.h
+ *
+ * Qualcomm MSM Camera Subsystem - TPG Module
+ *
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef QC_MSM_CAMSS_TPG_H
+#define QC_MSM_CAMSS_TPG_H
+
+#include <linux/clk.h>
+#include <media/media-entity.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-subdev.h>
+
+#define MSM_TPG_PAD_SINK 0
+#define MSM_TPG_PAD_SRC 1
+#define MSM_TPG_PADS_NUM 2
+
+#define DATA_TYPE_RAW_8BIT		0x2a
+#define DATA_TYPE_RAW_10BIT		0x2b
+#define DATA_TYPE_RAW_12BIT		0x2c
+
+#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT	0x1
+#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT	0x2
+#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT	0x3
+#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT	0x4
+#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT	0x5
+#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT	0x6
+#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT	0x7
+
+#define TPG_GUP_ID 0
+#define MSM_TPG_NAME "msm_tpg"
+
+enum tpg_testgen_mode {
+	TPG_PAYLOAD_MODE_DISABLED = 0,
+	TPG_PAYLOAD_MODE_INCREMENTING = 1,
+	TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
+	TPG_PAYLOAD_MODE_RANDOM = 5,
+	TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
+	TPG_PAYLOAD_MODE_COLOR_BARS = 9,
+	TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
+};
+
+struct tpg_testgen_config {
+	enum tpg_testgen_mode mode;
+	const char * const*modes;
+	u8 nmodes;
+};
+
+struct tpg_format_info {
+	u32 code;
+	u8 data_type;
+	u8 encode_format;
+	u8 bpp;
+};
+
+struct tpg_formats {
+	unsigned int nformats;
+	const struct tpg_format_info *formats;
+};
+
+struct tpg_device;
+
+struct tpg_hw_ops {
+	int (*configure_stream)(struct tpg_device *tpg, u8 enable);
+
+	int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
+
+	u32 (*hw_version)(struct tpg_device *tpg);
+
+	int (*reset)(struct tpg_device *tpg);
+
+	void (*subdev_init)(struct tpg_device *tpg);
+};
+
+struct tpg_subdev_resources {
+	u8 lane_cnt;
+	u8 vc_cnt;
+	const struct tpg_formats *formats;
+	const struct tpg_hw_ops *hw_ops;
+};
+
+struct tpg_device {
+	struct camss *camss;
+	u8 id;
+	struct v4l2_subdev subdev;
+	struct media_pad pads[MSM_TPG_PADS_NUM];
+	void __iomem *base;
+	struct camss_clock *clock;
+	int nclocks;
+	struct tpg_testgen_config testgen;
+	struct v4l2_mbus_framefmt fmt[MSM_TPG_PADS_NUM];
+	struct v4l2_ctrl_handler ctrls;
+	struct v4l2_ctrl *testgen_mode;
+	const struct tpg_subdev_resources *res;
+	const struct tpg_format *formats;
+	unsigned int nformats;
+	u32 hw_version;
+};
+
+struct camss_subdev_resources;
+
+const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
+						const struct tpg_format_info *formats,
+						unsigned int nformats,
+						u32 code);
+
+int msm_tpg_subdev_init(struct camss *camss,
+			struct tpg_device *tpg,
+			const struct camss_subdev_resources *res, u8 id);
+
+int msm_tpg_register_entity(struct tpg_device *tpg,
+			    struct v4l2_device *v4l2_dev);
+
+void msm_tpg_unregister_entity(struct tpg_device *tpg);
+
+extern const char * const testgen_payload_modes[];
+
+extern const struct tpg_formats tpg_formats_gen1;
+
+extern const struct tpg_hw_ops tpg_ops_gen1;
+
+#endif /* QC_MSM_CAMSS_TPG_H */
diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h
index 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..a892a87bed8bde8919200d6eac2b7a5338763c0e 100644
--- a/drivers/media/platform/qcom/camss/camss.h
+++ b/drivers/media/platform/qcom/camss/camss.h
@@ -21,6 +21,7 @@
 #include "camss-csid.h"
 #include "camss-csiphy.h"
 #include "camss-ispif.h"
+#include "camss-tpg.h"
 #include "camss-vfe.h"
 #include "camss-format.h"
 
@@ -52,6 +53,7 @@ struct camss_subdev_resources {
 	char *interrupt[CAMSS_RES_MAX];
 	union {
 		struct csiphy_subdev_resources csiphy;
+		struct tpg_subdev_resources tpg;
 		struct csid_subdev_resources csid;
 		struct vfe_subdev_resources vfe;
 	};
@@ -104,6 +106,7 @@ struct camss_resources {
 	enum camss_version version;
 	const char *pd_name;
 	const struct camss_subdev_resources *csiphy_res;
+	const struct camss_subdev_resources *tpg_res;
 	const struct camss_subdev_resources *csid_res;
 	const struct camss_subdev_resources *ispif_res;
 	const struct camss_subdev_resources *vfe_res;
@@ -111,6 +114,7 @@ struct camss_resources {
 	const struct resources_icc *icc_res;
 	const unsigned int icc_path_num;
 	const unsigned int csiphy_num;
+	const unsigned int tpg_num;
 	const unsigned int csid_num;
 	const unsigned int vfe_num;
 };
@@ -121,6 +125,7 @@ struct camss {
 	struct media_device media_dev;
 	struct device *dev;
 	struct csiphy_device *csiphy;
+	struct tpg_device *tpg;
 	struct csid_device *csid;
 	struct ispif_device *ispif;
 	struct vfe_device *vfe;

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v8 2/3] media: qcom: camss: Add link support for TPG
  2026-01-13  9:03 [PATCH v8 0/3] media: qcom: camss: Add camss TPG support for multiple targets Wenmeng Liu
  2026-01-13  9:03 ` [PATCH v8 1/3] media: qcom: camss: Add common TPG support Wenmeng Liu
@ 2026-01-13  9:03 ` Wenmeng Liu
  2026-01-13  9:03 ` [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets Wenmeng Liu
  2 siblings, 0 replies; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-13  9:03 UTC (permalink / raw)
  To: Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm, Wenmeng Liu

TPG is connected to the csid as an entity, the link
needs to be adapted.

Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
---
 drivers/media/platform/qcom/camss/camss-csid.c   | 45 +++++++++++++------
 drivers/media/platform/qcom/camss/camss-csid.h   |  1 +
 drivers/media/platform/qcom/camss/camss-csiphy.c |  1 +
 drivers/media/platform/qcom/camss/camss-csiphy.h |  2 +
 drivers/media/platform/qcom/camss/camss.c        | 55 ++++++++++++++++++++++++
 5 files changed, 90 insertions(+), 14 deletions(-)

diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c
index 5284b5857368c37c202cd89dad6ae8042b637537..42fb299a655354face660308c3d535b09caa8ed2 100644
--- a/drivers/media/platform/qcom/camss/camss-csid.c
+++ b/drivers/media/platform/qcom/camss/camss-csid.c
@@ -35,6 +35,8 @@
 #define		HW_VERSION_REVISION	16
 #define		HW_VERSION_GENERATION	28
 
+#define LANE_CFG_BITWIDTH 4
+
 #define MSM_CSID_NAME "msm_csid"
 
 const char * const csid_testgen_modes[] = {
@@ -1227,18 +1229,22 @@ void msm_csid_get_csid_id(struct media_entity *entity, u8 *id)
 }
 
 /*
- * csid_get_lane_assign - Calculate CSI2 lane assign configuration parameter
- * @lane_cfg - CSI2 lane configuration
+ * csid_get_lane_assign - Calculate lane assign by csiphy/tpg lane num
+ * @num: lane num
+ * @pos_array: Array of lane positions
  *
  * Return lane assign
  */
-static u32 csid_get_lane_assign(struct csiphy_lanes_cfg *lane_cfg)
+static u32 csid_get_lane_assign(struct csiphy_lanes_cfg *lane_cfg, int num_lanes)
 {
 	u32 lane_assign = 0;
+	int pos;
 	int i;
 
-	for (i = 0; i < lane_cfg->num_data; i++)
-		lane_assign |= lane_cfg->data[i].pos << (i * 4);
+	for (i = 0; i < num_lanes; i++) {
+		pos = lane_cfg ? lane_cfg->data[i].pos : i;
+		lane_assign |= pos << (i * LANE_CFG_BITWIDTH);
+	}
 
 	return lane_assign;
 }
@@ -1263,6 +1269,7 @@ static int csid_link_setup(struct media_entity *entity,
 	if ((local->flags & MEDIA_PAD_FL_SINK) &&
 	    (flags & MEDIA_LNK_FL_ENABLED)) {
 		struct v4l2_subdev *sd;
+		struct tpg_device *tpg;
 		struct csid_device *csid;
 		struct csiphy_device *csiphy;
 		struct csiphy_lanes_cfg *lane_cfg;
@@ -1277,18 +1284,28 @@ static int csid_link_setup(struct media_entity *entity,
 			return -EBUSY;
 
 		sd = media_entity_to_v4l2_subdev(remote->entity);
-		csiphy = v4l2_get_subdevdata(sd);
+		if (sd->grp_id == TPG_GUP_ID) {
+			tpg = v4l2_get_subdevdata(sd);
 
-		/* If a sensor is not linked to CSIPHY */
-		/* do no allow a link from CSIPHY to CSID */
-		if (!csiphy->cfg.csi2)
-			return -EPERM;
+			csid->phy.lane_cnt = tpg->res->lane_cnt;
+			csid->phy.csiphy_id = tpg->id;
+			csid->phy.lane_assign = csid_get_lane_assign(NULL, csid->phy.lane_cnt);
+			csid->tpg_linked = true;
+		} else {
+			csiphy = v4l2_get_subdevdata(sd);
 
-		csid->phy.csiphy_id = csiphy->id;
+			/* If a sensor is not linked to CSIPHY */
+			/* do no allow a link from CSIPHY to CSID */
+			if (!csiphy->cfg.csi2)
+				return -EPERM;
 
-		lane_cfg = &csiphy->cfg.csi2->lane_cfg;
-		csid->phy.lane_cnt = lane_cfg->num_data;
-		csid->phy.lane_assign = csid_get_lane_assign(lane_cfg);
+			csid->phy.csiphy_id = csiphy->id;
+
+			lane_cfg = &csiphy->cfg.csi2->lane_cfg;
+			csid->phy.lane_cnt = lane_cfg->num_data;
+			csid->phy.lane_assign = csid_get_lane_assign(lane_cfg, lane_cfg->num_data);
+			csid->tpg_linked = false;
+		}
 	}
 	/* Decide which virtual channels to enable based on which source pads are enabled */
 	if (local->flags & MEDIA_PAD_FL_SOURCE) {
diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h
index aedc96ed84b2fcc3f352160dcfd31554a671d0fc..5296b10f6bac839a3faa1039bdbf0fbbbe9456ac 100644
--- a/drivers/media/platform/qcom/camss/camss-csid.h
+++ b/drivers/media/platform/qcom/camss/camss-csid.h
@@ -161,6 +161,7 @@ struct csid_device {
 	int num_supplies;
 	struct completion reset_complete;
 	struct csid_testgen_config testgen;
+	bool tpg_linked;
 	struct csid_phy_config phy;
 	struct v4l2_mbus_framefmt fmt[MSM_CSID_PADS_NUM];
 	struct v4l2_ctrl_handler ctrls;
diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c
index a734fb7dde0a492cf6e33f53e379557665d54f64..c15990d9d09cc8f9960729bdc112d81751b4938c 100644
--- a/drivers/media/platform/qcom/camss/camss-csiphy.c
+++ b/drivers/media/platform/qcom/camss/camss-csiphy.c
@@ -800,6 +800,7 @@ int msm_csiphy_register_entity(struct csiphy_device *csiphy,
 	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
 	snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
 		 MSM_CSIPHY_NAME, csiphy->id);
+	sd->grp_id = CSIPHY_GUP_ID;
 	v4l2_set_subdevdata(sd, csiphy);
 
 	ret = csiphy_init_formats(sd, NULL);
diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h
index 895f80003c441dcacf98435f91567f90afa29279..b7bcf2bdd2124f77b5354b15b33aa1e0983143e8 100644
--- a/drivers/media/platform/qcom/camss/camss-csiphy.h
+++ b/drivers/media/platform/qcom/camss/camss-csiphy.h
@@ -21,6 +21,8 @@
 #define MSM_CSIPHY_PAD_SRC 1
 #define MSM_CSIPHY_PADS_NUM 2
 
+#define CSIPHY_GUP_ID 1
+
 struct csiphy_lane {
 	u8 pos;
 	u8 pol;
diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c
index fcc2b2c3cba076e598bc8aacd34efce5d71713ca..43fdcb9af101ef34b118035ca9c68757b66118df 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -4145,6 +4145,19 @@ static int camss_init_subdevices(struct camss *camss)
 		}
 	}
 
+	if (camss->tpg) {
+		for (i = 0; i < camss->res->tpg_num; i++) {
+			ret = msm_tpg_subdev_init(camss, &camss->tpg[i],
+						  &res->tpg_res[i], i);
+			if (ret < 0) {
+				dev_err(camss->dev,
+					"Failed to init tpg%d sub-device: %d\n",
+					i, ret);
+				return ret;
+			}
+		}
+	}
+
 	/* note: SM8250 requires VFE to be initialized before CSID */
 	for (i = 0; i < camss->res->vfe_num; i++) {
 		ret = msm_vfe_subdev_init(camss, &camss->vfe[i],
@@ -4233,6 +4246,23 @@ static int camss_link_entities(struct camss *camss)
 		}
 	}
 
+	for (i = 0; i < camss->res->tpg_num; i++) {
+		for (j = 0; j < camss->res->csid_num; j++) {
+			ret = media_create_pad_link(&camss->tpg[i].subdev.entity,
+						    MSM_TPG_PAD_SRC,
+						    &camss->csid[j].subdev.entity,
+						    MSM_CSID_PAD_SINK,
+						    0);
+			if (ret < 0) {
+				camss_link_err(camss,
+					       camss->tpg[i].subdev.entity.name,
+					       camss->csid[j].subdev.entity.name,
+					       ret);
+				return ret;
+			}
+		}
+	}
+
 	if (camss->ispif) {
 		for (i = 0; i < camss->res->csid_num; i++) {
 			for (j = 0; j < camss->ispif->line_num; j++) {
@@ -4337,6 +4367,19 @@ static int camss_register_entities(struct camss *camss)
 		}
 	}
 
+	if (camss->tpg) {
+		for (i = 0; i < camss->res->tpg_num; i++) {
+			ret = msm_tpg_register_entity(&camss->tpg[i],
+						      &camss->v4l2_dev);
+			if (ret < 0) {
+				dev_err(camss->dev,
+					"Failed to register tpg%d entity: %d\n",
+					i, ret);
+				goto err_reg_tpg;
+			}
+		}
+	}
+
 	for (i = 0; i < camss->res->csid_num; i++) {
 		ret = msm_csid_register_entity(&camss->csid[i],
 					       &camss->v4l2_dev);
@@ -4380,6 +4423,13 @@ static int camss_register_entities(struct camss *camss)
 	for (i--; i >= 0; i--)
 		msm_csid_unregister_entity(&camss->csid[i]);
 
+	i = camss->res->tpg_num;
+err_reg_tpg:
+	if (camss->tpg) {
+		for (i--; i >= 0; i--)
+			msm_tpg_unregister_entity(&camss->tpg[i]);
+	}
+
 	i = camss->res->csiphy_num;
 err_reg_csiphy:
 	for (i--; i >= 0; i--)
@@ -4401,6 +4451,11 @@ static void camss_unregister_entities(struct camss *camss)
 	for (i = 0; i < camss->res->csiphy_num; i++)
 		msm_csiphy_unregister_entity(&camss->csiphy[i]);
 
+	if (camss->tpg) {
+		for (i = 0; i < camss->res->tpg_num; i++)
+			msm_tpg_unregister_entity(&camss->tpg[i]);
+	}
+
 	for (i = 0; i < camss->res->csid_num; i++)
 		msm_csid_unregister_entity(&camss->csid[i]);
 

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets
  2026-01-13  9:03 [PATCH v8 0/3] media: qcom: camss: Add camss TPG support for multiple targets Wenmeng Liu
  2026-01-13  9:03 ` [PATCH v8 1/3] media: qcom: camss: Add common TPG support Wenmeng Liu
  2026-01-13  9:03 ` [PATCH v8 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
@ 2026-01-13  9:03 ` Wenmeng Liu
  2026-01-15 15:48   ` kernel test robot
  2026-01-16 19:32   ` Vijay Kumar Tumati
  2 siblings, 2 replies; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-13  9:03 UTC (permalink / raw)
  To: Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm, Wenmeng Liu

Add support for TPG found on LeMans, Monaco, Hamoa.

Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
---
 drivers/media/platform/qcom/camss/Makefile         |   1 +
 drivers/media/platform/qcom/camss/camss-csid-680.c |  14 ++
 .../media/platform/qcom/camss/camss-csid-gen3.c    |  14 ++
 drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 257 +++++++++++++++++++++
 drivers/media/platform/qcom/camss/camss.c          | 128 ++++++++++
 5 files changed, 414 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index d355e67c25700ac061b878543c32ed8defc03ad0..e8996dacf1771d13ec1936c9bebc0e71566898ef 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -28,5 +28,6 @@ qcom-camss-objs += \
 		camss-video.o \
 		camss-format.o \
 		camss-tpg.o \
+		camss-tpg-gen1.o \
 
 obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
diff --git a/drivers/media/platform/qcom/camss/camss-csid-680.c b/drivers/media/platform/qcom/camss/camss-csid-680.c
index 3ad3a174bcfb8c0d319930d0010df92308cb5ae4..a5da35cae2eb9acf642795c0a91db58d845f211c 100644
--- a/drivers/media/platform/qcom/camss/camss-csid-680.c
+++ b/drivers/media/platform/qcom/camss/camss-csid-680.c
@@ -103,6 +103,8 @@
 #define		CSI2_RX_CFG0_PHY_NUM_SEL			20
 #define		CSI2_RX_CFG0_PHY_SEL_BASE_IDX			1
 #define		CSI2_RX_CFG0_PHY_TYPE_SEL			24
+#define		CSI2_RX_CFG0_TPG_NUM_EN				BIT(27)
+#define		CSI2_RX_CFG0_TPG_NUM_SEL			GENMASK(29, 28)
 
 #define CSID_CSI2_RX_CFG1					0x204
 #define		CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN		BIT(0)
@@ -185,11 +187,23 @@ static void __csid_configure_rx(struct csid_device *csid,
 				struct csid_phy_config *phy, int vc)
 {
 	u32 val;
+	struct camss *camss;
+	struct tpg_device *tpg;
 
+	camss = csid->camss;
 	val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
 	val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
 	val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << CSI2_RX_CFG0_PHY_NUM_SEL;
 
+	if (camss->tpg) {
+		tpg = &camss->tpg[phy->csiphy_id];
+
+		if (csid->tpg_linked && tpg->testgen.mode > 0) {
+			val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
+			val |= CSI2_RX_CFG0_TPG_NUM_EN;
+		}
+	}
+
 	writel(val, csid->base + CSID_CSI2_RX_CFG0);
 
 	val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen3.c b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
index 664245cf6eb0cac662b02f8b920cd1c72db0aeb2..5f9eb533723f2864df64fd6c63e2682fed4a12ae 100644
--- a/drivers/media/platform/qcom/camss/camss-csid-gen3.c
+++ b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
@@ -66,6 +66,8 @@
 #define		CSI2_RX_CFG0_VC_MODE		3
 #define		CSI2_RX_CFG0_DL0_INPUT_SEL	4
 #define		CSI2_RX_CFG0_PHY_NUM_SEL	20
+#define		CSI2_RX_CFG0_TPG_NUM_EN		BIT(27)
+#define		CSI2_RX_CFG0_TPG_NUM_SEL	GENMASK(29, 28)
 
 #define CSID_CSI2_RX_CFG1		0x204
 #define		CSI2_RX_CFG1_ECC_CORRECTION_EN	BIT(0)
@@ -109,11 +111,23 @@ static void __csid_configure_rx(struct csid_device *csid,
 				struct csid_phy_config *phy, int vc)
 {
 	int val;
+	struct camss *camss;
+	struct tpg_device *tpg;
 
+	camss = csid->camss;
 	val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
 	val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
 	val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << CSI2_RX_CFG0_PHY_NUM_SEL;
 
+	if (camss->tpg) {
+		tpg = &camss->tpg[phy->csiphy_id];
+
+		if (csid->tpg_linked && tpg->testgen.mode > 0) {
+			val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
+			val |= CSI2_RX_CFG0_TPG_NUM_EN;
+		}
+	}
+
 	writel(val, csid->base + CSID_CSI2_RX_CFG0);
 
 	val = CSI2_RX_CFG1_ECC_CORRECTION_EN;
diff --git a/drivers/media/platform/qcom/camss/camss-tpg-gen1.c b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
new file mode 100644
index 0000000000000000000000000000000000000000..d7ef7a1709648406dc59c210d355851397980769
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *
+ * Qualcomm MSM Camera Subsystem - TPG (Test Patter Generator) Module
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+
+#include "camss-tpg.h"
+#include "camss.h"
+
+#define TPG_HW_VERSION		0x0
+# define HW_VERSION_STEPPING		GENMASK(15, 0)
+# define HW_VERSION_REVISION		GENMASK(27, 16)
+# define HW_VERSION_GENERATION		GENMASK(31, 28)
+
+#define TPG_HW_VER(gen, rev, step) \
+	(((u32)(gen) << 28) | ((u32)(rev) << 16) | (u32)(step))
+
+#define TPG_HW_VER_2_0_0                TPG_HW_VER(2, 0, 0)
+#define TPG_HW_VER_2_1_0                TPG_HW_VER(2, 1, 0)
+
+#define TPG_HW_STATUS		0x4
+
+#define TPG_VC_n_GAIN_CFG(n)		(0x60 + (n) * 0x60)
+
+#define TPG_CTRL		0x64
+# define TPG_CTRL_TEST_EN		BIT(0)
+# define TPG_CTRL_PHY_SEL		BIT(3)
+# define TPG_CTRL_NUM_ACTIVE_LANES	GENMASK(5, 4)
+# define TPG_CTRL_VC_DT_PATTERN_ID	GENMASK(8, 6)
+# define TPG_CTRL_OVERLAP_SHDR_EN	BIT(10)
+# define TPG_CTRL_NUM_ACTIVE_VC		GENMASK(31, 30)
+#  define NUM_ACTIVE_VC_0_ENABLED		0
+#  define NUM_ACTIVE_VC_0_1_ENABLED		1
+#  define NUM_ACTIVE_VC_0_1_2_ENABLED		2
+#  define NUM_ACTIVE_VC_0_1_3_ENABLED		3
+
+#define TPG_VC_n_CFG0(n)	(0x68 + (n) * 0x60)
+# define TPG_VC_n_CFG0_VC_NUM			GENMASK(4, 0)
+# define TPG_VC_n_CFG0_NUM_ACTIVE_DT		GENMASK(9, 8)
+#  define NUM_ACTIVE_SLOTS_0_ENABLED			0
+#  define NUM_ACTIVE_SLOTS_0_1_ENABLED			1
+#  define NUM_ACTIVE_SLOTS_0_1_2_ENABLED		2
+#  define NUM_ACTIVE_SLOTS_0_1_3_ENABLED		3
+# define TPG_VC_n_CFG0_NUM_BATCH		GENMASK(15, 12)
+# define TPG_VC_n_CFG0_NUM_FRAMES		GENMASK(31, 16)
+
+#define TPG_VC_n_LSFR_SEED(n)	(0x6C + (n) * 0x60)
+
+#define TPG_VC_n_HBI_CFG(n)	(0x70 + (n) * 0x60)
+
+#define TPG_VC_n_VBI_CFG(n)	(0x74 + (n) * 0x60)
+
+#define TPG_VC_n_COLOR_BARS_CFG(n)		(0x78 + (n) * 0x60)
+# define TPG_VC_n_COLOR_BARS_CFG_PIX_PATTERN		GENMASK(2, 0)
+# define TPG_VC_n_COLOR_BARS_CFG_QCFA_EN		BIT(3)
+# define TPG_VC_n_COLOR_BARS_CFG_SPLIT_EN		BIT(4)
+# define TPG_VC_n_COLOR_BARS_CFG_NOISE_EN		BIT(5)
+# define TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD		GENMASK(13, 8)
+# define TPG_VC_n_COLOR_BARS_CFG_XCFA_EN		BIT(16)
+# define TPG_VC_n_COLOR_BARS_CFG_SIZE_X			GENMASK(26, 24)
+# define TPG_VC_n_COLOR_BARS_CFG_SIZE_Y			GENMASK(30, 28)
+
+#define TPG_VC_m_DT_n_CFG_0(m, n)		(0x7C + (m) * 0x60 + (n) * 0xC)
+# define TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT	GENMASK(15, 0)
+# define TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH	GENMASK(31, 16)
+
+#define TPG_VC_m_DT_n_CFG_1(m, n)		(0x80 + (m) * 0x60 + (n) * 0xC)
+# define TPG_VC_m_DT_n_CFG_1_DATA_TYPE		GENMASK(5, 0)
+# define TPG_VC_m_DT_n_CFG_1_ECC_XOR_MASK	GENMASK(13, 8)
+# define TPG_VC_m_DT_n_CFG_1_CRC_XOR_MASK	GENMASK(31, 16)
+
+#define TPG_VC_m_DT_n_CFG_2(m, n)		(0x84 + (m) * 0x60 + (n) * 0xC)
+# define TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE		GENMASK(3, 0)
+/* v2.0.0: USER[19:4], ENC[23:20] */
+# define TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD		GENMASK(19, 4)
+# define TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT			GENMASK(23, 20)
+/* v2.1.0: USER[27:4], ENC[31:28] */
+# define TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD	GENMASK(27, 4)
+# define TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT			GENMASK(31, 28)
+
+#define TPG_VC_n_COLOR_BAR_CFA_COLOR0(n)	(0xB0 + (n) * 0x60)
+#define TPG_VC_n_COLOR_BAR_CFA_COLOR1(n)	(0xB4 + (n) * 0x60)
+#define TPG_VC_n_COLOR_BAR_CFA_COLOR2(n)	(0xB8 + (n) * 0x60)
+#define TPG_VC_n_COLOR_BAR_CFA_COLOR3(n)	(0xBC + (n) * 0x60)
+
+/* Line offset between VC(n) and VC(n-1), n form 1 to 3 */
+#define TPG_VC_n_SHDR_CFG	(0x84 + (n) * 0x60)
+
+#define TPG_CLEAR		0x1F4
+
+#define TPG_HBI_PCT_DEFAULT			545	/* 545% */
+#define TPG_VBI_PCT_DEFAULT			10	/* 10% */
+#define PERCENT_BASE				100
+#define BITS_PER_BYTE				8
+
+/* Default user-specified payload for TPG test generator.
+ * Keep consistent with CSID TPG default: 0xBE.
+ */
+#define TPG_USER_SPECIFIED_PAYLOAD_DEFAULT	0xBE
+#define TPG_LFSR_SEED_DEFAULT			0x12345678
+#define TPG_COLOR_BARS_CFG_STANDARD \
+	FIELD_PREP(TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD, 0xA)
+
+static int tpg_stream_on(struct tpg_device *tpg)
+{
+	struct tpg_testgen_config *tg = &tpg->testgen;
+	struct v4l2_mbus_framefmt *input_format;
+	const struct tpg_format_info *format;
+	u8 lane_cnt = tpg->res->lane_cnt;
+	u8 dt_cnt = 0;
+	u8 i;
+	u32 val;
+
+	/* Loop through all enabled VCs and configure stream for each */
+	for (i = 0; i < tpg->res->vc_cnt; i++) {
+		input_format = &tpg->fmt[MSM_TPG_PAD_SRC + i];
+		format = tpg_get_fmt_entry(tpg,
+					   tpg->res->formats->formats,
+					   tpg->res->formats->nformats,
+					   input_format->code);
+		if (IS_ERR(format))
+			return -EINVAL;
+
+		val = FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT, input_format->height & 0xffff) |
+		      FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH, input_format->width & 0xffff);
+		writel(val, tpg->base + TPG_VC_m_DT_n_CFG_0(i, dt_cnt));
+
+		val = FIELD_PREP(TPG_VC_m_DT_n_CFG_1_DATA_TYPE, format->data_type);
+		writel(val, tpg->base + TPG_VC_m_DT_n_CFG_1(i, dt_cnt));
+
+		if (tpg->hw_version == TPG_HW_VER_2_0_0) {
+			val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg->mode - 1) |
+				FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
+					   TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
+				FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
+					   format->encode_format);
+		} else if (tpg->hw_version >= TPG_HW_VER_2_1_0) {
+			val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg->mode - 1) |
+				FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
+					   TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
+				FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
+					   format->encode_format);
+		}
+		writel(val, tpg->base + TPG_VC_m_DT_n_CFG_2(i, dt_cnt));
+
+		writel(TPG_COLOR_BARS_CFG_STANDARD, tpg->base + TPG_VC_n_COLOR_BARS_CFG(i));
+
+		val = DIV_ROUND_UP(input_format->width * format->bpp * TPG_HBI_PCT_DEFAULT,
+				   BITS_PER_BYTE * lane_cnt * PERCENT_BASE);
+		writel(val, tpg->base + TPG_VC_n_HBI_CFG(i));
+		val = input_format->height * TPG_VBI_PCT_DEFAULT / PERCENT_BASE;
+		writel(val, tpg->base + TPG_VC_n_VBI_CFG(i));
+
+		writel(TPG_LFSR_SEED_DEFAULT, tpg->base + TPG_VC_n_LSFR_SEED(i));
+
+		/* configure one DT, infinite frames */
+		val = FIELD_PREP(TPG_VC_n_CFG0_VC_NUM, i) |
+		      FIELD_PREP(TPG_VC_n_CFG0_NUM_FRAMES, 0);
+		writel(val, tpg->base + TPG_VC_n_CFG0(i));
+	}
+
+	val = FIELD_PREP(TPG_CTRL_TEST_EN, 1) |
+		  FIELD_PREP(TPG_CTRL_PHY_SEL, 0) |
+		  FIELD_PREP(TPG_CTRL_NUM_ACTIVE_LANES, lane_cnt - 1) |
+		  FIELD_PREP(TPG_CTRL_VC_DT_PATTERN_ID, 0) |
+		  FIELD_PREP(TPG_CTRL_NUM_ACTIVE_VC, tpg->res->vc_cnt - 1);
+	writel(val, tpg->base + TPG_CTRL);
+
+	return 0;
+}
+
+static void tpg_stream_off(struct tpg_device *tpg)
+{
+	writel(0, tpg->base + TPG_CTRL);
+	writel(1, tpg->base + TPG_CLEAR);
+}
+
+static int tpg_configure_stream(struct tpg_device *tpg, u8 enable)
+{
+	int ret = 0;
+
+	if (enable)
+		ret = tpg_stream_on(tpg);
+	else
+		tpg_stream_off(tpg);
+
+	return ret;
+}
+
+static int tpg_configure_testgen_pattern(struct tpg_device *tpg, s32 val)
+{
+	if (val >= 0 && val <= TPG_PAYLOAD_MODE_COLOR_BARS)
+		tpg->testgen.mode = val;
+
+	return 0;
+}
+
+/*
+ * tpg_hw_version - tpg hardware version query
+ * @tpg: tpg device
+ *
+ * Return HW version or error
+ */
+static u32 tpg_hw_version(struct tpg_device *tpg)
+{
+	u32 hw_version;
+	u32 hw_gen;
+	u32 hw_rev;
+	u32 hw_step;
+
+	hw_version = readl(tpg->base + TPG_HW_VERSION);
+	hw_gen = FIELD_GET(HW_VERSION_GENERATION, hw_version);
+	hw_rev = FIELD_GET(HW_VERSION_REVISION, hw_version);
+	hw_step = FIELD_GET(HW_VERSION_STEPPING, hw_version);
+
+	tpg->hw_version = hw_version;
+
+	dev_dbg_once(tpg->camss->dev, "tpg HW Version = %u.%u.%u\n",
+		     hw_gen, hw_rev, hw_step);
+
+	return hw_version;
+}
+
+/*
+ * tpg_reset - Trigger reset on tpg module and wait to complete
+ * @tpg: tpg device
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int tpg_reset(struct tpg_device *tpg)
+{
+	writel(0, tpg->base + TPG_CTRL);
+	writel(1, tpg->base + TPG_CLEAR);
+
+	return 0;
+}
+
+static void tpg_subdev_init(struct tpg_device *tpg)
+{
+	tpg->testgen.modes = testgen_payload_modes;
+	tpg->testgen.nmodes = TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
+}
+
+const struct tpg_hw_ops tpg_ops_gen1 = {
+	.configure_stream = tpg_configure_stream,
+	.configure_testgen_pattern = tpg_configure_testgen_pattern,
+	.hw_version = tpg_hw_version,
+	.reset = tpg_reset,
+	.subdev_init = tpg_subdev_init,
+};
diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c
index 43fdcb9af101ef34b118035ca9c68757b66118df..5cddf1bc09f97c2c61f907939bb54663d8eab3d4 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -3199,6 +3199,65 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = {
 	},
 };
 
+static const struct camss_subdev_resources tpg_res_8775p[] = {
+	/* TPG0 */
+	{
+		.regulators = {  },
+		.clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
+		.clock_rate = {
+			{ 400000000 },
+			{ 0 },
+			{ 400000000 },
+		},
+		.reg = { "tpg0" },
+		.interrupt = { "tpg0" },
+		.tpg = {
+			.lane_cnt = 4,
+			.vc_cnt = 1,
+			.formats = &tpg_formats_gen1,
+			.hw_ops = &tpg_ops_gen1
+		}
+	},
+
+	/* TPG1 */
+	{
+		.regulators = {  },
+		.clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
+		.clock_rate = {
+			{ 400000000 },
+			{ 0 },
+			{ 400000000 },
+		},
+		.reg = { "tpg1" },
+		.interrupt = { "tpg1" },
+		.tpg = {
+			.lane_cnt = 4,
+			.vc_cnt = 1,
+			.formats = &tpg_formats_gen1,
+			.hw_ops = &tpg_ops_gen1
+		}
+	},
+
+	/* TPG2 */
+	{
+		.regulators = {  },
+		.clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
+		.clock_rate = {
+			{ 400000000 },
+			{ 0 },
+			{ 400000000 },
+		},
+		.reg = { "tpg2" },
+		.interrupt = { "tpg2" },
+		.tpg = {
+			.lane_cnt = 4,
+			.vc_cnt = 1,
+			.formats = &tpg_formats_gen1,
+			.hw_ops = &tpg_ops_gen1
+		}
+	},
+};
+
 static const struct camss_subdev_resources csid_res_8775p[] = {
 	/* CSID0 */
 	{
@@ -3595,6 +3654,62 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = {
 	},
 };
 
+static const struct camss_subdev_resources tpg_res_x1e80100[] = {
+	/* TPG0 */
+	{
+		.regulators = {  },
+		.clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
+		.clock_rate = {
+			{ 400000000 },
+			{ 0 },
+			{ 400000000 },
+		},
+		.reg = { "csitpg0" },
+		.tpg = {
+			.lane_cnt = 4,
+			.vc_cnt = 1,
+			.formats = &tpg_formats_gen1,
+			.hw_ops = &tpg_ops_gen1
+		}
+	},
+
+	/* TPG1 */
+	{
+		.regulators = {  },
+		.clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
+		.clock_rate = {
+			{ 400000000 },
+			{ 0 },
+			{ 400000000 },
+		},
+		.reg = { "csitpg1" },
+		.tpg = {
+			.lane_cnt = 4,
+			.vc_cnt = 1,
+			.formats = &tpg_formats_gen1,
+			.hw_ops = &tpg_ops_gen1
+		}
+	},
+
+	/* TPG2 */
+	{
+		.regulators = {  },
+		.clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
+		.clock_rate = {
+			{ 400000000 },
+			{ 0 },
+			{ 400000000 },
+		},
+		.reg = { "csitpg2" },
+		.tpg = {
+			.lane_cnt = 4,
+			.vc_cnt = 1,
+			.formats = &tpg_formats_gen1,
+			.hw_ops = &tpg_ops_gen1
+		}
+	},
+};
+
 static const struct camss_subdev_resources csid_res_x1e80100[] = {
 	/* CSID0 */
 	{
@@ -4674,6 +4789,13 @@ static int camss_probe(struct platform_device *pdev)
 	if (!camss->csiphy)
 		return -ENOMEM;
 
+	if (camss->res->tpg_num > 0) {
+		camss->tpg = devm_kcalloc(dev, camss->res->tpg_num,
+					  sizeof(*camss->tpg), GFP_KERNEL);
+		if (!camss->tpg)
+			return -ENOMEM;
+	}
+
 	camss->csid = devm_kcalloc(dev, camss->res->csid_num, sizeof(*camss->csid),
 				   GFP_KERNEL);
 	if (!camss->csid)
@@ -4863,11 +4985,13 @@ static const struct camss_resources qcs8300_resources = {
 	.version = CAMSS_8300,
 	.pd_name = "top",
 	.csiphy_res = csiphy_res_8300,
+	.tpg_res = tpg_res_8775p,
 	.csid_res = csid_res_8775p,
 	.csid_wrapper_res = &csid_wrapper_res_sm8550,
 	.vfe_res = vfe_res_8775p,
 	.icc_res = icc_res_qcs8300,
 	.csiphy_num = ARRAY_SIZE(csiphy_res_8300),
+	.tpg_num = ARRAY_SIZE(tpg_res_8775p),
 	.csid_num = ARRAY_SIZE(csid_res_8775p),
 	.vfe_num = ARRAY_SIZE(vfe_res_8775p),
 	.icc_path_num = ARRAY_SIZE(icc_res_qcs8300),
@@ -4877,11 +5001,13 @@ static const struct camss_resources sa8775p_resources = {
 	.version = CAMSS_8775P,
 	.pd_name = "top",
 	.csiphy_res = csiphy_res_8775p,
+	.tpg_res = tpg_res_8775p,
 	.csid_res = csid_res_8775p,
 	.csid_wrapper_res = &csid_wrapper_res_sm8550,
 	.vfe_res = vfe_res_8775p,
 	.icc_res = icc_res_sa8775p,
 	.csiphy_num = ARRAY_SIZE(csiphy_res_8775p),
+	.tpg_num = ARRAY_SIZE(tpg_res_8775p),
 	.csid_num = ARRAY_SIZE(csid_res_8775p),
 	.vfe_num = ARRAY_SIZE(vfe_res_8775p),
 	.icc_path_num = ARRAY_SIZE(icc_res_sa8775p),
@@ -4992,11 +5118,13 @@ static const struct camss_resources x1e80100_resources = {
 	.pd_name = "top",
 	.csiphy_res = csiphy_res_x1e80100,
 	.csid_res = csid_res_x1e80100,
+	.tpg_res = tpg_res_x1e80100,
 	.vfe_res = vfe_res_x1e80100,
 	.csid_wrapper_res = &csid_wrapper_res_x1e80100,
 	.icc_res = icc_res_x1e80100,
 	.icc_path_num = ARRAY_SIZE(icc_res_x1e80100),
 	.csiphy_num = ARRAY_SIZE(csiphy_res_x1e80100),
+	.tpg_num = ARRAY_SIZE(tpg_res_x1e80100),
 	.csid_num = ARRAY_SIZE(csid_res_x1e80100),
 	.vfe_num = ARRAY_SIZE(vfe_res_x1e80100),
 };

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-13  9:03 ` [PATCH v8 1/3] media: qcom: camss: Add common TPG support Wenmeng Liu
@ 2026-01-13 16:27   ` Vladimir Zapolskiy
  2026-01-14  3:04     ` Wenmeng Liu
  0 siblings, 1 reply; 19+ messages in thread
From: Vladimir Zapolskiy @ 2026-01-13 16:27 UTC (permalink / raw)
  To: Wenmeng Liu, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

Hello Wenmeng.

On 1/13/26 11:03, Wenmeng Liu wrote:
> Introduce a new common Test Pattern Generator (TPG) implementation for
> Qualcomm CAMSS. This module provides a generic interface for pattern
> generation that can be reused by multiple platforms.
> 
> Unlike CSID-integrated TPG, this TPG acts as a standalone block
> that emulates both CSIPHY and sensor behavior, enabling flexible test
> patterns without external hardware.
> 
> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
> ---
>   drivers/media/platform/qcom/camss/Makefile    |   1 +
>   drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++++++++++++++++++
>   drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>   drivers/media/platform/qcom/camss/camss.h     |   5 +
>   4 files changed, 843 insertions(+)
> 
> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
> index 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
> --- a/drivers/media/platform/qcom/camss/Makefile
> +++ b/drivers/media/platform/qcom/camss/Makefile
> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>   		camss-vfe.o \
>   		camss-video.o \
>   		camss-format.o \
> +		camss-tpg.o \

While you're here, please sort and keep the lines in alphabetical order.

>   
>   obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/media/platform/qcom/camss/camss-tpg.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
> --- /dev/null
> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
> @@ -0,0 +1,710 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + *
> + * Qualcomm MSM Camera Subsystem - TPG Module
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "camss-tpg.h"
> +#include "camss.h"
> +
> +const char * const testgen_payload_modes[] = {
> +	"Disabled",
> +	"Incrementing",
> +	"Alternating 0x55/0xAA",
> +	"Reserved",
> +	"Reserved",
> +	"Pseudo-random Data",
> +	"User Specified",
> +	"Reserved",
> +	"Reserved",
> +	"Color bars",
> +	"Reserved"

It makes little sense to mention the unsupported values, and then
introduce enum tpg_testgen_mode to list the supported ones.

> +};

Are these test pattern modes specific to TPG Gen1 only?

CSID TPG uses a different 'csid_testgen_modes' list, and if the list above is
TPG Gen1 specific, it would make sense to place it right in camss-tpg-gen1.c

> +
> +static const struct tpg_format_info formats_gen1[] = {
> +	{
> +		MEDIA_BUS_FMT_SBGGR8_1X8,
> +		DATA_TYPE_RAW_8BIT,

Please replace it with MIPI_CSI2_DT_RAW8

> +		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
> +		8,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SGBRG8_1X8,
> +		DATA_TYPE_RAW_8BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
> +		8,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SGRBG8_1X8,
> +		DATA_TYPE_RAW_8BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
> +		8,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SRGGB8_1X8,
> +		DATA_TYPE_RAW_8BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
> +		8,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SBGGR10_1X10,
> +		DATA_TYPE_RAW_10BIT,

MIPI_CSI2_DT_RAW10

> +		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
> +		10,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SGBRG10_1X10,
> +		DATA_TYPE_RAW_10BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
> +		10,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SGRBG10_1X10,
> +		DATA_TYPE_RAW_10BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
> +		10,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SRGGB10_1X10,
> +		DATA_TYPE_RAW_10BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
> +		10,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SBGGR12_1X12,
> +		DATA_TYPE_RAW_12BIT,

MIPI_CSI2_DT_RAW12

> +		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
> +		12,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SGBRG12_1X12,
> +		DATA_TYPE_RAW_12BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
> +		12,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SGRBG12_1X12,
> +		DATA_TYPE_RAW_12BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
> +		12,
> +	},
> +	{
> +		MEDIA_BUS_FMT_SRGGB12_1X12,
> +		DATA_TYPE_RAW_12BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
> +		12,
> +	},
> +	{
> +		MEDIA_BUS_FMT_Y8_1X8,
> +		DATA_TYPE_RAW_8BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
> +		8,
> +	},
> +	{
> +		MEDIA_BUS_FMT_Y10_1X10,
> +		DATA_TYPE_RAW_10BIT,
> +		ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
> +		10,
> +	},
> +};
> +
> +const struct tpg_formats tpg_formats_gen1 = {
> +	.nformats = ARRAY_SIZE(formats_gen1),
> +	.formats = formats_gen1
> +};
> +
> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
> +						const struct tpg_format_info *formats,
> +						unsigned int nformats,
> +						u32 code)
> +{
> +	struct device *dev = tpg->camss->dev;
> +	size_t i;

unsigned int i, size_t is very unexpected to get here.

> +
> +	for (i = 0; i < nformats; i++)
> +		if (code == formats[i].code)
> +			return &formats[i];
> +
> +	dev_warn(dev, "Unknown pixel format code=0x%08x\n", code);

Please remove dev_warn() completely, it opens a way to flood the kernel log.

> +
> +	return ERR_PTR(-EINVAL);
> +}
> +
> +/*
> + * tpg_set_clock_rates - set clock rates on tpg module
> + * @tpg: tpg device
> + */
> +static int tpg_set_clock_rates(struct tpg_device *tpg)
> +{
> +	struct device *dev = tpg->camss->dev;
> +	int ret;
> +	int i;
> +
> +	for (i = 0; i < tpg->nclocks; i++) {
> +		struct camss_clock *clock = &tpg->clock[i];
> +		long round_rate;
> +
> +		if (clock->freq) {
> +			round_rate = clk_round_rate(clock->clk, clock->freq[0]);
> +			if (round_rate < 0) {
> +				dev_err(dev, "clk round rate failed: %ld\n",
> +					round_rate);
> +				return -EINVAL;
> +			}
> +
> +			ret = clk_set_rate(clock->clk, round_rate);
> +			if (ret < 0) {
> +				dev_err(dev, "clk set rate failed: %d\n", ret);
> +				return ret;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_set_power - Power on/off tpg module
> + * @sd: tpg V4L2 subdevice
> + * @on: Requested power state
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +static int tpg_set_power(struct v4l2_subdev *sd, int on)
> +{
> +	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
> +	struct device *dev = tpg->camss->dev;
> +
> +	if (on) {
> +		int ret;
> +
> +		ret = pm_runtime_resume_and_get(dev);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = tpg_set_clock_rates(tpg);
> +		if (ret < 0) {
> +			pm_runtime_put_sync(dev);
> +			return ret;
> +		}
> +
> +		ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
> +		if (ret < 0) {
> +			pm_runtime_put_sync(dev);
> +			return ret;
> +		}
> +
> +		tpg->res->hw_ops->reset(tpg);
> +
> +		tpg->res->hw_ops->hw_version(tpg);
> +	} else {
> +		camss_disable_clocks(tpg->nclocks, tpg->clock);
> +
> +		pm_runtime_put_sync(dev);
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_set_stream - Enable/disable streaming on tpg module
> + * @sd: tpg V4L2 subdevice
> + * @enable: Requested streaming state
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
> +	int ret = 0;
> +
> +	if (enable) {
> +		ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
> +		if (ret < 0) {
> +			dev_err(tpg->camss->dev,
> +				"could not sync v4l2 controls: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	ret = tpg->res->hw_ops->configure_stream(tpg, enable);
> +
> +	return ret;
> +}
> +
> +/*
> + * __tpg_get_format - Get pointer to format structure
> + * @tpg: tpg device
> + * @cfg: V4L2 subdev pad configuration

There is no such function argument. There are much more errors in
the doxygen descriptions of functions, please remove all these
doxygen comments, they do not bring anything valuable here.

> + * @pad: pad from which format is requested
> + * @which: TRY or ACTIVE format
> + *
> + * Return pointer to TRY or ACTIVE format structure
> + */
> +static struct v4l2_mbus_framefmt *
> +__tpg_get_format(struct tpg_device *tpg,
> +		 struct v4l2_subdev_state *sd_state,
> +		 unsigned int pad,
> +		 enum v4l2_subdev_format_whence which)
> +{
> +	if (which == V4L2_SUBDEV_FORMAT_TRY)
> +		return v4l2_subdev_state_get_format(sd_state,
> +						    pad);
> +
> +	return &tpg->fmt[pad];
> +}
> +
> +/*
> + * tpg_try_format - Handle try format by pad subdev method
> + * @tpg: tpg device
> + * @cfg: V4L2 subdev pad configuration

No such argument.

> + * @pad: pad on which format is requested
> + * @fmt: pointer to v4l2 format structure
> + * @which: wanted subdev format
> + */
> +static void tpg_try_format(struct tpg_device *tpg,
> +			   struct v4l2_subdev_state *sd_state,
> +			   unsigned int pad,
> +			   struct v4l2_mbus_framefmt *fmt,
> +			   enum v4l2_subdev_format_whence which)
> +{
> +	unsigned int i;
> +
> +	switch (pad) {
> +	case MSM_TPG_PAD_SINK:
> +		for (i = 0; i < tpg->res->formats->nformats; i++)
> +			if (tpg->res->formats->formats[i].code == fmt->code)
> +				break;
> +
> +		/* If not found, use SBGGR8 as default */
> +		if (i >= tpg->res->formats->nformats)
> +			fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
> +
> +		fmt->width = clamp_t(u32, fmt->width, 1, 8191);
> +		fmt->height = clamp_t(u32, fmt->height, 1, 8191);
> +
> +		fmt->field = V4L2_FIELD_NONE;
> +		fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +
> +		break;
> +	case MSM_TPG_PAD_SRC:
> +		*fmt = *__tpg_get_format(tpg, sd_state,
> +					 MSM_TPG_PAD_SINK,
> +					 which);
> +
> +		break;
> +	}
> +}
> +
> +/*
> + * tpg_enum_mbus_code - Handle format enumeration
> + * @sd: tpg V4L2 subdevice
> + * @cfg: V4L2 subdev pad configuration

No such argument.

> + * @code: pointer to v4l2_subdev_mbus_code_enum structure
> + * return -EINVAL or zero on success
> + */
> +static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
> +			      struct v4l2_subdev_state *sd_state,
> +			      struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
> +	struct v4l2_mbus_framefmt *format;
> +
> +	if (code->pad == MSM_TPG_PAD_SINK) {
> +		if (code->index >= tpg->res->formats->nformats)
> +			return -EINVAL;
> +
> +		code->code = tpg->res->formats->formats[code->index].code;
> +	} else {
> +		if (code->index > 0)
> +			return -EINVAL;
> +
> +		format = __tpg_get_format(tpg, sd_state,
> +					  MSM_TPG_PAD_SINK,
> +					  code->which);
> +
> +		code->code = format->code;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_enum_frame_size - Handle frame size enumeration
> + * @sd: tpg V4L2 subdevice
> + * @cfg: V4L2 subdev pad configuration
> + * @fse: pointer to v4l2_subdev_frame_size_enum structure
> + * return -EINVAL or zero on success
> + */
> +static int tpg_enum_frame_size(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *sd_state,
> +			       struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
> +	struct v4l2_mbus_framefmt format;
> +
> +	if (fse->index != 0)
> +		return -EINVAL;
> +
> +	format.code = fse->code;
> +	format.width = 1;
> +	format.height = 1;
> +	tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
> +	fse->min_width = format.width;
> +	fse->min_height = format.height;
> +
> +	if (format.code != fse->code)
> +		return -EINVAL;
> +
> +	format.code = fse->code;
> +	format.width = -1;
> +	format.height = -1;

format.width and format.height are rewritten in the tpg_try_format(),
so it makes no sense to assign them.

The problem is that for whatever reason you can tpg_try_format() twice
in a raw, it looks wrong, and I'm certain you can modify the functions
so that only one call would be needed.

> +	tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
> +	fse->max_width = format.width;
> +	fse->max_height = format.height;
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_get_format - Handle get format by pads subdev method
> + * @sd: tpg V4L2 subdevice
> + * @cfg: V4L2 subdev pad configuration
> + * @fmt: pointer to v4l2 subdev format structure
> + *
> + * Return -EINVAL or zero on success
> + */
> +static int tpg_get_format(struct v4l2_subdev *sd,
> +			  struct v4l2_subdev_state *sd_state,
> +			  struct v4l2_subdev_format *fmt)
> +{
> +	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
> +	struct v4l2_mbus_framefmt *format;
> +
> +	format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
> +	if (!format)
> +		return -EINVAL;
> +
> +	fmt->format = *format;
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_set_format - Handle set format by pads subdev method
> + * @sd: tpg V4L2 subdevice
> + * @cfg: V4L2 subdev pad configuration
> + * @fmt: pointer to v4l2 subdev format structure
> + *
> + * Return -EINVAL or zero on success
> + */
> +static int tpg_set_format(struct v4l2_subdev *sd,
> +			  struct v4l2_subdev_state *sd_state,
> +			  struct v4l2_subdev_format *fmt)
> +{
> +	struct tpg_device *tpg = v4l2_get_subdevdata(sd);
> +	struct v4l2_mbus_framefmt *format;
> +
> +	format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
> +	if (!format)
> +		return -EINVAL;
> +
> +	tpg_try_format(tpg, sd_state, fmt->pad, &fmt->format,
> +		       fmt->which);
> +	*format = fmt->format;
> +
> +	if (fmt->pad == MSM_TPG_PAD_SINK) {
> +		format = __tpg_get_format(tpg, sd_state,
> +					  MSM_TPG_PAD_SRC,
> +					  fmt->which);
> +
> +		*format = fmt->format;
> +		tpg_try_format(tpg, sd_state, MSM_TPG_PAD_SRC,
> +			       format,
> +			       fmt->which);
> +	}
> +	return 0;
> +}
> +
> +/*
> + * tpg_init_formats - Initialize formats on all pads
> + * @sd: tpg V4L2 subdevice
> + * @fh: V4L2 subdev file handle
> + *
> + * Initialize all pad formats with default values.
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +static int tpg_init_formats(struct v4l2_subdev *sd,
> +			    struct v4l2_subdev_fh *fh)
> +{
> +	struct v4l2_subdev_format format = {
> +		.pad = MSM_TPG_PAD_SINK,
> +		.which = fh ? V4L2_SUBDEV_FORMAT_TRY :
> +			      V4L2_SUBDEV_FORMAT_ACTIVE,
> +		.format = {
> +			.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +			.width = 1920,
> +			.height = 1080
> +		}
> +	};
> +
> +	return tpg_set_format(sd, fh ? fh->state : NULL, &format);
> +}
> +
> +/*
> + * tpg_set_test_pattern - Set test generator's pattern mode
> + * @tpg: TPG device
> + * @value: desired test pattern mode
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +static int tpg_set_test_pattern(struct tpg_device *tpg, s32 value)
> +{
> +	return tpg->res->hw_ops->configure_testgen_pattern(tpg, value);
> +}
> +
> +/*
> + * tpg_s_ctrl - Handle set control subdev method
> + * @ctrl: pointer to v4l2 control structure
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct tpg_device *tpg = container_of(ctrl->handler,
> +					      struct tpg_device, ctrls);
> +	int ret = -EINVAL;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_TEST_PATTERN:
> +		ret = tpg_set_test_pattern(tpg, ctrl->val);
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
> +	.s_ctrl = tpg_s_ctrl,
> +};
> +
> +/*
> + * msm_tpg_subdev_init - Initialize tpg device structure and resources
> + * @tpg: tpg device

And no 'camss' argument. The whole function description comment is quite
useless, it can be just removed with no losses.

> + * @res: tpg module resources table
> + * @id: tpg module id
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +int msm_tpg_subdev_init(struct camss *camss,
> +			struct tpg_device *tpg,
> +			const struct camss_subdev_resources *res, u8 id)
> +{
> +	struct platform_device *pdev;
> +	struct device *dev;
> +	int i, j;
> +
> +	dev  = camss->dev;
> +	pdev = to_platform_device(dev);
> +
> +	tpg->camss = camss;
> +	tpg->id = id;
> +	tpg->res = &res->tpg;
> +	tpg->res->hw_ops->subdev_init(tpg);
> +
> +	tpg->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]);
> +	if (IS_ERR(tpg->base))
> +		return PTR_ERR(tpg->base);
> +
> +	tpg->nclocks = 0;
> +	while (res->clock[tpg->nclocks])
> +		tpg->nclocks++;
> +
> +	if (tpg->nclocks) {

if (!tpg->nclocks)
	return 0;

> +		tpg->clock = devm_kcalloc(dev,
> +					  tpg->nclocks, sizeof(*tpg->clock),
> +					  GFP_KERNEL);
> +		if (!tpg->clock)
> +			return -ENOMEM;
> +
> +		for (i = 0; i < tpg->nclocks; i++) {
> +			struct camss_clock *clock = &tpg->clock[i];
> +
> +			clock->clk = devm_clk_get(dev, res->clock[i]);
> +			if (IS_ERR(clock->clk))
> +				return PTR_ERR(clock->clk);
> +
> +			clock->name = res->clock[i];
> +
> +			clock->nfreqs = 0;
> +			while (res->clock_rate[i][clock->nfreqs])
> +				clock->nfreqs++;
> +
> +			if (!clock->nfreqs) {
> +				clock->freq = NULL;
> +				continue;
> +			}
> +
> +			clock->freq = devm_kcalloc(dev,
> +						   clock->nfreqs,
> +						   sizeof(*clock->freq),
> +						   GFP_KERNEL);
> +			if (!clock->freq)
> +				return -ENOMEM;
> +
> +			for (j = 0; j < clock->nfreqs; j++)
> +				clock->freq[j] = res->clock_rate[i][j];
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_link_setup - Setup tpg connections
> + * @entity: Pointer to media entity structure
> + * @local: Pointer to local pad
> + * @remote: Pointer to remote pad
> + * @flags: Link flags
> + *
> + * Return 0 on success
> + */
> +static int tpg_link_setup(struct media_entity *entity,
> +			  const struct media_pad *local,
> +			  const struct media_pad *remote, u32 flags)
> +{
> +	if (flags & MEDIA_LNK_FL_ENABLED)
> +		if (media_pad_remote_pad_first(local))
> +			return -EBUSY;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops tpg_core_ops = {
> +	.s_power = tpg_set_power,
> +};
> +
> +static const struct v4l2_subdev_video_ops tpg_video_ops = {
> +	.s_stream = tpg_set_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
> +	.enum_mbus_code = tpg_enum_mbus_code,
> +	.enum_frame_size = tpg_enum_frame_size,
> +	.get_fmt = tpg_get_format,
> +	.set_fmt = tpg_set_format,
> +};
> +
> +static const struct v4l2_subdev_ops tpg_v4l2_ops = {
> +	.core = &tpg_core_ops,
> +	.video = &tpg_video_ops,
> +	.pad = &tpg_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
> +	.open = tpg_init_formats,
> +};
> +
> +static const struct media_entity_operations tpg_media_ops = {
> +	.link_setup = tpg_link_setup,
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +/*
> + * msm_tpg_register_entity - Register subdev node for tpg module
> + * @tpg: tpg device
> + * @v4l2_dev: V4L2 device
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +int msm_tpg_register_entity(struct tpg_device *tpg,
> +			    struct v4l2_device *v4l2_dev)
> +{
> +	struct v4l2_subdev *sd = &tpg->subdev;
> +	struct media_pad *pads = tpg->pads;
> +	struct device *dev = tpg->camss->dev;
> +	int ret;
> +
> +	v4l2_subdev_init(sd, &tpg_v4l2_ops);
> +	sd->internal_ops = &tpg_v4l2_internal_ops;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> +		     V4L2_SUBDEV_FL_HAS_EVENTS;
> +	snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
> +		 MSM_TPG_NAME, tpg->id);
> +	sd->grp_id = TPG_GUP_ID;
> +	v4l2_set_subdevdata(sd, tpg);
> +
> +	ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
> +							 &tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
> +							 tpg->testgen.nmodes, 0, 0,
> +							 tpg->testgen.modes);
> +

Please remove a blank line above.

> +	if (tpg->ctrls.error) {
> +		dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
> +		ret = tpg->ctrls.error;
> +		goto free_ctrl;
> +	}
> +
> +	tpg->subdev.ctrl_handler = &tpg->ctrls;
> +
> +	ret = tpg_init_formats(sd, NULL);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to init format: %d\n", ret);
> +		goto free_ctrl;
> +	}
> +
> +	pads[MSM_TPG_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	pads[MSM_TPG_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;

Why do you need two pads for TPG?

> +
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;

TPG is not a video pixel encoding converter device.

> +	sd->entity.ops = &tpg_media_ops;
> +	ret = media_entity_pads_init(&sd->entity, MSM_TPG_PADS_NUM, pads);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to init media entity: %d\n", ret);
> +		goto free_ctrl;
> +	}
> +
> +	ret = v4l2_device_register_subdev(v4l2_dev, sd);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to register subdev: %d\n", ret);
> +		media_entity_cleanup(&sd->entity);
> +		goto free_ctrl;
> +	}
> +
> +	return 0;
> +
> +free_ctrl:
> +	v4l2_ctrl_handler_free(&tpg->ctrls);
> +
> +	return ret;
> +}
> +
> +/*
> + * msm_tpg_unregister_entity - Unregister tpg module subdev node
> + * @tpg: tpg device
> + */
> +void msm_tpg_unregister_entity(struct tpg_device *tpg)
> +{
> +	v4l2_device_unregister_subdev(&tpg->subdev);
> +	media_entity_cleanup(&tpg->subdev.entity);
> +	v4l2_ctrl_handler_free(&tpg->ctrls);
> +}
> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.h b/drivers/media/platform/qcom/camss/camss-tpg.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..1a16addac19418f2f11d0b8abb1c865c99888bde
> --- /dev/null
> +++ b/drivers/media/platform/qcom/camss/camss-tpg.h
> @@ -0,0 +1,127 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * camss-tpg.h
> + *
> + * Qualcomm MSM Camera Subsystem - TPG Module
> + *
> + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +#ifndef QC_MSM_CAMSS_TPG_H
> +#define QC_MSM_CAMSS_TPG_H
> +
> +#include <linux/clk.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define MSM_TPG_PAD_SINK 0
> +#define MSM_TPG_PAD_SRC 1
> +#define MSM_TPG_PADS_NUM 2
> +
> +#define DATA_TYPE_RAW_8BIT		0x2a
> +#define DATA_TYPE_RAW_10BIT		0x2b
> +#define DATA_TYPE_RAW_12BIT		0x2c

Remove all above to get the macro values from include/media/mipi-csi2.h

> +
> +#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT	0x1
> +#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT	0x2
> +#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT	0x3
> +#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT	0x4
> +#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT	0x5
> +#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT	0x6
> +#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT	0x7
> +
> +#define TPG_GUP_ID 0
> +#define MSM_TPG_NAME "msm_tpg"

Remove the macro, it's used only once in the code and more usecases are
not expected happen.

> +
> +enum tpg_testgen_mode {
> +	TPG_PAYLOAD_MODE_DISABLED = 0,
> +	TPG_PAYLOAD_MODE_INCREMENTING = 1,
> +	TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
> +	TPG_PAYLOAD_MODE_RANDOM = 5,
> +	TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
> +	TPG_PAYLOAD_MODE_COLOR_BARS = 9,
> +	TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
> +};
> +
> +struct tpg_testgen_config {
> +	enum tpg_testgen_mode mode;
> +	const char * const*modes;
> +	u8 nmodes;
> +};
> +
> +struct tpg_format_info {
> +	u32 code;
> +	u8 data_type;
> +	u8 encode_format;
> +	u8 bpp;
> +};
> +
> +struct tpg_formats {
> +	unsigned int nformats;
> +	const struct tpg_format_info *formats;
> +};
> +
> +struct tpg_device;
> +
> +struct tpg_hw_ops {
> +	int (*configure_stream)(struct tpg_device *tpg, u8 enable);
> +
> +	int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
> +
> +	u32 (*hw_version)(struct tpg_device *tpg);
> +
> +	int (*reset)(struct tpg_device *tpg);
> +
> +	void (*subdev_init)(struct tpg_device *tpg);
> +};
> +
> +struct tpg_subdev_resources {
> +	u8 lane_cnt;
> +	u8 vc_cnt;
> +	const struct tpg_formats *formats;
> +	const struct tpg_hw_ops *hw_ops;
> +};
> +
> +struct tpg_device {
> +	struct camss *camss;
> +	u8 id;
> +	struct v4l2_subdev subdev;
> +	struct media_pad pads[MSM_TPG_PADS_NUM];
> +	void __iomem *base;
> +	struct camss_clock *clock;
> +	int nclocks;
> +	struct tpg_testgen_config testgen;
> +	struct v4l2_mbus_framefmt fmt[MSM_TPG_PADS_NUM];
> +	struct v4l2_ctrl_handler ctrls;
> +	struct v4l2_ctrl *testgen_mode;
> +	const struct tpg_subdev_resources *res;
> +	const struct tpg_format *formats;
> +	unsigned int nformats;
> +	u32 hw_version;
> +};
> +
> +struct camss_subdev_resources;
> +
> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
> +						const struct tpg_format_info *formats,
> +						unsigned int nformats,
> +						u32 code);
> +
> +int msm_tpg_subdev_init(struct camss *camss,
> +			struct tpg_device *tpg,
> +			const struct camss_subdev_resources *res, u8 id);
> +
> +int msm_tpg_register_entity(struct tpg_device *tpg,
> +			    struct v4l2_device *v4l2_dev);
> +
> +void msm_tpg_unregister_entity(struct tpg_device *tpg);
> +
> +extern const char * const testgen_payload_modes[];
> +
> +extern const struct tpg_formats tpg_formats_gen1;
> +
> +extern const struct tpg_hw_ops tpg_ops_gen1;
> +
> +#endif /* QC_MSM_CAMSS_TPG_H */
> diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h
> index 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..a892a87bed8bde8919200d6eac2b7a5338763c0e 100644
> --- a/drivers/media/platform/qcom/camss/camss.h
> +++ b/drivers/media/platform/qcom/camss/camss.h
> @@ -21,6 +21,7 @@
>   #include "camss-csid.h"
>   #include "camss-csiphy.h"
>   #include "camss-ispif.h"
> +#include "camss-tpg.h"
>   #include "camss-vfe.h"
>   #include "camss-format.h"
>   
> @@ -52,6 +53,7 @@ struct camss_subdev_resources {
>   	char *interrupt[CAMSS_RES_MAX];
>   	union {
>   		struct csiphy_subdev_resources csiphy;
> +		struct tpg_subdev_resources tpg;
>   		struct csid_subdev_resources csid;
>   		struct vfe_subdev_resources vfe;
>   	};
> @@ -104,6 +106,7 @@ struct camss_resources {
>   	enum camss_version version;
>   	const char *pd_name;
>   	const struct camss_subdev_resources *csiphy_res;
> +	const struct camss_subdev_resources *tpg_res;
>   	const struct camss_subdev_resources *csid_res;
>   	const struct camss_subdev_resources *ispif_res;
>   	const struct camss_subdev_resources *vfe_res;
> @@ -111,6 +114,7 @@ struct camss_resources {
>   	const struct resources_icc *icc_res;
>   	const unsigned int icc_path_num;
>   	const unsigned int csiphy_num;
> +	const unsigned int tpg_num;
>   	const unsigned int csid_num;
>   	const unsigned int vfe_num;
>   };
> @@ -121,6 +125,7 @@ struct camss {
>   	struct media_device media_dev;
>   	struct device *dev;
>   	struct csiphy_device *csiphy;
> +	struct tpg_device *tpg;
>   	struct csid_device *csid;
>   	struct ispif_device *ispif;
>   	struct vfe_device *vfe;
> 

-- 
Best wishes,
Vladimir

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-13 16:27   ` Vladimir Zapolskiy
@ 2026-01-14  3:04     ` Wenmeng Liu
  2026-01-14  5:05       ` Vladimir Zapolskiy
  0 siblings, 1 reply; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-14  3:04 UTC (permalink / raw)
  To: Vladimir Zapolskiy, Robert Foss, Todor Tomov,
	Bryan O'Donoghue, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

Hi Vladimir,

On 1/14/2026 12:27 AM, Vladimir Zapolskiy wrote:
> Hello Wenmeng.
> 
> On 1/13/26 11:03, Wenmeng Liu wrote:
>> Introduce a new common Test Pattern Generator (TPG) implementation for
>> Qualcomm CAMSS. This module provides a generic interface for pattern
>> generation that can be reused by multiple platforms.
>>
>> Unlike CSID-integrated TPG, this TPG acts as a standalone block
>> that emulates both CSIPHY and sensor behavior, enabling flexible test
>> patterns without external hardware.
>>
>> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
>> ---
>>   drivers/media/platform/qcom/camss/Makefile    |   1 +
>>   drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++++++++ 
>> ++++++++++
>>   drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>>   drivers/media/platform/qcom/camss/camss.h     |   5 +
>>   4 files changed, 843 insertions(+)
>>
>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/ 
>> media/platform/qcom/camss/Makefile
>> index 
>> 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
>> --- a/drivers/media/platform/qcom/camss/Makefile
>> +++ b/drivers/media/platform/qcom/camss/Makefile
>> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>>           camss-vfe.o \
>>           camss-video.o \
>>           camss-format.o \
>> +        camss-tpg.o \
> 
> While you're here, please sort and keep the lines in alphabetical order.
ACK.

> 
>>   obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/ 
>> media/platform/qcom/camss/camss-tpg.c
>> new file mode 100644
>> index 
>> 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
>> --- /dev/null
>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
>> @@ -0,0 +1,710 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + *
>> + * Qualcomm MSM Camera Subsystem - TPG Module
>> + *
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#include "camss-tpg.h"
>> +#include "camss.h"
>> +
>> +const char * const testgen_payload_modes[] = {
>> +    "Disabled",
>> +    "Incrementing",
>> +    "Alternating 0x55/0xAA",
>> +    "Reserved",
>> +    "Reserved",
>> +    "Pseudo-random Data",
>> +    "User Specified",
>> +    "Reserved",
>> +    "Reserved",
>> +    "Color bars",
>> +    "Reserved"
> 
> It makes little sense to mention the unsupported values, and then
> introduce enum tpg_testgen_mode to list the supported ones.
> 
This is for ctrl menu, will do as follow:
static const char * const testgen_payload_modes[] = {
     [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
     [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
     [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating 0x55/0xAA",
     [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
     [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
     [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
};

>> +};
> 
> Are these test pattern modes specific to TPG Gen1 only?
> 
> CSID TPG uses a different 'csid_testgen_modes' list, and if the list 
> above is
> TPG Gen1 specific, it would make sense to place it right in camss-tpg- 
> gen1.c
> 

Like other CAMSS nodes, the files placed in the core are meant to 
maintain consistency with the others.

>> +
>> +static const struct tpg_format_info formats_gen1[] = {
>> +    {
>> +        MEDIA_BUS_FMT_SBGGR8_1X8,
>> +        DATA_TYPE_RAW_8BIT,
> 
> Please replace it with MIPI_CSI2_DT_RAW8
>

ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>> +        8,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SGBRG8_1X8,
>> +        DATA_TYPE_RAW_8BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>> +        8,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SGRBG8_1X8,
>> +        DATA_TYPE_RAW_8BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>> +        8,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SRGGB8_1X8,
>> +        DATA_TYPE_RAW_8BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>> +        8,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SBGGR10_1X10,
>> +        DATA_TYPE_RAW_10BIT,
> 
> MIPI_CSI2_DT_RAW10>

ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>> +        10,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SGBRG10_1X10,
>> +        DATA_TYPE_RAW_10BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>> +        10,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SGRBG10_1X10,
>> +        DATA_TYPE_RAW_10BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>> +        10,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SRGGB10_1X10,
>> +        DATA_TYPE_RAW_10BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>> +        10,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SBGGR12_1X12,
>> +        DATA_TYPE_RAW_12BIT,
> 
> MIPI_CSI2_DT_RAW12
> 

ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>> +        12,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SGBRG12_1X12,
>> +        DATA_TYPE_RAW_12BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>> +        12,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SGRBG12_1X12,
>> +        DATA_TYPE_RAW_12BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>> +        12,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_SRGGB12_1X12,
>> +        DATA_TYPE_RAW_12BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>> +        12,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_Y8_1X8,
>> +        DATA_TYPE_RAW_8BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>> +        8,
>> +    },
>> +    {
>> +        MEDIA_BUS_FMT_Y10_1X10,
>> +        DATA_TYPE_RAW_10BIT,
>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>> +        10,
>> +    },
>> +};
>> +
>> +const struct tpg_formats tpg_formats_gen1 = {
>> +    .nformats = ARRAY_SIZE(formats_gen1),
>> +    .formats = formats_gen1
>> +};
>> +
>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
>> +                        const struct tpg_format_info *formats,
>> +                        unsigned int nformats,
>> +                        u32 code)
>> +{
>> +    struct device *dev = tpg->camss->dev;
>> +    size_t i;
> 
> unsigned int i, size_t is very unexpected to get here.
> 

I have received comments on this.

https://lore.kernel.org/all/449ac3c3-1f6a-4e69-899d-c4e4577714a4@oss.qualcomm.com/

https://staticthinking.wordpress.com/2022/06/01/unsigned-int-i-is-stupid/>> 
+
>> +    for (i = 0; i < nformats; i++)
>> +        if (code == formats[i].code)
>> +            return &formats[i];
>> +
>> +    dev_warn(dev, "Unknown pixel format code=0x%08x\n", code);
> 
> Please remove dev_warn() completely, it opens a way to flood the kernel 
> log.
> 

This is an exception printout and will not appear under normal 
circumstances.>> +
>> +    return ERR_PTR(-EINVAL);
>> +}
>> +
>> +/*
>> + * tpg_set_clock_rates - set clock rates on tpg module
>> + * @tpg: tpg device
>> + */
>> +static int tpg_set_clock_rates(struct tpg_device *tpg)
>> +{
>> +    struct device *dev = tpg->camss->dev;
>> +    int ret;
>> +    int i;
>> +
>> +    for (i = 0; i < tpg->nclocks; i++) {
>> +        struct camss_clock *clock = &tpg->clock[i];
>> +        long round_rate;
>> +
>> +        if (clock->freq) {
>> +            round_rate = clk_round_rate(clock->clk, clock->freq[0]);
>> +            if (round_rate < 0) {
>> +                dev_err(dev, "clk round rate failed: %ld\n",
>> +                    round_rate);
>> +                return -EINVAL;
>> +            }
>> +
>> +            ret = clk_set_rate(clock->clk, round_rate);
>> +            if (ret < 0) {
>> +                dev_err(dev, "clk set rate failed: %d\n", ret);
>> +                return ret;
>> +            }
>> +        }
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_set_power - Power on/off tpg module
>> + * @sd: tpg V4L2 subdevice
>> + * @on: Requested power state
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +static int tpg_set_power(struct v4l2_subdev *sd, int on)
>> +{
>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>> +    struct device *dev = tpg->camss->dev;
>> +
>> +    if (on) {
>> +        int ret;
>> +
>> +        ret = pm_runtime_resume_and_get(dev);
>> +        if (ret < 0)
>> +            return ret;
>> +
>> +        ret = tpg_set_clock_rates(tpg);
>> +        if (ret < 0) {
>> +            pm_runtime_put_sync(dev);
>> +            return ret;
>> +        }
>> +
>> +        ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
>> +        if (ret < 0) {
>> +            pm_runtime_put_sync(dev);
>> +            return ret;
>> +        }
>> +
>> +        tpg->res->hw_ops->reset(tpg);
>> +
>> +        tpg->res->hw_ops->hw_version(tpg);
>> +    } else {
>> +        camss_disable_clocks(tpg->nclocks, tpg->clock);
>> +
>> +        pm_runtime_put_sync(dev);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_set_stream - Enable/disable streaming on tpg module
>> + * @sd: tpg V4L2 subdevice
>> + * @enable: Requested streaming state
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
>> +{
>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>> +    int ret = 0;
>> +
>> +    if (enable) {
>> +        ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
>> +        if (ret < 0) {
>> +            dev_err(tpg->camss->dev,
>> +                "could not sync v4l2 controls: %d\n", ret);
>> +            return ret;
>> +        }
>> +    }
>> +
>> +    ret = tpg->res->hw_ops->configure_stream(tpg, enable);
>> +
>> +    return ret;
>> +}
>> +
>> +/*
>> + * __tpg_get_format - Get pointer to format structure
>> + * @tpg: tpg device
>> + * @cfg: V4L2 subdev pad configuration
> 
> There is no such function argument. There are much more errors in
> the doxygen descriptions of functions, please remove all these
> doxygen comments, they do not bring anything valuable here.
> 


ACK.>> + * @pad: pad from which format is requested
>> + * @which: TRY or ACTIVE format
>> + *
>> + * Return pointer to TRY or ACTIVE format structure
>> + */
>> +static struct v4l2_mbus_framefmt *
>> +__tpg_get_format(struct tpg_device *tpg,
>> +         struct v4l2_subdev_state *sd_state,
>> +         unsigned int pad,
>> +         enum v4l2_subdev_format_whence which)
>> +{
>> +    if (which == V4L2_SUBDEV_FORMAT_TRY)
>> +        return v4l2_subdev_state_get_format(sd_state,
>> +                            pad);
>> +
>> +    return &tpg->fmt[pad];
>> +}
>> +
>> +/*
>> + * tpg_try_format - Handle try format by pad subdev method
>> + * @tpg: tpg device
>> + * @cfg: V4L2 subdev pad configuration
> 
> No such argument.
> 

ACK.>> + * @pad: pad on which format is requested
>> + * @fmt: pointer to v4l2 format structure
>> + * @which: wanted subdev format
>> + */
>> +static void tpg_try_format(struct tpg_device *tpg,
>> +               struct v4l2_subdev_state *sd_state,
>> +               unsigned int pad,
>> +               struct v4l2_mbus_framefmt *fmt,
>> +               enum v4l2_subdev_format_whence which)
>> +{
>> +    unsigned int i;
>> +
>> +    switch (pad) {
>> +    case MSM_TPG_PAD_SINK:
>> +        for (i = 0; i < tpg->res->formats->nformats; i++)
>> +            if (tpg->res->formats->formats[i].code == fmt->code)
>> +                break;
>> +
>> +        /* If not found, use SBGGR8 as default */
>> +        if (i >= tpg->res->formats->nformats)
>> +            fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
>> +
>> +        fmt->width = clamp_t(u32, fmt->width, 1, 8191);
>> +        fmt->height = clamp_t(u32, fmt->height, 1, 8191);
>> +
>> +        fmt->field = V4L2_FIELD_NONE;
>> +        fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +
>> +        break;
>> +    case MSM_TPG_PAD_SRC:
>> +        *fmt = *__tpg_get_format(tpg, sd_state,
>> +                     MSM_TPG_PAD_SINK,
>> +                     which);
>> +
>> +        break;
>> +    }
>> +}
>> +
>> +/*
>> + * tpg_enum_mbus_code - Handle format enumeration
>> + * @sd: tpg V4L2 subdevice
>> + * @cfg: V4L2 subdev pad configuration
> 
> No such argument.
> 

ACK.>> + * @code: pointer to v4l2_subdev_mbus_code_enum structure
>> + * return -EINVAL or zero on success
>> + */
>> +static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
>> +                  struct v4l2_subdev_state *sd_state,
>> +                  struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>> +    struct v4l2_mbus_framefmt *format;
>> +
>> +    if (code->pad == MSM_TPG_PAD_SINK) {
>> +        if (code->index >= tpg->res->formats->nformats)
>> +            return -EINVAL;
>> +
>> +        code->code = tpg->res->formats->formats[code->index].code;
>> +    } else {
>> +        if (code->index > 0)
>> +            return -EINVAL;
>> +
>> +        format = __tpg_get_format(tpg, sd_state,
>> +                      MSM_TPG_PAD_SINK,
>> +                      code->which);
>> +
>> +        code->code = format->code;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_enum_frame_size - Handle frame size enumeration
>> + * @sd: tpg V4L2 subdevice
>> + * @cfg: V4L2 subdev pad configuration
>> + * @fse: pointer to v4l2_subdev_frame_size_enum structure
>> + * return -EINVAL or zero on success
>> + */
>> +static int tpg_enum_frame_size(struct v4l2_subdev *sd,
>> +                   struct v4l2_subdev_state *sd_state,
>> +                   struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>> +    struct v4l2_mbus_framefmt format;
>> +
>> +    if (fse->index != 0)
>> +        return -EINVAL;
>> +
>> +    format.code = fse->code;
>> +    format.width = 1;
>> +    format.height = 1;
>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>> +    fse->min_width = format.width;
>> +    fse->min_height = format.height;
>> +
>> +    if (format.code != fse->code)
>> +        return -EINVAL;
>> +
>> +    format.code = fse->code;
>> +    format.width = -1;
>> +    format.height = -1;
> 
> format.width and format.height are rewritten in the tpg_try_format(),
> so it makes no sense to assign them.
> 

This is use for pass parameters in.

> The problem is that for whatever reason you can tpg_try_format() twice
> in a raw, it looks wrong, and I'm certain you can modify the functions
> so that only one call would be needed.
> The first call is to get the supported minimum size, and the second call 
is to get the maximum size.

The tpg_try_format function can be used multiple times; I don't think 
it's necessary to write a new interface.

>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>> +    fse->max_width = format.width;
>> +    fse->max_height = format.height;
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_get_format - Handle get format by pads subdev method
>> + * @sd: tpg V4L2 subdevice
>> + * @cfg: V4L2 subdev pad configuration
>> + * @fmt: pointer to v4l2 subdev format structure
>> + *
>> + * Return -EINVAL or zero on success
>> + */
>> +static int tpg_get_format(struct v4l2_subdev *sd,
>> +              struct v4l2_subdev_state *sd_state,
>> +              struct v4l2_subdev_format *fmt)
>> +{
>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>> +    struct v4l2_mbus_framefmt *format;
>> +
>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>> +    if (!format)
>> +        return -EINVAL;
>> +
>> +    fmt->format = *format;
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_set_format - Handle set format by pads subdev method
>> + * @sd: tpg V4L2 subdevice
>> + * @cfg: V4L2 subdev pad configuration
>> + * @fmt: pointer to v4l2 subdev format structure
>> + *
>> + * Return -EINVAL or zero on success
>> + */
>> +static int tpg_set_format(struct v4l2_subdev *sd,
>> +              struct v4l2_subdev_state *sd_state,
>> +              struct v4l2_subdev_format *fmt)
>> +{
>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>> +    struct v4l2_mbus_framefmt *format;
>> +
>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>> +    if (!format)
>> +        return -EINVAL;
>> +
>> +    tpg_try_format(tpg, sd_state, fmt->pad, &fmt->format,
>> +               fmt->which);
>> +    *format = fmt->format;
>> +
>> +    if (fmt->pad == MSM_TPG_PAD_SINK) {
>> +        format = __tpg_get_format(tpg, sd_state,
>> +                      MSM_TPG_PAD_SRC,
>> +                      fmt->which);
>> +
>> +        *format = fmt->format;
>> +        tpg_try_format(tpg, sd_state, MSM_TPG_PAD_SRC,
>> +                   format,
>> +                   fmt->which);
>> +    }
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_init_formats - Initialize formats on all pads
>> + * @sd: tpg V4L2 subdevice
>> + * @fh: V4L2 subdev file handle
>> + *
>> + * Initialize all pad formats with default values.
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +static int tpg_init_formats(struct v4l2_subdev *sd,
>> +                struct v4l2_subdev_fh *fh)
>> +{
>> +    struct v4l2_subdev_format format = {
>> +        .pad = MSM_TPG_PAD_SINK,
>> +        .which = fh ? V4L2_SUBDEV_FORMAT_TRY :
>> +                  V4L2_SUBDEV_FORMAT_ACTIVE,
>> +        .format = {
>> +            .code = MEDIA_BUS_FMT_SBGGR8_1X8,
>> +            .width = 1920,
>> +            .height = 1080
>> +        }
>> +    };
>> +
>> +    return tpg_set_format(sd, fh ? fh->state : NULL, &format);
>> +}
>> +
>> +/*
>> + * tpg_set_test_pattern - Set test generator's pattern mode
>> + * @tpg: TPG device
>> + * @value: desired test pattern mode
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +static int tpg_set_test_pattern(struct tpg_device *tpg, s32 value)
>> +{
>> +    return tpg->res->hw_ops->configure_testgen_pattern(tpg, value);
>> +}
>> +
>> +/*
>> + * tpg_s_ctrl - Handle set control subdev method
>> + * @ctrl: pointer to v4l2 control structure
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +    struct tpg_device *tpg = container_of(ctrl->handler,
>> +                          struct tpg_device, ctrls);
>> +    int ret = -EINVAL;
>> +
>> +    switch (ctrl->id) {
>> +    case V4L2_CID_TEST_PATTERN:
>> +        ret = tpg_set_test_pattern(tpg, ctrl->val);
>> +        break;
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
>> +    .s_ctrl = tpg_s_ctrl,
>> +};
>> +
>> +/*
>> + * msm_tpg_subdev_init - Initialize tpg device structure and resources
>> + * @tpg: tpg device
> 
> And no 'camss' argument. The whole function description comment is quite
> useless, it can be just removed with no losses.
> 
ACK. will remove it all.>> + * @res: tpg module resources table
>> + * @id: tpg module id
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +int msm_tpg_subdev_init(struct camss *camss,
>> +            struct tpg_device *tpg,
>> +            const struct camss_subdev_resources *res, u8 id)
>> +{
>> +    struct platform_device *pdev;
>> +    struct device *dev;
>> +    int i, j;
>> +
>> +    dev  = camss->dev;
>> +    pdev = to_platform_device(dev);
>> +
>> +    tpg->camss = camss;
>> +    tpg->id = id;
>> +    tpg->res = &res->tpg;
>> +    tpg->res->hw_ops->subdev_init(tpg);
>> +
>> +    tpg->base = devm_platform_ioremap_resource_byname(pdev, res- 
>> >reg[0]);
>> +    if (IS_ERR(tpg->base))
>> +        return PTR_ERR(tpg->base);
>> +
>> +    tpg->nclocks = 0;
>> +    while (res->clock[tpg->nclocks])
>> +        tpg->nclocks++;
>> +
>> +    if (tpg->nclocks) {
> 
> if (!tpg->nclocks)
>      return 0;
> 
ACK.>> +        tpg->clock = devm_kcalloc(dev,
>> +                      tpg->nclocks, sizeof(*tpg->clock),
>> +                      GFP_KERNEL);
>> +        if (!tpg->clock)
>> +            return -ENOMEM;
>> +
>> +        for (i = 0; i < tpg->nclocks; i++) {
>> +            struct camss_clock *clock = &tpg->clock[i];
>> +
>> +            clock->clk = devm_clk_get(dev, res->clock[i]);
>> +            if (IS_ERR(clock->clk))
>> +                return PTR_ERR(clock->clk);
>> +
>> +            clock->name = res->clock[i];
>> +
>> +            clock->nfreqs = 0;
>> +            while (res->clock_rate[i][clock->nfreqs])
>> +                clock->nfreqs++;
>> +
>> +            if (!clock->nfreqs) {
>> +                clock->freq = NULL;
>> +                continue;
>> +            }
>> +
>> +            clock->freq = devm_kcalloc(dev,
>> +                           clock->nfreqs,
>> +                           sizeof(*clock->freq),
>> +                           GFP_KERNEL);
>> +            if (!clock->freq)
>> +                return -ENOMEM;
>> +
>> +            for (j = 0; j < clock->nfreqs; j++)
>> +                clock->freq[j] = res->clock_rate[i][j];
>> +        }
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_link_setup - Setup tpg connections
>> + * @entity: Pointer to media entity structure
>> + * @local: Pointer to local pad
>> + * @remote: Pointer to remote pad
>> + * @flags: Link flags
>> + *
>> + * Return 0 on success
>> + */
>> +static int tpg_link_setup(struct media_entity *entity,
>> +              const struct media_pad *local,
>> +              const struct media_pad *remote, u32 flags)
>> +{
>> +    if (flags & MEDIA_LNK_FL_ENABLED)
>> +        if (media_pad_remote_pad_first(local))
>> +            return -EBUSY;
>> +
>> +    return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops tpg_core_ops = {
>> +    .s_power = tpg_set_power,
>> +};
>> +
>> +static const struct v4l2_subdev_video_ops tpg_video_ops = {
>> +    .s_stream = tpg_set_stream,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
>> +    .enum_mbus_code = tpg_enum_mbus_code,
>> +    .enum_frame_size = tpg_enum_frame_size,
>> +    .get_fmt = tpg_get_format,
>> +    .set_fmt = tpg_set_format,
>> +};
>> +
>> +static const struct v4l2_subdev_ops tpg_v4l2_ops = {
>> +    .core = &tpg_core_ops,
>> +    .video = &tpg_video_ops,
>> +    .pad = &tpg_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
>> +    .open = tpg_init_formats,
>> +};
>> +
>> +static const struct media_entity_operations tpg_media_ops = {
>> +    .link_setup = tpg_link_setup,
>> +    .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +/*
>> + * msm_tpg_register_entity - Register subdev node for tpg module
>> + * @tpg: tpg device
>> + * @v4l2_dev: V4L2 device
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>> +                struct v4l2_device *v4l2_dev)
>> +{
>> +    struct v4l2_subdev *sd = &tpg->subdev;
>> +    struct media_pad *pads = tpg->pads;
>> +    struct device *dev = tpg->camss->dev;
>> +    int ret;
>> +
>> +    v4l2_subdev_init(sd, &tpg_v4l2_ops);
>> +    sd->internal_ops = &tpg_v4l2_internal_ops;
>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
>> +             V4L2_SUBDEV_FL_HAS_EVENTS;
>> +    snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
>> +         MSM_TPG_NAME, tpg->id);
>> +    sd->grp_id = TPG_GUP_ID;
>> +    v4l2_set_subdevdata(sd, tpg);
>> +
>> +    ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
>> +        return ret;
>> +    }
>> +
>> +    tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
>> +                             &tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
>> +                             tpg->testgen.nmodes, 0, 0,
>> +                             tpg->testgen.modes);
>> +
> 
> Please remove a blank line above.
> 
ACK.>> +    if (tpg->ctrls.error) {
>> +        dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
>> +        ret = tpg->ctrls.error;
>> +        goto free_ctrl;
>> +    }
>> +
>> +    tpg->subdev.ctrl_handler = &tpg->ctrls;
>> +
>> +    ret = tpg_init_formats(sd, NULL);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to init format: %d\n", ret);
>> +        goto free_ctrl;
>> +    }
>> +
>> +    pads[MSM_TPG_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +    pads[MSM_TPG_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> 
> Why do you need two pads for TPG?
> 
will fix it next version.

>> +
>> +    sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> 
> TPG is not a video pixel encoding converter device.
> 

How about MEDIA_ENT_F_CAM_SENSOR?


>> +    sd->entity.ops = &tpg_media_ops;
>> +    ret = media_entity_pads_init(&sd->entity, MSM_TPG_PADS_NUM, pads);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to init media entity: %d\n", ret);
>> +        goto free_ctrl;
>> +    }
>> +
>> +    ret = v4l2_device_register_subdev(v4l2_dev, sd);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to register subdev: %d\n", ret);
>> +        media_entity_cleanup(&sd->entity);
>> +        goto free_ctrl;
>> +    }
>> +
>> +    return 0;
>> +
>> +free_ctrl:
>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>> +
>> +    return ret;
>> +}
>> +
>> +/*
>> + * msm_tpg_unregister_entity - Unregister tpg module subdev node
>> + * @tpg: tpg device
>> + */
>> +void msm_tpg_unregister_entity(struct tpg_device *tpg)
>> +{
>> +    v4l2_device_unregister_subdev(&tpg->subdev);
>> +    media_entity_cleanup(&tpg->subdev.entity);
>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>> +}
>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.h b/drivers/ 
>> media/platform/qcom/camss/camss-tpg.h
>> new file mode 100644
>> index 
>> 0000000000000000000000000000000000000000..1a16addac19418f2f11d0b8abb1c865c99888bde
>> --- /dev/null
>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.h
>> @@ -0,0 +1,127 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * camss-tpg.h
>> + *
>> + * Qualcomm MSM Camera Subsystem - TPG Module
>> + *
>> + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights 
>> reserved.
>> + */
>> +#ifndef QC_MSM_CAMSS_TPG_H
>> +#define QC_MSM_CAMSS_TPG_H
>> +
>> +#include <linux/clk.h>
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-mediabus.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define MSM_TPG_PAD_SINK 0
>> +#define MSM_TPG_PAD_SRC 1
>> +#define MSM_TPG_PADS_NUM 2
>> +
>> +#define DATA_TYPE_RAW_8BIT        0x2a
>> +#define DATA_TYPE_RAW_10BIT        0x2b
>> +#define DATA_TYPE_RAW_12BIT        0x2c
> 
> Remove all above to get the macro values from include/media/mipi-csi2.h
> 
ACK.>> +
>> +#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT    0x1
>> +#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT    0x2
>> +#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT    0x3
>> +#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT    0x4
>> +#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT    0x5
>> +#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT    0x6
>> +#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT    0x7
>> +
>> +#define TPG_GUP_ID 0
>> +#define MSM_TPG_NAME "msm_tpg"
> 
> Remove the macro, it's used only once in the code and more usecases are
> not expected happen.
> 
ACK.>> +
>> +enum tpg_testgen_mode {
>> +    TPG_PAYLOAD_MODE_DISABLED = 0,
>> +    TPG_PAYLOAD_MODE_INCREMENTING = 1,
>> +    TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
>> +    TPG_PAYLOAD_MODE_RANDOM = 5,
>> +    TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
>> +    TPG_PAYLOAD_MODE_COLOR_BARS = 9,
>> +    TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
>> +};
>> +
>> +struct tpg_testgen_config {
>> +    enum tpg_testgen_mode mode;
>> +    const char * const*modes;
>> +    u8 nmodes;
>> +};
>> +
>> +struct tpg_format_info {
>> +    u32 code;
>> +    u8 data_type;
>> +    u8 encode_format;
>> +    u8 bpp;
>> +};
>> +
>> +struct tpg_formats {
>> +    unsigned int nformats;
>> +    const struct tpg_format_info *formats;
>> +};
>> +
>> +struct tpg_device;
>> +
>> +struct tpg_hw_ops {
>> +    int (*configure_stream)(struct tpg_device *tpg, u8 enable);
>> +
>> +    int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
>> +
>> +    u32 (*hw_version)(struct tpg_device *tpg);
>> +
>> +    int (*reset)(struct tpg_device *tpg);
>> +
>> +    void (*subdev_init)(struct tpg_device *tpg);
>> +};
>> +
>> +struct tpg_subdev_resources {
>> +    u8 lane_cnt;
>> +    u8 vc_cnt;
>> +    const struct tpg_formats *formats;
>> +    const struct tpg_hw_ops *hw_ops;
>> +};
>> +
>> +struct tpg_device {
>> +    struct camss *camss;
>> +    u8 id;
>> +    struct v4l2_subdev subdev;
>> +    struct media_pad pads[MSM_TPG_PADS_NUM];
>> +    void __iomem *base;
>> +    struct camss_clock *clock;
>> +    int nclocks;
>> +    struct tpg_testgen_config testgen;
>> +    struct v4l2_mbus_framefmt fmt[MSM_TPG_PADS_NUM];
>> +    struct v4l2_ctrl_handler ctrls;
>> +    struct v4l2_ctrl *testgen_mode;
>> +    const struct tpg_subdev_resources *res;
>> +    const struct tpg_format *formats;
>> +    unsigned int nformats;
>> +    u32 hw_version;
>> +};
>> +
>> +struct camss_subdev_resources;
>> +
>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
>> +                        const struct tpg_format_info *formats,
>> +                        unsigned int nformats,
>> +                        u32 code);
>> +
>> +int msm_tpg_subdev_init(struct camss *camss,
>> +            struct tpg_device *tpg,
>> +            const struct camss_subdev_resources *res, u8 id);
>> +
>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>> +                struct v4l2_device *v4l2_dev);
>> +
>> +void msm_tpg_unregister_entity(struct tpg_device *tpg);
>> +
>> +extern const char * const testgen_payload_modes[];
>> +
>> +extern const struct tpg_formats tpg_formats_gen1;
>> +
>> +extern const struct tpg_hw_ops tpg_ops_gen1;
>> +
>> +#endif /* QC_MSM_CAMSS_TPG_H */
>> diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/ 
>> media/platform/qcom/camss/camss.h
>> index 
>> 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..a892a87bed8bde8919200d6eac2b7a5338763c0e 100644
>> --- a/drivers/media/platform/qcom/camss/camss.h
>> +++ b/drivers/media/platform/qcom/camss/camss.h
>> @@ -21,6 +21,7 @@
>>   #include "camss-csid.h"
>>   #include "camss-csiphy.h"
>>   #include "camss-ispif.h"
>> +#include "camss-tpg.h"
>>   #include "camss-vfe.h"
>>   #include "camss-format.h"
>> @@ -52,6 +53,7 @@ struct camss_subdev_resources {
>>       char *interrupt[CAMSS_RES_MAX];
>>       union {
>>           struct csiphy_subdev_resources csiphy;
>> +        struct tpg_subdev_resources tpg;
>>           struct csid_subdev_resources csid;
>>           struct vfe_subdev_resources vfe;
>>       };
>> @@ -104,6 +106,7 @@ struct camss_resources {
>>       enum camss_version version;
>>       const char *pd_name;
>>       const struct camss_subdev_resources *csiphy_res;
>> +    const struct camss_subdev_resources *tpg_res;
>>       const struct camss_subdev_resources *csid_res;
>>       const struct camss_subdev_resources *ispif_res;
>>       const struct camss_subdev_resources *vfe_res;
>> @@ -111,6 +114,7 @@ struct camss_resources {
>>       const struct resources_icc *icc_res;
>>       const unsigned int icc_path_num;
>>       const unsigned int csiphy_num;
>> +    const unsigned int tpg_num;
>>       const unsigned int csid_num;
>>       const unsigned int vfe_num;
>>   };
>> @@ -121,6 +125,7 @@ struct camss {
>>       struct media_device media_dev;
>>       struct device *dev;
>>       struct csiphy_device *csiphy;
>> +    struct tpg_device *tpg;
>>       struct csid_device *csid;
>>       struct ispif_device *ispif;
>>       struct vfe_device *vfe;
>>
> 

Best regards,
Wenmeng


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-14  3:04     ` Wenmeng Liu
@ 2026-01-14  5:05       ` Vladimir Zapolskiy
  2026-01-14  9:43         ` Konrad Dybcio
  2026-01-14 12:18         ` Wenmeng Liu
  0 siblings, 2 replies; 19+ messages in thread
From: Vladimir Zapolskiy @ 2026-01-14  5:05 UTC (permalink / raw)
  To: Wenmeng Liu, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

Hi Wenmeng.

On 1/14/26 05:04, Wenmeng Liu wrote:
> Hi Vladimir,
> 
> On 1/14/2026 12:27 AM, Vladimir Zapolskiy wrote:
>> Hello Wenmeng.
>>
>> On 1/13/26 11:03, Wenmeng Liu wrote:
>>> Introduce a new common Test Pattern Generator (TPG) implementation for
>>> Qualcomm CAMSS. This module provides a generic interface for pattern
>>> generation that can be reused by multiple platforms.
>>>
>>> Unlike CSID-integrated TPG, this TPG acts as a standalone block
>>> that emulates both CSIPHY and sensor behavior, enabling flexible test
>>> patterns without external hardware.
>>>
>>> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
>>> ---
>>>    drivers/media/platform/qcom/camss/Makefile    |   1 +
>>>    drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++++++++
>>> ++++++++++
>>>    drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>>>    drivers/media/platform/qcom/camss/camss.h     |   5 +
>>>    4 files changed, 843 insertions(+)
>>>
>>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/
>>> media/platform/qcom/camss/Makefile
>>> index
>>> 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
>>> --- a/drivers/media/platform/qcom/camss/Makefile
>>> +++ b/drivers/media/platform/qcom/camss/Makefile
>>> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>>>            camss-vfe.o \
>>>            camss-video.o \
>>>            camss-format.o \
>>> +        camss-tpg.o \
>>
>> While you're here, please sort and keep the lines in alphabetical order.
> ACK.
> 
>>
>>>    obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/
>>> media/platform/qcom/camss/camss-tpg.c
>>> new file mode 100644
>>> index
>>> 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
>>> --- /dev/null
>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
>>> @@ -0,0 +1,710 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + *
>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>> + *
>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>> + */
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/io.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/of.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <media/media-entity.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-subdev.h>
>>> +
>>> +#include "camss-tpg.h"
>>> +#include "camss.h"
>>> +
>>> +const char * const testgen_payload_modes[] = {
>>> +    "Disabled",
>>> +    "Incrementing",
>>> +    "Alternating 0x55/0xAA",
>>> +    "Reserved",
>>> +    "Reserved",
>>> +    "Pseudo-random Data",
>>> +    "User Specified",
>>> +    "Reserved",
>>> +    "Reserved",
>>> +    "Color bars",
>>> +    "Reserved"
>>
>> It makes little sense to mention the unsupported values, and then
>> introduce enum tpg_testgen_mode to list the supported ones.
>>
> This is for ctrl menu, will do as follow:
> static const char * const testgen_payload_modes[] = {
>       [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
>       [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
>       [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating 0x55/0xAA",
>       [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
>       [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
>       [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
> };
> 

This is also not perfect, still userspace is misinformed about a number
of possible TPG modes vs. a number of actually supported TPG modes.

>>> +};
>>
>> Are these test pattern modes specific to TPG Gen1 only?
>>
>> CSID TPG uses a different 'csid_testgen_modes' list, and if the list
>> above is
>> TPG Gen1 specific, it would make sense to place it right in camss-tpg-
>> gen1.c
>>
> 
> Like other CAMSS nodes, the files placed in the core are meant to
> maintain consistency with the others.

Please elaborate, what does it mean "files placed in the core"?

If technically possible, all local data shall be placed closer to the code,
which uses it, and here the exported data is exported for nothing, but
some "consistency".

You draw a line what data to place into the generic camss-tpg.c and to the
specific camss-tpg-gen1.c, you should explain why data specific to TPG Gen1
is placed into the generic code, while you've already set a different and
dedicated place/file for storing this type of data?

>>> +
>>> +static const struct tpg_format_info formats_gen1[] = {
>>> +    {
>>> +        MEDIA_BUS_FMT_SBGGR8_1X8,
>>> +        DATA_TYPE_RAW_8BIT,
>>
>> Please replace it with MIPI_CSI2_DT_RAW8
>>
> 
> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>> +        8,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SGBRG8_1X8,
>>> +        DATA_TYPE_RAW_8BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>> +        8,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SGRBG8_1X8,
>>> +        DATA_TYPE_RAW_8BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>> +        8,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SRGGB8_1X8,
>>> +        DATA_TYPE_RAW_8BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>> +        8,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SBGGR10_1X10,
>>> +        DATA_TYPE_RAW_10BIT,
>>
>> MIPI_CSI2_DT_RAW10>
> 
> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>> +        10,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SGBRG10_1X10,
>>> +        DATA_TYPE_RAW_10BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>> +        10,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SGRBG10_1X10,
>>> +        DATA_TYPE_RAW_10BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>> +        10,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SRGGB10_1X10,
>>> +        DATA_TYPE_RAW_10BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>> +        10,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SBGGR12_1X12,
>>> +        DATA_TYPE_RAW_12BIT,
>>
>> MIPI_CSI2_DT_RAW12
>>
> 
> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>> +        12,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SGBRG12_1X12,
>>> +        DATA_TYPE_RAW_12BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>> +        12,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SGRBG12_1X12,
>>> +        DATA_TYPE_RAW_12BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>> +        12,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_SRGGB12_1X12,
>>> +        DATA_TYPE_RAW_12BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>> +        12,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_Y8_1X8,
>>> +        DATA_TYPE_RAW_8BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>> +        8,
>>> +    },
>>> +    {
>>> +        MEDIA_BUS_FMT_Y10_1X10,
>>> +        DATA_TYPE_RAW_10BIT,
>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>> +        10,
>>> +    },
>>> +};
>>> +
>>> +const struct tpg_formats tpg_formats_gen1 = {
>>> +    .nformats = ARRAY_SIZE(formats_gen1),
>>> +    .formats = formats_gen1
>>> +};
>>> +
>>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
>>> +                        const struct tpg_format_info *formats,
>>> +                        unsigned int nformats,
>>> +                        u32 code)
>>> +{
>>> +    struct device *dev = tpg->camss->dev;
>>> +    size_t i;
>>
>> unsigned int i, size_t is very unexpected to get here.
>>
> 
> I have received comments on this.
> 
> https://lore.kernel.org/all/449ac3c3-1f6a-4e69-899d-c4e4577714a4@oss.qualcomm.com/
> 
> https://staticthinking.wordpress.com/2022/06/01/unsigned-int-i-is-stupid/>>
> +

I see, in every programming language without type inference, including C, types
shall be as precise as possible. Here the usage of signed int or size_t is stupid,
because for a human being it adds completely wasted efforts to comprehend, what
does happen, when the local variable is negative, while it just can not be such.

>>> +    for (i = 0; i < nformats; i++)
>>> +        if (code == formats[i].code)
>>> +            return &formats[i];
>>> +
>>> +    dev_warn(dev, "Unknown pixel format code=0x%08x\n", code);
>>
>> Please remove dev_warn() completely, it opens a way to flood the kernel
>> log.
>>
> 
> This is an exception printout and will not appear under normal
> circumstances.>> +

If it can be trivially triggered from the userspace, it is not an exception.

I haven't yet tested the changes, what does happen, when an unsupported pixel
format is asked to be set from userspace? It's a regular operation, and it
shall not litter the kernel log buffer.

>>> +    return ERR_PTR(-EINVAL);

This one is sufficient.

>>> +}
>>> +
>>> +/*
>>> + * tpg_set_clock_rates - set clock rates on tpg module
>>> + * @tpg: tpg device
>>> + */
>>> +static int tpg_set_clock_rates(struct tpg_device *tpg)
>>> +{
>>> +    struct device *dev = tpg->camss->dev;
>>> +    int ret;
>>> +    int i;
>>> +
>>> +    for (i = 0; i < tpg->nclocks; i++) {
>>> +        struct camss_clock *clock = &tpg->clock[i];
>>> +        long round_rate;
>>> +
>>> +        if (clock->freq) {
>>> +            round_rate = clk_round_rate(clock->clk, clock->freq[0]);
>>> +            if (round_rate < 0) {
>>> +                dev_err(dev, "clk round rate failed: %ld\n",
>>> +                    round_rate);
>>> +                return -EINVAL;
>>> +            }
>>> +
>>> +            ret = clk_set_rate(clock->clk, round_rate);
>>> +            if (ret < 0) {
>>> +                dev_err(dev, "clk set rate failed: %d\n", ret);
>>> +                return ret;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_set_power - Power on/off tpg module
>>> + * @sd: tpg V4L2 subdevice
>>> + * @on: Requested power state
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +static int tpg_set_power(struct v4l2_subdev *sd, int on)
>>> +{
>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>> +    struct device *dev = tpg->camss->dev;
>>> +
>>> +    if (on) {
>>> +        int ret;
>>> +
>>> +        ret = pm_runtime_resume_and_get(dev);
>>> +        if (ret < 0)
>>> +            return ret;
>>> +
>>> +        ret = tpg_set_clock_rates(tpg);
>>> +        if (ret < 0) {
>>> +            pm_runtime_put_sync(dev);
>>> +            return ret;
>>> +        }
>>> +
>>> +        ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
>>> +        if (ret < 0) {
>>> +            pm_runtime_put_sync(dev);
>>> +            return ret;
>>> +        }
>>> +
>>> +        tpg->res->hw_ops->reset(tpg);
>>> +
>>> +        tpg->res->hw_ops->hw_version(tpg);
>>> +    } else {
>>> +        camss_disable_clocks(tpg->nclocks, tpg->clock);
>>> +
>>> +        pm_runtime_put_sync(dev);
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_set_stream - Enable/disable streaming on tpg module
>>> + * @sd: tpg V4L2 subdevice
>>> + * @enable: Requested streaming state
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
>>> +{
>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>> +    int ret = 0;
>>> +
>>> +    if (enable) {
>>> +        ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
>>> +        if (ret < 0) {
>>> +            dev_err(tpg->camss->dev,
>>> +                "could not sync v4l2 controls: %d\n", ret);
>>> +            return ret;
>>> +        }
>>> +    }
>>> +
>>> +    ret = tpg->res->hw_ops->configure_stream(tpg, enable);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/*
>>> + * __tpg_get_format - Get pointer to format structure
>>> + * @tpg: tpg device
>>> + * @cfg: V4L2 subdev pad configuration
>>
>> There is no such function argument. There are much more errors in
>> the doxygen descriptions of functions, please remove all these
>> doxygen comments, they do not bring anything valuable here.
>>
> 
> 
> ACK.

For clarity, I haven't commented all errors in the doxygen descriptions,
because I expect that all of them will be removed.

>> + * @pad: pad from which format is requested
>>> + * @which: TRY or ACTIVE format
>>> + *
>>> + * Return pointer to TRY or ACTIVE format structure
>>> + */
>>> +static struct v4l2_mbus_framefmt *
>>> +__tpg_get_format(struct tpg_device *tpg,
>>> +         struct v4l2_subdev_state *sd_state,
>>> +         unsigned int pad,
>>> +         enum v4l2_subdev_format_whence which)
>>> +{
>>> +    if (which == V4L2_SUBDEV_FORMAT_TRY)
>>> +        return v4l2_subdev_state_get_format(sd_state,
>>> +                            pad);
>>> +
>>> +    return &tpg->fmt[pad];
>>> +}
>>> +
>>> +/*
>>> + * tpg_try_format - Handle try format by pad subdev method
>>> + * @tpg: tpg device
>>> + * @cfg: V4L2 subdev pad configuration
>>
>> No such argument.
>>
> 
> ACK.>> + * @pad: pad on which format is requested
>>> + * @fmt: pointer to v4l2 format structure
>>> + * @which: wanted subdev format
>>> + */
>>> +static void tpg_try_format(struct tpg_device *tpg,
>>> +               struct v4l2_subdev_state *sd_state,
>>> +               unsigned int pad,
>>> +               struct v4l2_mbus_framefmt *fmt,
>>> +               enum v4l2_subdev_format_whence which)
>>> +{
>>> +    unsigned int i;
>>> +
>>> +    switch (pad) {
>>> +    case MSM_TPG_PAD_SINK:
>>> +        for (i = 0; i < tpg->res->formats->nformats; i++)
>>> +            if (tpg->res->formats->formats[i].code == fmt->code)
>>> +                break;
>>> +
>>> +        /* If not found, use SBGGR8 as default */
>>> +        if (i >= tpg->res->formats->nformats)
>>> +            fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
>>> +
>>> +        fmt->width = clamp_t(u32, fmt->width, 1, 8191);
>>> +        fmt->height = clamp_t(u32, fmt->height, 1, 8191);
>>> +
>>> +        fmt->field = V4L2_FIELD_NONE;
>>> +        fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>> +
>>> +        break;
>>> +    case MSM_TPG_PAD_SRC:
>>> +        *fmt = *__tpg_get_format(tpg, sd_state,
>>> +                     MSM_TPG_PAD_SINK,
>>> +                     which);
>>> +
>>> +        break;
>>> +    }
>>> +}
>>> +
>>> +/*
>>> + * tpg_enum_mbus_code - Handle format enumeration
>>> + * @sd: tpg V4L2 subdevice
>>> + * @cfg: V4L2 subdev pad configuration
>>
>> No such argument.
>>
> 
> ACK.>> + * @code: pointer to v4l2_subdev_mbus_code_enum structure
>>> + * return -EINVAL or zero on success
>>> + */
>>> +static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
>>> +                  struct v4l2_subdev_state *sd_state,
>>> +                  struct v4l2_subdev_mbus_code_enum *code)
>>> +{
>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>> +    struct v4l2_mbus_framefmt *format;
>>> +
>>> +    if (code->pad == MSM_TPG_PAD_SINK) {
>>> +        if (code->index >= tpg->res->formats->nformats)
>>> +            return -EINVAL;
>>> +
>>> +        code->code = tpg->res->formats->formats[code->index].code;
>>> +    } else {
>>> +        if (code->index > 0)
>>> +            return -EINVAL;
>>> +
>>> +        format = __tpg_get_format(tpg, sd_state,
>>> +                      MSM_TPG_PAD_SINK,
>>> +                      code->which);
>>> +
>>> +        code->code = format->code;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_enum_frame_size - Handle frame size enumeration
>>> + * @sd: tpg V4L2 subdevice
>>> + * @cfg: V4L2 subdev pad configuration
>>> + * @fse: pointer to v4l2_subdev_frame_size_enum structure
>>> + * return -EINVAL or zero on success
>>> + */
>>> +static int tpg_enum_frame_size(struct v4l2_subdev *sd,
>>> +                   struct v4l2_subdev_state *sd_state,
>>> +                   struct v4l2_subdev_frame_size_enum *fse)
>>> +{
>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>> +    struct v4l2_mbus_framefmt format;
>>> +
>>> +    if (fse->index != 0)
>>> +        return -EINVAL;
>>> +
>>> +    format.code = fse->code;
>>> +    format.width = 1;
>>> +    format.height = 1;
>>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>>> +    fse->min_width = format.width;
>>> +    fse->min_height = format.height;
>>> +
>>> +    if (format.code != fse->code)
>>> +        return -EINVAL;
>>> +
>>> +    format.code = fse->code;
>>> +    format.width = -1;
>>> +    format.height = -1;
>>
>> format.width and format.height are rewritten in the tpg_try_format(),
>> so it makes no sense to assign them.
>>
> 
> This is use for pass parameters in.
> 
>> The problem is that for whatever reason you can tpg_try_format() twice
>> in a raw, it looks wrong, and I'm certain you can modify the functions
>> so that only one call would be needed.
>> The first call is to get the supported minimum size, and the second call
> is to get the maximum size.
> 
> The tpg_try_format function can be used multiple times; I don't think
> it's necessary to write a new interface.

I didn't ask to write a new interface, please reread my comment.

Yon can write tpg_enum_frame_size() function, that tpg_try_format()
is not called at all, but for an unclear reason here it's called twice.

>>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>>> +    fse->max_width = format.width;
>>> +    fse->max_height = format.height;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_get_format - Handle get format by pads subdev method
>>> + * @sd: tpg V4L2 subdevice
>>> + * @cfg: V4L2 subdev pad configuration
>>> + * @fmt: pointer to v4l2 subdev format structure
>>> + *
>>> + * Return -EINVAL or zero on success
>>> + */
>>> +static int tpg_get_format(struct v4l2_subdev *sd,
>>> +              struct v4l2_subdev_state *sd_state,
>>> +              struct v4l2_subdev_format *fmt)
>>> +{
>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>> +    struct v4l2_mbus_framefmt *format;
>>> +
>>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>>> +    if (!format)
>>> +        return -EINVAL;
>>> +
>>> +    fmt->format = *format;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_set_format - Handle set format by pads subdev method
>>> + * @sd: tpg V4L2 subdevice
>>> + * @cfg: V4L2 subdev pad configuration
>>> + * @fmt: pointer to v4l2 subdev format structure
>>> + *
>>> + * Return -EINVAL or zero on success
>>> + */
>>> +static int tpg_set_format(struct v4l2_subdev *sd,
>>> +              struct v4l2_subdev_state *sd_state,
>>> +              struct v4l2_subdev_format *fmt)
>>> +{
>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>> +    struct v4l2_mbus_framefmt *format;
>>> +
>>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>>> +    if (!format)
>>> +        return -EINVAL;
>>> +
>>> +    tpg_try_format(tpg, sd_state, fmt->pad, &fmt->format,
>>> +               fmt->which);
>>> +    *format = fmt->format;
>>> +
>>> +    if (fmt->pad == MSM_TPG_PAD_SINK) {
>>> +        format = __tpg_get_format(tpg, sd_state,
>>> +                      MSM_TPG_PAD_SRC,
>>> +                      fmt->which);
>>> +
>>> +        *format = fmt->format;
>>> +        tpg_try_format(tpg, sd_state, MSM_TPG_PAD_SRC,
>>> +                   format,
>>> +                   fmt->which);
>>> +    }
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_init_formats - Initialize formats on all pads
>>> + * @sd: tpg V4L2 subdevice
>>> + * @fh: V4L2 subdev file handle
>>> + *
>>> + * Initialize all pad formats with default values.
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +static int tpg_init_formats(struct v4l2_subdev *sd,
>>> +                struct v4l2_subdev_fh *fh)
>>> +{
>>> +    struct v4l2_subdev_format format = {
>>> +        .pad = MSM_TPG_PAD_SINK,
>>> +        .which = fh ? V4L2_SUBDEV_FORMAT_TRY :
>>> +                  V4L2_SUBDEV_FORMAT_ACTIVE,
>>> +        .format = {
>>> +            .code = MEDIA_BUS_FMT_SBGGR8_1X8,
>>> +            .width = 1920,
>>> +            .height = 1080
>>> +        }
>>> +    };
>>> +
>>> +    return tpg_set_format(sd, fh ? fh->state : NULL, &format);
>>> +}
>>> +
>>> +/*
>>> + * tpg_set_test_pattern - Set test generator's pattern mode
>>> + * @tpg: TPG device
>>> + * @value: desired test pattern mode
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +static int tpg_set_test_pattern(struct tpg_device *tpg, s32 value)
>>> +{
>>> +    return tpg->res->hw_ops->configure_testgen_pattern(tpg, value);
>>> +}
>>> +
>>> +/*
>>> + * tpg_s_ctrl - Handle set control subdev method
>>> + * @ctrl: pointer to v4l2 control structure
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
>>> +{
>>> +    struct tpg_device *tpg = container_of(ctrl->handler,
>>> +                          struct tpg_device, ctrls);
>>> +    int ret = -EINVAL;
>>> +
>>> +    switch (ctrl->id) {
>>> +    case V4L2_CID_TEST_PATTERN:
>>> +        ret = tpg_set_test_pattern(tpg, ctrl->val);
>>> +        break;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
>>> +    .s_ctrl = tpg_s_ctrl,
>>> +};
>>> +
>>> +/*
>>> + * msm_tpg_subdev_init - Initialize tpg device structure and resources
>>> + * @tpg: tpg device
>>
>> And no 'camss' argument. The whole function description comment is quite
>> useless, it can be just removed with no losses.
>>
> ACK. will remove it all.

Good, thank you.

>> + * @res: tpg module resources table
>>> + * @id: tpg module id
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +int msm_tpg_subdev_init(struct camss *camss,
>>> +            struct tpg_device *tpg,
>>> +            const struct camss_subdev_resources *res, u8 id)
>>> +{
>>> +    struct platform_device *pdev;
>>> +    struct device *dev;
>>> +    int i, j;
>>> +
>>> +    dev  = camss->dev;
>>> +    pdev = to_platform_device(dev);
>>> +
>>> +    tpg->camss = camss;
>>> +    tpg->id = id;
>>> +    tpg->res = &res->tpg;
>>> +    tpg->res->hw_ops->subdev_init(tpg);
>>> +
>>> +    tpg->base = devm_platform_ioremap_resource_byname(pdev, res-
>>>> reg[0]);
>>> +    if (IS_ERR(tpg->base))
>>> +        return PTR_ERR(tpg->base);
>>> +
>>> +    tpg->nclocks = 0;
>>> +    while (res->clock[tpg->nclocks])
>>> +        tpg->nclocks++;
>>> +
>>> +    if (tpg->nclocks) {
>>
>> if (!tpg->nclocks)
>>       return 0;
>>
> ACK.>> +        tpg->clock = devm_kcalloc(dev,
>>> +                      tpg->nclocks, sizeof(*tpg->clock),
>>> +                      GFP_KERNEL);
>>> +        if (!tpg->clock)
>>> +            return -ENOMEM;
>>> +
>>> +        for (i = 0; i < tpg->nclocks; i++) {
>>> +            struct camss_clock *clock = &tpg->clock[i];
>>> +
>>> +            clock->clk = devm_clk_get(dev, res->clock[i]);
>>> +            if (IS_ERR(clock->clk))
>>> +                return PTR_ERR(clock->clk);
>>> +
>>> +            clock->name = res->clock[i];
>>> +
>>> +            clock->nfreqs = 0;
>>> +            while (res->clock_rate[i][clock->nfreqs])
>>> +                clock->nfreqs++;
>>> +
>>> +            if (!clock->nfreqs) {
>>> +                clock->freq = NULL;
>>> +                continue;
>>> +            }
>>> +
>>> +            clock->freq = devm_kcalloc(dev,
>>> +                           clock->nfreqs,
>>> +                           sizeof(*clock->freq),
>>> +                           GFP_KERNEL);
>>> +            if (!clock->freq)
>>> +                return -ENOMEM;
>>> +
>>> +            for (j = 0; j < clock->nfreqs; j++)
>>> +                clock->freq[j] = res->clock_rate[i][j];
>>> +        }
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_link_setup - Setup tpg connections
>>> + * @entity: Pointer to media entity structure
>>> + * @local: Pointer to local pad
>>> + * @remote: Pointer to remote pad
>>> + * @flags: Link flags
>>> + *
>>> + * Return 0 on success
>>> + */
>>> +static int tpg_link_setup(struct media_entity *entity,
>>> +              const struct media_pad *local,
>>> +              const struct media_pad *remote, u32 flags)
>>> +{
>>> +    if (flags & MEDIA_LNK_FL_ENABLED)
>>> +        if (media_pad_remote_pad_first(local))
>>> +            return -EBUSY;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static const struct v4l2_subdev_core_ops tpg_core_ops = {
>>> +    .s_power = tpg_set_power,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_video_ops tpg_video_ops = {
>>> +    .s_stream = tpg_set_stream,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
>>> +    .enum_mbus_code = tpg_enum_mbus_code,
>>> +    .enum_frame_size = tpg_enum_frame_size,
>>> +    .get_fmt = tpg_get_format,
>>> +    .set_fmt = tpg_set_format,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops tpg_v4l2_ops = {
>>> +    .core = &tpg_core_ops,
>>> +    .video = &tpg_video_ops,
>>> +    .pad = &tpg_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
>>> +    .open = tpg_init_formats,
>>> +};
>>> +
>>> +static const struct media_entity_operations tpg_media_ops = {
>>> +    .link_setup = tpg_link_setup,
>>> +    .link_validate = v4l2_subdev_link_validate,
>>> +};
>>> +
>>> +/*
>>> + * msm_tpg_register_entity - Register subdev node for tpg module
>>> + * @tpg: tpg device
>>> + * @v4l2_dev: V4L2 device
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>>> +                struct v4l2_device *v4l2_dev)
>>> +{
>>> +    struct v4l2_subdev *sd = &tpg->subdev;
>>> +    struct media_pad *pads = tpg->pads;
>>> +    struct device *dev = tpg->camss->dev;
>>> +    int ret;
>>> +
>>> +    v4l2_subdev_init(sd, &tpg_v4l2_ops);
>>> +    sd->internal_ops = &tpg_v4l2_internal_ops;
>>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
>>> +             V4L2_SUBDEV_FL_HAS_EVENTS;
>>> +    snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
>>> +         MSM_TPG_NAME, tpg->id);
>>> +    sd->grp_id = TPG_GUP_ID;
>>> +    v4l2_set_subdevdata(sd, tpg);
>>> +
>>> +    ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +
>>> +    tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
>>> +                             &tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
>>> +                             tpg->testgen.nmodes, 0, 0,
>>> +                             tpg->testgen.modes);
>>> +
>>
>> Please remove a blank line above.
>>
> ACK.>> +    if (tpg->ctrls.error) {
>>> +        dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
>>> +        ret = tpg->ctrls.error;
>>> +        goto free_ctrl;
>>> +    }
>>> +
>>> +    tpg->subdev.ctrl_handler = &tpg->ctrls;
>>> +
>>> +    ret = tpg_init_formats(sd, NULL);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to init format: %d\n", ret);
>>> +        goto free_ctrl;
>>> +    }
>>> +
>>> +    pads[MSM_TPG_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>> +    pads[MSM_TPG_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>
>> Why do you need two pads for TPG?
>>
> will fix it next version.
> 
>>> +
>>> +    sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>
>> TPG is not a video pixel encoding converter device.
>>
> 
> How about MEDIA_ENT_F_CAM_SENSOR?
> 

Still not perfect, but probably it's better... At least it won't implicitly
assume a presense of two pads on the media entity.

>>> +    sd->entity.ops = &tpg_media_ops;
>>> +    ret = media_entity_pads_init(&sd->entity, MSM_TPG_PADS_NUM, pads);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to init media entity: %d\n", ret);
>>> +        goto free_ctrl;
>>> +    }
>>> +
>>> +    ret = v4l2_device_register_subdev(v4l2_dev, sd);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to register subdev: %d\n", ret);
>>> +        media_entity_cleanup(&sd->entity);
>>> +        goto free_ctrl;
>>> +    }
>>> +
>>> +    return 0;
>>> +
>>> +free_ctrl:
>>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/*
>>> + * msm_tpg_unregister_entity - Unregister tpg module subdev node
>>> + * @tpg: tpg device
>>> + */
>>> +void msm_tpg_unregister_entity(struct tpg_device *tpg)
>>> +{
>>> +    v4l2_device_unregister_subdev(&tpg->subdev);
>>> +    media_entity_cleanup(&tpg->subdev.entity);
>>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>>> +}
>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.h b/drivers/
>>> media/platform/qcom/camss/camss-tpg.h
>>> new file mode 100644
>>> index
>>> 0000000000000000000000000000000000000000..1a16addac19418f2f11d0b8abb1c865c99888bde
>>> --- /dev/null
>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.h
>>> @@ -0,0 +1,127 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * camss-tpg.h
>>> + *
>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>> + *
>>> + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights
>>> reserved.
>>> + */
>>> +#ifndef QC_MSM_CAMSS_TPG_H
>>> +#define QC_MSM_CAMSS_TPG_H
>>> +
>>> +#include <linux/clk.h>
>>> +#include <media/media-entity.h>
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-device.h>
>>> +#include <media/v4l2-mediabus.h>
>>> +#include <media/v4l2-subdev.h>
>>> +
>>> +#define MSM_TPG_PAD_SINK 0
>>> +#define MSM_TPG_PAD_SRC 1
>>> +#define MSM_TPG_PADS_NUM 2
>>> +
>>> +#define DATA_TYPE_RAW_8BIT        0x2a
>>> +#define DATA_TYPE_RAW_10BIT        0x2b
>>> +#define DATA_TYPE_RAW_12BIT        0x2c
>>
>> Remove all above to get the macro values from include/media/mipi-csi2.h
>>
> ACK.>> +
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT    0x1
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT    0x2
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT    0x3
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT    0x4
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT    0x5
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT    0x6
>>> +#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT    0x7
>>> +
>>> +#define TPG_GUP_ID 0

In addition to the previous review comments, please change the value to
something other than default 0. This type of data can be placed outside
of TPG specific code, it has a potential for further outbreak.

>>> +#define MSM_TPG_NAME "msm_tpg"
>>
>> Remove the macro, it's used only once in the code and more usecases are
>> not expected happen.
>>
> ACK.

>> +
>>> +enum tpg_testgen_mode {
>>> +    TPG_PAYLOAD_MODE_DISABLED = 0,
>>> +  �� TPG_PAYLOAD_MODE_INCREMENTING = 1,
>>> +    TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
>>> +    TPG_PAYLOAD_MODE_RANDOM = 5,
>>> +    TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
>>> +    TPG_PAYLOAD_MODE_COLOR_BARS = 9,
>>> +    TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
>>> +};
>>> +
>>> +struct tpg_testgen_config {
>>> +    enum tpg_testgen_mode mode;
>>> +    const char * const*modes;
>>> +    u8 nmodes;
>>> +};
>>> +
>>> +struct tpg_format_info {
>>> +    u32 code;
>>> +    u8 data_type;
>>> +    u8 encode_format;
>>> +    u8 bpp;
>>> +};
>>> +
>>> +struct tpg_formats {
>>> +    unsigned int nformats;
>>> +    const struct tpg_format_info *formats;
>>> +};
>>> +
>>> +struct tpg_device;
>>> +
>>> +struct tpg_hw_ops {
>>> +    int (*configure_stream)(struct tpg_device *tpg, u8 enable);
>>> +
>>> +    int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
>>> +
>>> +    u32 (*hw_version)(struct tpg_device *tpg);
>>> +
>>> +    int (*reset)(struct tpg_device *tpg);
>>> +
>>> +    void (*subdev_init)(struct tpg_device *tpg);
>>> +};
>>> +
>>> +struct tpg_subdev_resources {
>>> +    u8 lane_cnt;
>>> +    u8 vc_cnt;
>>> +    const struct tpg_formats *formats;
>>> +    const struct tpg_hw_ops *hw_ops;
>>> +};
>>> +
>>> +struct tpg_device {
>>> +    struct camss *camss;
>>> +    u8 id;
>>> +    struct v4l2_subdev subdev;
>>> +    struct media_pad pads[MSM_TPG_PADS_NUM];
>>> +    void __iomem *base;
>>> +    struct camss_clock *clock;
>>> +    int nclocks;
>>> +    struct tpg_testgen_config testgen;
>>> +    struct v4l2_mbus_framefmt fmt[MSM_TPG_PADS_NUM];
>>> +    struct v4l2_ctrl_handler ctrls;
>>> +    struct v4l2_ctrl *testgen_mode;
>>> +    const struct tpg_subdev_resources *res;
>>> +    const struct tpg_format *formats;
>>> +    unsigned int nformats;
>>> +    u32 hw_version;
>>> +};
>>> +
>>> +struct camss_subdev_resources;
>>> +
>>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device *tpg,
>>> +                        const struct tpg_format_info *formats,
>>> +                        unsigned int nformats,
>>> +                        u32 code);
>>> +
>>> +int msm_tpg_subdev_init(struct camss *camss,
>>> +            struct tpg_device *tpg,
>>> +            const struct camss_subdev_resources *res, u8 id);
>>> +
>>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>>> +                struct v4l2_device *v4l2_dev);
>>> +
>>> +void msm_tpg_unregister_entity(struct tpg_device *tpg);
>>> +
>>> +extern const char * const testgen_payload_modes[];
>>> +
>>> +extern const struct tpg_formats tpg_formats_gen1;
>>> +
>>> +extern const struct tpg_hw_ops tpg_ops_gen1;
>>> +
>>> +#endif /* QC_MSM_CAMSS_TPG_H */
>>> diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/
>>> media/platform/qcom/camss/camss.h
>>> index
>>> 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..a892a87bed8bde8919200d6eac2b7a5338763c0e 100644
>>> --- a/drivers/media/platform/qcom/camss/camss.h
>>> +++ b/drivers/media/platform/qcom/camss/camss.h
>>> @@ -21,6 +21,7 @@
>>>    #include "camss-csid.h"
>>>    #include "camss-csiphy.h"
>>>    #include "camss-ispif.h"
>>> +#include "camss-tpg.h"
>>>    #include "camss-vfe.h"
>>>    #include "camss-format.h"
>>> @@ -52,6 +53,7 @@ struct camss_subdev_resources {
>>>        char *interrupt[CAMSS_RES_MAX];
>>>        union {
>>>            struct csiphy_subdev_resources csiphy;
>>> +        struct tpg_subdev_resources tpg;
>>>            struct csid_subdev_resources csid;
>>>            struct vfe_subdev_resources vfe;
>>>        };
>>> @@ -104,6 +106,7 @@ struct camss_resources {
>>>        enum camss_version version;
>>>        const char *pd_name;
>>>        const struct camss_subdev_resources *csiphy_res;
>>> +    const struct camss_subdev_resources *tpg_res;
>>>        const struct camss_subdev_resources *csid_res;
>>>        const struct camss_subdev_resources *ispif_res;
>>>        const struct camss_subdev_resources *vfe_res;
>>> @@ -111,6 +114,7 @@ struct camss_resources {
>>>        const struct resources_icc *icc_res;
>>>        const unsigned int icc_path_num;
>>>        const unsigned int csiphy_num;
>>> +    const unsigned int tpg_num;
>>>        const unsigned int csid_num;
>>>        const unsigned int vfe_num;
>>>    };
>>> @@ -121,6 +125,7 @@ struct camss {
>>>        struct media_device media_dev;
>>>        struct device *dev;
>>>        struct csiphy_device *csiphy;
>>> +    struct tpg_device *tpg;
>>>        struct csid_device *csid;
>>>        struct ispif_device *ispif;
>>>        struct vfe_device *vfe;
>>>
>>

-- 
Best wishes,
Vladimir


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-14  5:05       ` Vladimir Zapolskiy
@ 2026-01-14  9:43         ` Konrad Dybcio
  2026-01-14 12:18         ` Wenmeng Liu
  1 sibling, 0 replies; 19+ messages in thread
From: Konrad Dybcio @ 2026-01-14  9:43 UTC (permalink / raw)
  To: Vladimir Zapolskiy, Wenmeng Liu, Robert Foss, Todor Tomov,
	Bryan O'Donoghue, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

On 1/14/26 6:05 AM, Vladimir Zapolskiy wrote:
> Hi Wenmeng.
> 
> On 1/14/26 05:04, Wenmeng Liu wrote:
>> Hi Vladimir,

[...]

>>>> +const char * const testgen_payload_modes[] = {
>>>> +    "Disabled",
>>>> +    "Incrementing",
>>>> +    "Alternating 0x55/0xAA",
>>>> +    "Reserved",
>>>> +    "Reserved",
>>>> +    "Pseudo-random Data",
>>>> +    "User Specified",
>>>> +    "Reserved",
>>>> +    "Reserved",
>>>> +    "Color bars",
>>>> +    "Reserved"
>>>
>>> It makes little sense to mention the unsupported values, and then
>>> introduce enum tpg_testgen_mode to list the supported ones.
>>>
>> This is for ctrl menu, will do as follow:
>> static const char * const testgen_payload_modes[] = {
>>       [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
>>       [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
>>       [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating 0x55/0xAA",
>>       [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
>>       [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
>>       [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
>> };
>>
> 
> This is also not perfect, still userspace is misinformed about a number
> of possible TPG modes vs. a number of actually supported TPG modes.

If the values are really reserved (i.e. don't do anything (useful) today),
let's just throw an -EOPNOTSUPP if selected

Konrad

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-14  5:05       ` Vladimir Zapolskiy
  2026-01-14  9:43         ` Konrad Dybcio
@ 2026-01-14 12:18         ` Wenmeng Liu
  2026-01-14 22:07           ` Vladimir Zapolskiy
  1 sibling, 1 reply; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-14 12:18 UTC (permalink / raw)
  To: Vladimir Zapolskiy, Robert Foss, Todor Tomov,
	Bryan O'Donoghue, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm


Hi Vladimir,


On 1/14/2026 1:05 PM, Vladimir Zapolskiy wrote:
> Hi Wenmeng.
> 
> On 1/14/26 05:04, Wenmeng Liu wrote:
>> Hi Vladimir,
>>
>> On 1/14/2026 12:27 AM, Vladimir Zapolskiy wrote:
>>> Hello Wenmeng.
>>>
>>> On 1/13/26 11:03, Wenmeng Liu wrote:
>>>> Introduce a new common Test Pattern Generator (TPG) implementation for
>>>> Qualcomm CAMSS. This module provides a generic interface for pattern
>>>> generation that can be reused by multiple platforms.
>>>>
>>>> Unlike CSID-integrated TPG, this TPG acts as a standalone block
>>>> that emulates both CSIPHY and sensor behavior, enabling flexible test
>>>> patterns without external hardware.
>>>>
>>>> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
>>>> ---
>>>>    drivers/media/platform/qcom/camss/Makefile    |   1 +
>>>>    drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++++++++
>>>> ++++++++++
>>>>    drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>>>>    drivers/media/platform/qcom/camss/camss.h     |   5 +
>>>>    4 files changed, 843 insertions(+)
>>>>
>>>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/
>>>> media/platform/qcom/camss/Makefile
>>>> index
>>>> 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
>>>> --- a/drivers/media/platform/qcom/camss/Makefile
>>>> +++ b/drivers/media/platform/qcom/camss/Makefile
>>>> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>>>>            camss-vfe.o \
>>>>            camss-video.o \
>>>>            camss-format.o \
>>>> +        camss-tpg.o \
>>>
>>> While you're here, please sort and keep the lines in alphabetical order.
>> ACK.
>>
>>>
>>>>    obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/
>>>> media/platform/qcom/camss/camss-tpg.c
>>>> new file mode 100644
>>>> index
>>>> 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
>>>> @@ -0,0 +1,710 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + *
>>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>>> + *
>>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>>> + */
>>>> +#include <linux/clk.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/kernel.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <media/media-entity.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +#include "camss-tpg.h"
>>>> +#include "camss.h"
>>>> +
>>>> +const char * const testgen_payload_modes[] = {
>>>> +    "Disabled",
>>>> +    "Incrementing",
>>>> +    "Alternating 0x55/0xAA",
>>>> +    "Reserved",
>>>> +    "Reserved",
>>>> +    "Pseudo-random Data",
>>>> +    "User Specified",
>>>> +    "Reserved",
>>>> +    "Reserved",
>>>> +    "Color bars",
>>>> +    "Reserved"
>>>
>>> It makes little sense to mention the unsupported values, and then
>>> introduce enum tpg_testgen_mode to list the supported ones.
>>>
>> This is for ctrl menu, will do as follow:
>> static const char * const testgen_payload_modes[] = {
>>       [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
>>       [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
>>       [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating 
>> 0x55/0xAA",
>>       [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
>>       [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
>>       [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
>> };
>>
> 
> This is also not perfect, still userspace is misinformed about a number
> of possible TPG modes vs. a number of actually supported TPG modes.
> 
0x0: INCREMENTING
0x1: ALTERNATING_55_AA
0x4: RANDOM
0x5: USER_SPECIFIED
0x8: COLOR_BARS

These values come from the register configuration, these pattern values 
are consistent with the CSID TPG.
If want to make it continuous, need to add a mapping table. How about this?

>>>> +};
>>>
>>> Are these test pattern modes specific to TPG Gen1 only?
>>>
>>> CSID TPG uses a different 'csid_testgen_modes' list, and if the list
>>> above is
>>> TPG Gen1 specific, it would make sense to place it right in camss-tpg-
>>> gen1.c
>>>
>>
>> Like other CAMSS nodes, the files placed in the core are meant to
>> maintain consistency with the others.
> 
> Please elaborate, what does it mean "files placed in the core"?
> 
sorry, let me clarify this,
core file: camss-cisd.c camss-csiphy.c ...
hw version file: camss-csid-gen2.c camss-tpg-gen1.c
  > If technically possible, all local data shall be placed closer to 
the code,
> which uses it, and here the exported data is exported for nothing, but
> some "consistency".
> 
> You draw a line what data to place into the generic camss-tpg.c and to the
> specific camss-tpg-gen1.c, you should explain why data specific to TPG Gen1
> is placed into the generic code, while you've already set a different and
> dedicated place/file for storing this type of data?
> 
The reason I want to implement it this way is that the TPG's ctrl 
requires this structure for initialization (msm_tpg_register_entity).

Also it`s ok to push it to gen1 file.

>>>> +
>>>> +static const struct tpg_format_info formats_gen1[] = {
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SBGGR8_1X8,
>>>> +        DATA_TYPE_RAW_8BIT,
>>>
>>> Please replace it with MIPI_CSI2_DT_RAW8
>>>
>>
>> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>> +        8,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SGBRG8_1X8,
>>>> +        DATA_TYPE_RAW_8BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>> +        8,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SGRBG8_1X8,
>>>> +        DATA_TYPE_RAW_8BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>> +        8,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SRGGB8_1X8,
>>>> +        DATA_TYPE_RAW_8BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>> +        8,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SBGGR10_1X10,
>>>> +        DATA_TYPE_RAW_10BIT,
>>>
>>> MIPI_CSI2_DT_RAW10>
>>
>> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>> +        10,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> +        DATA_TYPE_RAW_10BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>> +        10,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> +        DATA_TYPE_RAW_10BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>> +        10,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> +        DATA_TYPE_RAW_10BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>> +        10,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SBGGR12_1X12,
>>>> +        DATA_TYPE_RAW_12BIT,
>>>
>>> MIPI_CSI2_DT_RAW12
>>>
>>
>> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>> +        12,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SGBRG12_1X12,
>>>> +        DATA_TYPE_RAW_12BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>> +        12,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SGRBG12_1X12,
>>>> +        DATA_TYPE_RAW_12BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>> +        12,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_SRGGB12_1X12,
>>>> +        DATA_TYPE_RAW_12BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>> +        12,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_Y8_1X8,
>>>> +        DATA_TYPE_RAW_8BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>> +        8,
>>>> +    },
>>>> +    {
>>>> +        MEDIA_BUS_FMT_Y10_1X10,
>>>> +        DATA_TYPE_RAW_10BIT,
>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>> +        10,
>>>> +    },
>>>> +};
>>>> +
>>>> +const struct tpg_formats tpg_formats_gen1 = {
>>>> +    .nformats = ARRAY_SIZE(formats_gen1),
>>>> +    .formats = formats_gen1
>>>> +};
>>>> +
>>>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device 
>>>> *tpg,
>>>> +                        const struct tpg_format_info *formats,
>>>> +                        unsigned int nformats,
>>>> +                        u32 code)
>>>> +{
>>>> +    struct device *dev = tpg->camss->dev;
>>>> +    size_t i;
>>>
>>> unsigned int i, size_t is very unexpected to get here.
>>>
>>
>> I have received comments on this.
>>
>> https://lore.kernel.org/all/449ac3c3-1f6a-4e69-899d- 
>> c4e4577714a4@oss.qualcomm.com/
>>
>> https://staticthinking.wordpress.com/2022/06/01/unsigned-int-i-is- 
>> stupid/>>
>> +
> 
> I see, in every programming language without type inference, including 
> C, types
> shall be as precise as possible. Here the usage of signed int or size_t 
> is stupid,
> because for a human being it adds completely wasted efforts to 
> comprehend, what
> does happen, when the local variable is negative, while it just can not 
> be such.
> 
ACK, will fix it.

>>>> +    for (i = 0; i < nformats; i++)
>>>> +        if (code == formats[i].code)
>>>> +            return &formats[i];
>>>> +
>>>> +    dev_warn(dev, "Unknown pixel format code=0x%08x\n", code);
>>>
>>> Please remove dev_warn() completely, it opens a way to flood the kernel
>>> log.
>>>
>>
>> This is an exception printout and will not appear under normal
>> circumstances.>> +
> 
> If it can be trivially triggered from the userspace, it is not an 
> exception.
> 
> I haven't yet tested the changes, what does happen, when an unsupported 
> pixel
> format is asked to be set from userspace? It's a regular operation, and it
> shall not litter the kernel log buffer.
> 
>>>> +    return ERR_PTR(-EINVAL);
> 
> This one is sufficient.
> 

ACK.>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_set_clock_rates - set clock rates on tpg module
>>>> + * @tpg: tpg device
>>>> + */
>>>> +static int tpg_set_clock_rates(struct tpg_device *tpg)
>>>> +{
>>>> +    struct device *dev = tpg->camss->dev;
>>>> +    int ret;
>>>> +    int i;
>>>> +
>>>> +    for (i = 0; i < tpg->nclocks; i++) {
>>>> +        struct camss_clock *clock = &tpg->clock[i];
>>>> +        long round_rate;
>>>> +
>>>> +        if (clock->freq) {
>>>> +            round_rate = clk_round_rate(clock->clk, clock->freq[0]);
>>>> +            if (round_rate < 0) {
>>>> +                dev_err(dev, "clk round rate failed: %ld\n",
>>>> +                    round_rate);
>>>> +                return -EINVAL;
>>>> +            }
>>>> +
>>>> +            ret = clk_set_rate(clock->clk, round_rate);
>>>> +            if (ret < 0) {
>>>> +                dev_err(dev, "clk set rate failed: %d\n", ret);
>>>> +                return ret;
>>>> +            }
>>>> +        }
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_set_power - Power on/off tpg module
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @on: Requested power state
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +static int tpg_set_power(struct v4l2_subdev *sd, int on)
>>>> +{
>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>> +    struct device *dev = tpg->camss->dev;
>>>> +
>>>> +    if (on) {
>>>> +        int ret;
>>>> +
>>>> +        ret = pm_runtime_resume_and_get(dev);
>>>> +        if (ret < 0)
>>>> +            return ret;
>>>> +
>>>> +        ret = tpg_set_clock_rates(tpg);
>>>> +        if (ret < 0) {
>>>> +            pm_runtime_put_sync(dev);
>>>> +            return ret;
>>>> +        }
>>>> +
>>>> +        ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
>>>> +        if (ret < 0) {
>>>> +            pm_runtime_put_sync(dev);
>>>> +            return ret;
>>>> +        }
>>>> +
>>>> +        tpg->res->hw_ops->reset(tpg);
>>>> +
>>>> +        tpg->res->hw_ops->hw_version(tpg);
>>>> +    } else {
>>>> +        camss_disable_clocks(tpg->nclocks, tpg->clock);
>>>> +
>>>> +        pm_runtime_put_sync(dev);
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_set_stream - Enable/disable streaming on tpg module
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @enable: Requested streaming state
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
>>>> +{
>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>> +    int ret = 0;
>>>> +
>>>> +    if (enable) {
>>>> +        ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
>>>> +        if (ret < 0) {
>>>> +            dev_err(tpg->camss->dev,
>>>> +                "could not sync v4l2 controls: %d\n", ret);
>>>> +            return ret;
>>>> +        }
>>>> +    }
>>>> +
>>>> +    ret = tpg->res->hw_ops->configure_stream(tpg, enable);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +/*
>>>> + * __tpg_get_format - Get pointer to format structure
>>>> + * @tpg: tpg device
>>>> + * @cfg: V4L2 subdev pad configuration
>>>
>>> There is no such function argument. There are much more errors in
>>> the doxygen descriptions of functions, please remove all these
>>> doxygen comments, they do not bring anything valuable here.
>>>
>>
>>
>> ACK.
> 
> For clarity, I haven't commented all errors in the doxygen descriptions,
> because I expect that all of them will be removed.
> 

Yes, I will removed it all.

>>> + * @pad: pad from which format is requested
>>>> + * @which: TRY or ACTIVE format
>>>> + *
>>>> + * Return pointer to TRY or ACTIVE format structure
>>>> + */
>>>> +static struct v4l2_mbus_framefmt *
>>>> +__tpg_get_format(struct tpg_device *tpg,
>>>> +         struct v4l2_subdev_state *sd_state,
>>>> +         unsigned int pad,
>>>> +         enum v4l2_subdev_format_whence which)
>>>> +{
>>>> +    if (which == V4L2_SUBDEV_FORMAT_TRY)
>>>> +        return v4l2_subdev_state_get_format(sd_state,
>>>> +                            pad);
>>>> +
>>>> +    return &tpg->fmt[pad];
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_try_format - Handle try format by pad subdev method
>>>> + * @tpg: tpg device
>>>> + * @cfg: V4L2 subdev pad configuration
>>>
>>> No such argument.
>>>
>>
>> ACK.>> + * @pad: pad on which format is requested
>>>> + * @fmt: pointer to v4l2 format structure
>>>> + * @which: wanted subdev format
>>>> + */
>>>> +static void tpg_try_format(struct tpg_device *tpg,
>>>> +               struct v4l2_subdev_state *sd_state,
>>>> +               unsigned int pad,
>>>> +               struct v4l2_mbus_framefmt *fmt,
>>>> +               enum v4l2_subdev_format_whence which)
>>>> +{
>>>> +    unsigned int i;
>>>> +
>>>> +    switch (pad) {
>>>> +    case MSM_TPG_PAD_SINK:
>>>> +        for (i = 0; i < tpg->res->formats->nformats; i++)
>>>> +            if (tpg->res->formats->formats[i].code == fmt->code)
>>>> +                break;
>>>> +
>>>> +        /* If not found, use SBGGR8 as default */
>>>> +        if (i >= tpg->res->formats->nformats)
>>>> +            fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
>>>> +
>>>> +        fmt->width = clamp_t(u32, fmt->width, 1, 8191);
>>>> +        fmt->height = clamp_t(u32, fmt->height, 1, 8191);
>>>> +
>>>> +        fmt->field = V4L2_FIELD_NONE;
>>>> +        fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> +
>>>> +        break;
>>>> +    case MSM_TPG_PAD_SRC:
>>>> +        *fmt = *__tpg_get_format(tpg, sd_state,
>>>> +                     MSM_TPG_PAD_SINK,
>>>> +                     which);
>>>> +
>>>> +        break;
>>>> +    }
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_enum_mbus_code - Handle format enumeration
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @cfg: V4L2 subdev pad configuration
>>>
>>> No such argument.
>>>
>>
>> ACK.>> + * @code: pointer to v4l2_subdev_mbus_code_enum structure
>>>> + * return -EINVAL or zero on success
>>>> + */
>>>> +static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +                  struct v4l2_subdev_state *sd_state,
>>>> +                  struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>> +    struct v4l2_mbus_framefmt *format;
>>>> +
>>>> +    if (code->pad == MSM_TPG_PAD_SINK) {
>>>> +        if (code->index >= tpg->res->formats->nformats)
>>>> +            return -EINVAL;
>>>> +
>>>> +        code->code = tpg->res->formats->formats[code->index].code;
>>>> +    } else {
>>>> +        if (code->index > 0)
>>>> +            return -EINVAL;
>>>> +
>>>> +        format = __tpg_get_format(tpg, sd_state,
>>>> +                      MSM_TPG_PAD_SINK,
>>>> +                      code->which);
>>>> +
>>>> +        code->code = format->code;
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_enum_frame_size - Handle frame size enumeration
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @cfg: V4L2 subdev pad configuration
>>>> + * @fse: pointer to v4l2_subdev_frame_size_enum structure
>>>> + * return -EINVAL or zero on success
>>>> + */
>>>> +static int tpg_enum_frame_size(struct v4l2_subdev *sd,
>>>> +                   struct v4l2_subdev_state *sd_state,
>>>> +                   struct v4l2_subdev_frame_size_enum *fse)
>>>> +{
>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>> +    struct v4l2_mbus_framefmt format;
>>>> +
>>>> +    if (fse->index != 0)
>>>> +        return -EINVAL;
>>>> +
>>>> +    format.code = fse->code;
>>>> +    format.width = 1;
>>>> +    format.height = 1;
>>>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>>>> +    fse->min_width = format.width;
>>>> +    fse->min_height = format.height;
>>>> +
>>>> +    if (format.code != fse->code)
>>>> +        return -EINVAL;
>>>> +
>>>> +    format.code = fse->code;
>>>> +    format.width = -1;
>>>> +    format.height = -1;
>>>
>>> format.width and format.height are rewritten in the tpg_try_format(),
>>> so it makes no sense to assign them.
>>>
>>
>> This is use for pass parameters in.
>>
>>> The problem is that for whatever reason you can tpg_try_format() twice
>>> in a raw, it looks wrong, and I'm certain you can modify the functions
>>> so that only one call would be needed.
>>> The first call is to get the supported minimum size, and the second call
>> is to get the maximum size.
>>
>> The tpg_try_format function can be used multiple times; I don't think
>> it's necessary to write a new interface.
> 
> I didn't ask to write a new interface, please reread my comment.
> 
> Yon can write tpg_enum_frame_size() function, that tpg_try_format()
> is not called at all, but for an unclear reason here it's called twice.
> 
Sure, it will be implemented in a simpler way.

>>>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>>>> +    fse->max_width = format.width;
>>>> +    fse->max_height = format.height;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_get_format - Handle get format by pads subdev method
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @cfg: V4L2 subdev pad configuration
>>>> + * @fmt: pointer to v4l2 subdev format structure
>>>> + *
>>>> + * Return -EINVAL or zero on success
>>>> + */
>>>> +static int tpg_get_format(struct v4l2_subdev *sd,
>>>> +              struct v4l2_subdev_state *sd_state,
>>>> +              struct v4l2_subdev_format *fmt)
>>>> +{
>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>> +    struct v4l2_mbus_framefmt *format;
>>>> +
>>>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>>>> +    if (!format)
>>>> +        return -EINVAL;
>>>> +
>>>> +    fmt->format = *format;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_set_format - Handle set format by pads subdev method
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @cfg: V4L2 subdev pad configuration
>>>> + * @fmt: pointer to v4l2 subdev format structure
>>>> + *
>>>> + * Return -EINVAL or zero on success
>>>> + */
>>>> +static int tpg_set_format(struct v4l2_subdev *sd,
>>>> +              struct v4l2_subdev_state *sd_state,
>>>> +              struct v4l2_subdev_format *fmt)
>>>> +{
>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>> +    struct v4l2_mbus_framefmt *format;
>>>> +
>>>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>>>> +    if (!format)
>>>> +        return -EINVAL;
>>>> +
>>>> +    tpg_try_format(tpg, sd_state, fmt->pad, &fmt->format,
>>>> +               fmt->which);
>>>> +    *format = fmt->format;
>>>> +
>>>> +    if (fmt->pad == MSM_TPG_PAD_SINK) {
>>>> +        format = __tpg_get_format(tpg, sd_state,
>>>> +                      MSM_TPG_PAD_SRC,
>>>> +                      fmt->which);
>>>> +
>>>> +        *format = fmt->format;
>>>> +        tpg_try_format(tpg, sd_state, MSM_TPG_PAD_SRC,
>>>> +                   format,
>>>> +                   fmt->which);
>>>> +    }
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_init_formats - Initialize formats on all pads
>>>> + * @sd: tpg V4L2 subdevice
>>>> + * @fh: V4L2 subdev file handle
>>>> + *
>>>> + * Initialize all pad formats with default values.
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +static int tpg_init_formats(struct v4l2_subdev *sd,
>>>> +                struct v4l2_subdev_fh *fh)
>>>> +{
>>>> +    struct v4l2_subdev_format format = {
>>>> +        .pad = MSM_TPG_PAD_SINK,
>>>> +        .which = fh ? V4L2_SUBDEV_FORMAT_TRY :
>>>> +                  V4L2_SUBDEV_FORMAT_ACTIVE,
>>>> +        .format = {
>>>> +            .code = MEDIA_BUS_FMT_SBGGR8_1X8,
>>>> +            .width = 1920,
>>>> +            .height = 1080
>>>> +        }
>>>> +    };
>>>> +
>>>> +    return tpg_set_format(sd, fh ? fh->state : NULL, &format);
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_set_test_pattern - Set test generator's pattern mode
>>>> + * @tpg: TPG device
>>>> + * @value: desired test pattern mode
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +static int tpg_set_test_pattern(struct tpg_device *tpg, s32 value)
>>>> +{
>>>> +    return tpg->res->hw_ops->configure_testgen_pattern(tpg, value);
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_s_ctrl - Handle set control subdev method
>>>> + * @ctrl: pointer to v4l2 control structure
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
>>>> +{
>>>> +    struct tpg_device *tpg = container_of(ctrl->handler,
>>>> +                          struct tpg_device, ctrls);
>>>> +    int ret = -EINVAL;
>>>> +
>>>> +    switch (ctrl->id) {
>>>> +    case V4L2_CID_TEST_PATTERN:
>>>> +        ret = tpg_set_test_pattern(tpg, ctrl->val);
>>>> +        break;
>>>> +    }
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
>>>> +    .s_ctrl = tpg_s_ctrl,
>>>> +};
>>>> +
>>>> +/*
>>>> + * msm_tpg_subdev_init - Initialize tpg device structure and resources
>>>> + * @tpg: tpg device
>>>
>>> And no 'camss' argument. The whole function description comment is quite
>>> useless, it can be just removed with no losses.
>>>
>> ACK. will remove it all.
> 
> Good, thank you.
> 
>>> + * @res: tpg module resources table
>>>> + * @id: tpg module id
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +int msm_tpg_subdev_init(struct camss *camss,
>>>> +            struct tpg_device *tpg,
>>>> +            const struct camss_subdev_resources *res, u8 id)
>>>> +{
>>>> +    struct platform_device *pdev;
>>>> +    struct device *dev;
>>>> +    int i, j;
>>>> +
>>>> +    dev  = camss->dev;
>>>> +    pdev = to_platform_device(dev);
>>>> +
>>>> +    tpg->camss = camss;
>>>> +    tpg->id = id;
>>>> +    tpg->res = &res->tpg;
>>>> +    tpg->res->hw_ops->subdev_init(tpg);
>>>> +
>>>> +    tpg->base = devm_platform_ioremap_resource_byname(pdev, res-
>>>>> reg[0]);
>>>> +    if (IS_ERR(tpg->base))
>>>> +        return PTR_ERR(tpg->base);
>>>> +
>>>> +    tpg->nclocks = 0;
>>>> +    while (res->clock[tpg->nclocks])
>>>> +        tpg->nclocks++;
>>>> +
>>>> +    if (tpg->nclocks) {
>>>
>>> if (!tpg->nclocks)
>>>       return 0;
>>>
>> ACK.>> +        tpg->clock = devm_kcalloc(dev,
>>>> +                      tpg->nclocks, sizeof(*tpg->clock),
>>>> +                      GFP_KERNEL);
>>>> +        if (!tpg->clock)
>>>> +            return -ENOMEM;
>>>> +
>>>> +        for (i = 0; i < tpg->nclocks; i++) {
>>>> +            struct camss_clock *clock = &tpg->clock[i];
>>>> +
>>>> +            clock->clk = devm_clk_get(dev, res->clock[i]);
>>>> +            if (IS_ERR(clock->clk))
>>>> +                return PTR_ERR(clock->clk);
>>>> +
>>>> +            clock->name = res->clock[i];
>>>> +
>>>> +            clock->nfreqs = 0;
>>>> +            while (res->clock_rate[i][clock->nfreqs])
>>>> +                clock->nfreqs++;
>>>> +
>>>> +            if (!clock->nfreqs) {
>>>> +                clock->freq = NULL;
>>>> +                continue;
>>>> +            }
>>>> +
>>>> +            clock->freq = devm_kcalloc(dev,
>>>> +                           clock->nfreqs,
>>>> +                           sizeof(*clock->freq),
>>>> +                           GFP_KERNEL);
>>>> +            if (!clock->freq)
>>>> +                return -ENOMEM;
>>>> +
>>>> +            for (j = 0; j < clock->nfreqs; j++)
>>>> +                clock->freq[j] = res->clock_rate[i][j];
>>>> +        }
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * tpg_link_setup - Setup tpg connections
>>>> + * @entity: Pointer to media entity structure
>>>> + * @local: Pointer to local pad
>>>> + * @remote: Pointer to remote pad
>>>> + * @flags: Link flags
>>>> + *
>>>> + * Return 0 on success
>>>> + */
>>>> +static int tpg_link_setup(struct media_entity *entity,
>>>> +              const struct media_pad *local,
>>>> +              const struct media_pad *remote, u32 flags)
>>>> +{
>>>> +    if (flags & MEDIA_LNK_FL_ENABLED)
>>>> +        if (media_pad_remote_pad_first(local))
>>>> +            return -EBUSY;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_core_ops tpg_core_ops = {
>>>> +    .s_power = tpg_set_power,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_video_ops tpg_video_ops = {
>>>> +    .s_stream = tpg_set_stream,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
>>>> +    .enum_mbus_code = tpg_enum_mbus_code,
>>>> +    .enum_frame_size = tpg_enum_frame_size,
>>>> +    .get_fmt = tpg_get_format,
>>>> +    .set_fmt = tpg_set_format,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops tpg_v4l2_ops = {
>>>> +    .core = &tpg_core_ops,
>>>> +    .video = &tpg_video_ops,
>>>> +    .pad = &tpg_pad_ops,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
>>>> +    .open = tpg_init_formats,
>>>> +};
>>>> +
>>>> +static const struct media_entity_operations tpg_media_ops = {
>>>> +    .link_setup = tpg_link_setup,
>>>> +    .link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +/*
>>>> + * msm_tpg_register_entity - Register subdev node for tpg module
>>>> + * @tpg: tpg device
>>>> + * @v4l2_dev: V4L2 device
>>>> + *
>>>> + * Return 0 on success or a negative error code otherwise
>>>> + */
>>>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>>>> +                struct v4l2_device *v4l2_dev)
>>>> +{
>>>> +    struct v4l2_subdev *sd = &tpg->subdev;
>>>> +    struct media_pad *pads = tpg->pads;
>>>> +    struct device *dev = tpg->camss->dev;
>>>> +    int ret;
>>>> +
>>>> +    v4l2_subdev_init(sd, &tpg_v4l2_ops);
>>>> +    sd->internal_ops = &tpg_v4l2_internal_ops;
>>>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
>>>> +             V4L2_SUBDEV_FL_HAS_EVENTS;
>>>> +    snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
>>>> +         MSM_TPG_NAME, tpg->id);
>>>> +    sd->grp_id = TPG_GUP_ID;
>>>> +    v4l2_set_subdevdata(sd, tpg);
>>>> +
>>>> +    ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
>>>> +                             &tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
>>>> +                             tpg->testgen.nmodes, 0, 0,
>>>> +                             tpg->testgen.modes);
>>>> +
>>>
>>> Please remove a blank line above.
>>>
>> ACK.>> +    if (tpg->ctrls.error) {
>>>> +        dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
>>>> +        ret = tpg->ctrls.error;
>>>> +        goto free_ctrl;
>>>> +    }
>>>> +
>>>> +    tpg->subdev.ctrl_handler = &tpg->ctrls;
>>>> +
>>>> +    ret = tpg_init_formats(sd, NULL);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to init format: %d\n", ret);
>>>> +        goto free_ctrl;
>>>> +    }
>>>> +
>>>> +    pads[MSM_TPG_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> +    pads[MSM_TPG_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>>
>>> Why do you need two pads for TPG?
>>>
>> will fix it next version.
>>
>>>> +
>>>> +    sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>>
>>> TPG is not a video pixel encoding converter device.
>>>
>>
>> How about MEDIA_ENT_F_CAM_SENSOR?
>>
> 
> Still not perfect, but probably it's better... At least it won't implicitly
> assume a presense of two pads on the media entity.
> 

Or just deleted it, if want to keep this,
will choose MEDIA_ENT_F_CAM_SENSOR.

and I will delete all sink code in this driver.

>>>> +    sd->entity.ops = &tpg_media_ops;
>>>> +    ret = media_entity_pads_init(&sd->entity, MSM_TPG_PADS_NUM, pads);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to init media entity: %d\n", ret);
>>>> +        goto free_ctrl;
>>>> +    }
>>>> +
>>>> +    ret = v4l2_device_register_subdev(v4l2_dev, sd);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to register subdev: %d\n", ret);
>>>> +        media_entity_cleanup(&sd->entity);
>>>> +        goto free_ctrl;
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +
>>>> +free_ctrl:
>>>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +/*
>>>> + * msm_tpg_unregister_entity - Unregister tpg module subdev node
>>>> + * @tpg: tpg device
>>>> + */
>>>> +void msm_tpg_unregister_entity(struct tpg_device *tpg)
>>>> +{
>>>> +    v4l2_device_unregister_subdev(&tpg->subdev);
>>>> +    media_entity_cleanup(&tpg->subdev.entity);
>>>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>>>> +}
>>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.h b/drivers/
>>>> media/platform/qcom/camss/camss-tpg.h
>>>> new file mode 100644
>>>> index
>>>> 0000000000000000000000000000000000000000..1a16addac19418f2f11d0b8abb1c865c99888bde
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.h
>>>> @@ -0,0 +1,127 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +/*
>>>> + * camss-tpg.h
>>>> + *
>>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>>> + *
>>>> + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights
>>>> reserved.
>>>> + */
>>>> +#ifndef QC_MSM_CAMSS_TPG_H
>>>> +#define QC_MSM_CAMSS_TPG_H
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <media/media-entity.h>
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-device.h>
>>>> +#include <media/v4l2-mediabus.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +#define MSM_TPG_PAD_SINK 0
>>>> +#define MSM_TPG_PAD_SRC 1
>>>> +#define MSM_TPG_PADS_NUM 2
>>>> +
>>>> +#define DATA_TYPE_RAW_8BIT        0x2a
>>>> +#define DATA_TYPE_RAW_10BIT        0x2b
>>>> +#define DATA_TYPE_RAW_12BIT        0x2c
>>>
>>> Remove all above to get the macro values from include/media/mipi-csi2.h
>>>
>> ACK.>> +
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT    0x1
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT    0x2
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT    0x3
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT    0x4
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT    0x5
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT    0x6
>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT    0x7
>>>> +
>>>> +#define TPG_GUP_ID 0
> 
> In addition to the previous review comments, please change the value to
> something other than default 0. This type of data can be placed outside
> of TPG specific code, it has a potential for further outbreak.
> 
ACK.

>>>> +#define MSM_TPG_NAME "msm_tpg"
>>>
>>> Remove the macro, it's used only once in the code and more usecases are
>>> not expected happen.
>>>
>> ACK.
> 
>>> +
>>>> +enum tpg_testgen_mode {
>>>> +    TPG_PAYLOAD_MODE_DISABLED = 0,
>>>> +  �� TPG_PAYLOAD_MODE_INCREMENTING = 1,
>>>> +    TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
>>>> +    TPG_PAYLOAD_MODE_RANDOM = 5,
>>>> +    TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
>>>> +    TPG_PAYLOAD_MODE_COLOR_BARS = 9,
>>>> +    TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
>>>> +};
>>>> +
>>>> +struct tpg_testgen_config {
>>>> +    enum tpg_testgen_mode mode;
>>>> +    const char * const*modes;
>>>> +    u8 nmodes;
>>>> +};
>>>> +
>>>> +struct tpg_format_info {
>>>> +    u32 code;
>>>> +    u8 data_type;
>>>> +    u8 encode_format;
>>>> +    u8 bpp;
>>>> +};
>>>> +
>>>> +struct tpg_formats {
>>>> +    unsigned int nformats;
>>>> +    const struct tpg_format_info *formats;
>>>> +};
>>>> +
>>>> +struct tpg_device;
>>>> +
>>>> +struct tpg_hw_ops {
>>>> +    int (*configure_stream)(struct tpg_device *tpg, u8 enable);
>>>> +
>>>> +    int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
>>>> +
>>>> +    u32 (*hw_version)(struct tpg_device *tpg);
>>>> +
>>>> +    int (*reset)(struct tpg_device *tpg);
>>>> +
>>>> +    void (*subdev_init)(struct tpg_device *tpg);
>>>> +};
>>>> +
>>>> +struct tpg_subdev_resources {
>>>> +    u8 lane_cnt;
>>>> +    u8 vc_cnt;
>>>> +    const struct tpg_formats *formats;
>>>> +    const struct tpg_hw_ops *hw_ops;
>>>> +};
>>>> +
>>>> +struct tpg_device {
>>>> +    struct camss *camss;
>>>> +    u8 id;
>>>> +    struct v4l2_subdev subdev;
>>>> +    struct media_pad pads[MSM_TPG_PADS_NUM];
>>>> +    void __iomem *base;
>>>> +    struct camss_clock *clock;
>>>> +    int nclocks;
>>>> +    struct tpg_testgen_config testgen;
>>>> +    struct v4l2_mbus_framefmt fmt[MSM_TPG_PADS_NUM];
>>>> +    struct v4l2_ctrl_handler ctrls;
>>>> +    struct v4l2_ctrl *testgen_mode;
>>>> +    const struct tpg_subdev_resources *res;
>>>> +    const struct tpg_format *formats;
>>>> +    unsigned int nformats;
>>>> +    u32 hw_version;
>>>> +};
>>>> +
>>>> +struct camss_subdev_resources;
>>>> +
>>>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device 
>>>> *tpg,
>>>> +                        const struct tpg_format_info *formats,
>>>> +                        unsigned int nformats,
>>>> +                        u32 code);
>>>> +
>>>> +int msm_tpg_subdev_init(struct camss *camss,
>>>> +            struct tpg_device *tpg,
>>>> +            const struct camss_subdev_resources *res, u8 id);
>>>> +
>>>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>>>> +                struct v4l2_device *v4l2_dev);
>>>> +
>>>> +void msm_tpg_unregister_entity(struct tpg_device *tpg);
>>>> +
>>>> +extern const char * const testgen_payload_modes[];
>>>> +
>>>> +extern const struct tpg_formats tpg_formats_gen1;
>>>> +
>>>> +extern const struct tpg_hw_ops tpg_ops_gen1;
>>>> +
>>>> +#endif /* QC_MSM_CAMSS_TPG_H */
>>>> diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/
>>>> media/platform/qcom/camss/camss.h
>>>> index
>>>> 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..a892a87bed8bde8919200d6eac2b7a5338763c0e 100644
>>>> --- a/drivers/media/platform/qcom/camss/camss.h
>>>> +++ b/drivers/media/platform/qcom/camss/camss.h
>>>> @@ -21,6 +21,7 @@
>>>>    #include "camss-csid.h"
>>>>    #include "camss-csiphy.h"
>>>>    #include "camss-ispif.h"
>>>> +#include "camss-tpg.h"
>>>>    #include "camss-vfe.h"
>>>>    #include "camss-format.h"
>>>> @@ -52,6 +53,7 @@ struct camss_subdev_resources {
>>>>        char *interrupt[CAMSS_RES_MAX];
>>>>        union {
>>>>            struct csiphy_subdev_resources csiphy;
>>>> +        struct tpg_subdev_resources tpg;
>>>>            struct csid_subdev_resources csid;
>>>>            struct vfe_subdev_resources vfe;
>>>>        };
>>>> @@ -104,6 +106,7 @@ struct camss_resources {
>>>>        enum camss_version version;
>>>>        const char *pd_name;
>>>>        const struct camss_subdev_resources *csiphy_res;
>>>> +    const struct camss_subdev_resources *tpg_res;
>>>>        const struct camss_subdev_resources *csid_res;
>>>>        const struct camss_subdev_resources *ispif_res;
>>>>        const struct camss_subdev_resources *vfe_res;
>>>> @@ -111,6 +114,7 @@ struct camss_resources {
>>>>        const struct resources_icc *icc_res;
>>>>        const unsigned int icc_path_num;
>>>>        const unsigned int csiphy_num;
>>>> +    const unsigned int tpg_num;
>>>>        const unsigned int csid_num;
>>>>        const unsigned int vfe_num;
>>>>    };
>>>> @@ -121,6 +125,7 @@ struct camss {
>>>>        struct media_device media_dev;
>>>>        struct device *dev;
>>>>        struct csiphy_device *csiphy;
>>>> +    struct tpg_device *tpg;
>>>>        struct csid_device *csid;
>>>>        struct ispif_device *ispif;
>>>>        struct vfe_device *vfe;
>>>>
>>>
> 
Thanks for your review.
Wenmeng

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-14 12:18         ` Wenmeng Liu
@ 2026-01-14 22:07           ` Vladimir Zapolskiy
  2026-01-15  1:06             ` Bryan O'Donoghue
  0 siblings, 1 reply; 19+ messages in thread
From: Vladimir Zapolskiy @ 2026-01-14 22:07 UTC (permalink / raw)
  To: Wenmeng Liu, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

Hi Wenmeng.

On 1/14/26 14:18, Wenmeng Liu wrote:
> 
> Hi Vladimir,
> 
> 
> On 1/14/2026 1:05 PM, Vladimir Zapolskiy wrote:
>> Hi Wenmeng.
>>
>> On 1/14/26 05:04, Wenmeng Liu wrote:
>>> Hi Vladimir,
>>>
>>> On 1/14/2026 12:27 AM, Vladimir Zapolskiy wrote:
>>>> Hello Wenmeng.
>>>>
>>>> On 1/13/26 11:03, Wenmeng Liu wrote:
>>>>> Introduce a new common Test Pattern Generator (TPG) implementation for
>>>>> Qualcomm CAMSS. This module provides a generic interface for pattern
>>>>> generation that can be reused by multiple platforms.
>>>>>
>>>>> Unlike CSID-integrated TPG, this TPG acts as a standalone block
>>>>> that emulates both CSIPHY and sensor behavior, enabling flexible test
>>>>> patterns without external hardware.
>>>>>
>>>>> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
>>>>> ---
>>>>>     drivers/media/platform/qcom/camss/Makefile    |   1 +
>>>>>     drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++++++++
>>>>> ++++++++++
>>>>>     drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>>>>>     drivers/media/platform/qcom/camss/camss.h     |   5 +
>>>>>     4 files changed, 843 insertions(+)
>>>>>
>>>>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/
>>>>> media/platform/qcom/camss/Makefile
>>>>> index
>>>>> 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
>>>>> --- a/drivers/media/platform/qcom/camss/Makefile
>>>>> +++ b/drivers/media/platform/qcom/camss/Makefile
>>>>> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>>>>>             camss-vfe.o \
>>>>>             camss-video.o \
>>>>>             camss-format.o \
>>>>> +        camss-tpg.o \
>>>>
>>>> While you're here, please sort and keep the lines in alphabetical order.
>>> ACK.
>>>
>>>>
>>>>>     obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>>>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/
>>>>> media/platform/qcom/camss/camss-tpg.c
>>>>> new file mode 100644
>>>>> index
>>>>> 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
>>>>> @@ -0,0 +1,710 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +/*
>>>>> + *
>>>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>>>> + *
>>>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>>>> + */
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/delay.h>
>>>>> +#include <linux/io.h>
>>>>> +#include <linux/kernel.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/pm_runtime.h>
>>>>> +#include <media/media-entity.h>
>>>>> +#include <media/v4l2-device.h>
>>>>> +#include <media/v4l2-subdev.h>
>>>>> +
>>>>> +#include "camss-tpg.h"
>>>>> +#include "camss.h"
>>>>> +
>>>>> +const char * const testgen_payload_modes[] = {
>>>>> +    "Disabled",
>>>>> +    "Incrementing",
>>>>> +    "Alternating 0x55/0xAA",
>>>>> +    "Reserved",
>>>>> +    "Reserved",
>>>>> +    "Pseudo-random Data",
>>>>> +    "User Specified",
>>>>> +    "Reserved",
>>>>> +    "Reserved",
>>>>> +    "Color bars",
>>>>> +    "Reserved"
>>>>
>>>> It makes little sense to mention the unsupported values, and then
>>>> introduce enum tpg_testgen_mode to list the supported ones.
>>>>
>>> This is for ctrl menu, will do as follow:
>>> static const char * const testgen_payload_modes[] = {
>>>        [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
>>>        [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
>>>        [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating
>>> 0x55/0xAA",
>>>        [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
>>>        [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
>>>        [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
>>> };
>>>
>>
>> This is also not perfect, still userspace is misinformed about a number
>> of possible TPG modes vs. a number of actually supported TPG modes.
>>
> 0x0: INCREMENTING
> 0x1: ALTERNATING_55_AA
> 0x4: RANDOM
> 0x5: USER_SPECIFIED
> 0x8: COLOR_BARS
> 
> These values come from the register configuration, these pattern values
> are consistent with the CSID TPG.

Userspace should not be aware of such low level details as register values,
there are many abstraction layers in-between to hide this type of information.

Writing proper values to registers should be a concern on the driver level,
it sounds improper to push this simple task and responsibility to userspace.

> If want to make it continuous, need to add a mapping table. How about this?

Yes, please.

>>>>> +};
>>>>
>>>> Are these test pattern modes specific to TPG Gen1 only?
>>>>
>>>> CSID TPG uses a different 'csid_testgen_modes' list, and if the list
>>>> above is
>>>> TPG Gen1 specific, it would make sense to place it right in camss-tpg-
>>>> gen1.c
>>>>
>>>
>>> Like other CAMSS nodes, the files placed in the core are meant to
>>> maintain consistency with the others.
>>
>> Please elaborate, what does it mean "files placed in the core"?
>>
> sorry, let me clarify this,
> core file: camss-cisd.c camss-csiphy.c ...
> hw version file: camss-csid-gen2.c camss-tpg-gen1.c
>> If technically possible, all local data shall be placed closer to the code,
>> which uses it, and here the exported data is exported for nothing, but
>> some "consistency".
>>
>> You draw a line what data to place into the generic camss-tpg.c and to the
>> specific camss-tpg-gen1.c, you should explain why data specific to TPG Gen1
>> is placed into the generic code, while you've already set a different and
>> dedicated place/file for storing this type of data?
>>
> The reason I want to implement it this way is that the TPG's ctrl
> requires this structure for initialization (msm_tpg_register_entity).

This function is generic, but data is specific, so let it be placed in
its specific location.

> 
> Also it`s ok to push it to gen1 file.
> 

Yes, please, thank you, I belive it will simplify the code.

>>>>> +
>>>>> +static const struct tpg_format_info formats_gen1[] = {
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SBGGR8_1X8,
>>>>> +        DATA_TYPE_RAW_8BIT,
>>>>
>>>> Please replace it with MIPI_CSI2_DT_RAW8
>>>>
>>>
>>> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>>> +        8,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SGBRG8_1X8,
>>>>> +        DATA_TYPE_RAW_8BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>>> +        8,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SGRBG8_1X8,
>>>>> +        DATA_TYPE_RAW_8BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>>> +        8,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SRGGB8_1X8,
>>>>> +        DATA_TYPE_RAW_8BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>>> +        8,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SBGGR10_1X10,
>>>>> +        DATA_TYPE_RAW_10BIT,
>>>>
>>>> MIPI_CSI2_DT_RAW10>
>>>
>>> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>>> +        10,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SGBRG10_1X10,
>>>>> +        DATA_TYPE_RAW_10BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>>> +        10,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SGRBG10_1X10,
>>>>> +        DATA_TYPE_RAW_10BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>>> +        10,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SRGGB10_1X10,
>>>>> +        DATA_TYPE_RAW_10BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>>> +        10,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SBGGR12_1X12,
>>>>> +        DATA_TYPE_RAW_12BIT,
>>>>
>>>> MIPI_CSI2_DT_RAW12
>>>>
>>>
>>> ACK.>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>>> +        12,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SGBRG12_1X12,
>>>>> +        DATA_TYPE_RAW_12BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>>> +        12,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SGRBG12_1X12,
>>>>> +        DATA_TYPE_RAW_12BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>>> +        12,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_SRGGB12_1X12,
>>>>> +        DATA_TYPE_RAW_12BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
>>>>> +        12,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_Y8_1X8,
>>>>> +        DATA_TYPE_RAW_8BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
>>>>> +        8,
>>>>> +    },
>>>>> +    {
>>>>> +        MEDIA_BUS_FMT_Y10_1X10,
>>>>> +        DATA_TYPE_RAW_10BIT,
>>>>> +        ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
>>>>> +        10,
>>>>> +    },
>>>>> +};
>>>>> +
>>>>> +const struct tpg_formats tpg_formats_gen1 = {
>>>>> +    .nformats = ARRAY_SIZE(formats_gen1),
>>>>> +    .formats = formats_gen1
>>>>> +};
>>>>> +
>>>>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device
>>>>> *tpg,
>>>>> +                        const struct tpg_format_info *formats,
>>>>> +                        unsigned int nformats,
>>>>> +                        u32 code)
>>>>> +{
>>>>> +    struct device *dev = tpg->camss->dev;
>>>>> +    size_t i;
>>>>
>>>> unsigned int i, size_t is very unexpected to get here.
>>>>
>>>
>>> I have received comments on this.
>>>
>>> https://lore.kernel.org/all/449ac3c3-1f6a-4e69-899d-
>>> c4e4577714a4@oss.qualcomm.com/
>>>
>>> https://staticthinking.wordpress.com/2022/06/01/unsigned-int-i-is-
>>> stupid/>>
>>> +
>>
>> I see, in every programming language without type inference, including
>> C, types
>> shall be as precise as possible. Here the usage of signed int or size_t
>> is stupid,
>> because for a human being it adds completely wasted efforts to
>> comprehend, what
>> does happen, when the local variable is negative, while it just can not
>> be such.
>>
> ACK, will fix it.
> 
>>>>> +    for (i = 0; i < nformats; i++)
>>>>> +        if (code == formats[i].code)
>>>>> +            return &formats[i];
>>>>> +
>>>>> +    dev_warn(dev, "Unknown pixel format code=0x%08x\n", code);
>>>>
>>>> Please remove dev_warn() completely, it opens a way to flood the kernel
>>>> log.
>>>>
>>>
>>> This is an exception printout and will not appear under normal
>>> circumstances.>> +
>>
>> If it can be trivially triggered from the userspace, it is not an
>> exception.
>>
>> I haven't yet tested the changes, what does happen, when an unsupported
>> pixel
>> format is asked to be set from userspace? It's a regular operation, and it
>> shall not litter the kernel log buffer.
>>
>>>>> +    return ERR_PTR(-EINVAL);
>>
>> This one is sufficient.
>>
> 
> ACK.>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_set_clock_rates - set clock rates on tpg module
>>>>> + * @tpg: tpg device
>>>>> + */
>>>>> +static int tpg_set_clock_rates(struct tpg_device *tpg)
>>>>> +{
>>>>> +    struct device *dev = tpg->camss->dev;
>>>>> +    int ret;
>>>>> +    int i;
>>>>> +
>>>>> +    for (i = 0; i < tpg->nclocks; i++) {
>>>>> +        struct camss_clock *clock = &tpg->clock[i];
>>>>> +        long round_rate;
>>>>> +
>>>>> +        if (clock->freq) {
>>>>> +            round_rate = clk_round_rate(clock->clk, clock->freq[0]);
>>>>> +            if (round_rate < 0) {
>>>>> +                dev_err(dev, "clk round rate failed: %ld\n",
>>>>> +                    round_rate);
>>>>> +                return -EINVAL;
>>>>> +            }
>>>>> +
>>>>> +            ret = clk_set_rate(clock->clk, round_rate);
>>>>> +            if (ret < 0) {
>>>>> +                dev_err(dev, "clk set rate failed: %d\n", ret);
>>>>> +                return ret;
>>>>> +            }
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_set_power - Power on/off tpg module
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @on: Requested power state
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +static int tpg_set_power(struct v4l2_subdev *sd, int on)
>>>>> +{
>>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>>> +    struct device *dev = tpg->camss->dev;
>>>>> +
>>>>> +    if (on) {
>>>>> +        int ret;
>>>>> +
>>>>> +        ret = pm_runtime_resume_and_get(dev);
>>>>> +        if (ret < 0)
>>>>> +            return ret;
>>>>> +
>>>>> +        ret = tpg_set_clock_rates(tpg);
>>>>> +        if (ret < 0) {
>>>>> +            pm_runtime_put_sync(dev);
>>>>> +            return ret;
>>>>> +        }
>>>>> +
>>>>> +        ret = camss_enable_clocks(tpg->nclocks, tpg->clock, dev);
>>>>> +        if (ret < 0) {
>>>>> +            pm_runtime_put_sync(dev);
>>>>> +            return ret;
>>>>> +        }
>>>>> +
>>>>> +        tpg->res->hw_ops->reset(tpg);
>>>>> +
>>>>> +        tpg->res->hw_ops->hw_version(tpg);
>>>>> +    } else {
>>>>> +        camss_disable_clocks(tpg->nclocks, tpg->clock);
>>>>> +
>>>>> +        pm_runtime_put_sync(dev);
>>>>> +    }
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_set_stream - Enable/disable streaming on tpg module
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @enable: Requested streaming state
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +static int tpg_set_stream(struct v4l2_subdev *sd, int enable)
>>>>> +{
>>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>>> +    int ret = 0;
>>>>> +
>>>>> +    if (enable) {
>>>>> +        ret = v4l2_ctrl_handler_setup(&tpg->ctrls);
>>>>> +        if (ret < 0) {
>>>>> +            dev_err(tpg->camss->dev,
>>>>> +                "could not sync v4l2 controls: %d\n", ret);
>>>>> +            return ret;
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    ret = tpg->res->hw_ops->configure_stream(tpg, enable);
>>>>> +
>>>>> +    return ret;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * __tpg_get_format - Get pointer to format structure
>>>>> + * @tpg: tpg device
>>>>> + * @cfg: V4L2 subdev pad configuration
>>>>
>>>> There is no such function argument. There are much more errors in
>>>> the doxygen descriptions of functions, please remove all these
>>>> doxygen comments, they do not bring anything valuable here.
>>>>
>>>
>>>
>>> ACK.
>>
>> For clarity, I haven't commented all errors in the doxygen descriptions,
>> because I expect that all of them will be removed.
>>
> 
> Yes, I will removed it all.
> 
>>>> + * @pad: pad from which format is requested
>>>>> + * @which: TRY or ACTIVE format
>>>>> + *
>>>>> + * Return pointer to TRY or ACTIVE format structure
>>>>> + */
>>>>> +static struct v4l2_mbus_framefmt *
>>>>> +__tpg_get_format(struct tpg_device *tpg,
>>>>> +         struct v4l2_subdev_state *sd_state,
>>>>> +         unsigned int pad,
>>>>> +         enum v4l2_subdev_format_whence which)
>>>>> +{
>>>>> +    if (which == V4L2_SUBDEV_FORMAT_TRY)
>>>>> +        return v4l2_subdev_state_get_format(sd_state,
>>>>> +                            pad);
>>>>> +
>>>>> +    return &tpg->fmt[pad];
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_try_format - Handle try format by pad subdev method
>>>>> + * @tpg: tpg device
>>>>> + * @cfg: V4L2 subdev pad configuration
>>>>
>>>> No such argument.
>>>>
>>>
>>> ACK.>> + * @pad: pad on which format is requested
>>>>> + * @fmt: pointer to v4l2 format structure
>>>>> + * @which: wanted subdev format
>>>>> + */
>>>>> +static void tpg_try_format(struct tpg_device *tpg,
>>>>> +               struct v4l2_subdev_state *sd_state,
>>>>> +               unsigned int pad,
>>>>> +               struct v4l2_mbus_framefmt *fmt,
>>>>> +               enum v4l2_subdev_format_whence which)
>>>>> +{
>>>>> +    unsigned int i;
>>>>> +
>>>>> +    switch (pad) {
>>>>> +    case MSM_TPG_PAD_SINK:
>>>>> +        for (i = 0; i < tpg->res->formats->nformats; i++)
>>>>> +            if (tpg->res->formats->formats[i].code == fmt->code)
>>>>> +                break;
>>>>> +
>>>>> +        /* If not found, use SBGGR8 as default */
>>>>> +        if (i >= tpg->res->formats->nformats)
>>>>> +            fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
>>>>> +
>>>>> +        fmt->width = clamp_t(u32, fmt->width, 1, 8191);
>>>>> +        fmt->height = clamp_t(u32, fmt->height, 1, 8191);
>>>>> +
>>>>> +        fmt->field = V4L2_FIELD_NONE;
>>>>> +        fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>>> +
>>>>> +        break;
>>>>> +    case MSM_TPG_PAD_SRC:
>>>>> +        *fmt = *__tpg_get_format(tpg, sd_state,
>>>>> +                     MSM_TPG_PAD_SINK,
>>>>> +                     which);
>>>>> +
>>>>> +        break;
>>>>> +    }
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_enum_mbus_code - Handle format enumeration
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @cfg: V4L2 subdev pad configuration
>>>>
>>>> No such argument.
>>>>
>>>
>>> ACK.>> + * @code: pointer to v4l2_subdev_mbus_code_enum structure
>>>>> + * return -EINVAL or zero on success
>>>>> + */
>>>>> +static int tpg_enum_mbus_code(struct v4l2_subdev *sd,
>>>>> +                  struct v4l2_subdev_state *sd_state,
>>>>> +                  struct v4l2_subdev_mbus_code_enum *code)
>>>>> +{
>>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>>> +    struct v4l2_mbus_framefmt *format;
>>>>> +
>>>>> +    if (code->pad == MSM_TPG_PAD_SINK) {
>>>>> +        if (code->index >= tpg->res->formats->nformats)
>>>>> +            return -EINVAL;
>>>>> +
>>>>> +        code->code = tpg->res->formats->formats[code->index].code;
>>>>> +    } else {
>>>>> +        if (code->index > 0)
>>>>> +            return -EINVAL;
>>>>> +
>>>>> +        format = __tpg_get_format(tpg, sd_state,
>>>>> +                      MSM_TPG_PAD_SINK,
>>>>> +                      code->which);
>>>>> +
>>>>> +        code->code = format->code;
>>>>> +    }
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_enum_frame_size - Handle frame size enumeration
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @cfg: V4L2 subdev pad configuration
>>>>> + * @fse: pointer to v4l2_subdev_frame_size_enum structure
>>>>> + * return -EINVAL or zero on success
>>>>> + */
>>>>> +static int tpg_enum_frame_size(struct v4l2_subdev *sd,
>>>>> +                   struct v4l2_subdev_state *sd_state,
>>>>> +                   struct v4l2_subdev_frame_size_enum *fse)
>>>>> +{
>>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>>> +    struct v4l2_mbus_framefmt format;
>>>>> +
>>>>> +    if (fse->index != 0)
>>>>> +        return -EINVAL;
>>>>> +
>>>>> +    format.code = fse->code;
>>>>> +    format.width = 1;
>>>>> +    format.height = 1;
>>>>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>>>>> +    fse->min_width = format.width;
>>>>> +    fse->min_height = format.height;
>>>>> +
>>>>> +    if (format.code != fse->code)
>>>>> +        return -EINVAL;
>>>>> +
>>>>> +    format.code = fse->code;
>>>>> +    format.width = -1;
>>>>> +    format.height = -1;
>>>>
>>>> format.width and format.height are rewritten in the tpg_try_format(),
>>>> so it makes no sense to assign them.
>>>>
>>>
>>> This is use for pass parameters in.
>>>
>>>> The problem is that for whatever reason you can tpg_try_format() twice
>>>> in a raw, it looks wrong, and I'm certain you can modify the functions
>>>> so that only one call would be needed.
>>>> The first call is to get the supported minimum size, and the second call
>>> is to get the maximum size.
>>>
>>> The tpg_try_format function can be used multiple times; I don't think
>>> it's necessary to write a new interface.
>>
>> I didn't ask to write a new interface, please reread my comment.
>>
>> Yon can write tpg_enum_frame_size() function, that tpg_try_format()
>> is not called at all, but for an unclear reason here it's called twice.
>>
> Sure, it will be implemented in a simpler way.
> 
>>>>> +    tpg_try_format(tpg, sd_state, fse->pad, &format, fse->which);
>>>>> +    fse->max_width = format.width;
>>>>> +    fse->max_height = format.height;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_get_format - Handle get format by pads subdev method
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @cfg: V4L2 subdev pad configuration
>>>>> + * @fmt: pointer to v4l2 subdev format structure
>>>>> + *
>>>>> + * Return -EINVAL or zero on success
>>>>> + */
>>>>> +static int tpg_get_format(struct v4l2_subdev *sd,
>>>>> +              struct v4l2_subdev_state *sd_state,
>>>>> +              struct v4l2_subdev_format *fmt)
>>>>> +{
>>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>>> +    struct v4l2_mbus_framefmt *format;
>>>>> +
>>>>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>>>>> +    if (!format)
>>>>> +        return -EINVAL;
>>>>> +
>>>>> +    fmt->format = *format;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_set_format - Handle set format by pads subdev method
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @cfg: V4L2 subdev pad configuration
>>>>> + * @fmt: pointer to v4l2 subdev format structure
>>>>> + *
>>>>> + * Return -EINVAL or zero on success
>>>>> + */
>>>>> +static int tpg_set_format(struct v4l2_subdev *sd,
>>>>> +              struct v4l2_subdev_state *sd_state,
>>>>> +              struct v4l2_subdev_format *fmt)
>>>>> +{
>>>>> +    struct tpg_device *tpg = v4l2_get_subdevdata(sd);
>>>>> +    struct v4l2_mbus_framefmt *format;
>>>>> +
>>>>> +    format = __tpg_get_format(tpg, sd_state, fmt->pad, fmt->which);
>>>>> +    if (!format)
>>>>> +        return -EINVAL;
>>>>> +
>>>>> +    tpg_try_format(tpg, sd_state, fmt->pad, &fmt->format,
>>>>> +               fmt->which);
>>>>> +    *format = fmt->format;
>>>>> +
>>>>> +    if (fmt->pad == MSM_TPG_PAD_SINK) {
>>>>> +        format = __tpg_get_format(tpg, sd_state,
>>>>> +                      MSM_TPG_PAD_SRC,
>>>>> +                      fmt->which);
>>>>> +
>>>>> +        *format = fmt->format;
>>>>> +        tpg_try_format(tpg, sd_state, MSM_TPG_PAD_SRC,
>>>>> +                   format,
>>>>> +                   fmt->which);
>>>>> +    }
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_init_formats - Initialize formats on all pads
>>>>> + * @sd: tpg V4L2 subdevice
>>>>> + * @fh: V4L2 subdev file handle
>>>>> + *
>>>>> + * Initialize all pad formats with default values.
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +static int tpg_init_formats(struct v4l2_subdev *sd,
>>>>> +                struct v4l2_subdev_fh *fh)
>>>>> +{
>>>>> +    struct v4l2_subdev_format format = {
>>>>> +        .pad = MSM_TPG_PAD_SINK,
>>>>> +        .which = fh ? V4L2_SUBDEV_FORMAT_TRY :
>>>>> +                  V4L2_SUBDEV_FORMAT_ACTIVE,
>>>>> +        .format = {
>>>>> +            .code = MEDIA_BUS_FMT_SBGGR8_1X8,
>>>>> +            .width = 1920,
>>>>> +            .height = 1080
>>>>> +        }
>>>>> +    };
>>>>> +
>>>>> +    return tpg_set_format(sd, fh ? fh->state : NULL, &format);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_set_test_pattern - Set test generator's pattern mode
>>>>> + * @tpg: TPG device
>>>>> + * @value: desired test pattern mode
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +static int tpg_set_test_pattern(struct tpg_device *tpg, s32 value)
>>>>> +{
>>>>> +    return tpg->res->hw_ops->configure_testgen_pattern(tpg, value);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_s_ctrl - Handle set control subdev method
>>>>> + * @ctrl: pointer to v4l2 control structure
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +static int tpg_s_ctrl(struct v4l2_ctrl *ctrl)
>>>>> +{
>>>>> +    struct tpg_device *tpg = container_of(ctrl->handler,
>>>>> +                          struct tpg_device, ctrls);
>>>>> +    int ret = -EINVAL;
>>>>> +
>>>>> +    switch (ctrl->id) {
>>>>> +    case V4L2_CID_TEST_PATTERN:
>>>>> +        ret = tpg_set_test_pattern(tpg, ctrl->val);
>>>>> +        break;
>>>>> +    }
>>>>> +
>>>>> +    return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct v4l2_ctrl_ops tpg_ctrl_ops = {
>>>>> +    .s_ctrl = tpg_s_ctrl,
>>>>> +};
>>>>> +
>>>>> +/*
>>>>> + * msm_tpg_subdev_init - Initialize tpg device structure and resources
>>>>> + * @tpg: tpg device
>>>>
>>>> And no 'camss' argument. The whole function description comment is quite
>>>> useless, it can be just removed with no losses.
>>>>
>>> ACK. will remove it all.
>>
>> Good, thank you.
>>
>>>> + * @res: tpg module resources table
>>>>> + * @id: tpg module id
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +int msm_tpg_subdev_init(struct camss *camss,
>>>>> +            struct tpg_device *tpg,
>>>>> +            const struct camss_subdev_resources *res, u8 id)
>>>>> +{
>>>>> +    struct platform_device *pdev;
>>>>> +    struct device *dev;
>>>>> +    int i, j;
>>>>> +
>>>>> +    dev  = camss->dev;
>>>>> +    pdev = to_platform_device(dev);
>>>>> +
>>>>> +    tpg->camss = camss;
>>>>> +    tpg->id = id;
>>>>> +    tpg->res = &res->tpg;
>>>>> +    tpg->res->hw_ops->subdev_init(tpg);
>>>>> +
>>>>> +    tpg->base = devm_platform_ioremap_resource_byname(pdev, res-
>>>>>> reg[0]);
>>>>> +    if (IS_ERR(tpg->base))
>>>>> +        return PTR_ERR(tpg->base);
>>>>> +
>>>>> +    tpg->nclocks = 0;
>>>>> +    while (res->clock[tpg->nclocks])
>>>>> +        tpg->nclocks++;
>>>>> +
>>>>> +    if (tpg->nclocks) {
>>>>
>>>> if (!tpg->nclocks)
>>>>        return 0;
>>>>
>>> ACK.>> +        tpg->clock = devm_kcalloc(dev,
>>>>> +                      tpg->nclocks, sizeof(*tpg->clock),
>>>>> +                      GFP_KERNEL);
>>>>> +        if (!tpg->clock)
>>>>> +            return -ENOMEM;
>>>>> +
>>>>> +        for (i = 0; i < tpg->nclocks; i++) {
>>>>> +            struct camss_clock *clock = &tpg->clock[i];
>>>>> +
>>>>> +            clock->clk = devm_clk_get(dev, res->clock[i]);
>>>>> +            if (IS_ERR(clock->clk))
>>>>> +                return PTR_ERR(clock->clk);
>>>>> +
>>>>> +            clock->name = res->clock[i];
>>>>> +
>>>>> +            clock->nfreqs = 0;
>>>>> +            while (res->clock_rate[i][clock->nfreqs])
>>>>> +                clock->nfreqs++;
>>>>> +
>>>>> +            if (!clock->nfreqs) {
>>>>> +                clock->freq = NULL;
>>>>> +                continue;
>>>>> +            }
>>>>> +
>>>>> +            clock->freq = devm_kcalloc(dev,
>>>>> +                           clock->nfreqs,
>>>>> +                           sizeof(*clock->freq),
>>>>> +                           GFP_KERNEL);
>>>>> +            if (!clock->freq)
>>>>> +                return -ENOMEM;
>>>>> +
>>>>> +            for (j = 0; j < clock->nfreqs; j++)
>>>>> +                clock->freq[j] = res->clock_rate[i][j];
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * tpg_link_setup - Setup tpg connections
>>>>> + * @entity: Pointer to media entity structure
>>>>> + * @local: Pointer to local pad
>>>>> + * @remote: Pointer to remote pad
>>>>> + * @flags: Link flags
>>>>> + *
>>>>> + * Return 0 on success
>>>>> + */
>>>>> +static int tpg_link_setup(struct media_entity *entity,
>>>>> +              const struct media_pad *local,
>>>>> +              const struct media_pad *remote, u32 flags)
>>>>> +{
>>>>> +    if (flags & MEDIA_LNK_FL_ENABLED)
>>>>> +        if (media_pad_remote_pad_first(local))
>>>>> +            return -EBUSY;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct v4l2_subdev_core_ops tpg_core_ops = {
>>>>> +    .s_power = tpg_set_power,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_video_ops tpg_video_ops = {
>>>>> +    .s_stream = tpg_set_stream,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_pad_ops tpg_pad_ops = {
>>>>> +    .enum_mbus_code = tpg_enum_mbus_code,
>>>>> +    .enum_frame_size = tpg_enum_frame_size,
>>>>> +    .get_fmt = tpg_get_format,
>>>>> +    .set_fmt = tpg_set_format,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_ops tpg_v4l2_ops = {
>>>>> +    .core = &tpg_core_ops,
>>>>> +    .video = &tpg_video_ops,
>>>>> +    .pad = &tpg_pad_ops,
>>>>> +};
>>>>> +
>>>>> +static const struct v4l2_subdev_internal_ops tpg_v4l2_internal_ops = {
>>>>> +    .open = tpg_init_formats,
>>>>> +};
>>>>> +
>>>>> +static const struct media_entity_operations tpg_media_ops = {
>>>>> +    .link_setup = tpg_link_setup,
>>>>> +    .link_validate = v4l2_subdev_link_validate,
>>>>> +};
>>>>> +
>>>>> +/*
>>>>> + * msm_tpg_register_entity - Register subdev node for tpg module
>>>>> + * @tpg: tpg device
>>>>> + * @v4l2_dev: V4L2 device
>>>>> + *
>>>>> + * Return 0 on success or a negative error code otherwise
>>>>> + */
>>>>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>>>>> +                struct v4l2_device *v4l2_dev)
>>>>> +{
>>>>> +    struct v4l2_subdev *sd = &tpg->subdev;
>>>>> +    struct media_pad *pads = tpg->pads;
>>>>> +    struct device *dev = tpg->camss->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    v4l2_subdev_init(sd, &tpg_v4l2_ops);
>>>>> +    sd->internal_ops = &tpg_v4l2_internal_ops;
>>>>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
>>>>> +             V4L2_SUBDEV_FL_HAS_EVENTS;
>>>>> +    snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
>>>>> +         MSM_TPG_NAME, tpg->id);
>>>>> +    sd->grp_id = TPG_GUP_ID;
>>>>> +    v4l2_set_subdevdata(sd, tpg);
>>>>> +
>>>>> +    ret = v4l2_ctrl_handler_init(&tpg->ctrls, 1);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to init ctrl handler: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +
>>>>> +    tpg->testgen_mode = v4l2_ctrl_new_std_menu_items(&tpg->ctrls,
>>>>> +                             &tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
>>>>> +                             tpg->testgen.nmodes, 0, 0,
>>>>> +                             tpg->testgen.modes);
>>>>> +
>>>>
>>>> Please remove a blank line above.
>>>>
>>> ACK.>> +    if (tpg->ctrls.error) {
>>>>> +        dev_err(dev, "Failed to init ctrl: %d\n", tpg->ctrls.error);
>>>>> +        ret = tpg->ctrls.error;
>>>>> +        goto free_ctrl;
>>>>> +    }
>>>>> +
>>>>> +    tpg->subdev.ctrl_handler = &tpg->ctrls;
>>>>> +
>>>>> +    ret = tpg_init_formats(sd, NULL);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to init format: %d\n", ret);
>>>>> +        goto free_ctrl;
>>>>> +    }
>>>>> +
>>>>> +    pads[MSM_TPG_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>>> +    pads[MSM_TPG_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>>>
>>>> Why do you need two pads for TPG?
>>>>
>>> will fix it next version.
>>>
>>>>> +
>>>>> +    sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>>>>
>>>> TPG is not a video pixel encoding converter device.
>>>>
>>>
>>> How about MEDIA_ENT_F_CAM_SENSOR?
>>>
>>
>> Still not perfect, but probably it's better... At least it won't implicitly
>> assume a presense of two pads on the media entity.
>>
> 
> Or just deleted it, if want to keep this,
> will choose MEDIA_ENT_F_CAM_SENSOR.

Formally a TPG is not a sensor, so it could be misleading for a user/reader.

Since no selected function seems an applicable choice, please go on with it.

> 
> and I will delete all sink code in this driver.
> 

Yes, please, thank you!

>>>>> +    sd->entity.ops = &tpg_media_ops;
>>>>> +    ret = media_entity_pads_init(&sd->entity, MSM_TPG_PADS_NUM, pads);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to init media entity: %d\n", ret);
>>>>> +        goto free_ctrl;
>>>>> +    }
>>>>> +
>>>>> +    ret = v4l2_device_register_subdev(v4l2_dev, sd);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to register subdev: %d\n", ret);
>>>>> +        media_entity_cleanup(&sd->entity);
>>>>> +        goto free_ctrl;
>>>>> +    }
>>>>> +
>>>>> +    return 0;
>>>>> +
>>>>> +free_ctrl:
>>>>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>>>>> +
>>>>> +    return ret;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * msm_tpg_unregister_entity - Unregister tpg module subdev node
>>>>> + * @tpg: tpg device
>>>>> + */
>>>>> +void msm_tpg_unregister_entity(struct tpg_device *tpg)
>>>>> +{
>>>>> +    v4l2_device_unregister_subdev(&tpg->subdev);
>>>>> +    media_entity_cleanup(&tpg->subdev.entity);
>>>>> +    v4l2_ctrl_handler_free(&tpg->ctrls);
>>>>> +}
>>>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.h b/drivers/
>>>>> media/platform/qcom/camss/camss-tpg.h
>>>>> new file mode 100644
>>>>> index
>>>>> 0000000000000000000000000000000000000000..1a16addac19418f2f11d0b8abb1c865c99888bde
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.h
>>>>> @@ -0,0 +1,127 @@
>>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>>> +/*
>>>>> + * camss-tpg.h
>>>>> + *
>>>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>>>> + *
>>>>> + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights
>>>>> reserved.
>>>>> + */
>>>>> +#ifndef QC_MSM_CAMSS_TPG_H
>>>>> +#define QC_MSM_CAMSS_TPG_H
>>>>> +
>>>>> +#include <linux/clk.h>
>>>>> +#include <media/media-entity.h>
>>>>> +#include <media/v4l2-ctrls.h>
>>>>> +#include <media/v4l2-device.h>
>>>>> +#include <media/v4l2-mediabus.h>
>>>>> +#include <media/v4l2-subdev.h>
>>>>> +
>>>>> +#define MSM_TPG_PAD_SINK 0
>>>>> +#define MSM_TPG_PAD_SRC 1
>>>>> +#define MSM_TPG_PADS_NUM 2
>>>>> +
>>>>> +#define DATA_TYPE_RAW_8BIT        0x2a
>>>>> +#define DATA_TYPE_RAW_10BIT        0x2b
>>>>> +#define DATA_TYPE_RAW_12BIT        0x2c
>>>>
>>>> Remove all above to get the macro values from include/media/mipi-csi2.h
>>>>
>>> ACK.>> +
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_8_BIT    0x1
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_10_BIT    0x2
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_12_BIT    0x3
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_14_BIT    0x4
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_16_BIT    0x5
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_20_BIT    0x6
>>>>> +#define ENCODE_FORMAT_UNCOMPRESSED_24_BIT    0x7
>>>>> +
>>>>> +#define TPG_GUP_ID 0
>>
>> In addition to the previous review comments, please change the value to
>> something other than default 0. This type of data can be placed outside
>> of TPG specific code, it has a potential for further outbreak.
>>
> ACK.
> 

Please move it to camss.h, a rename would be also appreciated like CAMSS_GID_TPG.

>>>>> +#define MSM_TPG_NAME "msm_tpg"
>>>>
>>>> Remove the macro, it's used only once in the code and more usecases are
>>>> not expected happen.
>>>>
>>> ACK.
>>
>>>> +
>>>>> +enum tpg_testgen_mode {
>>>>> +    TPG_PAYLOAD_MODE_DISABLED = 0,
>>>>> +    TPG_PAYLOAD_MODE_INCREMENTING = 1,
>>>>> +    TPG_PAYLOAD_MODE_ALTERNATING_55_AA = 2,
>>>>> +    TPG_PAYLOAD_MODE_RANDOM = 5,
>>>>> +    TPG_PAYLOAD_MODE_USER_SPECIFIED = 6,
>>>>> +    TPG_PAYLOAD_MODE_COLOR_BARS = 9,
>>>>> +    TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 9,
>>>>> +};
>>>>> +
>>>>> +struct tpg_testgen_config {
>>>>> +    enum tpg_testgen_mode mode;
>>>>> +    const char * const*modes;
>>>>> +    u8 nmodes;
>>>>> +};
>>>>> +
>>>>> +struct tpg_format_info {
>>>>> +    u32 code;
>>>>> +    u8 data_type;
>>>>> +    u8 encode_format;
>>>>> +    u8 bpp;
>>>>> +};
>>>>> +
>>>>> +struct tpg_formats {
>>>>> +    unsigned int nformats;
>>>>> +    const struct tpg_format_info *formats;
>>>>> +};
>>>>> +
>>>>> +struct tpg_device;
>>>>> +
>>>>> +struct tpg_hw_ops {
>>>>> +    int (*configure_stream)(struct tpg_device *tpg, u8 enable);
>>>>> +
>>>>> +    int (*configure_testgen_pattern)(struct tpg_device *tpg, s32 val);
>>>>> +
>>>>> +    u32 (*hw_version)(struct tpg_device *tpg);
>>>>> +
>>>>> +    int (*reset)(struct tpg_device *tpg);
>>>>> +
>>>>> +    void (*subdev_init)(struct tpg_device *tpg);
>>>>> +};
>>>>> +
>>>>> +struct tpg_subdev_resources {
>>>>> +    u8 lane_cnt;
>>>>> +    u8 vc_cnt;
>>>>> +    const struct tpg_formats *formats;
>>>>> +    const struct tpg_hw_ops *hw_ops;
>>>>> +};
>>>>> +
>>>>> +struct tpg_device {
>>>>> +    struct camss *camss;
>>>>> +    u8 id;
>>>>> +    struct v4l2_subdev subdev;
>>>>> +    struct media_pad pads[MSM_TPG_PADS_NUM];
>>>>> +    void __iomem *base;
>>>>> +    struct camss_clock *clock;
>>>>> +    int nclocks;
>>>>> +    struct tpg_testgen_config testgen;
>>>>> +    struct v4l2_mbus_framefmt fmt[MSM_TPG_PADS_NUM];
>>>>> +    struct v4l2_ctrl_handler ctrls;
>>>>> +    struct v4l2_ctrl *testgen_mode;
>>>>> +    const struct tpg_subdev_resources *res;
>>>>> +    const struct tpg_format *formats;
>>>>> +    unsigned int nformats;
>>>>> +    u32 hw_version;
>>>>> +};
>>>>> +
>>>>> +struct camss_subdev_resources;
>>>>> +
>>>>> +const struct tpg_format_info *tpg_get_fmt_entry(struct tpg_device
>>>>> *tpg,
>>>>> +                        const struct tpg_format_info *formats,
>>>>> +                        unsigned int nformats,
>>>>> +                        u32 code);
>>>>> +
>>>>> +int msm_tpg_subdev_init(struct camss *camss,
>>>>> +            struct tpg_device *tpg,
>>>>> +            const struct camss_subdev_resources *res, u8 id);
>>>>> +
>>>>> +int msm_tpg_register_entity(struct tpg_device *tpg,
>>>>> +                struct v4l2_device *v4l2_dev);
>>>>> +
>>>>> +void msm_tpg_unregister_entity(struct tpg_device *tpg);
>>>>> +
>>>>> +extern const char * const testgen_payload_modes[];
>>>>> +
>>>>> +extern const struct tpg_formats tpg_formats_gen1;
>>>>> +
>>>>> +extern const struct tpg_hw_ops tpg_ops_gen1;
>>>>> +
>>>>> +#endif /* QC_MSM_CAMSS_TPG_H */
>>>>> diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/
>>>>> media/platform/qcom/camss/camss.h
>>>>> index
>>>>> 9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..a892a87bed8bde8919200d6eac2b7a5338763c0e 100644
>>>>> --- a/drivers/media/platform/qcom/camss/camss.h
>>>>> +++ b/drivers/media/platform/qcom/camss/camss.h
>>>>> @@ -21,6 +21,7 @@
>>>>>     #include "camss-csid.h"
>>>>>     #include "camss-csiphy.h"
>>>>>     #include "camss-ispif.h"
>>>>> +#include "camss-tpg.h"
>>>>>     #include "camss-vfe.h"
>>>>>     #include "camss-format.h"
>>>>> @@ -52,6 +53,7 @@ struct camss_subdev_resources {
>>>>>         char *interrupt[CAMSS_RES_MAX];
>>>>>         union {
>>>>>             struct csiphy_subdev_resources csiphy;
>>>>> +        struct tpg_subdev_resources tpg;
>>>>>             struct csid_subdev_resources csid;
>>>>>             struct vfe_subdev_resources vfe;
>>>>>         };
>>>>> @@ -104,6 +106,7 @@ struct camss_resources {
>>>>>         enum camss_version version;
>>>>>         const char *pd_name;
>>>>>         const struct camss_subdev_resources *csiphy_res;
>>>>> +    const struct camss_subdev_resources *tpg_res;
>>>>>         const struct camss_subdev_resources *csid_res;
>>>>>         const struct camss_subdev_resources *ispif_res;
>>>>>         const struct camss_subdev_resources *vfe_res;
>>>>> @@ -111,6 +114,7 @@ struct camss_resources {
>>>>>         const struct resources_icc *icc_res;
>>>>>         const unsigned int icc_path_num;
>>>>>         const unsigned int csiphy_num;
>>>>> +    const unsigned int tpg_num;
>>>>>         const unsigned int csid_num;
>>>>>         const unsigned int vfe_num;
>>>>>     };
>>>>> @@ -121,6 +125,7 @@ struct camss {
>>>>>         struct media_device media_dev;
>>>>>         struct device *dev;
>>>>>         struct csiphy_device *csiphy;
>>>>> +    struct tpg_device *tpg;
>>>>>         struct csid_device *csid;
>>>>>         struct ispif_device *ispif;
>>>>>         struct vfe_device *vfe;
>>>>>
>>>>
>>
> Thanks for your review.

You are welcome! I will try my best to keep on with the reviews.

-- 
Best wishes,
Vladimir

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-14 22:07           ` Vladimir Zapolskiy
@ 2026-01-15  1:06             ` Bryan O'Donoghue
  2026-01-15  2:58               ` Vladimir Zapolskiy
  0 siblings, 1 reply; 19+ messages in thread
From: Bryan O'Donoghue @ 2026-01-15  1:06 UTC (permalink / raw)
  To: Vladimir Zapolskiy, Wenmeng Liu, Robert Foss, Todor Tomov,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

On 14/01/2026 22:07, Vladimir Zapolskiy wrote:
> Hi Wenmeng.
> 
> On 1/14/26 14:18, Wenmeng Liu wrote:
>>
>> Hi Vladimir,
>>
>>
>> On 1/14/2026 1:05 PM, Vladimir Zapolskiy wrote:
>>> Hi Wenmeng.
>>>
>>> On 1/14/26 05:04, Wenmeng Liu wrote:
>>>> Hi Vladimir,
>>>>
>>>> On 1/14/2026 12:27 AM, Vladimir Zapolskiy wrote:
>>>>> Hello Wenmeng.
>>>>>
>>>>> On 1/13/26 11:03, Wenmeng Liu wrote:
>>>>>> Introduce a new common Test Pattern Generator (TPG) implementation 
>>>>>> for
>>>>>> Qualcomm CAMSS. This module provides a generic interface for pattern
>>>>>> generation that can be reused by multiple platforms.
>>>>>>
>>>>>> Unlike CSID-integrated TPG, this TPG acts as a standalone block
>>>>>> that emulates both CSIPHY and sensor behavior, enabling flexible test
>>>>>> patterns without external hardware.
>>>>>>
>>>>>> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
>>>>>> ---
>>>>>>     drivers/media/platform/qcom/camss/Makefile    |   1 +
>>>>>>     drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++ 
>>>>>> ++++++
>>>>>> ++++++++++
>>>>>>     drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>>>>>>     drivers/media/platform/qcom/camss/camss.h     |   5 +
>>>>>>     4 files changed, 843 insertions(+)
>>>>>>
>>>>>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/
>>>>>> media/platform/qcom/camss/Makefile
>>>>>> index
>>>>>> 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
>>>>>> --- a/drivers/media/platform/qcom/camss/Makefile
>>>>>> +++ b/drivers/media/platform/qcom/camss/Makefile
>>>>>> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>>>>>>             camss-vfe.o \
>>>>>>             camss-video.o \
>>>>>>             camss-format.o \
>>>>>> +        camss-tpg.o \
>>>>>
>>>>> While you're here, please sort and keep the lines in alphabetical 
>>>>> order.
>>>> ACK.
>>>>
>>>>>
>>>>>>     obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>>>>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/
>>>>>> media/platform/qcom/camss/camss-tpg.c
>>>>>> new file mode 100644
>>>>>> index
>>>>>> 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
>>>>>> @@ -0,0 +1,710 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>>> +/*
>>>>>> + *
>>>>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>>>>> + *
>>>>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its 
>>>>>> subsidiaries.
>>>>>> + */
>>>>>> +#include <linux/clk.h>
>>>>>> +#include <linux/delay.h>
>>>>>> +#include <linux/io.h>
>>>>>> +#include <linux/kernel.h>
>>>>>> +#include <linux/of.h>
>>>>>> +#include <linux/platform_device.h>
>>>>>> +#include <linux/pm_runtime.h>
>>>>>> +#include <media/media-entity.h>
>>>>>> +#include <media/v4l2-device.h>
>>>>>> +#include <media/v4l2-subdev.h>
>>>>>> +
>>>>>> +#include "camss-tpg.h"
>>>>>> +#include "camss.h"
>>>>>> +
>>>>>> +const char * const testgen_payload_modes[] = {
>>>>>> +    "Disabled",
>>>>>> +    "Incrementing",
>>>>>> +    "Alternating 0x55/0xAA",
>>>>>> +    "Reserved",
>>>>>> +    "Reserved",
>>>>>> +    "Pseudo-random Data",
>>>>>> +    "User Specified",
>>>>>> +    "Reserved",
>>>>>> +    "Reserved",
>>>>>> +    "Color bars",
>>>>>> +    "Reserved"
>>>>>
>>>>> It makes little sense to mention the unsupported values, and then
>>>>> introduce enum tpg_testgen_mode to list the supported ones.
>>>>>
>>>> This is for ctrl menu, will do as follow:
>>>> static const char * const testgen_payload_modes[] = {
>>>>        [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
>>>>        [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
>>>>        [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating
>>>> 0x55/0xAA",
>>>>        [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
>>>>        [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
>>>>        [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
>>>> };
>>>>
>>>
>>> This is also not perfect, still userspace is misinformed about a number
>>> of possible TPG modes vs. a number of actually supported TPG modes.
>>>
>> 0x0: INCREMENTING
>> 0x1: ALTERNATING_55_AA
>> 0x4: RANDOM
>> 0x5: USER_SPECIFIED
>> 0x8: COLOR_BARS
>>
>> These values come from the register configuration, these pattern values
>> are consistent with the CSID TPG.
> 
> Userspace should not be aware of such low level details as register values,
> there are many abstraction layers in-between to hide this type of 
> information.
> 
> Writing proper values to registers should be a concern on the driver level,
> it sounds improper to push this simple task and responsibility to 
> userspace.

I think we should stick to the same format as is already upstream for 
the CSID version of this - which is the same data.

---
bod

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-15  1:06             ` Bryan O'Donoghue
@ 2026-01-15  2:58               ` Vladimir Zapolskiy
  2026-01-15 15:54                 ` Bryan O'Donoghue
  0 siblings, 1 reply; 19+ messages in thread
From: Vladimir Zapolskiy @ 2026-01-15  2:58 UTC (permalink / raw)
  To: Bryan O'Donoghue, Wenmeng Liu, Robert Foss, Todor Tomov,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

On 1/15/26 03:06, Bryan O'Donoghue wrote:
> On 14/01/2026 22:07, Vladimir Zapolskiy wrote:
>> Hi Wenmeng.
>>
>> On 1/14/26 14:18, Wenmeng Liu wrote:
>>>
>>> Hi Vladimir,
>>>
>>>
>>> On 1/14/2026 1:05 PM, Vladimir Zapolskiy wrote:
>>>> Hi Wenmeng.
>>>>
>>>> On 1/14/26 05:04, Wenmeng Liu wrote:
>>>>> Hi Vladimir,
>>>>>
>>>>> On 1/14/2026 12:27 AM, Vladimir Zapolskiy wrote:
>>>>>> Hello Wenmeng.
>>>>>>
>>>>>> On 1/13/26 11:03, Wenmeng Liu wrote:
>>>>>>> Introduce a new common Test Pattern Generator (TPG) implementation
>>>>>>> for
>>>>>>> Qualcomm CAMSS. This module provides a generic interface for pattern
>>>>>>> generation that can be reused by multiple platforms.
>>>>>>>
>>>>>>> Unlike CSID-integrated TPG, this TPG acts as a standalone block
>>>>>>> that emulates both CSIPHY and sensor behavior, enabling flexible test
>>>>>>> patterns without external hardware.
>>>>>>>
>>>>>>> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
>>>>>>> ---
>>>>>>>      drivers/media/platform/qcom/camss/Makefile    |   1 +
>>>>>>>      drivers/media/platform/qcom/camss/camss-tpg.c | 710 ++++++++++
>>>>>>> ++++++
>>>>>>> ++++++++++
>>>>>>>      drivers/media/platform/qcom/camss/camss-tpg.h | 127 +++++
>>>>>>>      drivers/media/platform/qcom/camss/camss.h     |   5 +
>>>>>>>      4 files changed, 843 insertions(+)
>>>>>>>
>>>>>>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/
>>>>>>> media/platform/qcom/camss/Makefile
>>>>>>> index
>>>>>>> 5e349b4915130c71dbff90e73102e46dfede1520..d355e67c25700ac061b878543c32ed8defc03ad0 100644
>>>>>>> --- a/drivers/media/platform/qcom/camss/Makefile
>>>>>>> +++ b/drivers/media/platform/qcom/camss/Makefile
>>>>>>> @@ -27,5 +27,6 @@ qcom-camss-objs += \
>>>>>>>              camss-vfe.o \
>>>>>>>              camss-video.o \
>>>>>>>              camss-format.o \
>>>>>>> +        camss-tpg.o \
>>>>>>
>>>>>> While you're here, please sort and keep the lines in alphabetical
>>>>>> order.
>>>>> ACK.
>>>>>
>>>>>>
>>>>>>>      obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>>>>>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/
>>>>>>> media/platform/qcom/camss/camss-tpg.c
>>>>>>> new file mode 100644
>>>>>>> index
>>>>>>> 0000000000000000000000000000000000000000..f4c015aafa202e5b64fafa3c543128fda6440b11
>>>>>>> --- /dev/null
>>>>>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg.c
>>>>>>> @@ -0,0 +1,710 @@
>>>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>>>> +/*
>>>>>>> + *
>>>>>>> + * Qualcomm MSM Camera Subsystem - TPG Module
>>>>>>> + *
>>>>>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its
>>>>>>> subsidiaries.
>>>>>>> + */
>>>>>>> +#include <linux/clk.h>
>>>>>>> +#include <linux/delay.h>
>>>>>>> +#include <linux/io.h>
>>>>>>> +#include <linux/kernel.h>
>>>>>>> +#include <linux/of.h>
>>>>>>> +#include <linux/platform_device.h>
>>>>>>> +#include <linux/pm_runtime.h>
>>>>>>> +#include <media/media-entity.h>
>>>>>>> +#include <media/v4l2-device.h>
>>>>>>> +#include <media/v4l2-subdev.h>
>>>>>>> +
>>>>>>> +#include "camss-tpg.h"
>>>>>>> +#include "camss.h"
>>>>>>> +
>>>>>>> +const char * const testgen_payload_modes[] = {
>>>>>>> +    "Disabled",
>>>>>>> +    "Incrementing",
>>>>>>> +    "Alternating 0x55/0xAA",
>>>>>>> +    "Reserved",
>>>>>>> +    "Reserved",
>>>>>>> +    "Pseudo-random Data",
>>>>>>> +    "User Specified",
>>>>>>> +    "Reserved",
>>>>>>> +    "Reserved",
>>>>>>> +    "Color bars",
>>>>>>> +    "Reserved"
>>>>>>
>>>>>> It makes little sense to mention the unsupported values, and then
>>>>>> introduce enum tpg_testgen_mode to list the supported ones.
>>>>>>
>>>>> This is for ctrl menu, will do as follow:
>>>>> static const char * const testgen_payload_modes[] = {
>>>>>         [TPG_PAYLOAD_MODE_DISABLED]          = "Disabled",
>>>>>         [TPG_PAYLOAD_MODE_INCREMENTING]      = "Incrementing",
>>>>>         [TPG_PAYLOAD_MODE_ALTERNATING_55_AA]       = "Alternating
>>>>> 0x55/0xAA",
>>>>>         [TPG_PAYLOAD_MODE_RANDOM]      = "Pseudo-random Data",
>>>>>         [TPG_PAYLOAD_MODE_USER_SPECIFIED]    = "User Specified",
>>>>>         [TPG_PAYLOAD_MODE_COLOR_BARS]        = "Color bars",
>>>>> };
>>>>>
>>>>
>>>> This is also not perfect, still userspace is misinformed about a number
>>>> of possible TPG modes vs. a number of actually supported TPG modes.
>>>>
>>> 0x0: INCREMENTING
>>> 0x1: ALTERNATING_55_AA
>>> 0x4: RANDOM
>>> 0x5: USER_SPECIFIED
>>> 0x8: COLOR_BARS
>>>
>>> These values come from the register configuration, these pattern values
>>> are consistent with the CSID TPG.
>>
>> Userspace should not be aware of such low level details as register values,
>> there are many abstraction layers in-between to hide this type of
>> information.
>>
>> Writing proper values to registers should be a concern on the driver level,
>> it sounds improper to push this simple task and responsibility to
>> userspace.
> 
> I think we should stick to the same format as is already upstream for
> the CSID version of this - which is the same data.
> 

It is not the same and it will not be the same, if the currently presented
version is taken. If TPG modes in CSID are continuous, here they are not,
so it makes a big difference for userspace, and better it should be removed.

-- 
Best wishes,
Vladimir

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets
  2026-01-13  9:03 ` [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets Wenmeng Liu
@ 2026-01-15 15:48   ` kernel test robot
  2026-01-16 19:32   ` Vijay Kumar Tumati
  1 sibling, 0 replies; 19+ messages in thread
From: kernel test robot @ 2026-01-15 15:48 UTC (permalink / raw)
  To: Wenmeng Liu, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: oe-kbuild-all, linux-media, linux-kernel, linux-arm-msm,
	Wenmeng Liu

Hi Wenmeng,

kernel test robot noticed the following build errors:

[auto build test ERROR on f417b7ffcbef7d76b0d8860518f50dae0e7e5eda]

url:    https://github.com/intel-lab-lkp/linux/commits/Wenmeng-Liu/media-qcom-camss-Add-common-TPG-support/20260113-171032
base:   f417b7ffcbef7d76b0d8860518f50dae0e7e5eda
patch link:    https://lore.kernel.org/r/20260113-camss_tpg-v8-3-fa2cb186a018%40oss.qualcomm.com
patch subject: [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets
config: parisc-randconfig-002-20260115 (https://download.01.org/0day-ci/archive/20260115/202601152315.fC7ckH9z-lkp@intel.com/config)
compiler: hppa-linux-gcc (GCC) 12.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260115/202601152315.fC7ckH9z-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601152315.fC7ckH9z-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/media/platform/qcom/camss/camss-csid-680.c: In function '__csid_configure_rx':
>> drivers/media/platform/qcom/camss/camss-csid-680.c:202:32: error: implicit declaration of function 'FIELD_PREP' [-Werror=implicit-function-declaration]
     202 |                         val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
         |                                ^~~~~~~~~~
   cc1: some warnings being treated as errors
--
   drivers/media/platform/qcom/camss/camss-csid-gen3.c: In function '__csid_configure_rx':
>> drivers/media/platform/qcom/camss/camss-csid-gen3.c:126:32: error: implicit declaration of function 'FIELD_PREP' [-Werror=implicit-function-declaration]
     126 |                         val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
         |                                ^~~~~~~~~~
   cc1: some warnings being treated as errors


vim +/FIELD_PREP +202 drivers/media/platform/qcom/camss/camss-csid-680.c

   185	
   186	static void __csid_configure_rx(struct csid_device *csid,
   187					struct csid_phy_config *phy, int vc)
   188	{
   189		u32 val;
   190		struct camss *camss;
   191		struct tpg_device *tpg;
   192	
   193		camss = csid->camss;
   194		val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
   195		val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
   196		val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << CSI2_RX_CFG0_PHY_NUM_SEL;
   197	
   198		if (camss->tpg) {
   199			tpg = &camss->tpg[phy->csiphy_id];
   200	
   201			if (csid->tpg_linked && tpg->testgen.mode > 0) {
 > 202				val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
   203				val |= CSI2_RX_CFG0_TPG_NUM_EN;
   204			}
   205		}
   206	
   207		writel(val, csid->base + CSID_CSI2_RX_CFG0);
   208	
   209		val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
   210		if (vc > 3)
   211			val |= CSI2_RX_CFG1_VC_MODE;
   212		writel(val, csid->base + CSID_CSI2_RX_CFG1);
   213	}
   214	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-15  2:58               ` Vladimir Zapolskiy
@ 2026-01-15 15:54                 ` Bryan O'Donoghue
  2026-01-16  9:24                   ` Vladimir Zapolskiy
  0 siblings, 1 reply; 19+ messages in thread
From: Bryan O'Donoghue @ 2026-01-15 15:54 UTC (permalink / raw)
  To: Vladimir Zapolskiy, Wenmeng Liu, Robert Foss, Todor Tomov,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

On 15/01/2026 02:58, Vladimir Zapolskiy wrote:
>>> Writing proper values to registers should be a concern on the driver 
>>> level,
>>> it sounds improper to push this simple task and responsibility to
>>> userspace.
>>
>> I think we should stick to the same format as is already upstream for
>> the CSID version of this - which is the same data.
>>
> 
> It is not the same and it will not be the same, if the currently presented
> version is taken. If TPG modes in CSID are continuous, here they are not,
> so it makes a big difference for userspace, and better it should be 
> removed.

Not sure I follow you here.

The set of strings for camss-csid we have now is:

const char * const csid_testgen_modes[] = {
         "Disabled",
         "Incrementing",
         "Alternating 0x55/0xAA",
         "All Zeros 0x00",
         "All Ones 0xFF",
         "Pseudo-random Data",
         "User Specified",
         "Complex pattern",
         "Color box",
         "Color bars",
         NULL
};

Wengmeng has

+const char * const testgen_payload_modes[] = {
+	"Disabled",
+	"Incrementing",
+	"Alternating 0x55/0xAA",
+	"Reserved",
+	"Reserved",
+	"Pseudo-random Data",
+	"User Specified",
+	"Reserved",
+	"Reserved",
+	"Color bars",
+	"Reserved"
+};

I think the "Reserved" should go away but, other than that we should 
keep namespace consistency between CSID-TPG and standalone-TPG.

---
bod

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-15 15:54                 ` Bryan O'Donoghue
@ 2026-01-16  9:24                   ` Vladimir Zapolskiy
  2026-01-19  9:59                     ` Bryan O'Donoghue
  0 siblings, 1 reply; 19+ messages in thread
From: Vladimir Zapolskiy @ 2026-01-16  9:24 UTC (permalink / raw)
  To: Bryan O'Donoghue, Wenmeng Liu, Robert Foss, Todor Tomov,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

On 1/15/26 17:54, Bryan O'Donoghue wrote:
> On 15/01/2026 02:58, Vladimir Zapolskiy wrote:
>>>> Writing proper values to registers should be a concern on the driver
>>>> level,
>>>> it sounds improper to push this simple task and responsibility to
>>>> userspace.
>>>
>>> I think we should stick to the same format as is already upstream for
>>> the CSID version of this - which is the same data.
>>>
>>
>> It is not the same and it will not be the same, if the currently presented
>> version is taken. If TPG modes in CSID are continuous, here they are not,
>> so it makes a big difference for userspace, and better it should be
>> removed.
> 
> Not sure I follow you here.
> 
> The set of strings for camss-csid we have now is:
> 
> const char * const csid_testgen_modes[] = {
>           "Disabled",
>           "Incrementing",
>           "Alternating 0x55/0xAA",
>           "All Zeros 0x00",
>           "All Ones 0xFF",
>           "Pseudo-random Data",
>           "User Specified",
>           "Complex pattern",
>           "Color box",
>           "Color bars",
>           NULL
> };
> 
> Wengmeng has
> 
> +const char * const testgen_payload_modes[] = {
> +	"Disabled",
> +	"Incrementing",
> +	"Alternating 0x55/0xAA",
> +	"Reserved",
> +	"Reserved",
> +	"Pseudo-random Data",
> +	"User Specified",
> +	"Reserved",
> +	"Reserved",
> +	"Color bars",
> +	"Reserved"
> +};
> 
> I think the "Reserved" should go away but, other than that we should

That's what I've asked, there is no dispute.

> keep namespace consistency between CSID-TPG and standalone-TPG.
> 

When "consistency" is not defined, it's just a fine sounding buzzword.

CSID TPG has:
* modes, which numbers are continuously incremented,
* the number of TPG modes for a user is expectedly the number of TPG modes.

The displayed v8 of the "standalone TPG" broke both assumptions from above,
so there is no more "consistency" between two TPGs, while I explicitly ask
to preserve the "consistency".

-- 
Best wishes,
Vladimir

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets
  2026-01-13  9:03 ` [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets Wenmeng Liu
  2026-01-15 15:48   ` kernel test robot
@ 2026-01-16 19:32   ` Vijay Kumar Tumati
  2026-01-19  3:29     ` Wenmeng Liu
  1 sibling, 1 reply; 19+ messages in thread
From: Vijay Kumar Tumati @ 2026-01-16 19:32 UTC (permalink / raw)
  To: Wenmeng Liu, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

Hi Wenmeng,

On 1/13/2026 1:03 AM, Wenmeng Liu wrote:
> Add support for TPG found on LeMans, Monaco, Hamoa.
>
> Signed-off-by: Wenmeng Liu<wenmeng.liu@oss.qualcomm.com>
> ---
>   drivers/media/platform/qcom/camss/Makefile         |   1 +
>   drivers/media/platform/qcom/camss/camss-csid-680.c |  14 ++
>   .../media/platform/qcom/camss/camss-csid-gen3.c    |  14 ++
>   drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 257 +++++++++++++++++++++
>   drivers/media/platform/qcom/camss/camss.c          | 128 ++++++++++
>   5 files changed, 414 insertions(+)
>
> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
> index d355e67c25700ac061b878543c32ed8defc03ad0..e8996dacf1771d13ec1936c9bebc0e71566898ef 100644
> --- a/drivers/media/platform/qcom/camss/Makefile
> +++ b/drivers/media/platform/qcom/camss/Makefile
> @@ -28,5 +28,6 @@ qcom-camss-objs += \
>   		camss-video.o \
>   		camss-format.o \
>   		camss-tpg.o \
> +		camss-tpg-gen1.o \
>   
>   obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
> diff --git a/drivers/media/platform/qcom/camss/camss-csid-680.c b/drivers/media/platform/qcom/camss/camss-csid-680.c
> index 3ad3a174bcfb8c0d319930d0010df92308cb5ae4..a5da35cae2eb9acf642795c0a91db58d845f211c 100644
> --- a/drivers/media/platform/qcom/camss/camss-csid-680.c
> +++ b/drivers/media/platform/qcom/camss/camss-csid-680.c
> @@ -103,6 +103,8 @@
>   #define		CSI2_RX_CFG0_PHY_NUM_SEL			20
>   #define		CSI2_RX_CFG0_PHY_SEL_BASE_IDX			1
>   #define		CSI2_RX_CFG0_PHY_TYPE_SEL			24
> +#define		CSI2_RX_CFG0_TPG_NUM_EN				BIT(27)
> +#define		CSI2_RX_CFG0_TPG_NUM_SEL			GENMASK(29, 28)
>   
>   #define CSID_CSI2_RX_CFG1					0x204
>   #define		CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN		BIT(0)
> @@ -185,11 +187,23 @@ static void __csid_configure_rx(struct csid_device *csid,
>   				struct csid_phy_config *phy, int vc)
>   {
>   	u32 val;
> +	struct camss *camss;
> +	struct tpg_device *tpg;
>   
> +	camss = csid->camss;
>   	val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
>   	val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
>   	val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << CSI2_RX_CFG0_PHY_NUM_SEL;
"phy_num_sel" and "tpg_num_sel" can be in if-else. They both are not 
required at once.
>   
> +	if (camss->tpg) {
> +		tpg = &camss->tpg[phy->csiphy_id];
> +
> +		if (csid->tpg_linked && tpg->testgen.mode > 0) {
If the tpg is linked and the mode is not valid, shouldn't you be 
throwing error?
> +			val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
> +			val |= CSI2_RX_CFG0_TPG_NUM_EN;
Can we rename this to CSI2_RX_CFG0_TPG_MUX_EN?
> +		}
> +	}
> +
>   	writel(val, csid->base + CSID_CSI2_RX_CFG0);
>   
>   	val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
> diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen3.c b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
> index 664245cf6eb0cac662b02f8b920cd1c72db0aeb2..5f9eb533723f2864df64fd6c63e2682fed4a12ae 100644
> --- a/drivers/media/platform/qcom/camss/camss-csid-gen3.c
> +++ b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
> @@ -66,6 +66,8 @@
>   #define		CSI2_RX_CFG0_VC_MODE		3
>   #define		CSI2_RX_CFG0_DL0_INPUT_SEL	4
>   #define		CSI2_RX_CFG0_PHY_NUM_SEL	20
> +#define		CSI2_RX_CFG0_TPG_NUM_EN		BIT(27)
> +#define		CSI2_RX_CFG0_TPG_NUM_SEL	GENMASK(29, 28)
>   
>   #define CSID_CSI2_RX_CFG1		0x204
>   #define		CSI2_RX_CFG1_ECC_CORRECTION_EN	BIT(0)
> @@ -109,11 +111,23 @@ static void __csid_configure_rx(struct csid_device *csid,
>   				struct csid_phy_config *phy, int vc)
Same as above.
>   {
>   	int val;
> +	struct camss *camss;
> +	struct tpg_device *tpg;
>   
> +	camss = csid->camss;
>   	val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
>   	val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
>   	val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << CSI2_RX_CFG0_PHY_NUM_SEL;
>   
> +	if (camss->tpg) {
> +		tpg = &camss->tpg[phy->csiphy_id];
> +
> +		if (csid->tpg_linked && tpg->testgen.mode > 0) {
> +			val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy->csiphy_id + 1);
> +			val |= CSI2_RX_CFG0_TPG_NUM_EN;
> +		}
> +	}
> +
>   	writel(val, csid->base + CSID_CSI2_RX_CFG0);
>   
>   	val = CSI2_RX_CFG1_ECC_CORRECTION_EN;
> diff --git a/drivers/media/platform/qcom/camss/camss-tpg-gen1.c b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..d7ef7a1709648406dc59c210d355851397980769
> --- /dev/null
> +++ b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
> @@ -0,0 +1,257 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + *
> + * Qualcomm MSM Camera Subsystem - TPG (Test Patter Generator) Module
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +#include <linux/bitfield.h>
> +#include <linux/completion.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/of.h>
> +
> +#include "camss-tpg.h"
> +#include "camss.h"
> +
> +#define TPG_HW_VERSION		0x0
> +# define HW_VERSION_STEPPING		GENMASK(15, 0)
> +# define HW_VERSION_REVISION		GENMASK(27, 16)
> +# define HW_VERSION_GENERATION		GENMASK(31, 28)
> +
> +#define TPG_HW_VER(gen, rev, step) \
> +	(((u32)(gen) << 28) | ((u32)(rev) << 16) | (u32)(step))
> +
> +#define TPG_HW_VER_2_0_0                TPG_HW_VER(2, 0, 0)
> +#define TPG_HW_VER_2_1_0                TPG_HW_VER(2, 1, 0)
> +
> +#define TPG_HW_STATUS		0x4
> +
> +#define TPG_VC_n_GAIN_CFG(n)		(0x60 + (n) * 0x60)
I know why this is here but it may be is better to group this with VC 
based registers. In fact, can you please segregate these macros into sub 
sections with headings like "TPG global registers", "TPG VC based 
registers", "TPG DT based registers" etc. Just for better readability.
> +
> +#define TPG_CTRL		0x64
> +# define TPG_CTRL_TEST_EN		BIT(0)
> +# define TPG_CTRL_PHY_SEL		BIT(3)
> +# define TPG_CTRL_NUM_ACTIVE_LANES	GENMASK(5, 4)
> +# define TPG_CTRL_VC_DT_PATTERN_ID	GENMASK(8, 6)
> +# define TPG_CTRL_OVERLAP_SHDR_EN	BIT(10)
> +# define TPG_CTRL_NUM_ACTIVE_VC		GENMASK(31, 30)
> +#  define NUM_ACTIVE_VC_0_ENABLED		0
> +#  define NUM_ACTIVE_VC_0_1_ENABLED		1
> +#  define NUM_ACTIVE_VC_0_1_2_ENABLED		2
> +#  define NUM_ACTIVE_VC_0_1_3_ENABLED		3
> +
> +#define TPG_VC_n_CFG0(n)	(0x68 + (n) * 0x60)
> +# define TPG_VC_n_CFG0_VC_NUM			GENMASK(4, 0)
> +# define TPG_VC_n_CFG0_NUM_ACTIVE_DT		GENMASK(9, 8)
> +#  define NUM_ACTIVE_SLOTS_0_ENABLED			0
> +#  define NUM_ACTIVE_SLOTS_0_1_ENABLED			1
> +#  define NUM_ACTIVE_SLOTS_0_1_2_ENABLED		2
> +#  define NUM_ACTIVE_SLOTS_0_1_3_ENABLED		3
s/NUM_ACTIVE_SLOTS/DT/?, if you really need these macros. Similarly for 
VCs enabled.
> +# define TPG_VC_n_CFG0_NUM_BATCH		GENMASK(15, 12)
> +# define TPG_VC_n_CFG0_NUM_FRAMES		GENMASK(31, 16)
> +
> +#define TPG_VC_n_LSFR_SEED(n)	(0x6C + (n) * 0x60)
> +
> +#define TPG_VC_n_HBI_CFG(n)	(0x70 + (n) * 0x60)
> +
> +#define TPG_VC_n_VBI_CFG(n)	(0x74 + (n) * 0x60)
> +
> +#define TPG_VC_n_COLOR_BARS_CFG(n)		(0x78 + (n) * 0x60)
> +# define TPG_VC_n_COLOR_BARS_CFG_PIX_PATTERN		GENMASK(2, 0)
> +# define TPG_VC_n_COLOR_BARS_CFG_QCFA_EN		BIT(3)
> +# define TPG_VC_n_COLOR_BARS_CFG_SPLIT_EN		BIT(4)
> +# define TPG_VC_n_COLOR_BARS_CFG_NOISE_EN		BIT(5)
> +# define TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD		GENMASK(13, 8)
> +# define TPG_VC_n_COLOR_BARS_CFG_XCFA_EN		BIT(16)
> +# define TPG_VC_n_COLOR_BARS_CFG_SIZE_X			GENMASK(26, 24)
> +# define TPG_VC_n_COLOR_BARS_CFG_SIZE_Y			GENMASK(30, 28)
> +
> +#define TPG_VC_m_DT_n_CFG_0(m, n)		(0x7C + (m) * 0x60 + (n) * 0xC)
> +# define TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT	GENMASK(15, 0)
> +# define TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH	GENMASK(31, 16)
> +
> +#define TPG_VC_m_DT_n_CFG_1(m, n)		(0x80 + (m) * 0x60 + (n) * 0xC)
> +# define TPG_VC_m_DT_n_CFG_1_DATA_TYPE		GENMASK(5, 0)
> +# define TPG_VC_m_DT_n_CFG_1_ECC_XOR_MASK	GENMASK(13, 8)
> +# define TPG_VC_m_DT_n_CFG_1_CRC_XOR_MASK	GENMASK(31, 16)
> +
> +#define TPG_VC_m_DT_n_CFG_2(m, n)		(0x84 + (m) * 0x60 + (n) * 0xC)
> +# define TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE		GENMASK(3, 0)
> +/* v2.0.0: USER[19:4], ENC[23:20] */
> +# define TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD		GENMASK(19, 4)
> +# define TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT			GENMASK(23, 20)
For better readability, can you make these TPG_V2_0_*?
> +/* v2.1.0: USER[27:4], ENC[31:28] */
> +# define TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD	GENMASK(27, 4)
> +# define TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT			GENMASK(31, 28)
> +
> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR0(n)	(0xB0 + (n) * 0x60)
> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR1(n)	(0xB4 + (n) * 0x60)
> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR2(n)	(0xB8 + (n) * 0x60)
> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR3(n)	(0xBC + (n) * 0x60)
> +
> +/* Line offset between VC(n) and VC(n-1), n form 1 to 3 */
> +#define TPG_VC_n_SHDR_CFG	(0x84 + (n) * 0x60)
> +
> +#define TPG_CLEAR		0x1F4
> +
> +#define TPG_HBI_PCT_DEFAULT			545	/* 545% */
> +#define TPG_VBI_PCT_DEFAULT			10	/* 10% */
> +#define PERCENT_BASE				100
> +#define BITS_PER_BYTE				8
> +
> +/* Default user-specified payload for TPG test generator.
> + * Keep consistent with CSID TPG default: 0xBE.
> + */
> +#define TPG_USER_SPECIFIED_PAYLOAD_DEFAULT	0xBE
> +#define TPG_LFSR_SEED_DEFAULT			0x12345678
> +#define TPG_COLOR_BARS_CFG_STANDARD \
> +	FIELD_PREP(TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD, 0xA)
> +
> +static int tpg_stream_on(struct tpg_device *tpg)
Add function headers? For this  and a few other below.
> +{
> +	struct tpg_testgen_config *tg = &tpg->testgen;
> +	struct v4l2_mbus_framefmt *input_format;
> +	const struct tpg_format_info *format;
> +	u8 lane_cnt = tpg->res->lane_cnt;
> +	u8 dt_cnt = 0;
> +	u8 i;
> +	u32 val;
> +
> +	/* Loop through all enabled VCs and configure stream for each */
> +	for (i = 0; i < tpg->res->vc_cnt; i++) {
Here as well, can we segregate the code to global, VC based and DT based 
configs with some comments?
> +		input_format = &tpg->fmt[MSM_TPG_PAD_SRC + i];
> +		format = tpg_get_fmt_entry(tpg,
> +					   tpg->res->formats->formats,
> +					   tpg->res->formats->nformats,
> +					   input_format->code);
> +		if (IS_ERR(format))
> +			return -EINVAL;
> +
> +		val = FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT, input_format->height & 0xffff) |
> +		      FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH, input_format->width & 0xffff);
> +		writel(val, tpg->base + TPG_VC_m_DT_n_CFG_0(i, dt_cnt));
> +
> +		val = FIELD_PREP(TPG_VC_m_DT_n_CFG_1_DATA_TYPE, format->data_type);
> +		writel(val, tpg->base + TPG_VC_m_DT_n_CFG_1(i, dt_cnt));
> +
> +		if (tpg->hw_version == TPG_HW_VER_2_0_0) {
> +			val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg->mode - 1) |
> +				FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
> +					   TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
> +				FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
> +					   format->encode_format);
> +		} else if (tpg->hw_version >= TPG_HW_VER_2_1_0) {
> +			val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg->mode - 1) |
> +				FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
> +					   TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
> +				FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
> +					   format->encode_format);
> +		}
> +		writel(val, tpg->base + TPG_VC_m_DT_n_CFG_2(i, dt_cnt));
> +
> +		writel(TPG_COLOR_BARS_CFG_STANDARD, tpg->base + TPG_VC_n_COLOR_BARS_CFG(i));
> +
> +		val = DIV_ROUND_UP(input_format->width * format->bpp * TPG_HBI_PCT_DEFAULT,
> +				   BITS_PER_BYTE * lane_cnt * PERCENT_BASE);
> +		writel(val, tpg->base + TPG_VC_n_HBI_CFG(i));
> +		val = input_format->height * TPG_VBI_PCT_DEFAULT / PERCENT_BASE;
> +		writel(val, tpg->base + TPG_VC_n_VBI_CFG(i));
> +
> +		writel(TPG_LFSR_SEED_DEFAULT, tpg->base + TPG_VC_n_LSFR_SEED(i));
> +
> +		/* configure one DT, infinite frames */
Although this driver is not supporting more than one DT in a VC right 
now, is there a way we can make the API generic enough to receive #DTs 
in each VS and their dimensions?
> +		val = FIELD_PREP(TPG_VC_n_CFG0_VC_NUM, i) |
> +		      FIELD_PREP(TPG_VC_n_CFG0_NUM_FRAMES, 0);
> +		writel(val, tpg->base + TPG_VC_n_CFG0(i));
> +	}
> +
> +	val = FIELD_PREP(TPG_CTRL_TEST_EN, 1) |
> +		  FIELD_PREP(TPG_CTRL_PHY_SEL, 0) |
Same here, is there a way to make the API generic to receive CPHY / DPHY 
mode required?
> +		  FIELD_PREP(TPG_CTRL_NUM_ACTIVE_LANES, lane_cnt - 1) |
> +		  FIELD_PREP(TPG_CTRL_VC_DT_PATTERN_ID, 0) |
You are assuming frame interleaved mode always. It may be is a good 
start but a bunch of functionality is missing here. Just please think of 
the scalability of the API even though the driver support is limited at 
this point.
> +		  FIELD_PREP(TPG_CTRL_NUM_ACTIVE_VC, tpg->res->vc_cnt - 1);
> +	writel(val, tpg->base + TPG_CTRL);
> +
> +	return 0;
> +}
> +
> +static void tpg_stream_off(struct tpg_device *tpg)
> +{
> +	writel(0, tpg->base + TPG_CTRL);
> +	writel(1, tpg->base + TPG_CLEAR);
Why not just reuse the reset function?
> +}
> +
> +static int tpg_configure_stream(struct tpg_device *tpg, u8 enable)
> +{
> +	int ret = 0;
> +
> +	if (enable)
> +		ret = tpg_stream_on(tpg);
> +	else
> +		tpg_stream_off(tpg);
> +
> +	return ret;
> +}
> +
> +static int tpg_configure_testgen_pattern(struct tpg_device *tpg, s32 val)
> +{
> +	if (val >= 0 && val <= TPG_PAYLOAD_MODE_COLOR_BARS)
> +		tpg->testgen.mode = val;
> +
> +	return 0;
> +}
> +
> +/*
> + * tpg_hw_version - tpg hardware version query
> + * @tpg: tpg device
> + *
> + * Return HW version or error
> + */
> +static u32 tpg_hw_version(struct tpg_device *tpg)
> +{
> +	u32 hw_version;
> +	u32 hw_gen;
> +	u32 hw_rev;
> +	u32 hw_step;
> +
> +	hw_version = readl(tpg->base + TPG_HW_VERSION);
> +	hw_gen = FIELD_GET(HW_VERSION_GENERATION, hw_version);
> +	hw_rev = FIELD_GET(HW_VERSION_REVISION, hw_version);
> +	hw_step = FIELD_GET(HW_VERSION_STEPPING, hw_version);
> +
> +	tpg->hw_version = hw_version;
> +
> +	dev_dbg_once(tpg->camss->dev, "tpg HW Version = %u.%u.%u\n",
> +		     hw_gen, hw_rev, hw_step);
> +
> +	return hw_version;
> +}
> +
> +/*
> + * tpg_reset - Trigger reset on tpg module and wait to complete
Doesn't seem like there is any wait here, right? Also, do you want to 
clear the IRQs in reset?
> + * @tpg: tpg device
> + *
> + * Return 0 on success or a negative error code otherwise
> + */
> +static int tpg_reset(struct tpg_device *tpg)
> +{
> +	writel(0, tpg->base + TPG_CTRL);
> +	writel(1, tpg->base + TPG_CLEAR);
> +
> +	return 0;
> +}
> +
> +static void tpg_subdev_init(struct tpg_device *tpg)
> +{
> +	tpg->testgen.modes = testgen_payload_modes;
> +	tpg->testgen.nmodes = TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
> +}
> +
> +const struct tpg_hw_ops tpg_ops_gen1 = {
> +	.configure_stream = tpg_configure_stream,
> +	.configure_testgen_pattern = tpg_configure_testgen_pattern,
> +	.hw_version = tpg_hw_version,
> +	.reset = tpg_reset,
> +	.subdev_init = tpg_subdev_init,
> +};
> diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c
> index 43fdcb9af101ef34b118035ca9c68757b66118df..5cddf1bc09f97c2c61f907939bb54663d8eab3d4 100644
> --- a/drivers/media/platform/qcom/camss/camss.c
> +++ b/drivers/media/platform/qcom/camss/camss.c
> @@ -3199,6 +3199,65 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = {
>   	},
>   };
>   
> +static const struct camss_subdev_resources tpg_res_8775p[] = {
> +	/* TPG0 */
> +	{
> +		.regulators = {  },
> +		.clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
Why should TPG need camnoc_rt_axi clk?
> +		.clock_rate = {
> +			{ 400000000 },
> +			{ 0 },
> +			{ 400000000 },
> +		},
> +		.reg = { "tpg0" },
> +		.interrupt = { "tpg0" },
> +		.tpg = {
> +			.lane_cnt = 4,
> +			.vc_cnt = 1,
> +			.formats = &tpg_formats_gen1,
> +			.hw_ops = &tpg_ops_gen1
> +		}
> +	},
> +
> +	/* TPG1 */
> +	{
> +		.regulators = {  },
> +		.clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
> +		.clock_rate = {
> +			{ 400000000 },
> +			{ 0 },
> +			{ 400000000 },
> +		},
> +		.reg = { "tpg1" },
> +		.interrupt = { "tpg1" },
> +		.tpg = {
> +			.lane_cnt = 4,
> +			.vc_cnt = 1,
> +			.formats = &tpg_formats_gen1,
> +			.hw_ops = &tpg_ops_gen1
> +		}
> +	},
> +
> +	/* TPG2 */
> +	{
> +		.regulators = {  },
> +		.clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
> +		.clock_rate = {
> +			{ 400000000 },
> +			{ 0 },
> +			{ 400000000 },
> +		},
> +		.reg = { "tpg2" },
> +		.interrupt = { "tpg2" }, + .tpg = { + .lane_cnt = 4, + .vc_cnt = 1, + .formats = 
> &tpg_formats_gen1, + .hw_ops = &tpg_ops_gen1 + } + }, +}; + static 
> const struct camss_subdev_resources csid_res_8775p[] = { /* CSID0 */ { 
> @@ -3595,6 +3654,62 @@ static const struct camss_subdev_resources 
> csiphy_res_x1e80100[] = { }, }; +static const struct 
> camss_subdev_resources tpg_res_x1e80100[] = { + /* TPG0 */ + { + 
> .regulators = { }, + .clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
> +		.clock_rate = {
> +			{ 400000000 },
> +			{ 0 },
> +			{ 400000000 },
> +		},
> +		.reg = { "csitpg0" },
> +		.tpg = {
> +			.lane_cnt = 4,
> +			.vc_cnt = 1,
> +			.formats = &tpg_formats_gen1,
> +			.hw_ops = &tpg_ops_gen1
> +		}
> +	},
> +
> +	/* TPG1 */
> +	{
> +		.regulators = {  },
> +		.clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
> +		.clock_rate = {
> +			{ 400000000 },
> +			{ 0 },
> +			{ 400000000 },
> +		},
> +		.reg = { "csitpg1" },
> +		.tpg = {
> +			.lane_cnt = 4,
> +			.vc_cnt = 1,
> +			.formats = &tpg_formats_gen1,
> +			.hw_ops = &tpg_ops_gen1
> +		}
> +	},
> +
> +	/* TPG2 */
> +	{
> +		.regulators = {  },
> +		.clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
> +		.clock_rate = {
> +			{ 400000000 },
> +			{ 0 },
> +			{ 400000000 },
> +		},
> +		.reg = { "csitpg2" },
> +		.tpg = {
> +			.lane_cnt = 4,
> +			.vc_cnt = 1,
> +			.formats = &tpg_formats_gen1,
> +			.hw_ops = &tpg_ops_gen1
> +		}
> +	},
> +};
> +
>   static const struct camss_subdev_resources csid_res_x1e80100[] = {
>   	/* CSID0 */
>   	{
> @@ -4674,6 +4789,13 @@ static int camss_probe(struct platform_device *pdev)
>   	if (!camss->csiphy)
>   		return -ENOMEM;
>   
> +	if (camss->res->tpg_num > 0) {
> +		camss->tpg = devm_kcalloc(dev, camss->res->tpg_num,
> +					  sizeof(*camss->tpg), GFP_KERNEL);
> +		if (!camss->tpg)
> +			return -ENOMEM;
> +	}
> +
>   	camss->csid = devm_kcalloc(dev, camss->res->csid_num, sizeof(*camss->csid),
>   				   GFP_KERNEL);
>   	if (!camss->csid)
> @@ -4863,11 +4985,13 @@ static const struct camss_resources qcs8300_resources = {
>   	.version = CAMSS_8300,
>   	.pd_name = "top", .csiphy_res = csiphy_res_8300, + .tpg_res = tpg_res_8775p, 
> .csid_res = csid_res_8775p, .csid_wrapper_res = 
> &csid_wrapper_res_sm8550, .vfe_res = vfe_res_8775p, .icc_res = 
> icc_res_qcs8300, .csiphy_num = ARRAY_SIZE(csiphy_res_8300), + .tpg_num 
> = ARRAY_SIZE(tpg_res_8775p), .csid_num = ARRAY_SIZE(csid_res_8775p), 
> .vfe_num = ARRAY_SIZE(vfe_res_8775p), .icc_path_num = 
> ARRAY_SIZE(icc_res_qcs8300), @@ -4877,11 +5001,13 @@ static const 
> struct camss_resources sa8775p_resources = { .version = CAMSS_8775P, 
> .pd_name = "top", .csiphy_res = csiphy_res_8775p, + .tpg_res = tpg_res_8775p, 
> .csid_res = csid_res_8775p, .csid_wrapper_res = 
> &csid_wrapper_res_sm8550, .vfe_res = vfe_res_8775p, .icc_res = 
> icc_res_sa8775p, .csiphy_num = ARRAY_SIZE(csiphy_res_8775p), + 
> .tpg_num = ARRAY_SIZE(tpg_res_8775p), .csid_num = 
> ARRAY_SIZE(csid_res_8775p), .vfe_num = ARRAY_SIZE(vfe_res_8775p), 
> .icc_path_num = ARRAY_SIZE(icc_res_sa8775p), @@ -4992,11 +5118,13 @@ 
> static const struct camss_resources x1e80100_resources = { .pd_name = "top",
>   	.csiphy_res = csiphy_res_x1e80100,
>   	.csid_res = csid_res_x1e80100,
> +	.tpg_res = tpg_res_x1e80100,
>   	.vfe_res = vfe_res_x1e80100,
>   	.csid_wrapper_res = &csid_wrapper_res_x1e80100,
>   	.icc_res = icc_res_x1e80100,
>   	.icc_path_num = ARRAY_SIZE(icc_res_x1e80100),
>   	.csiphy_num = ARRAY_SIZE(csiphy_res_x1e80100),
> +	.tpg_num = ARRAY_SIZE(tpg_res_x1e80100),
>   	.csid_num = ARRAY_SIZE(csid_res_x1e80100),
>   	.vfe_num = ARRAY_SIZE(vfe_res_x1e80100),
>   };

Thanks,

Vijay.


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets
  2026-01-16 19:32   ` Vijay Kumar Tumati
@ 2026-01-19  3:29     ` Wenmeng Liu
  2026-01-21 18:45       ` Vijay Kumar Tumati
  0 siblings, 1 reply; 19+ messages in thread
From: Wenmeng Liu @ 2026-01-19  3:29 UTC (permalink / raw)
  To: Vijay Kumar Tumati, Robert Foss, Todor Tomov,
	Bryan O'Donoghue, Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm


Hi Vijay,

On 1/17/2026 3:32 AM, Vijay Kumar Tumati wrote:
> Hi Wenmeng,
> 
> On 1/13/2026 1:03 AM, Wenmeng Liu wrote:
>> Add support for TPG found on LeMans, Monaco, Hamoa.
>>
>> Signed-off-by: Wenmeng Liu<wenmeng.liu@oss.qualcomm.com>
>> ---
>>   drivers/media/platform/qcom/camss/Makefile         |   1 +
>>   drivers/media/platform/qcom/camss/camss-csid-680.c |  14 ++
>>   .../media/platform/qcom/camss/camss-csid-gen3.c    |  14 ++
>>   drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 257 +++++++++++ 
>> ++++++++++
>>   drivers/media/platform/qcom/camss/camss.c          | 128 ++++++++++
>>   5 files changed, 414 insertions(+)
>>
>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/ 
>> media/platform/qcom/camss/Makefile
>> index 
>> d355e67c25700ac061b878543c32ed8defc03ad0..e8996dacf1771d13ec1936c9bebc0e71566898ef 100644
>> --- a/drivers/media/platform/qcom/camss/Makefile
>> +++ b/drivers/media/platform/qcom/camss/Makefile
>> @@ -28,5 +28,6 @@ qcom-camss-objs += \
>>           camss-video.o \
>>           camss-format.o \
>>           camss-tpg.o \
>> +        camss-tpg-gen1.o \
>>   obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>> diff --git a/drivers/media/platform/qcom/camss/camss-csid-680.c b/ 
>> drivers/media/platform/qcom/camss/camss-csid-680.c
>> index 
>> 3ad3a174bcfb8c0d319930d0010df92308cb5ae4..a5da35cae2eb9acf642795c0a91db58d845f211c 100644
>> --- a/drivers/media/platform/qcom/camss/camss-csid-680.c
>> +++ b/drivers/media/platform/qcom/camss/camss-csid-680.c
>> @@ -103,6 +103,8 @@
>>   #define        CSI2_RX_CFG0_PHY_NUM_SEL            20
>>   #define        CSI2_RX_CFG0_PHY_SEL_BASE_IDX            1
>>   #define        CSI2_RX_CFG0_PHY_TYPE_SEL            24
>> +#define        CSI2_RX_CFG0_TPG_NUM_EN                BIT(27)
>> +#define        CSI2_RX_CFG0_TPG_NUM_SEL            GENMASK(29, 28)
>>   #define CSID_CSI2_RX_CFG1                    0x204
>>   #define        CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN        BIT(0)
>> @@ -185,11 +187,23 @@ static void __csid_configure_rx(struct 
>> csid_device *csid,
>>                   struct csid_phy_config *phy, int vc)
>>   {
>>       u32 val;
>> +    struct camss *camss;
>> +    struct tpg_device *tpg;
>> +    camss = csid->camss;
>>       val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
>>       val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
>>       val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << 
>> CSI2_RX_CFG0_PHY_NUM_SEL;
> "phy_num_sel" and "tpg_num_sel" can be in if-else. They both are not 
> required at once.

ACK

>> +    if (camss->tpg) {
>> +        tpg = &camss->tpg[phy->csiphy_id];
>> +
>> +        if (csid->tpg_linked && tpg->testgen.mode > 0) {
> If the tpg is linked and the mode is not valid, shouldn't you be 
> throwing error?

ACK
>> +            val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy- 
>> >csiphy_id + 1);
>> +            val |= CSI2_RX_CFG0_TPG_NUM_EN;
> Can we rename this to CSI2_RX_CFG0_TPG_MUX_EN?

ACK

>> +        }
>> +    }
>> +
>>       writel(val, csid->base + CSID_CSI2_RX_CFG0);
>>       val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
>> diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen3.c b/ 
>> drivers/media/platform/qcom/camss/camss-csid-gen3.c
>> index 
>> 664245cf6eb0cac662b02f8b920cd1c72db0aeb2..5f9eb533723f2864df64fd6c63e2682fed4a12ae 100644
>> --- a/drivers/media/platform/qcom/camss/camss-csid-gen3.c
>> +++ b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
>> @@ -66,6 +66,8 @@
>>   #define        CSI2_RX_CFG0_VC_MODE        3
>>   #define        CSI2_RX_CFG0_DL0_INPUT_SEL    4
>>   #define        CSI2_RX_CFG0_PHY_NUM_SEL    20
>> +#define        CSI2_RX_CFG0_TPG_NUM_EN        BIT(27)
>> +#define        CSI2_RX_CFG0_TPG_NUM_SEL    GENMASK(29, 28)
>>   #define CSID_CSI2_RX_CFG1        0x204
>>   #define        CSI2_RX_CFG1_ECC_CORRECTION_EN    BIT(0)
>> @@ -109,11 +111,23 @@ static void __csid_configure_rx(struct 
>> csid_device *csid,
>>                   struct csid_phy_config *phy, int vc)
> Same as above.

ACK

>>   {
>>       int val;
>> +    struct camss *camss;
>> +    struct tpg_device *tpg;
>> +    camss = csid->camss;
>>       val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
>>       val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
>>       val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << 
>> CSI2_RX_CFG0_PHY_NUM_SEL;
>> +    if (camss->tpg) {
>> +        tpg = &camss->tpg[phy->csiphy_id];
>> +
>> +        if (csid->tpg_linked && tpg->testgen.mode > 0) {
>> +            val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy- 
>> >csiphy_id + 1);
>> +            val |= CSI2_RX_CFG0_TPG_NUM_EN;
>> +        }
>> +    }
>> +
>>       writel(val, csid->base + CSID_CSI2_RX_CFG0);
>>       val = CSI2_RX_CFG1_ECC_CORRECTION_EN;
>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg-gen1.c b/ 
>> drivers/media/platform/qcom/camss/camss-tpg-gen1.c
>> new file mode 100644
>> index 
>> 0000000000000000000000000000000000000000..d7ef7a1709648406dc59c210d355851397980769
>> --- /dev/null
>> +++ b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
>> @@ -0,0 +1,257 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + *
>> + * Qualcomm MSM Camera Subsystem - TPG (Test Patter Generator) Module
>> + *
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +#include <linux/bitfield.h>
>> +#include <linux/completion.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/of.h>
>> +
>> +#include "camss-tpg.h"
>> +#include "camss.h"
>> +
>> +#define TPG_HW_VERSION        0x0
>> +# define HW_VERSION_STEPPING        GENMASK(15, 0)
>> +# define HW_VERSION_REVISION        GENMASK(27, 16)
>> +# define HW_VERSION_GENERATION        GENMASK(31, 28)
>> +
>> +#define TPG_HW_VER(gen, rev, step) \
>> +    (((u32)(gen) << 28) | ((u32)(rev) << 16) | (u32)(step))
>> +
>> +#define TPG_HW_VER_2_0_0                TPG_HW_VER(2, 0, 0)
>> +#define TPG_HW_VER_2_1_0                TPG_HW_VER(2, 1, 0)
>> +
>> +#define TPG_HW_STATUS        0x4
>> +
>> +#define TPG_VC_n_GAIN_CFG(n)        (0x60 + (n) * 0x60)
> I know why this is here but it may be is better to group this with VC 
> based registers. In fact, can you please segregate these macros into sub 
> sections with headings like "TPG global registers", "TPG VC based 
> registers", "TPG DT based registers" etc. Just for better readability.

ACK, registers are arranged according to their addresses, and I will 
mark the global registers.

>> +
>> +#define TPG_CTRL        0x64
>> +# define TPG_CTRL_TEST_EN        BIT(0)
>> +# define TPG_CTRL_PHY_SEL        BIT(3)
>> +# define TPG_CTRL_NUM_ACTIVE_LANES    GENMASK(5, 4)
>> +# define TPG_CTRL_VC_DT_PATTERN_ID    GENMASK(8, 6)
>> +# define TPG_CTRL_OVERLAP_SHDR_EN    BIT(10)
>> +# define TPG_CTRL_NUM_ACTIVE_VC        GENMASK(31, 30)
>> +#  define NUM_ACTIVE_VC_0_ENABLED        0
>> +#  define NUM_ACTIVE_VC_0_1_ENABLED        1
>> +#  define NUM_ACTIVE_VC_0_1_2_ENABLED        2
>> +#  define NUM_ACTIVE_VC_0_1_3_ENABLED        3
>> +
>> +#define TPG_VC_n_CFG0(n)    (0x68 + (n) * 0x60)
>> +# define TPG_VC_n_CFG0_VC_NUM            GENMASK(4, 0)
>> +# define TPG_VC_n_CFG0_NUM_ACTIVE_DT        GENMASK(9, 8)
>> +#  define NUM_ACTIVE_SLOTS_0_ENABLED            0
>> +#  define NUM_ACTIVE_SLOTS_0_1_ENABLED            1
>> +#  define NUM_ACTIVE_SLOTS_0_1_2_ENABLED        2
>> +#  define NUM_ACTIVE_SLOTS_0_1_3_ENABLED        3
> s/NUM_ACTIVE_SLOTS/DT/?, if you really need these macros. Similarly for 
> VCs enabled.

ACK

>> +# define TPG_VC_n_CFG0_NUM_BATCH        GENMASK(15, 12)
>> +# define TPG_VC_n_CFG0_NUM_FRAMES        GENMASK(31, 16)
>> +
>> +#define TPG_VC_n_LSFR_SEED(n)    (0x6C + (n) * 0x60)
>> +
>> +#define TPG_VC_n_HBI_CFG(n)    (0x70 + (n) * 0x60)
>> +
>> +#define TPG_VC_n_VBI_CFG(n)    (0x74 + (n) * 0x60)
>> +
>> +#define TPG_VC_n_COLOR_BARS_CFG(n)        (0x78 + (n) * 0x60)
>> +# define TPG_VC_n_COLOR_BARS_CFG_PIX_PATTERN        GENMASK(2, 0)
>> +# define TPG_VC_n_COLOR_BARS_CFG_QCFA_EN        BIT(3)
>> +# define TPG_VC_n_COLOR_BARS_CFG_SPLIT_EN        BIT(4)
>> +# define TPG_VC_n_COLOR_BARS_CFG_NOISE_EN        BIT(5)
>> +# define TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD        GENMASK(13, 8)
>> +# define TPG_VC_n_COLOR_BARS_CFG_XCFA_EN        BIT(16)
>> +# define TPG_VC_n_COLOR_BARS_CFG_SIZE_X            GENMASK(26, 24)
>> +# define TPG_VC_n_COLOR_BARS_CFG_SIZE_Y            GENMASK(30, 28)
>> +
>> +#define TPG_VC_m_DT_n_CFG_0(m, n)        (0x7C + (m) * 0x60 + (n) * 0xC)
>> +# define TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT    GENMASK(15, 0)
>> +# define TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH    GENMASK(31, 16)
>> +
>> +#define TPG_VC_m_DT_n_CFG_1(m, n)        (0x80 + (m) * 0x60 + (n) * 0xC)
>> +# define TPG_VC_m_DT_n_CFG_1_DATA_TYPE        GENMASK(5, 0)
>> +# define TPG_VC_m_DT_n_CFG_1_ECC_XOR_MASK    GENMASK(13, 8)
>> +# define TPG_VC_m_DT_n_CFG_1_CRC_XOR_MASK    GENMASK(31, 16)
>> +
>> +#define TPG_VC_m_DT_n_CFG_2(m, n)        (0x84 + (m) * 0x60 + (n) * 0xC)
>> +# define TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE        GENMASK(3, 0)
>> +/* v2.0.0: USER[19:4], ENC[23:20] */
>> +# define TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD        
>> GENMASK(19, 4)
>> +# define TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT            GENMASK(23, 20)
> For better readability, can you make these TPG_V2_0_*?
>> +/* v2.1.0: USER[27:4], ENC[31:28] */
>> +# define TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD    
>> GENMASK(27, 4)
>> +# define TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT            
>> GENMASK(31, 28)
>> +
>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR0(n)    (0xB0 + (n) * 0x60)
>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR1(n)    (0xB4 + (n) * 0x60)
>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR2(n)    (0xB8 + (n) * 0x60)
>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR3(n)    (0xBC + (n) * 0x60)
>> +
>> +/* Line offset between VC(n) and VC(n-1), n form 1 to 3 */
>> +#define TPG_VC_n_SHDR_CFG    (0x84 + (n) * 0x60)
>> +
>> +#define TPG_CLEAR        0x1F4
>> +
>> +#define TPG_HBI_PCT_DEFAULT            545    /* 545% */
>> +#define TPG_VBI_PCT_DEFAULT            10    /* 10% */
>> +#define PERCENT_BASE                100
>> +#define BITS_PER_BYTE                8
>> +
>> +/* Default user-specified payload for TPG test generator.
>> + * Keep consistent with CSID TPG default: 0xBE.
>> + */
>> +#define TPG_USER_SPECIFIED_PAYLOAD_DEFAULT    0xBE
>> +#define TPG_LFSR_SEED_DEFAULT            0x12345678
>> +#define TPG_COLOR_BARS_CFG_STANDARD \
>> +    FIELD_PREP(TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD, 0xA)
>> +
>> +static int tpg_stream_on(struct tpg_device *tpg)
> Add function headers? For this  and a few other below.

I received a comment asking me to remove the comments,
if a function is not called in multiple places or its purpose cannot be 
directly understood, adding extra documentation is useless.

>> +{
>> +    struct tpg_testgen_config *tg = &tpg->testgen;
>> +    struct v4l2_mbus_framefmt *input_format;
>> +    const struct tpg_format_info *format;
>> +    u8 lane_cnt = tpg->res->lane_cnt;
>> +    u8 dt_cnt = 0;
>> +    u8 i;
>> +    u32 val;
>> +
>> +    /* Loop through all enabled VCs and configure stream for each */
>> +    for (i = 0; i < tpg->res->vc_cnt; i++) {
> Here as well, can we segregate the code to global, VC based and DT based 
> configs with some comments?

This loop all contains configurations related to VC/DT.
Will add some comments for this.

>> +        input_format = &tpg->fmt[MSM_TPG_PAD_SRC + i];
>> +        format = tpg_get_fmt_entry(tpg,
>> +                       tpg->res->formats->formats,
>> +                       tpg->res->formats->nformats,
>> +                       input_format->code);
>> +        if (IS_ERR(format))
>> +            return -EINVAL;
>> +
>> +        val = FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT, 
>> input_format->height & 0xffff) |
>> +              FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH, 
>> input_format->width & 0xffff);
>> +        writel(val, tpg->base + TPG_VC_m_DT_n_CFG_0(i, dt_cnt));
>> +
>> +        val = FIELD_PREP(TPG_VC_m_DT_n_CFG_1_DATA_TYPE, format- 
>> >data_type);
>> +        writel(val, tpg->base + TPG_VC_m_DT_n_CFG_1(i, dt_cnt));
>> +
>> +        if (tpg->hw_version == TPG_HW_VER_2_0_0) {
>> +            val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg- 
>> >mode - 1) |
>> +                
>> FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
>> +                       TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
>> +                FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
>> +                       format->encode_format);
>> +        } else if (tpg->hw_version >= TPG_HW_VER_2_1_0) {
>> +            val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg- 
>> >mode - 1) |
>> +                
>> FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
>> +                       TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
>> +                FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
>> +                       format->encode_format);
>> +        }
>> +        writel(val, tpg->base + TPG_VC_m_DT_n_CFG_2(i, dt_cnt));
>> +
>> +        writel(TPG_COLOR_BARS_CFG_STANDARD, tpg->base + 
>> TPG_VC_n_COLOR_BARS_CFG(i));
>> +
>> +        val = DIV_ROUND_UP(input_format->width * format->bpp * 
>> TPG_HBI_PCT_DEFAULT,
>> +                   BITS_PER_BYTE * lane_cnt * PERCENT_BASE);
>> +        writel(val, tpg->base + TPG_VC_n_HBI_CFG(i));
>> +        val = input_format->height * TPG_VBI_PCT_DEFAULT / PERCENT_BASE;
>> +        writel(val, tpg->base + TPG_VC_n_VBI_CFG(i));
>> +
>> +        writel(TPG_LFSR_SEED_DEFAULT, tpg->base + 
>> TPG_VC_n_LSFR_SEED(i));
>> +
>> +        /* configure one DT, infinite frames */
> Although this driver is not supporting more than one DT in a VC right 
> now, is there a way we can make the API generic enough to receive #DTs 
> in each VS and their dimensions?

ACK

>> +        val = FIELD_PREP(TPG_VC_n_CFG0_VC_NUM, i) |
>> +              FIELD_PREP(TPG_VC_n_CFG0_NUM_FRAMES, 0);
>> +        writel(val, tpg->base + TPG_VC_n_CFG0(i));
>> +    }
>> +
>> +    val = FIELD_PREP(TPG_CTRL_TEST_EN, 1) |
>> +          FIELD_PREP(TPG_CTRL_PHY_SEL, 0) |
> Same here, is there a way to make the API generic to receive CPHY / DPHY 
> mode required?

ACK

>> +          FIELD_PREP(TPG_CTRL_NUM_ACTIVE_LANES, lane_cnt - 1) |
>> +          FIELD_PREP(TPG_CTRL_VC_DT_PATTERN_ID, 0) |
> You are assuming frame interleaved mode always. It may be is a good 
> start but a bunch of functionality is missing here. Just please think of 
> the scalability of the API even though the driver support is limited at 
> this point.

ACK

>> +          FIELD_PREP(TPG_CTRL_NUM_ACTIVE_VC, tpg->res->vc_cnt - 1);
>> +    writel(val, tpg->base + TPG_CTRL);
>> +
>> +    return 0;
>> +}
>> +
>> +static void tpg_stream_off(struct tpg_device *tpg)
>> +{
>> +    writel(0, tpg->base + TPG_CTRL);
>> +    writel(1, tpg->base + TPG_CLEAR);
> Why not just reuse the reset function?

ACK

>> +}
>> +
>> +static int tpg_configure_stream(struct tpg_device *tpg, u8 enable)
>> +{
>> +    int ret = 0;
>> +
>> +    if (enable)
>> +        ret = tpg_stream_on(tpg);
>> +    else
>> +        tpg_stream_off(tpg);
>> +
>> +    return ret;
>> +}
>> +
>> +static int tpg_configure_testgen_pattern(struct tpg_device *tpg, s32 
>> val)
>> +{
>> +    if (val >= 0 && val <= TPG_PAYLOAD_MODE_COLOR_BARS)
>> +        tpg->testgen.mode = val;
>> +
>> +    return 0;
>> +}
>> +
>> +/*
>> + * tpg_hw_version - tpg hardware version query
>> + * @tpg: tpg device
>> + *
>> + * Return HW version or error
>> + */
>> +static u32 tpg_hw_version(struct tpg_device *tpg)
>> +{
>> +    u32 hw_version;
>> +    u32 hw_gen;
>> +    u32 hw_rev;
>> +    u32 hw_step;
>> +
>> +    hw_version = readl(tpg->base + TPG_HW_VERSION);
>> +    hw_gen = FIELD_GET(HW_VERSION_GENERATION, hw_version);
>> +    hw_rev = FIELD_GET(HW_VERSION_REVISION, hw_version);
>> +    hw_step = FIELD_GET(HW_VERSION_STEPPING, hw_version);
>> +
>> +    tpg->hw_version = hw_version;
>> +
>> +    dev_dbg_once(tpg->camss->dev, "tpg HW Version = %u.%u.%u\n",
>> +             hw_gen, hw_rev, hw_step);
>> +
>> +    return hw_version;
>> +}
>> +
>> +/*
>> + * tpg_reset - Trigger reset on tpg module and wait to complete
> Doesn't seem like there is any wait here, right?

Do you have any suggestions on this? I noticed that there is no delay 
downstream either.

> Also, do you want to 
> clear the IRQs in reset?

Following the maintainer's suggestion, I removed IRQ support, but we do 
need to add IRQ clearing.

>> + * @tpg: tpg device
>> + *
>> + * Return 0 on success or a negative error code otherwise
>> + */
>> +static int tpg_reset(struct tpg_device *tpg)
>> +{
>> +    writel(0, tpg->base + TPG_CTRL);
>> +    writel(1, tpg->base + TPG_CLEAR);
>> +
>> +    return 0;
>> +}
>> +
>> +static void tpg_subdev_init(struct tpg_device *tpg)
>> +{
>> +    tpg->testgen.modes = testgen_payload_modes;
>> +    tpg->testgen.nmodes = TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
>> +}
>> +
>> +const struct tpg_hw_ops tpg_ops_gen1 = {
>> +    .configure_stream = tpg_configure_stream,
>> +    .configure_testgen_pattern = tpg_configure_testgen_pattern,
>> +    .hw_version = tpg_hw_version,
>> +    .reset = tpg_reset,
>> +    .subdev_init = tpg_subdev_init,
>> +};
>> diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/ 
>> media/platform/qcom/camss/camss.c
>> index 
>> 43fdcb9af101ef34b118035ca9c68757b66118df..5cddf1bc09f97c2c61f907939bb54663d8eab3d4 100644
>> --- a/drivers/media/platform/qcom/camss/camss.c
>> +++ b/drivers/media/platform/qcom/camss/camss.c
>> @@ -3199,6 +3199,65 @@ static const struct camss_subdev_resources 
>> csiphy_res_8775p[] = {
>>       },
>>   };
>> +static const struct camss_subdev_resources tpg_res_8775p[] = {
>> +    /* TPG0 */
>> +    {
>> +        .regulators = {  },
>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
> Why should TPG need camnoc_rt_axi clk?

As tested ,TPG can`t streaming without camnoc_rt_axi clk.
For Pixel path, some platform can stream without camnoc_rt_axi clk but 
TPG not.

>> +        .clock_rate = {
>> +            { 400000000 },
>> +            { 0 },
>> +            { 400000000 },
>> +        },
>> +        .reg = { "tpg0" },
>> +        .interrupt = { "tpg0" },
>> +        .tpg = {
>> +            .lane_cnt = 4,
>> +            .vc_cnt = 1,
>> +            .formats = &tpg_formats_gen1,
>> +            .hw_ops = &tpg_ops_gen1
>> +        }
>> +    },
>> +
>> +    /* TPG1 */
>> +    {
>> +        .regulators = {  },
>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
>> +        .clock_rate = {
>> +            { 400000000 },
>> +            { 0 },
>> +            { 400000000 },
>> +        },
>> +        .reg = { "tpg1" },
>> +        .interrupt = { "tpg1" },
>> +        .tpg = {
>> +            .lane_cnt = 4,
>> +            .vc_cnt = 1,
>> +            .formats = &tpg_formats_gen1,
>> +            .hw_ops = &tpg_ops_gen1
>> +        }
>> +    },
>> +
>> +    /* TPG2 */
>> +    {
>> +        .regulators = {  },
>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
>> +        .clock_rate = {
>> +            { 400000000 },
>> +            { 0 },
>> +            { 400000000 },
>> +        },
>> +        .reg = { "tpg2" },
>> +        .interrupt = { "tpg2" }, + .tpg = { + .lane_cnt = 4, 
>> + .vc_cnt = 1, + .formats = &tpg_formats_gen1, + .hw_ops = 
>> &tpg_ops_gen1 + } + }, +}; + static const struct 
>> camss_subdev_resources csid_res_8775p[] = { /* CSID0 */ { @@ -3595,6 
>> +3654,62 @@ static const struct camss_subdev_resources 
>> csiphy_res_x1e80100[] = { }, }; +static const struct 
>> camss_subdev_resources tpg_res_x1e80100[] = { + /* TPG0 */ + 
>> { + .regulators = { }, + .clock = { "camnoc_rt_axi", "cpas_ahb", 
>> "csid_csiphy_rx" },
>> +        .clock_rate = {
>> +            { 400000000 },
>> +            { 0 },
>> +            { 400000000 },
>> +        },
>> +        .reg = { "csitpg0" },
>> +        .tpg = {
>> +            .lane_cnt = 4,
>> +            .vc_cnt = 1,
>> +            .formats = &tpg_formats_gen1,
>> +            .hw_ops = &tpg_ops_gen1
>> +        }
>> +    },
>> +
>> +    /* TPG1 */
>> +    {
>> +        .regulators = {  },
>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
>> +        .clock_rate = {
>> +            { 400000000 },
>> +            { 0 },
>> +            { 400000000 },
>> +        },
>> +        .reg = { "csitpg1" },
>> +        .tpg = {
>> +            .lane_cnt = 4,
>> +            .vc_cnt = 1,
>> +            .formats = &tpg_formats_gen1,
>> +            .hw_ops = &tpg_ops_gen1
>> +        }
>> +    },
>> +
>> +    /* TPG2 */
>> +    {
>> +        .regulators = {  },
>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
>> +        .clock_rate = {
>> +            { 400000000 },
>> +            { 0 },
>> +            { 400000000 },
>> +        },
>> +        .reg = { "csitpg2" },
>> +        .tpg = {
>> +            .lane_cnt = 4,
>> +            .vc_cnt = 1,
>> +            .formats = &tpg_formats_gen1,
>> +            .hw_ops = &tpg_ops_gen1
>> +        }
>> +    },
>> +};
>> +
>>   static const struct camss_subdev_resources csid_res_x1e80100[] = {
>>       /* CSID0 */
>>       {
>> @@ -4674,6 +4789,13 @@ static int camss_probe(struct platform_device 
>> *pdev)
>>       if (!camss->csiphy)
>>           return -ENOMEM;
>> +    if (camss->res->tpg_num > 0) {
>> +        camss->tpg = devm_kcalloc(dev, camss->res->tpg_num,
>> +                      sizeof(*camss->tpg), GFP_KERNEL);
>> +        if (!camss->tpg)
>> +            return -ENOMEM;
>> +    }
>> +
>>       camss->csid = devm_kcalloc(dev, camss->res->csid_num, 
>> sizeof(*camss->csid),
>>                      GFP_KERNEL);
>>       if (!camss->csid)
>> @@ -4863,11 +4985,13 @@ static const struct camss_resources 
>> qcs8300_resources = {
>>       .version = CAMSS_8300,
>>       .pd_name = "top", .csiphy_res = csiphy_res_8300, + .tpg_res = 
>> tpg_res_8775p, .csid_res = csid_res_8775p, .csid_wrapper_res = 
>> &csid_wrapper_res_sm8550, .vfe_res = vfe_res_8775p, .icc_res = 
>> icc_res_qcs8300, .csiphy_num = ARRAY_SIZE(csiphy_res_8300), + .tpg_num 
>> = ARRAY_SIZE(tpg_res_8775p), .csid_num = 
>> ARRAY_SIZE(csid_res_8775p), .vfe_num = 
>> ARRAY_SIZE(vfe_res_8775p), .icc_path_num = 
>> ARRAY_SIZE(icc_res_qcs8300), @@ -4877,11 +5001,13 @@ static const 
>> struct camss_resources sa8775p_resources = { .version = 
>> CAMSS_8775P, .pd_name = "top", .csiphy_res = csiphy_res_8775p, 
>> + .tpg_res = tpg_res_8775p, .csid_res = 
>> csid_res_8775p, .csid_wrapper_res = &csid_wrapper_res_sm8550, .vfe_res 
>> = vfe_res_8775p, .icc_res = icc_res_sa8775p, .csiphy_num = 
>> ARRAY_SIZE(csiphy_res_8775p), + .tpg_num = 
>> ARRAY_SIZE(tpg_res_8775p), .csid_num = 
>> ARRAY_SIZE(csid_res_8775p), .vfe_num = 
>> ARRAY_SIZE(vfe_res_8775p), .icc_path_num = 
>> ARRAY_SIZE(icc_res_sa8775p), @@ -4992,11 +5118,13 @@ static const 
>> struct camss_resources x1e80100_resources = { .pd_name = "top",
>>       .csiphy_res = csiphy_res_x1e80100,
>>       .csid_res = csid_res_x1e80100,
>> +    .tpg_res = tpg_res_x1e80100,
>>       .vfe_res = vfe_res_x1e80100,
>>       .csid_wrapper_res = &csid_wrapper_res_x1e80100,
>>       .icc_res = icc_res_x1e80100,
>>       .icc_path_num = ARRAY_SIZE(icc_res_x1e80100),
>>       .csiphy_num = ARRAY_SIZE(csiphy_res_x1e80100),
>> +    .tpg_num = ARRAY_SIZE(tpg_res_x1e80100),
>>       .csid_num = ARRAY_SIZE(csid_res_x1e80100),
>>       .vfe_num = ARRAY_SIZE(vfe_res_x1e80100),
>>   };
> 
> Thanks,
> 
> Vijay.
> 

Thanks,
Wenmeng

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 1/3] media: qcom: camss: Add common TPG support
  2026-01-16  9:24                   ` Vladimir Zapolskiy
@ 2026-01-19  9:59                     ` Bryan O'Donoghue
  0 siblings, 0 replies; 19+ messages in thread
From: Bryan O'Donoghue @ 2026-01-19  9:59 UTC (permalink / raw)
  To: Vladimir Zapolskiy, Wenmeng Liu, Robert Foss, Todor Tomov,
	Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm

On 16/01/2026 09:24, Vladimir Zapolskiy wrote:
> 
>> keep namespace consistency between CSID-TPG and standalone-TPG.
>>
> 
> When "consistency" is not defined, it's just a fine sounding buzzword.

I mean all spoken words /buzz/s//vibrate/ :)

> CSID TPG has:
> * modes, which numbers are continuously incremented,
> * the number of TPG modes for a user is expectedly the number of TPG modes.
> 
> The displayed v8 of the "standalone TPG" broke both assumptions from above,
> so there is no more "consistency" between two TPGs, while I explicitly ask
> to preserve the "consistency".

I'm not sure I really follow what you're saying so, I'll restate what 
I'm saying.

The values that you can set on the TPG from userspace via yavta should 
be the same names/values when talking to the CSID version as the TPG 
version.

Just looking at the list of strings, I think omitting the Reserved 
pretty much does that.

But you certainly shouldn't have one set of strings for the CSID-TPG and 
the dedicated TPG which produce the same test patterns, no matter how 
its implemented under the hood.

---
bod

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets
  2026-01-19  3:29     ` Wenmeng Liu
@ 2026-01-21 18:45       ` Vijay Kumar Tumati
  0 siblings, 0 replies; 19+ messages in thread
From: Vijay Kumar Tumati @ 2026-01-21 18:45 UTC (permalink / raw)
  To: Wenmeng Liu, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-kernel, linux-media, linux-arm-msm



On 1/18/2026 7:29 PM, Wenmeng Liu wrote:
> 
> Hi Vijay,
> 
> On 1/17/2026 3:32 AM, Vijay Kumar Tumati wrote:
>> Hi Wenmeng,
>>
>> On 1/13/2026 1:03 AM, Wenmeng Liu wrote:
>>> Add support for TPG found on LeMans, Monaco, Hamoa.
>>>
>>> Signed-off-by: Wenmeng Liu<wenmeng.liu@oss.qualcomm.com>
>>> ---
>>>   drivers/media/platform/qcom/camss/Makefile         |   1 +
>>>   drivers/media/platform/qcom/camss/camss-csid-680.c |  14 ++
>>>   .../media/platform/qcom/camss/camss-csid-gen3.c    |  14 ++
>>>   drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 257 ++++++++++ 
>>> + ++++++++++
>>>   drivers/media/platform/qcom/camss/camss.c          | 128 ++++++++++
>>>   5 files changed, 414 insertions(+)
>>>
>>> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/ 
>>> media/platform/qcom/camss/Makefile
>>> index 
>>> d355e67c25700ac061b878543c32ed8defc03ad0..e8996dacf1771d13ec1936c9bebc0e71566898ef 100644
>>> --- a/drivers/media/platform/qcom/camss/Makefile
>>> +++ b/drivers/media/platform/qcom/camss/Makefile
>>> @@ -28,5 +28,6 @@ qcom-camss-objs += \
>>>           camss-video.o \
>>>           camss-format.o \
>>>           camss-tpg.o \
>>> +        camss-tpg-gen1.o \
>>>   obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
>>> diff --git a/drivers/media/platform/qcom/camss/camss-csid-680.c b/ 
>>> drivers/media/platform/qcom/camss/camss-csid-680.c
>>> index 
>>> 3ad3a174bcfb8c0d319930d0010df92308cb5ae4..a5da35cae2eb9acf642795c0a91db58d845f211c 100644
>>> --- a/drivers/media/platform/qcom/camss/camss-csid-680.c
>>> +++ b/drivers/media/platform/qcom/camss/camss-csid-680.c
>>> @@ -103,6 +103,8 @@
>>>   #define        CSI2_RX_CFG0_PHY_NUM_SEL            20
>>>   #define        CSI2_RX_CFG0_PHY_SEL_BASE_IDX            1
>>>   #define        CSI2_RX_CFG0_PHY_TYPE_SEL            24
>>> +#define        CSI2_RX_CFG0_TPG_NUM_EN                BIT(27)
>>> +#define        CSI2_RX_CFG0_TPG_NUM_SEL            GENMASK(29, 28)
>>>   #define CSID_CSI2_RX_CFG1                    0x204
>>>   #define        CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN        BIT(0)
>>> @@ -185,11 +187,23 @@ static void __csid_configure_rx(struct 
>>> csid_device *csid,
>>>                   struct csid_phy_config *phy, int vc)
>>>   {
>>>       u32 val;
>>> +    struct camss *camss;
>>> +    struct tpg_device *tpg;
>>> +    camss = csid->camss;
>>>       val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
>>>       val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
>>>       val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << 
>>> CSI2_RX_CFG0_PHY_NUM_SEL;
>> "phy_num_sel" and "tpg_num_sel" can be in if-else. They both are not 
>> required at once.
> 
> ACK
> 
>>> +    if (camss->tpg) {
>>> +        tpg = &camss->tpg[phy->csiphy_id];
>>> +
>>> +        if (csid->tpg_linked && tpg->testgen.mode > 0) {
>> If the tpg is linked and the mode is not valid, shouldn't you be 
>> throwing error?
> 
> ACK
>>> +            val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy- 
>>> >csiphy_id + 1);
>>> +            val |= CSI2_RX_CFG0_TPG_NUM_EN;
>> Can we rename this to CSI2_RX_CFG0_TPG_MUX_EN?
> 
> ACK
> 
>>> +        }
>>> +    }
>>> +
>>>       writel(val, csid->base + CSID_CSI2_RX_CFG0);
>>>       val = CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN;
>>> diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen3.c b/ 
>>> drivers/media/platform/qcom/camss/camss-csid-gen3.c
>>> index 
>>> 664245cf6eb0cac662b02f8b920cd1c72db0aeb2..5f9eb533723f2864df64fd6c63e2682fed4a12ae 100644
>>> --- a/drivers/media/platform/qcom/camss/camss-csid-gen3.c
>>> +++ b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
>>> @@ -66,6 +66,8 @@
>>>   #define        CSI2_RX_CFG0_VC_MODE        3
>>>   #define        CSI2_RX_CFG0_DL0_INPUT_SEL    4
>>>   #define        CSI2_RX_CFG0_PHY_NUM_SEL    20
>>> +#define        CSI2_RX_CFG0_TPG_NUM_EN        BIT(27)
>>> +#define        CSI2_RX_CFG0_TPG_NUM_SEL    GENMASK(29, 28)
>>>   #define CSID_CSI2_RX_CFG1        0x204
>>>   #define        CSI2_RX_CFG1_ECC_CORRECTION_EN    BIT(0)
>>> @@ -109,11 +111,23 @@ static void __csid_configure_rx(struct 
>>> csid_device *csid,
>>>                   struct csid_phy_config *phy, int vc)
>> Same as above.
> 
> ACK
> 
>>>   {
>>>       int val;
>>> +    struct camss *camss;
>>> +    struct tpg_device *tpg;
>>> +    camss = csid->camss;
>>>       val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES;
>>>       val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL;
>>>       val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << 
>>> CSI2_RX_CFG0_PHY_NUM_SEL;
>>> +    if (camss->tpg) {
>>> +        tpg = &camss->tpg[phy->csiphy_id];
>>> +
>>> +        if (csid->tpg_linked && tpg->testgen.mode > 0) {
>>> +            val |= FIELD_PREP(CSI2_RX_CFG0_TPG_NUM_SEL, phy- 
>>> >csiphy_id + 1);
>>> +            val |= CSI2_RX_CFG0_TPG_NUM_EN;
>>> +        }
>>> +    }
>>> +
>>>       writel(val, csid->base + CSID_CSI2_RX_CFG0);
>>>       val = CSI2_RX_CFG1_ECC_CORRECTION_EN;
>>> diff --git a/drivers/media/platform/qcom/camss/camss-tpg-gen1.c b/ 
>>> drivers/media/platform/qcom/camss/camss-tpg-gen1.c
>>> new file mode 100644
>>> index 
>>> 0000000000000000000000000000000000000000..d7ef7a1709648406dc59c210d355851397980769
>>> --- /dev/null
>>> +++ b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
>>> @@ -0,0 +1,257 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + *
>>> + * Qualcomm MSM Camera Subsystem - TPG (Test Patter Generator) Module
>>> + *
>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>> + */
>>> +#include <linux/bitfield.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/io.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/of.h>
>>> +
>>> +#include "camss-tpg.h"
>>> +#include "camss.h"
>>> +
>>> +#define TPG_HW_VERSION        0x0
>>> +# define HW_VERSION_STEPPING        GENMASK(15, 0)
>>> +# define HW_VERSION_REVISION        GENMASK(27, 16)
>>> +# define HW_VERSION_GENERATION        GENMASK(31, 28)
>>> +
>>> +#define TPG_HW_VER(gen, rev, step) \
>>> +    (((u32)(gen) << 28) | ((u32)(rev) << 16) | (u32)(step))
>>> +
>>> +#define TPG_HW_VER_2_0_0                TPG_HW_VER(2, 0, 0)
>>> +#define TPG_HW_VER_2_1_0                TPG_HW_VER(2, 1, 0)
>>> +
>>> +#define TPG_HW_STATUS        0x4
>>> +
>>> +#define TPG_VC_n_GAIN_CFG(n)        (0x60 + (n) * 0x60)
>> I know why this is here but it may be is better to group this with VC 
>> based registers. In fact, can you please segregate these macros into 
>> sub sections with headings like "TPG global registers", "TPG VC based 
>> registers", "TPG DT based registers" etc. Just for better readability.
> 
> ACK, registers are arranged according to their addresses, and I will 
> mark the global registers.
> 
>>> +
>>> +#define TPG_CTRL        0x64
>>> +# define TPG_CTRL_TEST_EN        BIT(0)
>>> +# define TPG_CTRL_PHY_SEL        BIT(3)
>>> +# define TPG_CTRL_NUM_ACTIVE_LANES    GENMASK(5, 4)
>>> +# define TPG_CTRL_VC_DT_PATTERN_ID    GENMASK(8, 6)
>>> +# define TPG_CTRL_OVERLAP_SHDR_EN    BIT(10)
>>> +# define TPG_CTRL_NUM_ACTIVE_VC        GENMASK(31, 30)
>>> +#  define NUM_ACTIVE_VC_0_ENABLED        0
>>> +#  define NUM_ACTIVE_VC_0_1_ENABLED        1
>>> +#  define NUM_ACTIVE_VC_0_1_2_ENABLED        2
>>> +#  define NUM_ACTIVE_VC_0_1_3_ENABLED        3
>>> +
>>> +#define TPG_VC_n_CFG0(n)    (0x68 + (n) * 0x60)
>>> +# define TPG_VC_n_CFG0_VC_NUM            GENMASK(4, 0)
>>> +# define TPG_VC_n_CFG0_NUM_ACTIVE_DT        GENMASK(9, 8)
>>> +#  define NUM_ACTIVE_SLOTS_0_ENABLED            0
>>> +#  define NUM_ACTIVE_SLOTS_0_1_ENABLED            1
>>> +#  define NUM_ACTIVE_SLOTS_0_1_2_ENABLED        2
>>> +#  define NUM_ACTIVE_SLOTS_0_1_3_ENABLED        3
>> s/NUM_ACTIVE_SLOTS/DT/?, if you really need these macros. Similarly 
>> for VCs enabled.
> 
> ACK
> 
>>> +# define TPG_VC_n_CFG0_NUM_BATCH        GENMASK(15, 12)
>>> +# define TPG_VC_n_CFG0_NUM_FRAMES        GENMASK(31, 16)
>>> +
>>> +#define TPG_VC_n_LSFR_SEED(n)    (0x6C + (n) * 0x60)
>>> +
>>> +#define TPG_VC_n_HBI_CFG(n)    (0x70 + (n) * 0x60)
>>> +
>>> +#define TPG_VC_n_VBI_CFG(n)    (0x74 + (n) * 0x60)
>>> +
>>> +#define TPG_VC_n_COLOR_BARS_CFG(n)        (0x78 + (n) * 0x60)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_PIX_PATTERN        GENMASK(2, 0)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_QCFA_EN        BIT(3)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_SPLIT_EN        BIT(4)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_NOISE_EN        BIT(5)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD        GENMASK(13, 8)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_XCFA_EN        BIT(16)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_SIZE_X            GENMASK(26, 24)
>>> +# define TPG_VC_n_COLOR_BARS_CFG_SIZE_Y            GENMASK(30, 28)
>>> +
>>> +#define TPG_VC_m_DT_n_CFG_0(m, n)        (0x7C + (m) * 0x60 + (n) * 
>>> 0xC)
>>> +# define TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT    GENMASK(15, 0)
>>> +# define TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH    GENMASK(31, 16)
>>> +
>>> +#define TPG_VC_m_DT_n_CFG_1(m, n)        (0x80 + (m) * 0x60 + (n) * 
>>> 0xC)
>>> +# define TPG_VC_m_DT_n_CFG_1_DATA_TYPE        GENMASK(5, 0)
>>> +# define TPG_VC_m_DT_n_CFG_1_ECC_XOR_MASK    GENMASK(13, 8)
>>> +# define TPG_VC_m_DT_n_CFG_1_CRC_XOR_MASK    GENMASK(31, 16)
>>> +
>>> +#define TPG_VC_m_DT_n_CFG_2(m, n)        (0x84 + (m) * 0x60 + (n) * 
>>> 0xC)
>>> +# define TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE        GENMASK(3, 0)
>>> +/* v2.0.0: USER[19:4], ENC[23:20] */
>>> +# define TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD GENMASK(19, 4)
>>> +# define TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT            GENMASK(23, 
>>> 20)
>> For better readability, can you make these TPG_V2_0_*?
>>> +/* v2.1.0: USER[27:4], ENC[31:28] */
>>> +# define TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD GENMASK(27, 4)
>>> +# define TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT GENMASK(31, 28)
>>> +
>>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR0(n)    (0xB0 + (n) * 0x60)
>>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR1(n)    (0xB4 + (n) * 0x60)
>>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR2(n)    (0xB8 + (n) * 0x60)
>>> +#define TPG_VC_n_COLOR_BAR_CFA_COLOR3(n)    (0xBC + (n) * 0x60)
>>> +
>>> +/* Line offset between VC(n) and VC(n-1), n form 1 to 3 */
>>> +#define TPG_VC_n_SHDR_CFG    (0x84 + (n) * 0x60)
>>> +
>>> +#define TPG_CLEAR        0x1F4
>>> +
>>> +#define TPG_HBI_PCT_DEFAULT            545    /* 545% */
>>> +#define TPG_VBI_PCT_DEFAULT            10    /* 10% */
>>> +#define PERCENT_BASE                100
>>> +#define BITS_PER_BYTE                8
>>> +
>>> +/* Default user-specified payload for TPG test generator.
>>> + * Keep consistent with CSID TPG default: 0xBE.
>>> + */
>>> +#define TPG_USER_SPECIFIED_PAYLOAD_DEFAULT    0xBE
>>> +#define TPG_LFSR_SEED_DEFAULT            0x12345678
>>> +#define TPG_COLOR_BARS_CFG_STANDARD \
>>> +    FIELD_PREP(TPG_VC_n_COLOR_BARS_CFG_ROTATE_PERIOD, 0xA)
>>> +
>>> +static int tpg_stream_on(struct tpg_device *tpg)
>> Add function headers? For this  and a few other below.
> 
> I received a comment asking me to remove the comments,
> if a function is not called in multiple places or its purpose cannot be 
> directly understood, adding extra documentation is useless.
> 
>>> +{
>>> +    struct tpg_testgen_config *tg = &tpg->testgen;
>>> +    struct v4l2_mbus_framefmt *input_format;
>>> +    const struct tpg_format_info *format;
>>> +    u8 lane_cnt = tpg->res->lane_cnt;
>>> +    u8 dt_cnt = 0;
>>> +    u8 i;
>>> +    u32 val;
>>> +
>>> +    /* Loop through all enabled VCs and configure stream for each */
>>> +    for (i = 0; i < tpg->res->vc_cnt; i++) {
>> Here as well, can we segregate the code to global, VC based and DT 
>> based configs with some comments?
> 
> This loop all contains configurations related to VC/DT.
> Will add some comments for this.
> 
>>> +        input_format = &tpg->fmt[MSM_TPG_PAD_SRC + i];
>>> +        format = tpg_get_fmt_entry(tpg,
>>> +                       tpg->res->formats->formats,
>>> +                       tpg->res->formats->nformats,
>>> +                       input_format->code);
>>> +        if (IS_ERR(format))
>>> +            return -EINVAL;
>>> +
>>> +        val = FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_HEIGHT, 
>>> input_format->height & 0xffff) |
>>> +              FIELD_PREP(TPG_VC_m_DT_n_CFG_0_FRAME_WIDTH, 
>>> input_format->width & 0xffff);
>>> +        writel(val, tpg->base + TPG_VC_m_DT_n_CFG_0(i, dt_cnt));
>>> +
>>> +        val = FIELD_PREP(TPG_VC_m_DT_n_CFG_1_DATA_TYPE, format- 
>>> >data_type);
>>> +        writel(val, tpg->base + TPG_VC_m_DT_n_CFG_1(i, dt_cnt));
>>> +
>>> +        if (tpg->hw_version == TPG_HW_VER_2_0_0) {
>>> +            val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg- 
>>> >mode - 1) |
>>> + FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
>>> +                       TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
>>> +                FIELD_PREP(TPG_V2_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
>>> +                       format->encode_format);
>>> +        } else if (tpg->hw_version >= TPG_HW_VER_2_1_0) {
>>> +            val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg- 
>>> >mode - 1) |
>>> + FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
>>> +                       TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
>>> +                FIELD_PREP(TPG_V2_1_VC_m_DT_n_CFG_2_ENCODE_FORMAT,
>>> +                       format->encode_format);
>>> +        }
>>> +        writel(val, tpg->base + TPG_VC_m_DT_n_CFG_2(i, dt_cnt));
>>> +
>>> +        writel(TPG_COLOR_BARS_CFG_STANDARD, tpg->base + 
>>> TPG_VC_n_COLOR_BARS_CFG(i));
>>> +
>>> +        val = DIV_ROUND_UP(input_format->width * format->bpp * 
>>> TPG_HBI_PCT_DEFAULT,
>>> +                   BITS_PER_BYTE * lane_cnt * PERCENT_BASE);
>>> +        writel(val, tpg->base + TPG_VC_n_HBI_CFG(i));
>>> +        val = input_format->height * TPG_VBI_PCT_DEFAULT / 
>>> PERCENT_BASE;
>>> +        writel(val, tpg->base + TPG_VC_n_VBI_CFG(i));
>>> +
>>> +        writel(TPG_LFSR_SEED_DEFAULT, tpg->base + 
>>> TPG_VC_n_LSFR_SEED(i));
>>> +
>>> +        /* configure one DT, infinite frames */
>> Although this driver is not supporting more than one DT in a VC right 
>> now, is there a way we can make the API generic enough to receive #DTs 
>> in each VS and their dimensions?
> 
> ACK
> 
>>> +        val = FIELD_PREP(TPG_VC_n_CFG0_VC_NUM, i) |
>>> +              FIELD_PREP(TPG_VC_n_CFG0_NUM_FRAMES, 0);
>>> +        writel(val, tpg->base + TPG_VC_n_CFG0(i));
>>> +    }
>>> +
>>> +    val = FIELD_PREP(TPG_CTRL_TEST_EN, 1) |
>>> +          FIELD_PREP(TPG_CTRL_PHY_SEL, 0) |
>> Same here, is there a way to make the API generic to receive CPHY / 
>> DPHY mode required?
> 
> ACK
> 
>>> +          FIELD_PREP(TPG_CTRL_NUM_ACTIVE_LANES, lane_cnt - 1) |
>>> +          FIELD_PREP(TPG_CTRL_VC_DT_PATTERN_ID, 0) |
>> You are assuming frame interleaved mode always. It may be is a good 
>> start but a bunch of functionality is missing here. Just please think 
>> of the scalability of the API even though the driver support is 
>> limited at this point.
> 
> ACK
> 
>>> +          FIELD_PREP(TPG_CTRL_NUM_ACTIVE_VC, tpg->res->vc_cnt - 1);
>>> +    writel(val, tpg->base + TPG_CTRL);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static void tpg_stream_off(struct tpg_device *tpg)
>>> +{
>>> +    writel(0, tpg->base + TPG_CTRL);
>>> +    writel(1, tpg->base + TPG_CLEAR);
>> Why not just reuse the reset function?
> 
> ACK
> 
>>> +}
>>> +
>>> +static int tpg_configure_stream(struct tpg_device *tpg, u8 enable)
>>> +{
>>> +    int ret = 0;
>>> +
>>> +    if (enable)
>>> +        ret = tpg_stream_on(tpg);
>>> +    else
>>> +        tpg_stream_off(tpg);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int tpg_configure_testgen_pattern(struct tpg_device *tpg, s32 
>>> val)
>>> +{
>>> +    if (val >= 0 && val <= TPG_PAYLOAD_MODE_COLOR_BARS)
>>> +        tpg->testgen.mode = val;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/*
>>> + * tpg_hw_version - tpg hardware version query
>>> + * @tpg: tpg device
>>> + *
>>> + * Return HW version or error
>>> + */
>>> +static u32 tpg_hw_version(struct tpg_device *tpg)
>>> +{
>>> +    u32 hw_version;
>>> +    u32 hw_gen;
>>> +    u32 hw_rev;
>>> +    u32 hw_step;
>>> +
>>> +    hw_version = readl(tpg->base + TPG_HW_VERSION);
>>> +    hw_gen = FIELD_GET(HW_VERSION_GENERATION, hw_version);
>>> +    hw_rev = FIELD_GET(HW_VERSION_REVISION, hw_version);
>>> +    hw_step = FIELD_GET(HW_VERSION_STEPPING, hw_version);
>>> +
>>> +    tpg->hw_version = hw_version;
>>> +
>>> +    dev_dbg_once(tpg->camss->dev, "tpg HW Version = %u.%u.%u\n",
>>> +             hw_gen, hw_rev, hw_step);
>>> +
>>> +    return hw_version;
>>> +}
>>> +
>>> +/*
>>> + * tpg_reset - Trigger reset on tpg module and wait to complete
>> Doesn't seem like there is any wait here, right?
> 
> Do you have any suggestions on this? I noticed that there is no delay 
> downstream either.
> 
>> Also, do you want to clear the IRQs in reset?
> 
> Following the maintainer's suggestion, I removed IRQ support, but we do 
> need to add IRQ clearing.
> 
>>> + * @tpg: tpg device
>>> + *
>>> + * Return 0 on success or a negative error code otherwise
>>> + */
>>> +static int tpg_reset(struct tpg_device *tpg)
>>> +{
>>> +    writel(0, tpg->base + TPG_CTRL);
>>> +    writel(1, tpg->base + TPG_CLEAR);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static void tpg_subdev_init(struct tpg_device *tpg)
>>> +{
>>> +    tpg->testgen.modes = testgen_payload_modes;
>>> +    tpg->testgen.nmodes = TPG_PAYLOAD_MODE_NUM_SUPPORTED_GEN1;
>>> +}
>>> +
>>> +const struct tpg_hw_ops tpg_ops_gen1 = {
>>> +    .configure_stream = tpg_configure_stream,
>>> +    .configure_testgen_pattern = tpg_configure_testgen_pattern,
>>> +    .hw_version = tpg_hw_version,
>>> +    .reset = tpg_reset,
>>> +    .subdev_init = tpg_subdev_init,
>>> +};
>>> diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/ 
>>> media/platform/qcom/camss/camss.c
>>> index 
>>> 43fdcb9af101ef34b118035ca9c68757b66118df..5cddf1bc09f97c2c61f907939bb54663d8eab3d4 100644
>>> --- a/drivers/media/platform/qcom/camss/camss.c
>>> +++ b/drivers/media/platform/qcom/camss/camss.c
>>> @@ -3199,6 +3199,65 @@ static const struct camss_subdev_resources 
>>> csiphy_res_8775p[] = {
>>>       },
>>>   };
>>> +static const struct camss_subdev_resources tpg_res_8775p[] = {
>>> +    /* TPG0 */
>>> +    {
>>> +        .regulators = {  },
>>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
>> Why should TPG need camnoc_rt_axi clk?
> 
> As tested ,TPG can`t streaming without camnoc_rt_axi clk.
> For Pixel path, some platform can stream without camnoc_rt_axi clk but 
> TPG not.
> 
It is not the expected behavior. There is no path from TPG to CAMNOC / 
DDR. TPG only drives the pattern data from the internal engine to CSID 
and you will only need CAMNOC_RT_AXI clock when the data is written from 
IFE WM to the DDR over CAMNOC, which anyway will be enabled from the IFE 
device. We can check this further together. Thanks.
>>> +        .clock_rate = {
>>> +            { 400000000 },
>>> +            { 0 },
>>> +            { 400000000 },
>>> +        },
>>> +        .reg = { "tpg0" },
>>> +        .interrupt = { "tpg0" },
>>> +        .tpg = {
>>> +            .lane_cnt = 4,
>>> +            .vc_cnt = 1,
>>> +            .formats = &tpg_formats_gen1,
>>> +            .hw_ops = &tpg_ops_gen1
>>> +        }
>>> +    },
>>> +
>>> +    /* TPG1 */
>>> +    {
>>> +        .regulators = {  },
>>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
>>> +        .clock_rate = {
>>> +            { 400000000 },
>>> +            { 0 },
>>> +            { 400000000 },
>>> +        },
>>> +        .reg = { "tpg1" },
>>> +        .interrupt = { "tpg1" },
>>> +        .tpg = {
>>> +            .lane_cnt = 4,
>>> +            .vc_cnt = 1,
>>> +            .formats = &tpg_formats_gen1,
>>> +            .hw_ops = &tpg_ops_gen1
>>> +        }
>>> +    },
>>> +
>>> +    /* TPG2 */
>>> +    {
>>> +        .regulators = {  },
>>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csiphy_rx" },
>>> +        .clock_rate = {
>>> +            { 400000000 },
>>> +            { 0 },
>>> +            { 400000000 },
>>> +        },
>>> +        .reg = { "tpg2" },
>>> +        .interrupt = { "tpg2" }, + .tpg = { + .lane_cnt = 4, 
>>> + .vc_cnt = 1, + .formats = &tpg_formats_gen1, + .hw_ops = 
>>> &tpg_ops_gen1 + } + }, +}; + static const struct 
>>> camss_subdev_resources csid_res_8775p[] = { /* CSID0 */ { @@ -3595,6 
>>> +3654,62 @@ static const struct camss_subdev_resources 
>>> csiphy_res_x1e80100[] = { }, }; +static const struct 
>>> camss_subdev_resources tpg_res_x1e80100[] = { + /* TPG0 */ + 
>>> { + .regulators = { }, + .clock = { "camnoc_rt_axi", "cpas_ahb", 
>>> "csid_csiphy_rx" },
>>> +        .clock_rate = {
>>> +            { 400000000 },
>>> +            { 0 },
>>> +            { 400000000 },
>>> +        },
>>> +        .reg = { "csitpg0" },
>>> +        .tpg = {
>>> +            .lane_cnt = 4,
>>> +            .vc_cnt = 1,
>>> +            .formats = &tpg_formats_gen1,
>>> +            .hw_ops = &tpg_ops_gen1
>>> +        }
>>> +    },
>>> +
>>> +    /* TPG1 */
>>> +    {
>>> +        .regulators = {  },
>>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
>>> +        .clock_rate = {
>>> +            { 400000000 },
>>> +            { 0 },
>>> +            { 400000000 },
>>> +        },
>>> +        .reg = { "csitpg1" },
>>> +        .tpg = {
>>> +            .lane_cnt = 4,
>>> +            .vc_cnt = 1,
>>> +            .formats = &tpg_formats_gen1,
>>> +            .hw_ops = &tpg_ops_gen1
>>> +        }
>>> +    },
>>> +
>>> +    /* TPG2 */
>>> +    {
>>> +        .regulators = {  },
>>> +        .clock = { "camnoc_rt_axi", "cpas_ahb", "csid_csiphy_rx" },
>>> +        .clock_rate = {
>>> +            { 400000000 },
>>> +            { 0 },
>>> +            { 400000000 },
>>> +        },
>>> +        .reg = { "csitpg2" },
>>> +        .tpg = {
>>> +            .lane_cnt = 4,
>>> +            .vc_cnt = 1,
>>> +            .formats = &tpg_formats_gen1,
>>> +            .hw_ops = &tpg_ops_gen1
>>> +        }
>>> +    },
>>> +};
>>> +
>>>   static const struct camss_subdev_resources csid_res_x1e80100[] = {
>>>       /* CSID0 */
>>>       {
>>> @@ -4674,6 +4789,13 @@ static int camss_probe(struct platform_device 
>>> *pdev)
>>>       if (!camss->csiphy)
>>>           return -ENOMEM;
>>> +    if (camss->res->tpg_num > 0) {
>>> +        camss->tpg = devm_kcalloc(dev, camss->res->tpg_num,
>>> +                      sizeof(*camss->tpg), GFP_KERNEL);
>>> +        if (!camss->tpg)
>>> +            return -ENOMEM;
>>> +    }
>>> +
>>>       camss->csid = devm_kcalloc(dev, camss->res->csid_num, 
>>> sizeof(*camss->csid),
>>>                      GFP_KERNEL);
>>>       if (!camss->csid)
>>> @@ -4863,11 +4985,13 @@ static const struct camss_resources 
>>> qcs8300_resources = {
>>>       .version = CAMSS_8300,
>>>       .pd_name = "top", .csiphy_res = csiphy_res_8300, + .tpg_res = 
>>> tpg_res_8775p, .csid_res = csid_res_8775p, .csid_wrapper_res = 
>>> &csid_wrapper_res_sm8550, .vfe_res = vfe_res_8775p, .icc_res = 
>>> icc_res_qcs8300, .csiphy_num = ARRAY_SIZE(csiphy_res_8300), 
>>> + .tpg_num = ARRAY_SIZE(tpg_res_8775p), .csid_num = 
>>> ARRAY_SIZE(csid_res_8775p), .vfe_num = 
>>> ARRAY_SIZE(vfe_res_8775p), .icc_path_num = 
>>> ARRAY_SIZE(icc_res_qcs8300), @@ -4877,11 +5001,13 @@ static const 
>>> struct camss_resources sa8775p_resources = { .version = 
>>> CAMSS_8775P, .pd_name = "top", .csiphy_res = csiphy_res_8775p, 
>>> + .tpg_res = tpg_res_8775p, .csid_res = 
>>> csid_res_8775p, .csid_wrapper_res = 
>>> &csid_wrapper_res_sm8550, .vfe_res = vfe_res_8775p, .icc_res = 
>>> icc_res_sa8775p, .csiphy_num = ARRAY_SIZE(csiphy_res_8775p), 
>>> + .tpg_num = ARRAY_SIZE(tpg_res_8775p), .csid_num = 
>>> ARRAY_SIZE(csid_res_8775p), .vfe_num = 
>>> ARRAY_SIZE(vfe_res_8775p), .icc_path_num = 
>>> ARRAY_SIZE(icc_res_sa8775p), @@ -4992,11 +5118,13 @@ static const 
>>> struct camss_resources x1e80100_resources = { .pd_name = "top",
>>>       .csiphy_res = csiphy_res_x1e80100,
>>>       .csid_res = csid_res_x1e80100,
>>> +    .tpg_res = tpg_res_x1e80100,
>>>       .vfe_res = vfe_res_x1e80100,
>>>       .csid_wrapper_res = &csid_wrapper_res_x1e80100,
>>>       .icc_res = icc_res_x1e80100,
>>>       .icc_path_num = ARRAY_SIZE(icc_res_x1e80100),
>>>       .csiphy_num = ARRAY_SIZE(csiphy_res_x1e80100),
>>> +    .tpg_num = ARRAY_SIZE(tpg_res_x1e80100),
>>>       .csid_num = ARRAY_SIZE(csid_res_x1e80100),
>>>       .vfe_num = ARRAY_SIZE(vfe_res_x1e80100),
>>>   };
>>
>> Thanks,
>>
>> Vijay.
>>
> 
> Thanks,
> Wenmeng


^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2026-01-21 18:45 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-13  9:03 [PATCH v8 0/3] media: qcom: camss: Add camss TPG support for multiple targets Wenmeng Liu
2026-01-13  9:03 ` [PATCH v8 1/3] media: qcom: camss: Add common TPG support Wenmeng Liu
2026-01-13 16:27   ` Vladimir Zapolskiy
2026-01-14  3:04     ` Wenmeng Liu
2026-01-14  5:05       ` Vladimir Zapolskiy
2026-01-14  9:43         ` Konrad Dybcio
2026-01-14 12:18         ` Wenmeng Liu
2026-01-14 22:07           ` Vladimir Zapolskiy
2026-01-15  1:06             ` Bryan O'Donoghue
2026-01-15  2:58               ` Vladimir Zapolskiy
2026-01-15 15:54                 ` Bryan O'Donoghue
2026-01-16  9:24                   ` Vladimir Zapolskiy
2026-01-19  9:59                     ` Bryan O'Donoghue
2026-01-13  9:03 ` [PATCH v8 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
2026-01-13  9:03 ` [PATCH v8 3/3] media: qcom: camss: tpg: Add TPG support for multiple targets Wenmeng Liu
2026-01-15 15:48   ` kernel test robot
2026-01-16 19:32   ` Vijay Kumar Tumati
2026-01-19  3:29     ` Wenmeng Liu
2026-01-21 18:45       ` Vijay Kumar Tumati

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox