/* * drivers/platform/tegra/cvnas.c * * Copyright (C) 2017-2018, 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 #include static int cvnas_debug; module_param(cvnas_debug, int, 0644); #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; int 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); } 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 nvcvnas_debugfs_init(struct cvnas_device *dev) { struct dentry *root; root = debugfs_create_dir("cvnas", NULL); if (!root) return PTR_ERR(root); 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; } 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; } 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) { int 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; } /* 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); phys_addr_t nvcvnas_get_cvsram_base(void) { struct cvnas_device *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 = 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; int ret; ret = kstrtoul(buf, 0, &max_rate); if (ret) return -EINVAL; ret = clk_set_max_rate(cvnas->clk, 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,tegra-cvnas", .data = (void *)false, }, { .compatible = "nvidia,tegra-cvnas-hv", .data = (void *)true, }, { } }; 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]; const struct of_device_id *match; if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA19 && 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; } match = of_match_device(nvcvnas_of_ids, &pdev->dev); if (match) cvnas_dev->virt = (bool)match->data; 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; 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; } dev_set_drvdata(&pdev->dev, cvnas_dev); /* TODO: Add interrupt handler */ return 0; err_cvsram_nvmap_heap_register: debugfs_remove(cvnas_dev->debugfs_root); 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(struct device *dev) { #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);