* [PATCH v5 0/3] media: qcom: camss: Add LeMans and Monaco camss TPG support
@ 2025-10-17 5:06 Wenmeng Liu
2025-10-17 5:06 ` [PATCH v5 1/3] media: qcom: camss: Add common " Wenmeng Liu
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Wenmeng Liu @ 2025-10-17 5:06 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 and Monaco.
We have tested this on LeMans EVK board and qcs8300-ride 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-subdev4 -c test_pattern=9
- yavta -B capture-mplane -n 5 -f SRGGB10P -s 4608x2592 /dev/video0
--capture=7
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 LeMans and Monaco
drivers/media/platform/qcom/camss/Makefile | 2 +
.../media/platform/qcom/camss/camss-csid-gen3.c | 17 +
drivers/media/platform/qcom/camss/camss-csid.c | 43 +-
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 | 220 +++++++
drivers/media/platform/qcom/camss/camss-tpg.c | 696 +++++++++++++++++++++
drivers/media/platform/qcom/camss/camss-tpg.h | 125 ++++
drivers/media/platform/qcom/camss/camss.c | 119 ++++
drivers/media/platform/qcom/camss/camss.h | 5 +
10 files changed, 1216 insertions(+), 14 deletions(-)
---
base-commit: 2433b84761658ef123ae683508bc461b07c5b0f0
change-id: 20251017-camss_tpg-320e21f05dc7
Best regards,
--
Wenmeng <wenmeng.liu@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v5 1/3] media: qcom: camss: Add common TPG support
2025-10-17 5:06 [PATCH v5 0/3] media: qcom: camss: Add LeMans and Monaco camss TPG support Wenmeng Liu
@ 2025-10-17 5:06 ` Wenmeng Liu
2025-10-20 12:53 ` Konrad Dybcio
2025-10-20 19:59 ` Bryan O'Donoghue
2025-10-17 5:06 ` [PATCH v5 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
2025-10-17 5:06 ` [PATCH v5 3/3] media: qcom: camss: tpg: Add TPG support for LeMans and Monaco Wenmeng Liu
2 siblings, 2 replies; 8+ messages in thread
From: Wenmeng Liu @ 2025-10-17 5:06 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 | 696 ++++++++++++++++++++++++++
drivers/media/platform/qcom/camss/camss-tpg.h | 125 +++++
drivers/media/platform/qcom/camss/camss.h | 5 +
4 files changed, 827 insertions(+)
diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index 23960d02877de132b63ebfe88affe55576256829..0eda4b18ad0e93f5e63135fabd5a02ae67bcd5ad 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -26,5 +26,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..c436cdb7041b555ce9458270eb46996e78f1d5eb
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-tpg.c
@@ -0,0 +1,696 @@
+// 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,
+ },
+ {
+ MEDIA_BUS_FMT_SGBRG8_1X8,
+ DATA_TYPE_RAW_8BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SGRBG8_1X8,
+ DATA_TYPE_RAW_8BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SRGGB8_1X8,
+ DATA_TYPE_RAW_8BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+ DATA_TYPE_RAW_10BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SGBRG10_1X10,
+ DATA_TYPE_RAW_10BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SGRBG10_1X10,
+ DATA_TYPE_RAW_10BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SRGGB10_1X10,
+ DATA_TYPE_RAW_10BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SBGGR12_1X12,
+ DATA_TYPE_RAW_12BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SGBRG12_1X12,
+ DATA_TYPE_RAW_12BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SGRBG12_1X12,
+ DATA_TYPE_RAW_12BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_SRGGB12_1X12,
+ DATA_TYPE_RAW_12BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_12_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_Y8_1X8,
+ DATA_TYPE_RAW_8BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_8_BIT,
+ },
+ {
+ MEDIA_BUS_FMT_Y10_1X10,
+ DATA_TYPE_RAW_10BIT,
+ ENCODE_FORMAT_UNCOMPRESSED_10_BIT,
+ },
+};
+
+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_once(dev, "Unknown format\n");
+
+ 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[0] > 0) {
+ 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;
+ }
+ }
+
+ tpg->res->hw_ops->configure_stream(tpg, enable);
+
+ return 0;
+}
+
+/*
+ * __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
+ *
+ * Rreturn 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..c40c10cc4ad1d7967c5d9dd878a8d69177b2281f
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-tpg.h
@@ -0,0 +1,125 @@
+/* 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;
+};
+
+struct tpg_formats {
+ unsigned int nformats;
+ const struct tpg_format_info *formats;
+};
+
+struct tpg_device;
+
+struct tpg_hw_ops {
+ void (*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;
+};
+
+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 a70fbc78ccc307c0abc2f3c834fb1e2dafd83c6b..9a66f3a90c02b4cc475c4d3033205feb7e98f5d3 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"
@@ -51,6 +52,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;
};
@@ -101,6 +103,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;
@@ -108,6 +111,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;
};
@@ -118,6 +122,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] 8+ messages in thread
* [PATCH v5 2/3] media: qcom: camss: Add link support for TPG
2025-10-17 5:06 [PATCH v5 0/3] media: qcom: camss: Add LeMans and Monaco camss TPG support Wenmeng Liu
2025-10-17 5:06 ` [PATCH v5 1/3] media: qcom: camss: Add common " Wenmeng Liu
@ 2025-10-17 5:06 ` Wenmeng Liu
2025-10-20 18:46 ` Bryan O'Donoghue
2025-10-17 5:06 ` [PATCH v5 3/3] media: qcom: camss: tpg: Add TPG support for LeMans and Monaco Wenmeng Liu
2 siblings, 1 reply; 8+ messages in thread
From: Wenmeng Liu @ 2025-10-17 5:06 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 | 43 +++++++++++++-------
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 | 52 ++++++++++++++++++++++++
4 files changed, 84 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..196cbc0b60e9bf95a06b053c69c967e345ffcd4b 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(int num, struct csiphy_lanes_cfg *lane_cfg)
{
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; i++) {
+ pos = lane_cfg ? lane_cfg->data[i].pos : i;
+ lane_assign |= pos << (i * LANE_CFG_BITWIDTH);
+ }
return lane_assign;
}
@@ -1266,6 +1272,7 @@ static int csid_link_setup(struct media_entity *entity,
struct csid_device *csid;
struct csiphy_device *csiphy;
struct csiphy_lanes_cfg *lane_cfg;
+ struct tpg_device *tpg;
sd = media_entity_to_v4l2_subdev(entity);
csid = v4l2_get_subdevdata(sd);
@@ -1277,18 +1284,26 @@ 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(csid->phy.lane_cnt, NULL);
+ } 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->num_data, lane_cfg);
+ }
}
/* 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-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c
index 2de97f58f9ae4f91e8bba39dcadf92bea8cf6f73..680580d7fe46a215777f3fa1b347f4297deea024 100644
--- a/drivers/media/platform/qcom/camss/camss-csiphy.c
+++ b/drivers/media/platform/qcom/camss/camss-csiphy.c
@@ -799,6 +799,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 2fbcd0e343aac9620a5a30719c42e1b887cf34ed..2ede19e1347ae32f2f6919905b535352bcd134be 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -3691,6 +3691,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],
@@ -3779,6 +3792,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++) {
@@ -3883,6 +3913,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);
@@ -3926,6 +3969,10 @@ 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:
+ 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--)
@@ -3947,6 +3994,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] 8+ messages in thread
* [PATCH v5 3/3] media: qcom: camss: tpg: Add TPG support for LeMans and Monaco
2025-10-17 5:06 [PATCH v5 0/3] media: qcom: camss: Add LeMans and Monaco camss TPG support Wenmeng Liu
2025-10-17 5:06 ` [PATCH v5 1/3] media: qcom: camss: Add common " Wenmeng Liu
2025-10-17 5:06 ` [PATCH v5 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
@ 2025-10-17 5:06 ` Wenmeng Liu
2025-10-20 19:58 ` Bryan O'Donoghue
2 siblings, 1 reply; 8+ messages in thread
From: Wenmeng Liu @ 2025-10-17 5:06 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 and Monaco.
Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
---
drivers/media/platform/qcom/camss/Makefile | 1 +
.../media/platform/qcom/camss/camss-csid-gen3.c | 17 ++
drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 220 +++++++++++++++++++++
drivers/media/platform/qcom/camss/camss.c | 67 +++++++
4 files changed, 305 insertions(+)
diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index 0eda4b18ad0e93f5e63135fabd5a02ae67bcd5ad..28bc3d9ba16dfa34a8fd35973beed0c3f2b67e00 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -27,5 +27,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-gen3.c b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
index 664245cf6eb0cac662b02f8b920cd1c72db0aeb2..8e0b0cbaa0010f4b4a156877ac2fe805e5c4422e 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 27
+#define CSI2_RX_CFG0_TPG_NUM_SEL 28
#define CSID_CSI2_RX_CFG1 0x204
#define CSI2_RX_CFG1_ECC_CORRECTION_EN BIT(0)
@@ -109,11 +111,26 @@ 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 (tpg->testgen.mode > 0) {
+ val |= (phy->csiphy_id + 1) << CSI2_RX_CFG0_TPG_NUM_SEL;
+ val |= 1 << CSI2_RX_CFG0_TPG_NUM_EN;
+ } else {
+ val |= 0 << CSI2_RX_CFG0_TPG_NUM_SEL;
+ val |= 0 << 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..ba463c16a8436cb40c61d1b483c6343c010b9744
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
@@ -0,0 +1,220 @@
+// 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_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)
+# define TPG_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD GENMASK(27, 4)
+# define TPG_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_USER_SPECIFIED_PAYLOAD_DEFAULT 0xBE
+#define TPG_HBI_CFG_DEFAULT 0x4701
+#define TPG_VBI_CFG_DEFAULT 0x438
+#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);
+
+ 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));
+
+ val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg->mode - 1) |
+ FIELD_PREP(TPG_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
+ TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
+ FIELD_PREP(TPG_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));
+
+ writel(TPG_HBI_CFG_DEFAULT, tpg->base + TPG_VC_n_HBI_CFG(i));
+ writel(TPG_VBI_CFG_DEFAULT, 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 void tpg_configure_stream(struct tpg_device *tpg, u8 enable)
+{
+ if (enable)
+ tpg_stream_on(tpg);
+ else
+ tpg_stream_off(tpg);
+}
+
+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);
+ 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 2ede19e1347ae32f2f6919905b535352bcd134be..446d3fb94f35412178b72c803274a5159c57c852 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -2745,6 +2745,62 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = {
},
};
+static const struct camss_subdev_resources tpg_res_8775p[] = {
+ /* TPG0 */
+ {
+ .regulators = { },
+ .clock = { "csiphy_rx", "camnoc_axi" },
+ .clock_rate = {
+ { 400000000 },
+ { 400000000 },
+ },
+ .reg = { "tpg0" },
+ .interrupt = { "tpg0" },
+ .tpg = {
+ .lane_cnt = 4,
+ .vc_cnt = 1,
+ .formats = &tpg_formats_gen1,
+ .hw_ops = &tpg_ops_gen1
+ }
+ },
+
+ /* TPG1 */
+ {
+ .regulators = { },
+ .clock = { "csiphy_rx", "camnoc_axi" },
+ .clock_rate = {
+ { 400000000 },
+ { 400000000 },
+ },
+ .reg = { "tpg1" },
+ .interrupt = { "tpg1" },
+ .tpg = {
+ .lane_cnt = 4,
+ .vc_cnt = 1,
+ .formats = &tpg_formats_gen1,
+ .hw_ops = &tpg_ops_gen1
+ }
+ },
+
+ /* TPG2 */
+ {
+ .regulators = { },
+ .clock = { "csiphy_rx", "camnoc_axi" },
+ .clock_rate = {
+ { 400000000 },
+ { 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 */
{
@@ -4217,6 +4273,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)
@@ -4394,11 +4457,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),
@@ -4408,11 +4473,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),
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v5 1/3] media: qcom: camss: Add common TPG support
2025-10-17 5:06 ` [PATCH v5 1/3] media: qcom: camss: Add common " Wenmeng Liu
@ 2025-10-20 12:53 ` Konrad Dybcio
2025-10-20 19:59 ` Bryan O'Donoghue
1 sibling, 0 replies; 8+ messages in thread
From: Konrad Dybcio @ 2025-10-20 12:53 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 10/17/25 7:06 AM, 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>
> ---
[...]
> +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_once(dev, "Unknown format\n");
It would probably be useful to denote which format is invalid (i.e.
print its code or so), and _once doesn't seem like a right choice,
because the user may choose 2 different invalid formats and only
the first one would cause a descriptive warning
Konrad
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 2/3] media: qcom: camss: Add link support for TPG
2025-10-17 5:06 ` [PATCH v5 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
@ 2025-10-20 18:46 ` Bryan O'Donoghue
0 siblings, 0 replies; 8+ messages in thread
From: Bryan O'Donoghue @ 2025-10-20 18:46 UTC (permalink / raw)
To: Wenmeng Liu, Robert Foss, Todor Tomov, Vladimir Zapolskiy,
Mauro Carvalho Chehab
Cc: linux-kernel, linux-media, linux-arm-msm
On 17/10/2025 06:06, Wenmeng Liu wrote:
> 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 | 43 +++++++++++++-------
> 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 | 52 ++++++++++++++++++++++++
> 4 files changed, 84 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..196cbc0b60e9bf95a06b053c69c967e345ffcd4b 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(int num, struct csiphy_lanes_cfg *lane_cfg)
ah semantic commnet
add your parameter to the end, not to the start please and give the "int
num" are more meaningful name - num_lanes ? Decided for yourself what
that name should be.
> {
> 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; i++) {
> + pos = lane_cfg ? lane_cfg->data[i].pos : i;
> + lane_assign |= pos << (i * LANE_CFG_BITWIDTH);
> + }
>
> return lane_assign;
> }
> @@ -1266,6 +1272,7 @@ static int csid_link_setup(struct media_entity *entity,
> struct csid_device *csid;
> struct csiphy_device *csiphy;
> struct csiphy_lanes_cfg *lane_cfg;
> + struct tpg_device *tpg;
>
> sd = media_entity_to_v4l2_subdev(entity);
> csid = v4l2_get_subdevdata(sd);
> @@ -1277,18 +1284,26 @@ 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(csid->phy.lane_cnt, NULL);
> + } 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->num_data, lane_cfg);
> + }
> }
> /* 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-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c
> index 2de97f58f9ae4f91e8bba39dcadf92bea8cf6f73..680580d7fe46a215777f3fa1b347f4297deea024 100644
> --- a/drivers/media/platform/qcom/camss/camss-csiphy.c
> +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c
> @@ -799,6 +799,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 2fbcd0e343aac9620a5a30719c42e1b887cf34ed..2ede19e1347ae32f2f6919905b535352bcd134be 100644
> --- a/drivers/media/platform/qcom/camss/camss.c
> +++ b/drivers/media/platform/qcom/camss/camss.c
> @@ -3691,6 +3691,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],
> @@ -3779,6 +3792,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++) {
> @@ -3883,6 +3913,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);
> @@ -3926,6 +3969,10 @@ 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;
\n
> +err_reg_tpg:
> + for (i--; i >= 0; i--)
> + msm_tpg_unregister_entity(&camss->tpg[i]);
This is broken for non-TPG cases - for example it is possible to jump to
err_reg_csid when camss->tpg == NULL and then to dereference &camss->tpg[i];
Please fix.
> i = camss->res->csiphy_num;
> err_reg_csiphy:
> for (i--; i >= 0; i--)
> @@ -3947,6 +3994,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]);
>
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 3/3] media: qcom: camss: tpg: Add TPG support for LeMans and Monaco
2025-10-17 5:06 ` [PATCH v5 3/3] media: qcom: camss: tpg: Add TPG support for LeMans and Monaco Wenmeng Liu
@ 2025-10-20 19:58 ` Bryan O'Donoghue
0 siblings, 0 replies; 8+ messages in thread
From: Bryan O'Donoghue @ 2025-10-20 19:58 UTC (permalink / raw)
To: Wenmeng Liu, Robert Foss, Todor Tomov, Vladimir Zapolskiy,
Mauro Carvalho Chehab
Cc: linux-kernel, linux-media, linux-arm-msm
On 17/10/2025 06:06, Wenmeng Liu wrote:
> Add support for TPG found on LeMans and Monaco.
>
> Signed-off-by: Wenmeng Liu <wenmeng.liu@oss.qualcomm.com>
> ---
> drivers/media/platform/qcom/camss/Makefile | 1 +
> .../media/platform/qcom/camss/camss-csid-gen3.c | 17 ++
> drivers/media/platform/qcom/camss/camss-tpg-gen1.c | 220 +++++++++++++++++++++
> drivers/media/platform/qcom/camss/camss.c | 67 +++++++
> 4 files changed, 305 insertions(+)
>
> diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
> index 0eda4b18ad0e93f5e63135fabd5a02ae67bcd5ad..28bc3d9ba16dfa34a8fd35973beed0c3f2b67e00 100644
> --- a/drivers/media/platform/qcom/camss/Makefile
> +++ b/drivers/media/platform/qcom/camss/Makefile
> @@ -27,5 +27,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-gen3.c b/drivers/media/platform/qcom/camss/camss-csid-gen3.c
> index 664245cf6eb0cac662b02f8b920cd1c72db0aeb2..8e0b0cbaa0010f4b4a156877ac2fe805e5c4422e 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 27
> +#define CSI2_RX_CFG0_TPG_NUM_SEL 28
>
> #define CSID_CSI2_RX_CFG1 0x204
> #define CSI2_RX_CFG1_ECC_CORRECTION_EN BIT(0)
> @@ -109,11 +111,26 @@ 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 (tpg->testgen.mode > 0) {
> + val |= (phy->csiphy_id + 1) << CSI2_RX_CFG0_TPG_NUM_SEL;
> + val |= 1 << CSI2_RX_CFG0_TPG_NUM_EN;
> + } else {
> + val |= 0 << CSI2_RX_CFG0_TPG_NUM_SEL;
> + val |= 0 << CSI2_RX_CFG0_TPG_NUM_EN;
Zero shifted by any amount is still zero.
Do you mean to negate these bits val &= ~ (CSI2_RX_CFG0_TPG_NUM_SEL |
CSI2_RX_CFG0_TPG_NUM_EN );
Anyway I see the pattern we have, perhaps am guilty of its perpetuation,
also am requesting its termination.
Please do something more sensible with these bits.
> + }
> + }
> +
> 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..ba463c16a8436cb40c61d1b483c6343c010b9744
> --- /dev/null
> +++ b/drivers/media/platform/qcom/camss/camss-tpg-gen1.c
> @@ -0,0 +1,220 @@
> +// 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_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)
> +# define TPG_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD GENMASK(27, 4)
> +# define TPG_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_USER_SPECIFIED_PAYLOAD_DEFAULT 0xBE
> +#define TPG_HBI_CFG_DEFAULT 0x4701
> +#define TPG_VBI_CFG_DEFAULT 0x438
Offsets are fine to define but magic value numbers are not welcome. You
only have to define five bits and four bits respectively i.e. the bits
you use but, for preference you should define all of the fields.
> +#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);
> +
> + 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));
> +
> + val = FIELD_PREP(TPG_VC_m_DT_n_CFG_2_PAYLOAD_MODE, tg->mode - 1) |
> + FIELD_PREP(TPG_VC_m_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD,
> + TPG_USER_SPECIFIED_PAYLOAD_DEFAULT) |
> + FIELD_PREP(TPG_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));
> +
> + writel(TPG_HBI_CFG_DEFAULT, tpg->base + TPG_VC_n_HBI_CFG(i));
> + writel(TPG_VBI_CFG_DEFAULT, 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 void tpg_configure_stream(struct tpg_device *tpg, u8 enable)
> +{
> + if (enable)
> + tpg_stream_on(tpg);
> + else
> + tpg_stream_off(tpg);
> +}
> +
> +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);
> + 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 2ede19e1347ae32f2f6919905b535352bcd134be..446d3fb94f35412178b72c803274a5159c57c852 100644
> --- a/drivers/media/platform/qcom/camss/camss.c
> +++ b/drivers/media/platform/qcom/camss/camss.c
> @@ -2745,6 +2745,62 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = {
> },
> };
>
> +static const struct camss_subdev_resources tpg_res_8775p[] = {
> + /* TPG0 */
> + {
> + .regulators = { },
> + .clock = { "csiphy_rx", "camnoc_axi" },
> + .clock_rate = {
> + { 400000000 },
> + { 400000000 },
> + },
> + .reg = { "tpg0" },
> + .interrupt = { "tpg0" },
> + .tpg = {
> + .lane_cnt = 4,
> + .vc_cnt = 1,
> + .formats = &tpg_formats_gen1,
> + .hw_ops = &tpg_ops_gen1
> + }
> + },
> +
> + /* TPG1 */
> + {
> + .regulators = { },
> + .clock = { "csiphy_rx", "camnoc_axi" },
> + .clock_rate = {
> + { 400000000 },
> + { 400000000 },
> + },
> + .reg = { "tpg1" },
> + .interrupt = { "tpg1" },
> + .tpg = {
> + .lane_cnt = 4,
> + .vc_cnt = 1,
> + .formats = &tpg_formats_gen1,
> + .hw_ops = &tpg_ops_gen1
> + }
> + },
> +
> + /* TPG2 */
> + {
> + .regulators = { },
> + .clock = { "csiphy_rx", "camnoc_axi" },
> + .clock_rate = {
> + { 400000000 },
> + { 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 */
> {
> @@ -4217,6 +4273,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)
> @@ -4394,11 +4457,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),
> @@ -4408,11 +4473,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),
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 1/3] media: qcom: camss: Add common TPG support
2025-10-17 5:06 ` [PATCH v5 1/3] media: qcom: camss: Add common " Wenmeng Liu
2025-10-20 12:53 ` Konrad Dybcio
@ 2025-10-20 19:59 ` Bryan O'Donoghue
1 sibling, 0 replies; 8+ messages in thread
From: Bryan O'Donoghue @ 2025-10-20 19:59 UTC (permalink / raw)
To: Wenmeng Liu, Robert Foss, Todor Tomov, Vladimir Zapolskiy,
Mauro Carvalho Chehab
Cc: linux-kernel, linux-media, linux-arm-msm
On 17/10/2025 06:06, Wenmeng Liu wrote:
> + * Rreturn 0 on success
You might as well zap this too since you're v6ing.
---
bod
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-10-20 19:59 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-17 5:06 [PATCH v5 0/3] media: qcom: camss: Add LeMans and Monaco camss TPG support Wenmeng Liu
2025-10-17 5:06 ` [PATCH v5 1/3] media: qcom: camss: Add common " Wenmeng Liu
2025-10-20 12:53 ` Konrad Dybcio
2025-10-20 19:59 ` Bryan O'Donoghue
2025-10-17 5:06 ` [PATCH v5 2/3] media: qcom: camss: Add link support for TPG Wenmeng Liu
2025-10-20 18:46 ` Bryan O'Donoghue
2025-10-17 5:06 ` [PATCH v5 3/3] media: qcom: camss: tpg: Add TPG support for LeMans and Monaco Wenmeng Liu
2025-10-20 19:58 ` Bryan O'Donoghue
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox