Files
linux-nv-oot/drivers/media/platform/tegra/tpg/tpg_t19x.c
Praveen AC e4e94d02c1 t23x:tpg:[WAR] Disable gain control to avoid insmod fail.
Disable gain control during tpg probe for t23x to avoid
insmod fail due to "NULL VI channel" when "tpg_gain_ctrl"
is enabled.

Bug 3695458

Change-Id: I15c035eaa2f187b927dc114e88959378f9e52a61
Signed-off-by: Praveen AC <pac@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2828420
(cherry picked from commit 06e543de3c238326bce4e3911129e60ec2be0880)
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3130692
Reviewed-by: Bibek Basu <bbasu@nvidia.com>
Reviewed-by: Shubham Chandra <shubhamc@nvidia.com>
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
2024-05-14 05:33:12 -07:00

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 = false;
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");