diff --git a/drivers/platform/tegra/cvnas/Makefile b/drivers/platform/tegra/cvnas/Makefile index 37a9470b..48ccb0a4 100644 --- a/drivers/platform/tegra/cvnas/Makefile +++ b/drivers/platform/tegra/cvnas/Makefile @@ -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 diff --git a/drivers/platform/tegra/cvnas/cvnas.c b/drivers/platform/tegra/cvnas/cvnas.c new file mode 100644 index 00000000..e4e0e8ab --- /dev/null +++ b/drivers/platform/tegra/cvnas/cvnas.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE +#include +#endif +#include +#include + +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"); diff --git a/include/linux/cvnas.h b/include/linux/cvnas.h new file mode 100644 index 00000000..956b993b --- /dev/null +++ b/include/linux/cvnas.h @@ -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 + +#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 */