mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-23 01:31:30 +03:00
Add tpg driver to verify drivers when no real sensor module is connected. Bug 3583587 Change-Id: I4ba3afc0c3d8273322c30f0f43c131bbf7fc62be Signed-off-by: Ankur Pawar <ankurp@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2857240 Reviewed-by: Frank Chen <frankc@nvidia.com> Reviewed-by: Semi Malinen <smalinen@nvidia.com> Reviewed-by: Bitan Biswas <bbiswas@nvidia.com> GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
361 lines
10 KiB
C
361 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2017-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
*
|
|
* Tegra VI test pattern generator driver
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/export.h>
|
|
#include <linux/module.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <media/mc_common.h>
|
|
#include <media/csi.h>
|
|
|
|
#include "nvcsi/nvcsi.h"
|
|
#include <linux/host1x.h>
|
|
#include "soc/tegra/camrtc-capture-messages.h"
|
|
|
|
#include <soc/tegra/fuse.h>
|
|
|
|
/*
|
|
* 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");
|