From f7846443d02435f0cbfd1a6e3bf3da8f1c9eb23b Mon Sep 17 00:00:00 2001 From: Ankur Pawar Date: Mon, 13 Feb 2023 09:23:46 +0000 Subject: [PATCH] camera: tpg: add tpg kernel driver Add tpg driver to verify drivers when no real sensor module is connected. Bug 3583587 Change-Id: I4ba3afc0c3d8273322c30f0f43c131bbf7fc62be Signed-off-by: Ankur Pawar Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2857240 Reviewed-by: Frank Chen Reviewed-by: Semi Malinen Reviewed-by: Bitan Biswas GVS: Gerrit_Virtual_Submit --- drivers/media/platform/tegra/Makefile | 3 +- drivers/media/platform/tegra/tpg/Makefile | 10 + drivers/media/platform/tegra/tpg/tpg_t19x.c | 360 ++++++++++++++++++++ 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 drivers/media/platform/tegra/tpg/Makefile create mode 100644 drivers/media/platform/tegra/tpg/tpg_t19x.c diff --git a/drivers/media/platform/tegra/Makefile b/drivers/media/platform/tegra/Makefile index bebe47ff..4d89e6e9 100644 --- a/drivers/media/platform/tegra/Makefile +++ b/drivers/media/platform/tegra/Makefile @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 -# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. LINUXINCLUDE += -I$(srctree.nvidia-oot)/include obj-m += cdi/ obj-m += isc/ obj-m += camera/ +obj-m += tpg/ diff --git a/drivers/media/platform/tegra/tpg/Makefile b/drivers/media/platform/tegra/tpg/Makefile new file mode 100644 index 00000000..eb26afa6 --- /dev/null +++ b/drivers/media/platform/tegra/tpg/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +ccflags-y += -Werror + +LINUXINCLUDE += -I$(srctree.nvidia)/drivers/gpu/host1x/hw/ +LINUXINCLUDE += -I$(srctree.nvidia)/drivers/video/tegra/host/ + +nvhost-vi-tpg-t19x-objs += tpg_t19x.o +obj-m += nvhost-vi-tpg-t19x.o diff --git a/drivers/media/platform/tegra/tpg/tpg_t19x.c b/drivers/media/platform/tegra/tpg/tpg_t19x.c new file mode 100644 index 00000000..ee4c299f --- /dev/null +++ b/drivers/media/platform/tegra/tpg/tpg_t19x.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * Tegra VI test pattern generator driver + * + */ + +#include +#include +#include +#include + +#include +#include + +#include "nvcsi/nvcsi.h" +#include +#include "soc/tegra/camrtc-capture-messages.h" + +#include + +/* + * T19x TPG is generating 64 bits per cycle + * it will insert (TPG_LANE_NUM-8) * nvcsi_clock cycles between + * two 64bit pixel_packages to reduce framerate + * TPG_LANE_NUM=8 means no blank insertion. + * 7 means insert 1 clock between two 64bit pixel packages, + * 6 means 2 clocks blank, …, 1 means 7 blank clocks. + */ +#define TPG_BLANK 6 + +#define TPG_HBLANK 0 +#define TPG_VBLANK 40800 + +static struct tpg_frmfmt *frmfmt_table; + +static bool override_frmfmt; +module_param(override_frmfmt, bool, 0444); +MODULE_PARM_DESC(override_frmfmt, "override existing format table"); + +static int framerate = 30; +module_param(framerate, int, 0444); + +static bool ls_le; +module_param(ls_le, bool, 0644); +MODULE_PARM_DESC(ls_le, "Enable/disable LS/LE (line start and line end) in TPG. Default is OFF"); + +/* Leaving uninitialised to keep default value as false */ +static bool emb_data; +module_param(emb_data, bool, 0644); +MODULE_PARM_DESC(emb_data, "Embedded-data generation by TPG. Default is OFF"); + +static bool phy; /* 0 - DPHY, 1 - CPHY */ +module_param(phy, bool, 0644); +MODULE_PARM_DESC(phy, "PHY mode, CPHY or DPHY. 0 - DPHY (default), 1 - CPHY"); + +static char *pattern = "PATCH"; +module_param(pattern, charp, 0644); +MODULE_PARM_DESC(pattern, "Supported TPG patterns, PATCH, SINE. Default is PATCH mode"); +#define PARAM_STRING_LENGTH 10 + +#define FREQ_RATE_RED 4 +#define FREQ_RATE_GREEN 3 +#define FREQ_RATE_BLUE 1 + +/* PG generate 32 bit per nvcsi_clk: + * clks_per_line = width * bits_per_pixel / 32 + * ((clks_per_line + hblank) * height + vblank) * fps * lanes = nvcsi_clk_freq + * + */ +static const struct tpg_frmfmt tegra19x_csi_tpg_frmfmt[] = { + {{320, 240}, V4L2_PIX_FMT_SRGGB10, 30, 0, 0}, + {{1280, 720}, V4L2_PIX_FMT_SRGGB10, 30, 0, 0}, + {{1920, 1080}, V4L2_PIX_FMT_SRGGB10, 30, 0, 0}, + {{3840, 2160}, V4L2_PIX_FMT_SRGGB10, 30, 0, 0}, + {{1280, 720}, V4L2_PIX_FMT_RGB32, 30, 0, 0}, + {{1920, 1080}, V4L2_PIX_FMT_RGB32, 30, 0, 0}, + {{3840, 2160}, V4L2_PIX_FMT_RGB32, 30, 0, 0}, + {{1280, 720}, V4L2_PIX_FMT_NV16, 30, 0, 0}, + {{1920, 1080}, V4L2_PIX_FMT_NV16, 30, 0, 0}, + {{3840, 2160}, V4L2_PIX_FMT_NV16, 30, 0, 0}, + {{1280, 720}, V4L2_PIX_FMT_UYVY, 30, 0, 0}, + {{1920, 1080}, V4L2_PIX_FMT_UYVY, 30, 0, 0}, + {{3840, 2160}, V4L2_PIX_FMT_UYVY, 30, 0, 0}, +}; + +#define TPG_PORT_IDX 0 + +static int tpg_debugfs_height_show(void *data, u64 *val) +{ + struct tegra_csi_channel *chan = data; + struct tegra_csi_port *port = &chan->ports[TPG_PORT_IDX]; + + mutex_lock(&chan->format_lock); + *val = port->format.height; + mutex_unlock(&chan->format_lock); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tpg_debugfs_height_fops, + tpg_debugfs_height_show, + NULL, + "%lld\n"); + +static int tpg_debugfs_width_show(void *data, u64 *val) +{ + struct tegra_csi_channel *chan = data; + struct tegra_csi_port *port = &chan->ports[TPG_PORT_IDX]; + + mutex_lock(&chan->format_lock); + *val = port->format.width; + mutex_unlock(&chan->format_lock); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tpg_debugfs_width_fops, + tpg_debugfs_width_show, + NULL, + "%lld\n"); + + +static void tpg_remove_debugfs(struct tegra_csi_device *csi) +{ + debugfs_remove_recursive(csi->debugdir); + csi->debugdir = NULL; +} + +static int tpg_create_debugfs(struct tegra_csi_device *csi) +{ + struct dentry *dir; + struct tegra_csi_channel *chan; + + csi->debugdir = dir = debugfs_create_dir("tpg", NULL); + if (dir == NULL) + return -ENOMEM; + + chan = csi->tpg_start; + list_for_each_entry_from(chan, &csi->csi_chans, list) { + const struct tegra_channel *vi_chan = + v4l2_get_subdev_hostdata(&chan->subdev); + if (vi_chan->pg_mode) { + const char *name = vi_chan->video->name; + + dev_dbg(csi->dev, "debugfs node installed %s\n", name); + dir = debugfs_create_dir(name, csi->debugdir); + if (!dir) + goto error; + + if (!debugfs_create_file("height", 0444, + dir, chan, + &tpg_debugfs_height_fops)) + goto error; + if (!debugfs_create_file("width", 0444, + dir, chan, + &tpg_debugfs_width_fops)) + goto error; + } + } + + return 0; +error: + tpg_remove_debugfs(csi); + return -ENOMEM; +} + +static void patch_pattern_tpg_settings(struct tegra_csi_port *port, + union nvcsi_tpg_config *const tpg_config) +{ + const size_t px_max = 0x4000; + const size_t py_max = 0x2000; + size_t hfreq = 0; + size_t vfreq = 0; + + hfreq = px_max / port->format.width; + vfreq = py_max / port->format.height; + + tpg_config->tpg_ng.initial_phase_red = 0U; + tpg_config->tpg_ng.red_horizontal_init_freq = hfreq; + tpg_config->tpg_ng.red_vertical_init_freq = vfreq; + + tpg_config->tpg_ng.initial_phase_green = 0U; + tpg_config->tpg_ng.green_horizontal_init_freq = hfreq; + tpg_config->tpg_ng.green_vertical_init_freq = vfreq; + + tpg_config->tpg_ng.initial_phase_blue = 0U; + tpg_config->tpg_ng.blue_horizontal_init_freq = hfreq; + tpg_config->tpg_ng.blue_vertical_init_freq = vfreq; +} + +#define freq(px, rate, size) ((px) - (rate * port->format.size / 2U)) + +static void sine_pattern_tpg_settings(struct tegra_csi_port *port, + union nvcsi_tpg_config *const tpg_config) +{ + const size_t px_max = 0x4000; + size_t hr_freq = freq(px_max, FREQ_RATE_RED, width); + size_t vr_freq = freq(px_max, FREQ_RATE_RED, height); + + size_t hg_freq = freq(px_max, FREQ_RATE_GREEN, width); + size_t vg_freq = freq(px_max, FREQ_RATE_GREEN, height); + + size_t hb_freq = freq(px_max, FREQ_RATE_BLUE, width); + size_t vb_freq = freq(px_max, FREQ_RATE_BLUE, height); + + tpg_config->tpg_ng.initial_phase_red = 0U; + tpg_config->tpg_ng.red_horizontal_init_freq = hr_freq; + tpg_config->tpg_ng.red_vertical_init_freq = vr_freq; + tpg_config->tpg_ng.red_horizontal_freq_rate = FREQ_RATE_RED; + tpg_config->tpg_ng.red_vertical_freq_rate = FREQ_RATE_RED; + + tpg_config->tpg_ng.initial_phase_green = 0U; + tpg_config->tpg_ng.green_horizontal_init_freq = hg_freq; + tpg_config->tpg_ng.green_vertical_init_freq = vg_freq; + tpg_config->tpg_ng.green_horizontal_freq_rate = FREQ_RATE_GREEN; + tpg_config->tpg_ng.green_vertical_freq_rate = FREQ_RATE_GREEN; + + tpg_config->tpg_ng.initial_phase_blue = 0U; + tpg_config->tpg_ng.blue_horizontal_init_freq = hb_freq; + tpg_config->tpg_ng.blue_vertical_init_freq = vb_freq; + tpg_config->tpg_ng.blue_horizontal_freq_rate = FREQ_RATE_BLUE; + tpg_config->tpg_ng.blue_vertical_freq_rate = FREQ_RATE_BLUE; +} + +static int get_tpg_settings_t23x(struct tegra_csi_port *port, + union nvcsi_tpg_config *const tpg_config) +{ + /* TPG native resolution */ + u16 flags = 0; + char param_name[PARAM_STRING_LENGTH]; + char *temp = NULL; + + dev_info(NULL, "pattern - %s cphy - %d ls-le - %d emb-data - %d\n", + pattern, phy, ls_le, emb_data); + + tpg_config->tpg_ng.virtual_channel_id = port->virtual_channel_id; + tpg_config->tpg_ng.datatype = port->core_format->img_dt; + tpg_config->tpg_ng.stream_id = port->stream_id; + + flags |= (ls_le == true) ? NVCSI_TPG_FLAG_ENABLE_LS_LE : 0; + flags |= (emb_data == true) ? NVCSI_TPG_FLAG_EMBEDDED_PATTERN_CONFIG_INFO : 0; + flags |= (phy == true) ? NVCSI_TPG_FLAG_PHY_MODE_CPHY : 0; + + strncpy(param_name, pattern, PARAM_STRING_LENGTH); + param_name[PARAM_STRING_LENGTH - 1] = '\0'; + temp = strstrip(param_name); + + if (strcmp(temp, "PATCH") == 0) { + flags |= NVCSI_TPG_FLAG_PATCH_MODE; + patch_pattern_tpg_settings(port, tpg_config); + } else if (strcmp(temp, "SINE") == 0) { + flags |= NVCSI_TPG_FLAG_SINE_MODE; + sine_pattern_tpg_settings(port, tpg_config); + } else { + dev_err(NULL, "Error: Incorrect pattern - %s\n", pattern); + return -EINVAL; + } + + tpg_config->tpg_ng.flags = flags; + + tpg_config->tpg_ng.initial_frame_number = 1; + tpg_config->tpg_ng.maximum_frame_number = 32768; + tpg_config->tpg_ng.image_width = port->format.width; + tpg_config->tpg_ng.image_height = port->format.height; + + tpg_config->tpg_ng.brightness_gain_ratio = + CAPTURE_CSI_STREAM_TPG_GAIN_RATIO_NONE; + + tpg_config->tpg_ng.embedded_lines_top = 0; + tpg_config->tpg_ng.embedded_line_width = 0; + tpg_config->tpg_ng.embedded_lines_bottom = 0; + if (emb_data) { + tpg_config->tpg_ng.embedded_lines_top = 1; + tpg_config->tpg_ng.embedded_line_width = 32; + tpg_config->tpg_ng.embedded_lines_bottom = 0; + /* Least significant 8 bits of flags */ + tpg_config->tpg_ng.emb_data_spare_0 = (uint8_t)(flags & 0xFF); + /* Most significant 8 bits of flags */ + tpg_config->tpg_ng.emb_data_spare_1 = + (uint8_t)((flags >> 8) & 0xFF); + } + + return 0; +} + +static int __init tpg_probe_t19x(void) +{ + struct tegra_csi_device *mc_csi = tegra_get_mc_csi(); + struct tegra_mc_vi *mc_vi = tegra_get_mc_vi(); + int err = 0; + int i = 0; + unsigned int table_size = ARRAY_SIZE(tegra19x_csi_tpg_frmfmt); + + if (!mc_vi || !mc_csi) + return -EINVAL; + + mc_csi->get_tpg_settings = get_tpg_settings_t23x; + mc_csi->tpg_gain_ctrl = true; + mc_csi->tpg_emb_data_config = emb_data; + + dev_info(mc_csi->dev, "%s\n", __func__); + mc_vi->csi = mc_csi; + /* Init CSI related media controller interface */ + frmfmt_table = devm_kzalloc(mc_csi->dev, + table_size * sizeof(struct tpg_frmfmt), GFP_KERNEL); + if (!frmfmt_table) + return -ENOMEM; + + mc_csi->tpg_frmfmt_table_size = table_size; + memcpy(frmfmt_table, tegra19x_csi_tpg_frmfmt, + table_size * sizeof(struct tpg_frmfmt)); + + if (override_frmfmt) { + for (i = 0; i < table_size; i++) + frmfmt_table[i].framerate = framerate; + } + mc_csi->tpg_frmfmt_table = frmfmt_table; + + err = tpg_csi_media_controller_init(mc_csi, TEGRA_VI_PG_PATCH); + if (err) + return -EINVAL; + err = tpg_vi_media_controller_init(mc_vi, TEGRA_VI_PG_PATCH); + if (err) + goto vi_init_err; + + err = tpg_create_debugfs(mc_csi); + if (err) + goto debugfs_init_err; + + return err; +debugfs_init_err: + tpg_remove_debugfs(mc_csi); +vi_init_err: + tpg_csi_media_controller_cleanup(mc_csi); + dev_err(mc_csi->dev, "%s error\n", __func__); + return err; +} +static void __exit tpg_remove_t19x(void) +{ + struct tegra_csi_device *mc_csi = tegra_get_mc_csi(); + struct tegra_mc_vi *mc_vi = tegra_get_mc_vi(); + + if (!mc_vi || !mc_csi) + return; + + dev_info(mc_csi->dev, "%s\n", __func__); + tpg_remove_debugfs(mc_csi); + tpg_csi_media_controller_cleanup(mc_csi); + tpg_vi_media_controller_cleanup(mc_vi); + + mc_csi->tpg_frmfmt_table = NULL; + mc_csi->tpg_frmfmt_table_size = 0; + devm_kfree(mc_csi->dev, frmfmt_table); +} + +module_init(tpg_probe_t19x); +module_exit(tpg_remove_t19x); +MODULE_LICENSE("GPL v2");