tegrakernel/kernel/nvidia/drivers/platform/tegra/tegra-roc.c

135 lines
3.3 KiB
C

/*
* Copyright (c) 2015-2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/sched.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 13, 0)
#include <linux/sched/signal.h>
#endif
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/tegra-mce.h>
#include <linux/tegra-roc.h>
#include <asm/cputype.h>
#define MCE_FLUSH_CTRL 0x0
#define MCE_FLUSH_ADDR_MATCH 0x4
#define MCE_FLUSH_ADDR_MASK 0x8
#define FLUSH_ADDR_MATCH_ALL (BIT(29) - 1)
#define FLUSH_ADDR_MASK_NULL 0x0
#define FLUSH_CTRL_CLEANINV 0x0
#define FLUSH_CTRL_DONE_MASK BIT(3)
#define FLUSH_CTRL_PEND_MASK BIT(2)
#define FLUSH_CTRL_ABORT BIT(1)
#define FLUSH_CTLR_TRIGGER BIT(0)
#define FLUSH_POLL_DELAY 1 /* 1ms */
static void __iomem *flush_base;
static DEFINE_MUTEX(flush_lock);
/**
* Flush cache of the whole CCPLEX.
*
* Needed before/after large DMA transfers with non-snooping devices.
* This function will sleep if there is outstanding flush operation.
*
* Returns 0 if success.
*/
int tegra_roc_flush_cache_all(void)
{
u32 ctrl;
int ret = 0;
if (!flush_base) {
pr_err("tegra-roc-flush: driver is not ready yet.\n");
return -EINVAL;
}
might_sleep();
if (mutex_lock_interruptible(&flush_lock))
return -ERESTARTSYS;
/* Disable filtering */
writel(FLUSH_ADDR_MATCH_ALL, flush_base + MCE_FLUSH_ADDR_MATCH);
writel(FLUSH_ADDR_MASK_NULL, flush_base + MCE_FLUSH_ADDR_MASK);
ctrl = readl(flush_base + MCE_FLUSH_CTRL);
if (unlikely(ctrl & FLUSH_CTRL_PEND_MASK)) {
pr_err("tegra-roc-flush: flush is pending.\n");
ret = -EINVAL;
goto unlock;
}
ctrl = FLUSH_CTRL_CLEANINV | FLUSH_CTLR_TRIGGER;
writel(ctrl, flush_base + MCE_FLUSH_CTRL);
for (;;) {
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto unlock;
}
ctrl = readl(flush_base + MCE_FLUSH_CTRL);
if (ctrl & FLUSH_CTRL_DONE_MASK)
break;
mdelay(FLUSH_POLL_DELAY);
}
unlock:
mutex_unlock(&flush_lock);
return ret;
}
EXPORT_SYMBOL(tegra_roc_flush_cache_all);
static const struct of_device_id tegra_roc_flush_of_match[] = {
{ .compatible = "nvidia,tegra186-roc-flush", },
{},
};
static __init int tegra_roc_flush_init(void)
{
struct device_node *np;
u32 cpuid;
np = of_find_compatible_node(NULL, NULL,
tegra_roc_flush_of_match[0].compatible);
if (!np) {
cpuid = read_cpuid_id() & MIDR_CPU_MODEL_MASK;
if (cpuid == MIDR_NVIDIA_DENVER)
pr_err("tegra-roc-flush: DT required.\n");
return -EINVAL;
}
flush_base = of_iomap(np, 0);
if (!flush_base) {
pr_err("tegra-roc-flush: failed to map device.\n");
return -EINVAL;
}
of_node_put(np);
return 0;
}
early_initcall(tegra_roc_flush_init);