mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-24 02:01:36 +03:00
In function nvcvnas_power_off casting nvcvsram_readl from unsigned int to int without checking its value may result in lost or misinterpreted data. Fix this by changing data type of val to u32. CID 427418 Bug 3512545 Change-Id: I8be65b1ca73270a4a0d76380207078da745c7057 Signed-off-by: Sagar Gulab Patil <sagargulabp@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2747340 Reviewed-by: Sachin Nikam <snikam@nvidia.com>
848 lines
20 KiB
C
848 lines
20 KiB
C
/*
|
|
* 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;
|
|
int 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");
|