mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
platform: tegra: Remove CVNAS driver
The CVNAS driver is not needed for Tegra234 and future devices. Remove the CVNAS driver and support for CVNAS from DLA, NVMAP and PVA dirvers. Bug 4037930 Change-Id: I5d1ae3c195485cb094f9b9b480f91d81fd04687c Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2903848 Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
This commit is contained in:
committed by
mobile promotions
parent
966af3a139
commit
9a3174280f
@@ -13,9 +13,6 @@ obj-m += tegra-fsicom.o
|
||||
tegra-camera-rtcpu-objs := tegra-camera-rtcpu-base.o rtcpu/tegra-rtcpu-trace.o rtcpu/rtcpu-monitor.o
|
||||
obj-m += tegra-camera-rtcpu.o
|
||||
|
||||
ifdef CONFIG_TEGRA_HOST1X
|
||||
obj-m += cvnas/
|
||||
endif
|
||||
obj-m += mce/
|
||||
ifdef CONFIG_PERF_EVENTS
|
||||
obj-m += uncore_pmu/
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
|
||||
#
|
||||
|
||||
ccflags-y += -Werror
|
||||
|
||||
# Enable UPSTREM FUSE as default for OOT module
|
||||
CONFIG_TEGRA_FUSE_UPSTREAM := y
|
||||
ccflags-y += -DCONFIG_TEGRA_FUSE_UPSTREAM
|
||||
|
||||
obj-m += cvnas.o
|
||||
ccflags-y += -DCVNAS_MODULE
|
||||
@@ -1,836 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2017-2023, NVIDIA Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifdef pr_fmt
|
||||
#undef pr_fmt
|
||||
#endif
|
||||
|
||||
#define pr_fmt(fmt) "cvnas: %s,%d" fmt, __func__, __LINE__
|
||||
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/cvnas.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_reserved_mem.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/nvmap_t19x.h>
|
||||
#include <linux/version.h>
|
||||
#if KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE
|
||||
#include <soc/tegra/chip-id.h>
|
||||
#endif
|
||||
#include <soc/tegra/fuse-helper.h>
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
static int cvnas_debug;
|
||||
module_param(cvnas_debug, int, 0644);
|
||||
#define CVNOC_MISC_DBG_APERTURE_CONTROL 0x8
|
||||
#define CVNOC_MISC_DBG_APERTURE_DISABLE 0x1
|
||||
|
||||
static bool cvnas_rail;
|
||||
|
||||
#define CVSRAM_MEM_INIT_OFFSET 0x00
|
||||
#define CVSRAM_MEM_INIT_START BIT(0)
|
||||
#define CVSRAM_MEM_INIT_STATUS BIT(1)
|
||||
|
||||
#define CVSRAM_RD_COUNT_OFFSET 0x008
|
||||
#define CVSRAM_WR_COUNT_OFFSET 0x00C
|
||||
#define CVSRAM_STALLED_RD_COUNT_OFFSET 0x010
|
||||
#define CVSRAM_STALLED_WR_COUNT_OFFSET 0x014
|
||||
|
||||
#define CVSRAM_PWR_CTRL_OFFSET 0x018
|
||||
|
||||
#define CVSRAM_EC_MERR_FORCE_OFFSET 0x83C
|
||||
#define CVSRAM_EC_MERR_ECC_INJECT 0xFFFFFF
|
||||
|
||||
#define ERRCOLLATOR_MISSIONERR_STATUS 0x840
|
||||
|
||||
#define CVNAS_EC_MERR_FORCE_OFFSET 0xF134
|
||||
#define CVNAS_EC_MERR_ECC_INJECT 0x1FE
|
||||
|
||||
#define MEM_INIT_FCM 0x1
|
||||
#define DEV_CVNAS_CLR_RST 0x2
|
||||
|
||||
#define HSM_CVSRAM_ECC_CORRECT_OFFSET 0x1A8
|
||||
#define HSM_CVSRAM_ECC_DED_OFFSET_0 0x180
|
||||
#define HSM_CVSRAM_ECC_DED_OFFSET_1 0x184
|
||||
|
||||
#define HSM_CVSRAM_ECC_CORRECT_MASK 0x0F000000
|
||||
#define HSM_CVSRAM_ECC_DED_MASK_0 0x80000000
|
||||
#define HSM_CVSRAM_ECC_DED_MASK_1 0x00000007
|
||||
|
||||
struct cvnas_device {
|
||||
struct dentry *debugfs_root;
|
||||
|
||||
void __iomem *cvsram_iobase;
|
||||
void __iomem *cvreg_iobase;
|
||||
void __iomem *hsm_iobase;
|
||||
|
||||
struct device dma_dev;
|
||||
|
||||
u32 nslices;
|
||||
u32 slice_size;
|
||||
phys_addr_t cvsram_base;
|
||||
size_t cvsram_size;
|
||||
|
||||
struct clk *clk;
|
||||
struct device_attribute *attr;
|
||||
|
||||
struct reset_control *rst;
|
||||
struct reset_control *rst_fcm;
|
||||
|
||||
bool virt;
|
||||
|
||||
int (*pmops_busy)(void);
|
||||
int (*pmops_idle)(void);
|
||||
};
|
||||
|
||||
static struct platform_device *cvnas_plat_dev;
|
||||
|
||||
static u32 nvcvsram_readl(struct cvnas_device *dev, int sid, u32 reg)
|
||||
{
|
||||
return readl(dev->cvsram_iobase + dev->slice_size * sid + reg);
|
||||
}
|
||||
|
||||
static void nvcvsram_writel(struct cvnas_device *dev, int sid, u32 val, u32 reg)
|
||||
{
|
||||
writel(val, dev->cvsram_iobase + dev->slice_size * sid + reg);
|
||||
}
|
||||
|
||||
static u32 nvhsm_readl(struct cvnas_device *dev, u32 reg)
|
||||
{
|
||||
return readl(dev->hsm_iobase + reg);
|
||||
}
|
||||
|
||||
/* Call at the time we allocate something from CVNAS */
|
||||
int nvcvnas_busy(void)
|
||||
{
|
||||
if (!cvnas_plat_dev) {
|
||||
pr_err("CVNAS Platform Device not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return pm_runtime_get_sync(&cvnas_plat_dev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(nvcvnas_busy);
|
||||
|
||||
/* Call after we release a buffer */
|
||||
int nvcvnas_idle(void)
|
||||
{
|
||||
if (!cvnas_plat_dev) {
|
||||
pr_err("CVNAS Platform Device not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return pm_runtime_put(&cvnas_plat_dev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(nvcvnas_idle);
|
||||
|
||||
|
||||
static int cvsram_perf_counters_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct cvnas_device *dev = s->private;
|
||||
int i;
|
||||
u32 val;
|
||||
|
||||
if (!dev) {
|
||||
seq_printf(s, "Invalid cvnas device!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
seq_printf(s, "RD: ");
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
val = nvcvsram_readl(dev, i, CVSRAM_RD_COUNT_OFFSET);
|
||||
seq_printf(s, "%x ", val);
|
||||
}
|
||||
seq_printf(s, "\nWR: ");
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
val = nvcvsram_readl(dev, i, CVSRAM_WR_COUNT_OFFSET);
|
||||
seq_printf(s, "%x ", val);
|
||||
}
|
||||
seq_printf(s, "\nSRD: ");
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
val = nvcvsram_readl(dev, i, CVSRAM_STALLED_RD_COUNT_OFFSET);
|
||||
seq_printf(s, "%x ", val);
|
||||
}
|
||||
seq_printf(s, "\nSWR: ");
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
val = nvcvsram_readl(dev, i, CVSRAM_STALLED_WR_COUNT_OFFSET);
|
||||
seq_printf(s, "%x ", val);
|
||||
}
|
||||
seq_printf(s, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cvsram_perf_counter_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, cvsram_perf_counters_show,
|
||||
inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations cvsram_perf_fops = {
|
||||
.open = cvsram_perf_counter_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int cvsram_ecc_err_inject(struct seq_file *s, void *data)
|
||||
{
|
||||
struct cvnas_device *dev = (struct cvnas_device *)s->private;
|
||||
int i;
|
||||
u32 val;
|
||||
|
||||
if (!dev) {
|
||||
seq_printf(s, "Invalid cvnas device!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
nvcvsram_writel(dev, i, CVSRAM_EC_MERR_ECC_INJECT,
|
||||
CVSRAM_EC_MERR_FORCE_OFFSET);
|
||||
if (cvnas_debug) {
|
||||
val = nvcvsram_readl(dev, i,
|
||||
CVSRAM_EC_MERR_FORCE_OFFSET);
|
||||
seq_printf(s, "CVSRAM_EC_MERR_FORCE_OFFSET_SLICE%d: 0x%x : 0x%x\n",
|
||||
i, CVSRAM_EC_MERR_FORCE_OFFSET, val);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
if (cvnas_debug) {
|
||||
val = nvcvsram_readl(dev, i,
|
||||
ERRCOLLATOR_MISSIONERR_STATUS);
|
||||
seq_printf(s, "ERRCOLLATOR_SLICE0_ERRSLICE0_MISSIONERR_STATUS_SLICE%d: 0x%x : 0x%x\n",
|
||||
i, ERRCOLLATOR_MISSIONERR_STATUS, val);
|
||||
}
|
||||
}
|
||||
|
||||
val = nvhsm_readl(dev, HSM_CVSRAM_ECC_CORRECT_OFFSET);
|
||||
if (val & HSM_CVSRAM_ECC_CORRECT_MASK) {
|
||||
seq_printf(s, "HSM received ECC corrected SEC error\n");
|
||||
}
|
||||
val = nvhsm_readl(dev, HSM_CVSRAM_ECC_DED_OFFSET_0);
|
||||
if (val & HSM_CVSRAM_ECC_DED_MASK_0) {
|
||||
seq_printf(s, "HSM received ECC DED_0 error\n");
|
||||
}
|
||||
val = nvhsm_readl(dev, HSM_CVSRAM_ECC_DED_OFFSET_1);
|
||||
if (val & HSM_CVSRAM_ECC_DED_MASK_1) {
|
||||
seq_printf(s, "HSM received ECC DED_1 error\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cvsram_ecc_err_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, cvsram_ecc_err_inject,
|
||||
inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations cvsram_ecc_err_fops = {
|
||||
.open = cvsram_ecc_err_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int rd_cvrail(void *data, u64 *val)
|
||||
{
|
||||
*val = cvnas_rail;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wr_cvrail(void *data, u64 val)
|
||||
{
|
||||
bool cvrail = (bool)val;
|
||||
int ret = 0;
|
||||
|
||||
if (cvrail) {
|
||||
ret = nvcvnas_busy();
|
||||
cvnas_rail = true;
|
||||
} else {
|
||||
ret = nvcvnas_idle();
|
||||
cvnas_rail = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEFINE_SIMPLE_ATTRIBUTE(cvnas_reg_fops, rd_cvrail, wr_cvrail,
|
||||
"%llu\n");
|
||||
|
||||
static int nvcvnas_debugfs_init(struct cvnas_device *dev)
|
||||
{
|
||||
struct dentry *root;
|
||||
|
||||
root = debugfs_create_dir("cvnas", NULL);
|
||||
if (!root)
|
||||
return PTR_ERR(root);
|
||||
|
||||
debugfs_create_file("cvrail", 0644,
|
||||
root, dev, &cvnas_reg_fops);
|
||||
debugfs_create_x64("cvsram_base", S_IRUGO, root, &dev->cvsram_base);
|
||||
debugfs_create_size_t("cvsram_size", S_IRUGO, root, &dev->cvsram_size);
|
||||
debugfs_create_file("cvsram_perf_counters", S_IRUGO, root, dev, &cvsram_perf_fops);
|
||||
debugfs_create_file("inject_cvsram_ecc_error", S_IRUGO, root, dev, &cvsram_ecc_err_fops);
|
||||
dev->debugfs_root = root;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int nvcvsram_ecc_setup(struct cvnas_device *dev)
|
||||
{
|
||||
u32 mem_init = 0;
|
||||
int i;
|
||||
|
||||
/* enable clock if disabled */
|
||||
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
mem_init = nvcvsram_readl(dev, i, CVSRAM_MEM_INIT_OFFSET);
|
||||
if (mem_init & CVSRAM_MEM_INIT_STATUS)
|
||||
return 0;
|
||||
nvcvsram_writel(dev, i, MEM_INIT_FCM, CVSRAM_MEM_INIT_OFFSET);
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->nslices; i++) {
|
||||
while (1) {
|
||||
usleep_range(100, 200);
|
||||
mem_init = nvcvsram_readl(dev, i,
|
||||
CVSRAM_MEM_INIT_OFFSET);
|
||||
/* FIXME: Use CCF to make sure clock runs
|
||||
* at fixed frequency and wait for just
|
||||
* that much time.
|
||||
*/
|
||||
if (((mem_init & CVSRAM_MEM_INIT_STATUS) >> 1) & 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mem_init & CVSRAM_MEM_INIT_STATUS)
|
||||
return 0;
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Disable CVNOC Debug Aperture */
|
||||
static void disable_cvnoc_debug_apert(struct cvnas_device *cvnas_dev)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
val = readl(cvnas_dev->cvreg_iobase + CVNOC_MISC_DBG_APERTURE_CONTROL);
|
||||
val = val | CVNOC_MISC_DBG_APERTURE_DISABLE;
|
||||
writel(val, cvnas_dev->cvreg_iobase + CVNOC_MISC_DBG_APERTURE_CONTROL);
|
||||
}
|
||||
|
||||
static int nvcvnas_power_on(struct cvnas_device *cvnas_dev)
|
||||
{
|
||||
u32 fcm_upg_seq[] =
|
||||
{0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00};
|
||||
|
||||
int i, j;
|
||||
int err;
|
||||
|
||||
if (!tegra_platform_is_silicon()) {
|
||||
pr_err("is not supported on this platform\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cvnas_dev->virt)
|
||||
return 0;
|
||||
|
||||
err = clk_prepare_enable(cvnas_dev->clk);
|
||||
if (err < 0)
|
||||
goto err_enable_clk;
|
||||
|
||||
err = reset_control_deassert(cvnas_dev->rst);
|
||||
if (err < 0)
|
||||
goto err_deassert_reset;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(fcm_upg_seq); i++) {
|
||||
for (j = 0; j < cvnas_dev->nslices; j++) {
|
||||
nvcvsram_writel(cvnas_dev, j, fcm_upg_seq[i],
|
||||
CVSRAM_PWR_CTRL_OFFSET);
|
||||
if (cvnas_debug) {
|
||||
u32 val = nvcvsram_readl(cvnas_dev, j,
|
||||
CVSRAM_PWR_CTRL_OFFSET);
|
||||
pr_info("Set SRAM%d_CVSRAM_PWR_CTRL %x to %x\n",
|
||||
j, CVSRAM_PWR_CTRL_OFFSET, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = reset_control_deassert(cvnas_dev->rst_fcm);
|
||||
if (err < 0)
|
||||
goto err_deassert_fcm_reset;
|
||||
|
||||
err = nvcvsram_ecc_setup(cvnas_dev);
|
||||
if (err < 0) {
|
||||
pr_err("ECC init failed\n");
|
||||
goto err_init_ecc;
|
||||
}
|
||||
|
||||
|
||||
if (!tegra_platform_is_sim())
|
||||
disable_cvnoc_debug_apert(cvnas_dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_init_ecc:
|
||||
reset_control_assert(cvnas_dev->rst_fcm);
|
||||
err_deassert_fcm_reset:
|
||||
reset_control_assert(cvnas_dev->rst);
|
||||
err_deassert_reset:
|
||||
clk_disable_unprepare(cvnas_dev->clk);
|
||||
err_enable_clk:
|
||||
return err;
|
||||
}
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
static int nvcvnas_power_off(struct cvnas_device *cvnas_dev)
|
||||
{
|
||||
u32 val, i, j;
|
||||
u32 fcm_pg_seq[] =
|
||||
{0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF};
|
||||
|
||||
if (!tegra_platform_is_silicon()) {
|
||||
pr_err("is not supported on this platform\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cvnas_dev->virt)
|
||||
return 0;
|
||||
|
||||
reset_control_assert(cvnas_dev->rst_fcm);
|
||||
|
||||
/* FCM low power mode */
|
||||
for (i = 0; i < ARRAY_SIZE(fcm_pg_seq); i++) {
|
||||
for (j = 0; j < cvnas_dev->nslices; j++) {
|
||||
nvcvsram_writel(cvnas_dev, j, fcm_pg_seq[i],
|
||||
CVSRAM_PWR_CTRL_OFFSET);
|
||||
if (cvnas_debug) {
|
||||
val = nvcvsram_readl(cvnas_dev, j,
|
||||
CVSRAM_PWR_CTRL_OFFSET);
|
||||
pr_info("Set SRAM%d_CVSRAM_PWR_CTRL %x to %x\n",
|
||||
j, CVSRAM_PWR_CTRL_OFFSET, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset_control_assert(cvnas_dev->rst);
|
||||
clk_disable_unprepare(cvnas_dev->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
phys_addr_t nvcvnas_get_cvsram_base(void)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
|
||||
if (!cvnas_plat_dev)
|
||||
return 0;
|
||||
|
||||
cvnas_dev = dev_get_drvdata(&cvnas_plat_dev->dev);
|
||||
return cvnas_dev->cvsram_base;
|
||||
}
|
||||
EXPORT_SYMBOL(nvcvnas_get_cvsram_base);
|
||||
|
||||
size_t nvcvnas_get_cvsram_size(void)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
|
||||
if (!cvnas_plat_dev)
|
||||
return 0;
|
||||
|
||||
cvnas_dev = dev_get_drvdata(&cvnas_plat_dev->dev);
|
||||
return cvnas_dev->cvsram_size;
|
||||
}
|
||||
EXPORT_SYMBOL(nvcvnas_get_cvsram_size);
|
||||
|
||||
int is_nvcvnas_probed(void)
|
||||
{
|
||||
if (cvnas_plat_dev && dev_get_drvdata(&cvnas_plat_dev->dev))
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int is_nvcvnas_clk_enabled(void)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev = dev_get_drvdata(&cvnas_plat_dev->dev);
|
||||
|
||||
if (cvnas_plat_dev && cvnas_dev)
|
||||
return __clk_is_enabled(cvnas_dev->clk);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(is_nvcvnas_clk_enabled);
|
||||
|
||||
static ssize_t clk_cap_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct cvnas_device *cvnas = dev_get_drvdata(dev);
|
||||
unsigned long max_rate;
|
||||
long rounded_max_rate;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 0, &max_rate);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
/* Remove previous freq cap to get correct rounted rate for new cap */
|
||||
ret = clk_set_max_rate(cvnas->clk, UINT_MAX);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
rounded_max_rate = clk_round_rate(cvnas->clk, max_rate);
|
||||
if (rounded_max_rate < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Apply new freq cap */
|
||||
ret = clk_set_max_rate(cvnas->clk, (unsigned long) rounded_max_rate);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* No dynamic frequency scaling support for CVNAS clock.
|
||||
* Set current rate to max */
|
||||
ret = clk_set_rate(cvnas->clk, (unsigned long) rounded_max_rate);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t clk_cap_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct cvnas_device *cvnas = dev_get_drvdata(dev);
|
||||
long max_rate;
|
||||
|
||||
max_rate = clk_round_rate(cvnas->clk, UINT_MAX);
|
||||
if (max_rate < 0)
|
||||
return max_rate;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%ld\n", max_rate);
|
||||
}
|
||||
|
||||
static const struct of_device_id nvcvnas_of_ids[] = {
|
||||
{ .compatible = "nvidia,tegra194-cvnas", },
|
||||
{ .compatible = "nvidia,tegra-cvnas-hv", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, nvcvnas_of_ids);
|
||||
|
||||
static int nvcvnas_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
int ret;
|
||||
u32 cvsram_slice_data[2];
|
||||
u32 cvsram_reg_data[4];
|
||||
|
||||
if (tegra_get_sku_id() == 0x9E) {
|
||||
dev_err(&pdev->dev, "CVNAS IP is disabled in SKU.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
cvnas_plat_dev = pdev;
|
||||
|
||||
cvnas_dev = (struct cvnas_device *)kzalloc(
|
||||
sizeof(*cvnas_dev), GFP_KERNEL);
|
||||
if (!cvnas_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
cvnas_dev->attr =
|
||||
devm_kmalloc(&pdev->dev, sizeof(*cvnas_dev->attr), GFP_KERNEL);
|
||||
if (!cvnas_dev->attr) {
|
||||
kfree(cvnas_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sysfs_attr_init(&cvnas_dev->attr->attr);
|
||||
cvnas_dev->attr->attr.name = "clk_cap";
|
||||
cvnas_dev->attr->attr.mode = 0644;
|
||||
cvnas_dev->attr->show = clk_cap_show;
|
||||
cvnas_dev->attr->store = clk_cap_store;
|
||||
|
||||
ret = device_create_file(&pdev->dev, cvnas_dev->attr);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "sysfs_create_file failed: %d\n", ret);
|
||||
goto err_device_create_file;
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "nvidia,tegra-cvnas-hv"))
|
||||
cvnas_dev->virt = true;
|
||||
|
||||
cvnas_dev->cvreg_iobase = of_iomap(pdev->dev.of_node, 0);
|
||||
if (!cvnas_dev->cvreg_iobase) {
|
||||
dev_err(&pdev->dev, "No cvnas reg property found\n");
|
||||
ret = PTR_ERR(cvnas_dev->cvreg_iobase);
|
||||
goto err_of_iomap;
|
||||
}
|
||||
|
||||
cvnas_dev->cvsram_iobase = of_iomap(pdev->dev.of_node, 1);
|
||||
if (!cvnas_dev->cvsram_iobase) {
|
||||
dev_err(&pdev->dev, "No cvsram reg property found\n");
|
||||
ret = PTR_ERR(cvnas_dev->cvsram_iobase);
|
||||
goto err_cvsram_of_iomap;
|
||||
}
|
||||
|
||||
cvnas_dev->hsm_iobase = of_iomap(pdev->dev.of_node, 2);
|
||||
if (!cvnas_dev->hsm_iobase) {
|
||||
dev_err(&pdev->dev, "No hsm reg property found\n");
|
||||
ret = PTR_ERR(cvnas_dev->hsm_iobase);
|
||||
goto err_hsm_of_iomap;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32_array(pdev->dev.of_node,
|
||||
"cvsramslice", cvsram_slice_data,
|
||||
ARRAY_SIZE(cvsram_slice_data));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "no cvsramslice property found\n");
|
||||
goto err_cvsram_get_slice_data;
|
||||
}
|
||||
cvnas_dev->nslices = cvsram_slice_data[0];
|
||||
cvnas_dev->slice_size = cvsram_slice_data[1];
|
||||
|
||||
ret = of_property_read_u32_array(pdev->dev.of_node,
|
||||
"cvsram-reg", cvsram_reg_data,
|
||||
ARRAY_SIZE(cvsram_reg_data));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "no cvsram-reg property found\n");
|
||||
goto err_cvsram_get_reg_data;
|
||||
}
|
||||
|
||||
cvnas_dev->cvsram_base = ((u64)cvsram_reg_data[0]) << 32;
|
||||
cvnas_dev->cvsram_base |= cvsram_reg_data[1];
|
||||
cvnas_dev->cvsram_size = ((u64)cvsram_reg_data[2]) << 32;
|
||||
cvnas_dev->cvsram_size |= cvsram_reg_data[3];
|
||||
|
||||
cvnas_dev->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(cvnas_dev->clk)) {
|
||||
ret = PTR_ERR(cvnas_dev->clk);
|
||||
goto err_get_clk;
|
||||
}
|
||||
|
||||
cvnas_dev->rst = devm_reset_control_get(&pdev->dev, "rst");
|
||||
if (IS_ERR(cvnas_dev->rst)) {
|
||||
ret = PTR_ERR(cvnas_dev->rst);
|
||||
goto err_get_reset;
|
||||
}
|
||||
|
||||
cvnas_dev->rst_fcm = devm_reset_control_get(&pdev->dev, "rst_fcm");
|
||||
if (IS_ERR(cvnas_dev->rst_fcm)) {
|
||||
ret = PTR_ERR(cvnas_dev->rst_fcm);
|
||||
goto err_get_reset_fcm;
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
ret = nvcvnas_debugfs_init(cvnas_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "debugfs init failed. ret=%d\n", ret);
|
||||
goto err_cvnas_debugfs_init;
|
||||
}
|
||||
|
||||
cvnas_dev->pmops_busy = nvcvnas_busy;
|
||||
cvnas_dev->pmops_idle = nvcvnas_idle;
|
||||
|
||||
#ifdef CVNAS_MODULE
|
||||
ret = nvmap_register_cvsram_carveout(&cvnas_dev->dma_dev,
|
||||
cvnas_dev->cvsram_base, cvnas_dev->cvsram_size,
|
||||
cvnas_dev->pmops_busy, cvnas_dev->pmops_idle);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"nvmap cvsram register failed. ret=%d\n", ret);
|
||||
goto err_cvsram_nvmap_heap_register;
|
||||
}
|
||||
#endif /* CVNAS_MODULE */
|
||||
dev_set_drvdata(&pdev->dev, cvnas_dev);
|
||||
|
||||
/* TODO: Add interrupt handler */
|
||||
|
||||
return 0;
|
||||
#ifdef CVNAS_MODULE
|
||||
err_cvsram_nvmap_heap_register:
|
||||
debugfs_remove(cvnas_dev->debugfs_root);
|
||||
#endif /* CVNAS_MODULE */
|
||||
err_cvnas_debugfs_init:
|
||||
err_get_reset_fcm:
|
||||
err_get_reset:
|
||||
err_get_clk:
|
||||
err_cvsram_get_reg_data:
|
||||
err_cvsram_get_slice_data:
|
||||
iounmap(cvnas_dev->hsm_iobase);
|
||||
err_hsm_of_iomap:
|
||||
iounmap(cvnas_dev->cvsram_iobase);
|
||||
err_cvsram_of_iomap:
|
||||
iounmap(cvnas_dev->cvreg_iobase);
|
||||
err_of_iomap:
|
||||
device_remove_file(&pdev->dev, cvnas_dev->attr);
|
||||
err_device_create_file:
|
||||
kfree(cvnas_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nvcvnas_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
|
||||
cvnas_dev = dev_get_drvdata(&pdev->dev);
|
||||
if (!cvnas_dev)
|
||||
return -ENODEV;
|
||||
|
||||
device_remove_file(&pdev->dev, cvnas_dev->attr);
|
||||
debugfs_remove(cvnas_dev->debugfs_root);
|
||||
of_reserved_mem_device_release(&pdev->dev);
|
||||
iounmap(cvnas_dev->cvsram_iobase);
|
||||
iounmap(cvnas_dev->cvreg_iobase);
|
||||
kfree(cvnas_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nvcvnas_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
int ret;
|
||||
|
||||
if (pm_runtime_suspended(&pdev->dev))
|
||||
return;
|
||||
|
||||
cvnas_dev = dev_get_drvdata(&pdev->dev);
|
||||
if (!cvnas_dev) {
|
||||
dev_err(&pdev->dev, "shutdown fail\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = nvcvnas_power_off(cvnas_dev);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "power off fail\n");
|
||||
}
|
||||
|
||||
/* TODO: Add runtime power management */
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int nvcvnas_suspend(struct device *dev)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
|
||||
cvnas_dev = dev_get_drvdata(dev);
|
||||
if (!cvnas_dev)
|
||||
return -ENODEV;
|
||||
|
||||
return nvcvnas_power_off(cvnas_dev);
|
||||
}
|
||||
|
||||
static int nvcvnas_resume(struct device *dev)
|
||||
{
|
||||
struct cvnas_device *cvnas_dev;
|
||||
int ret;
|
||||
|
||||
cvnas_dev = dev_get_drvdata(dev);
|
||||
if (!cvnas_dev) {
|
||||
dev_err(dev, "empty drvdata!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = nvcvnas_power_on(cvnas_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "cvnas power on failed\n");
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nvcvnas_runtime_suspend(struct device *dev)
|
||||
{
|
||||
nvcvnas_suspend(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nvcvnas_runtime_resume(struct device *dev)
|
||||
{
|
||||
nvcvnas_resume(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops nvcvnas_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(nvcvnas_runtime_suspend,
|
||||
nvcvnas_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
#define NVCVNAS_PM_OPS (&nvcvnas_pm_ops)
|
||||
#else
|
||||
#define NVCVNAS_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
/* Function to resume CV without using runtime pm.
|
||||
* CVNOC register setting is required by CBB driver during resume to
|
||||
* enable reporting CVNOC errors for illegal register accesses.
|
||||
*/
|
||||
int nvcvnas_busy_no_rpm(void)
|
||||
{
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
if (cvnas_plat_dev && dev_get_drvdata(&cvnas_plat_dev->dev))
|
||||
return nvcvnas_resume(&cvnas_plat_dev->dev);
|
||||
else
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nvcvnas_busy_no_rpm);
|
||||
|
||||
/*
|
||||
* Function to suspend CV without using runtime pm.
|
||||
*/
|
||||
int nvcvnas_idle_no_rpm(void)
|
||||
{
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
if (cvnas_plat_dev && dev_get_drvdata(&cvnas_plat_dev->dev))
|
||||
return nvcvnas_suspend(&cvnas_plat_dev->dev);
|
||||
else
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nvcvnas_idle_no_rpm);
|
||||
|
||||
static struct platform_driver nvcvnas_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-cvnas",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = nvcvnas_of_ids,
|
||||
#ifdef CONFIG_PM
|
||||
.pm = NVCVNAS_PM_OPS,
|
||||
#endif
|
||||
},
|
||||
|
||||
.probe = nvcvnas_probe,
|
||||
.remove = nvcvnas_remove,
|
||||
.shutdown = nvcvnas_shutdown,
|
||||
};
|
||||
|
||||
static int __init nvcvnas_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&nvcvnas_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(nvcvnas_init);
|
||||
|
||||
static void __exit nvcvnas_exit(void)
|
||||
{
|
||||
}
|
||||
module_exit(nvcvnas_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -11,9 +11,6 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#ifdef CONFIG_TEGRA_CVNAS
|
||||
#include <linux/cvnas.h>
|
||||
#endif
|
||||
|
||||
#include "nvdla_buffer.h"
|
||||
|
||||
@@ -108,10 +105,6 @@ static int nvdla_buffer_map(struct platform_device *pdev,
|
||||
struct nvdla_mem_share_handle *desc,
|
||||
struct nvdla_vm_buffer *vm)
|
||||
{
|
||||
#ifdef CONFIG_TEGRA_CVNAS
|
||||
const dma_addr_t cvnas_begin = nvcvnas_get_cvsram_base();
|
||||
const dma_addr_t cvnas_end = cvnas_begin + nvcvnas_get_cvsram_size();
|
||||
#endif
|
||||
struct dma_buf_attachment *attach;
|
||||
struct dma_buf *dmabuf;
|
||||
struct sg_table *sgt;
|
||||
@@ -152,21 +145,6 @@ static int nvdla_buffer_map(struct platform_device *pdev,
|
||||
|
||||
phys_addr = sg_phys(sgt->sgl);
|
||||
dma_addr = sg_dma_address(sgt->sgl);
|
||||
|
||||
#ifdef CONFIG_TEGRA_CVNAS
|
||||
/* Determine the heap */
|
||||
if (phys_addr >= cvnas_begin && phys_addr < cvnas_end)
|
||||
vm->heap = NVDLA_BUFFERS_HEAP_CVNAS;
|
||||
else
|
||||
vm->heap = NVDLA_BUFFERS_HEAP_DRAM;
|
||||
|
||||
/*
|
||||
* If dma address is not available or heap is in CVNAS, use the
|
||||
* physical address.
|
||||
*/
|
||||
if (!dma_addr || vm->heap == NVDLA_BUFFERS_HEAP_CVNAS)
|
||||
dma_addr = phys_addr;
|
||||
#else
|
||||
vm->heap = NVDLA_BUFFERS_HEAP_DRAM;
|
||||
|
||||
/*
|
||||
@@ -174,7 +152,6 @@ static int nvdla_buffer_map(struct platform_device *pdev,
|
||||
*/
|
||||
if (!dma_addr)
|
||||
dma_addr = phys_addr;
|
||||
#endif
|
||||
|
||||
vm->sgt = sgt;
|
||||
vm->handle = desc->share_id;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
enum nvdla_buffers_heap {
|
||||
NVDLA_BUFFERS_HEAP_DRAM = 0,
|
||||
NVDLA_BUFFERS_HEAP_CVNAS
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#ifdef CONFIG_TEGRA_CVNAS
|
||||
#include <linux/cvnas.h>
|
||||
#endif
|
||||
#include <linux/nvhost.h>
|
||||
|
||||
#include "pva.h"
|
||||
@@ -230,10 +227,6 @@ nvpva_buffer_map(struct platform_device *pdev,
|
||||
|
||||
struct nvhost_device_data *pdata = platform_get_drvdata(pdev);
|
||||
struct pva *pva = pdata->private_data;
|
||||
#ifdef CONFIG_TEGRA_CVNAS
|
||||
const dma_addr_t cvnas_begin = nvcvnas_get_cvsram_base();
|
||||
const dma_addr_t cvnas_end = cvnas_begin + nvcvnas_get_cvsram_size();
|
||||
#endif
|
||||
struct dma_buf_attachment *attach;
|
||||
struct sg_table *sgt;
|
||||
dma_addr_t dma_addr;
|
||||
@@ -263,21 +256,6 @@ nvpva_buffer_map(struct platform_device *pdev,
|
||||
|
||||
phys_addr = sg_phys(sgt->sgl);
|
||||
dma_addr = sg_dma_address(sgt->sgl);
|
||||
|
||||
#ifdef CONFIG_TEGRA_CVNAS
|
||||
/* Determine the heap */
|
||||
if (phys_addr >= cvnas_begin && phys_addr < cvnas_end)
|
||||
vm->heap = NVPVA_BUFFERS_HEAP_CVNAS;
|
||||
else
|
||||
vm->heap = NVPVA_BUFFERS_HEAP_DRAM;
|
||||
|
||||
/*
|
||||
* If dma address is not available or heap is in CVNAS, use the
|
||||
* physical address.
|
||||
*/
|
||||
if (!dma_addr || vm->heap == NVPVA_BUFFERS_HEAP_CVNAS)
|
||||
dma_addr = phys_addr;
|
||||
#else
|
||||
vm->heap = NVPVA_BUFFERS_HEAP_DRAM;
|
||||
|
||||
/*
|
||||
@@ -285,7 +263,6 @@ nvpva_buffer_map(struct platform_device *pdev,
|
||||
*/
|
||||
if (!dma_addr)
|
||||
dma_addr = phys_addr;
|
||||
#endif
|
||||
|
||||
vm->sgt = sgt;
|
||||
vm->attach = attach;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
enum nvpva_buffers_heap {
|
||||
NVPVA_BUFFERS_HEAP_DRAM = 0,
|
||||
NVPVA_BUFFERS_HEAP_CVNAS
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -221,10 +221,6 @@ ifeq ($(NVMAP_CONFIG_HANDLE_AS_ID),y)
|
||||
ccflags-y += -DNVMAP_CONFIG_HANDLE_AS_ID
|
||||
endif #NVMAP_CONFIG_HANDLE_AS_ID
|
||||
|
||||
ifeq ($(CONFIG_TEGRA_CVNAS),y)
|
||||
ccflags-y += -DCVNAS_BUILTIN
|
||||
endif #CONFIG_TEGRA_CVNAS
|
||||
|
||||
ifeq ($(NVMAP_CONFIG_CACHE_FLUSH_AT_ALLOC),y)
|
||||
ccflags-y += -DNVMAP_CONFIG_CACHE_FLUSH_AT_ALLOC
|
||||
endif #NVMAP_CONFIG_CACHE_FLUSH_AT_ALLOC
|
||||
|
||||
@@ -32,11 +32,6 @@
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#ifdef CVNAS_BUILTIN
|
||||
#include <linux/cvnas.h>
|
||||
#include <linux/nvmap_t19x.h>
|
||||
#endif /* CVNAS_BUILTIN */
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
|
||||
#include <soc/tegra/chip-id.h>
|
||||
#else
|
||||
@@ -1492,21 +1487,6 @@ int __init nvmap_probe(struct platform_device *pdev)
|
||||
if (e)
|
||||
goto fail_heaps;
|
||||
|
||||
#ifdef CVNAS_BUILTIN
|
||||
if (tegra_get_chip_id() == TEGRA194) {
|
||||
phys_addr_t cvs_base = nvcvnas_get_cvsram_base();
|
||||
size_t cvs_size = nvcvnas_get_cvsram_size();
|
||||
|
||||
e = nvmap_register_cvsram_carveout(NULL,
|
||||
cvs_base, cvs_size,
|
||||
nvcvnas_busy, nvcvnas_idle);
|
||||
if (e) {
|
||||
dev_err(&pdev->dev, "failed to register cvsram carveout\n");
|
||||
goto fail_sci_ipc;
|
||||
}
|
||||
}
|
||||
#endif /* CVNAS_BUILTIN */
|
||||
|
||||
e = misc_register(&dev->dev_user);
|
||||
if (e) {
|
||||
dev_err(&pdev->dev, "unable to register miscdevice %s\n",
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2023, NVIDIA Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_CVNAS_H
|
||||
#define __LINUX_CVNAS_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#if defined(CONFIG_TEGRA_CVNAS) || defined(CVNAS_MODULE)
|
||||
int nvcvnas_busy(void);
|
||||
int nvcvnas_busy_no_rpm(void);
|
||||
int nvcvnas_idle(void);
|
||||
int nvcvnas_idle_no_rpm(void);
|
||||
int is_nvcvnas_probed(void);
|
||||
phys_addr_t nvcvnas_get_cvsram_base(void);
|
||||
size_t nvcvnas_get_cvsram_size(void);
|
||||
int is_nvcvnas_clk_enabled(void);
|
||||
#else
|
||||
static inline int nvcvnas_busy(void) { return 0; }
|
||||
static inline int nvcvnas_busy_no_rpm(void) { return 0; }
|
||||
static inline int nvcvnas_idle(void) { return 0; }
|
||||
static inline int nvcvnas_idle_no_rpm(void) { return 0; }
|
||||
static inline int is_nvcvnas_probed(void) { return 0; }
|
||||
static inline phys_addr_t nvcvnas_get_cvsram_base(void) { return 0; }
|
||||
static inline size_t nvcvnas_get_cvsram_size(void) { return 0; }
|
||||
static inline int is_nvcvnas_clk_enabled(void) { return 0; }
|
||||
#endif /* CONFIG_TEGRA_CVNAS || CVNAS_MODULE */
|
||||
|
||||
#endif /* __LINUX_CVNAS_H */
|
||||
Reference in New Issue
Block a user