mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
Merge "cvnas: Integrate drivers from kernel/nvidia to kernel/nvidia-oot" into dev-main
This commit is contained in:
@@ -1,10 +1,24 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Tegra CVNAS Driver.
|
||||
#
|
||||
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
||||
#
|
||||
|
||||
# NOTE: Do not change or add anything in this makefile.
|
||||
# The source code and makefile rules are copied from the
|
||||
# kernel/nvidia/drivers/platform/tegra/cvnas. This file is
|
||||
# just place-holder for empty makefile to avoid any build
|
||||
# issue when copy is not done from command line and building
|
||||
# the tree independent of source copy.
|
||||
ccflags-y += -I$(srctree.nvidia)/include
|
||||
ccflags-y += -Werror
|
||||
|
||||
# Set config when build as OOT module.
|
||||
ifeq ($(CONFIG_TEGRA_OOT_MODULE),m)
|
||||
CONFIG_TEGRA_FUSE_UPSTREAM := y
|
||||
CONFIG_TEGRA_CVNAS := m
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_TEGRA_FUSE_UPSTREAM),y)
|
||||
ccflags-y += -DCONFIG_TEGRA_FUSE_UPSTREAM
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_TEGRA_CVNAS) += cvnas.o
|
||||
ifeq ($(CONFIG_TEGRA_CVNAS),m)
|
||||
ccflags-y += -DCVNAS_MODULE
|
||||
endif
|
||||
|
||||
847
drivers/platform/tegra/cvnas/cvnas.c
Normal file
847
drivers/platform/tegra/cvnas/cvnas.c
Normal file
@@ -0,0 +1,847 @@
|
||||
/*
|
||||
* drivers/platform/tegra/cvnas.c
|
||||
*
|
||||
* Copyright (C) 2017-2022, NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#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");
|
||||
45
include/linux/cvnas.h
Normal file
45
include/linux/cvnas.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* include/linux/cvnas.h
|
||||
*
|
||||
* Tegra cvnas driver
|
||||
*
|
||||
* Copyright (c) 2018-2022, NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#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