ARM: tegra: Support TZ-only access to PMC
authorThierry Reding <treding@nvidia.com>
Mon, 15 Apr 2019 09:32:25 +0000 (11:32 +0200)
committerTom Warren <twarren@nvidia.com>
Wed, 5 Jun 2019 16:16:34 +0000 (09:16 -0700)
Some devices may restrict access to the PMC to TrustZone software only.
Non-TZ software can detect this and use SMC calls to the firmware that
runs in the TrustZone to perform accesses to PMC registers.

Note that this also fixes reset_cpu() and the enterrcm command on
Tegra186 where they were previously trying to access the PMC at a wrong
physical address.

Based on work by Kalyani Chidambaram <kalyanic@nvidia.com> and Tom
Warren <twarren@nvidia.com>.

Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Tom Warren <twarren@nvidia.com>
arch/arm/include/asm/arch-tegra/pmc.h
arch/arm/include/asm/arch-tegra/tegra.h
arch/arm/mach-tegra/Kconfig
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/clock.c
arch/arm/mach-tegra/cmd_enterrcm.c
arch/arm/mach-tegra/cpu.c
arch/arm/mach-tegra/lowlevel_init.S [deleted file]
arch/arm/mach-tegra/pmc.c [new file with mode: 0644]
arch/arm/mach-tegra/powergate.c

index 34bbe75d5fdb1b7ca4f6aa902af3f6882e66bc4a..1524bf29116433f3ff8e126d338f51acddacc897 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0+ */
 /*
- *  (C) Copyright 2010-2015
+ *  (C) Copyright 2010-2019
  *  NVIDIA Corporation <www.nvidia.com>
  */
 
@@ -388,4 +388,22 @@ struct pmc_ctlr {
 /* APBDEV_PMC_CNTRL2_0 0x440 */
 #define HOLD_CKE_LOW_EN                                (1 << 12)
 
+/* PMC read/write functions */
+u32 tegra_pmc_readl(unsigned long offset);
+void tegra_pmc_writel(u32 value, unsigned long offset);
+
+#define PMC_CNTRL              0x0
+#define  PMC_CNTRL_MAIN_RST    BIT(4)
+
+#if IS_ENABLED(CONFIG_TEGRA186)
+#  define PMC_SCRATCH0 0x32000
+#else
+#  define PMC_SCRATCH0 0x00050
+#endif
+
+/* for secure PMC */
+#define TEGRA_SMC_PMC          0xc2fffe00
+#define  TEGRA_SMC_PMC_READ    0xaa
+#define  TEGRA_SMC_PMC_WRITE   0xbb
+
 #endif /* PMC_H */
index 7ae0129e2db3d65b08cae7ba7b23849779658ce3..7a4e0972fb76f4d56de28247b6f6eee547465af5 100644 (file)
 #define NV_PA_SLINK5_BASE      (NV_PA_APB_MISC_BASE + 0xDC00)
 #define NV_PA_SLINK6_BASE      (NV_PA_APB_MISC_BASE + 0xDE00)
 #define TEGRA_DVC_BASE         (NV_PA_APB_MISC_BASE + 0xD000)
+#if defined(CONFIG_TEGRA20) || defined(CONFIG_TEGRA30) || \
+       defined(CONFIG_TEGRA114) || defined(CONFIG_TEGRA124) || \
+       defined(CONFIG_TEGRA132) || defined(CONFIG_TEGRA210)
 #define NV_PA_PMC_BASE         (NV_PA_APB_MISC_BASE + 0xE400)
+#else
+#define NV_PA_PMC_BASE         0xc360000
+#endif
 #define NV_PA_EMC_BASE         (NV_PA_APB_MISC_BASE + 0xF400)
 #define NV_PA_FUSE_BASE                (NV_PA_APB_MISC_BASE + 0xF800)
 #if defined(CONFIG_TEGRA20) || defined(CONFIG_TEGRA30) || \
index db9198348d3f22eb5c365ca64171c080d159204e..28914a34a1b5456b97923a29d1b1dee3614c2bdf 100644 (file)
@@ -35,6 +35,10 @@ config TEGRA_PINCTRL
 config TEGRA_PMC
        bool
 
+config TEGRA_PMC_SECURE
+       bool
+       depends on TEGRA_PMC
+
 config TEGRA_COMMON
        bool "Tegra common options"
        select BINMAN
@@ -127,6 +131,7 @@ config TEGRA210
        select TEGRA_NO_BPMP
        select TEGRA_PINCTRL
        select TEGRA_PMC
+       select TEGRA_PMC_SECURE
 
 config TEGRA186
        bool "Tegra186 family"
index 517be21ee5f5dbbdf1a00fad819a548d37da6d12..f8bc65aa8b184b66381702c46ee7210719d9564b 100644 (file)
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0+
 #
-# (C) Copyright 2010-2015 Nvidia Corporation.
+# (C) Copyright 2010-2019 Nvidia Corporation.
 #
 # (C) Copyright 2000-2008
 # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
@@ -27,11 +27,11 @@ obj-y += dt-setup.o
 obj-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o
 obj-$(CONFIG_TEGRA_GPU) += gpu.o
 obj-$(CONFIG_TEGRA_IVC) += ivc.o
-obj-y += lowlevel_init.o
 ifndef CONFIG_SPL_BUILD
 obj-$(CONFIG_ARMV7_PSCI) += psci.o
 endif
 obj-$(CONFIG_DISPLAY_CPUINFO) += sys_info.o
+obj-y += pmc.o
 
 obj-$(CONFIG_TEGRA20) += tegra20/
 obj-$(CONFIG_TEGRA30) += tegra30/
index dc5f16b41b60fa03737829751316a7dc7858b101..e539ad8b30a7bbe532e267cd2eb0679252e04a0c 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (c) 2010-2015, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (c) 2010-2019, NVIDIA CORPORATION.  All rights reserved.
  */
 
 /* Tegra SoC common clock control functions */
@@ -815,11 +815,16 @@ void tegra30_set_up_pllp(void)
 
 int clock_external_output(int clk_id)
 {
-       struct pmc_ctlr *pmc = (struct pmc_ctlr *)NV_PA_PMC_BASE;
+       u32 val;
 
        if (clk_id >= 1 && clk_id <= 3) {
-               setbits_le32(&pmc->pmc_clk_out_cntrl,
-                            1 << (2 + (clk_id - 1) * 8));
+               val = tegra_pmc_readl(offsetof(struct pmc_ctlr,
+                                     pmc_clk_out_cntrl));
+               val |= 1 << (2 + (clk_id - 1) * 8);
+               tegra_pmc_writel(val,
+                                offsetof(struct pmc_ctlr,
+                                pmc_clk_out_cntrl));
+
        } else {
                printf("%s: Unknown output clock id %d\n", __func__, clk_id);
                return -EINVAL;
index 4e6beb3e5bb472b11bf27b11704d91b401b56737..4a889f0e3422fc1261f37bc37c7a52161cc32d26 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
- * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (c) 2012-2019, NVIDIA CORPORATION. All rights reserved.
  *
  * Derived from code (arch/arm/lib/reset.c) that is:
  *
 static int do_enterrcm(cmd_tbl_t *cmdtp, int flag, int argc,
                       char * const argv[])
 {
-       struct pmc_ctlr *pmc = (struct pmc_ctlr *)NV_PA_PMC_BASE;
-
        puts("Entering RCM...\n");
        udelay(50000);
 
-       pmc->pmc_scratch0 = 2;
+       tegra_pmc_writel(2, PMC_SCRATCH0);
        disable_interrupts();
        reset_cpu(0);
 
index 1b6ad074ed8fcd7fd9a729445bcc4e67081fa3e4..3d140760e68fed6b52a9a45f7e76b6d601c936c5 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (c) 2010-2015, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (c) 2010-2019, NVIDIA CORPORATION.  All rights reserved.
  */
 
 #include <common.h>
@@ -299,21 +299,19 @@ void enable_cpu_clock(int enable)
 
 static int is_cpu_powered(void)
 {
-       struct pmc_ctlr *pmc = (struct pmc_ctlr *)NV_PA_PMC_BASE;
-
-       return (readl(&pmc->pmc_pwrgate_status) & CPU_PWRED) ? 1 : 0;
+       return (tegra_pmc_readl(offsetof(struct pmc_ctlr,
+                               pmc_pwrgate_status)) & CPU_PWRED) ? 1 : 0;
 }
 
 static void remove_cpu_io_clamps(void)
 {
-       struct pmc_ctlr *pmc = (struct pmc_ctlr *)NV_PA_PMC_BASE;
        u32 reg;
        debug("%s entry\n", __func__);
 
        /* Remove the clamps on the CPU I/O signals */
-       reg = readl(&pmc->pmc_remove_clamping);
+       reg = tegra_pmc_readl(offsetof(struct pmc_ctlr, pmc_remove_clamping));
        reg |= CPU_CLMP;
-       writel(reg, &pmc->pmc_remove_clamping);
+       tegra_pmc_writel(reg, offsetof(struct pmc_ctlr, pmc_remove_clamping));
 
        /* Give I/O signals time to stabilize */
        udelay(IO_STABILIZATION_DELAY);
@@ -321,17 +319,19 @@ static void remove_cpu_io_clamps(void)
 
 void powerup_cpu(void)
 {
-       struct pmc_ctlr *pmc = (struct pmc_ctlr *)NV_PA_PMC_BASE;
        u32 reg;
        int timeout = IO_STABILIZATION_DELAY;
        debug("%s entry\n", __func__);
 
        if (!is_cpu_powered()) {
                /* Toggle the CPU power state (OFF -> ON) */
-               reg = readl(&pmc->pmc_pwrgate_toggle);
+               reg = tegra_pmc_readl(offsetof(struct pmc_ctlr,
+                                     pmc_pwrgate_toggle));
                reg &= PARTID_CP;
                reg |= START_CP;
-               writel(reg, &pmc->pmc_pwrgate_toggle);
+               tegra_pmc_writel(reg,
+                                offsetof(struct pmc_ctlr,
+                                pmc_pwrgate_toggle));
 
                /* Wait for the power to come up */
                while (!is_cpu_powered()) {
diff --git a/arch/arm/mach-tegra/lowlevel_init.S b/arch/arm/mach-tegra/lowlevel_init.S
deleted file mode 100644 (file)
index 626f1b6..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * SoC-specific setup info
- *
- * (C) Copyright 2010,2011
- * NVIDIA Corporation <www.nvidia.com>
- */
-
-#include <config.h>
-#include <linux/linkage.h>
-
-#ifdef CONFIG_ARM64
-       .align  5
-ENTRY(reset_cpu)
-       /* get address for global reset register */
-       ldr     x1, =PRM_RSTCTRL
-       ldr     w3, [x1]
-       /* force reset */
-       orr     w3, w3, #0x10
-       str     w3, [x1]
-       mov     w0, w0
-1:
-       b       1b
-ENDPROC(reset_cpu)
-#else
-       .align  5
-ENTRY(reset_cpu)
-       ldr     r1, rstctl                      @ get addr for global reset
-                                               @ reg
-       ldr     r3, [r1]
-       orr     r3, r3, #0x10
-       str     r3, [r1]                        @ force reset
-       mov     r0, r0
-_loop_forever:
-       b       _loop_forever
-rstctl:
-       .word   PRM_RSTCTRL
-ENDPROC(reset_cpu)
-#endif
diff --git a/arch/arm/mach-tegra/pmc.c b/arch/arm/mach-tegra/pmc.c
new file mode 100644 (file)
index 0000000..afd3c54
--- /dev/null
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <common.h>
+
+#include <linux/arm-smccc.h>
+
+#include <asm/io.h>
+#include <asm/arch-tegra/pmc.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#if IS_ENABLED(CONFIG_TEGRA_PMC_SECURE)
+static bool tegra_pmc_detect_tz_only(void)
+{
+       static bool initialized = false;
+       static bool is_tz_only = false;
+       u32 value, saved;
+
+       if (!initialized) {
+               saved = readl(NV_PA_PMC_BASE + PMC_SCRATCH0);
+               value = saved ^ 0xffffffff;
+
+               if (value == 0xffffffff)
+                       value = 0xdeadbeef;
+
+               /* write pattern and read it back */
+               writel(value, NV_PA_PMC_BASE + PMC_SCRATCH0);
+               value = readl(NV_PA_PMC_BASE + PMC_SCRATCH0);
+
+               /* if we read all-zeroes, access is restricted to TZ only */
+               if (value == 0) {
+                       debug("access to PMC is restricted to TZ\n");
+                       is_tz_only = true;
+               } else {
+                       /* restore original value */
+                       writel(saved, NV_PA_PMC_BASE + PMC_SCRATCH0);
+               }
+
+               initialized = true;
+       }
+
+       return is_tz_only;
+}
+#endif
+
+uint32_t tegra_pmc_readl(unsigned long offset)
+{
+#if IS_ENABLED(CONFIG_TEGRA_PMC_SECURE)
+       if (tegra_pmc_detect_tz_only()) {
+               struct arm_smccc_res res;
+
+               arm_smccc_smc(TEGRA_SMC_PMC, TEGRA_SMC_PMC_READ, offset, 0, 0,
+                             0, 0, 0, &res);
+               if (res.a0)
+                       printf("%s(): SMC failed: %lu\n", __func__, res.a0);
+
+               return res.a1;
+       }
+#endif
+
+       return readl(NV_PA_PMC_BASE + offset);
+}
+
+void tegra_pmc_writel(u32 value, unsigned long offset)
+{
+#if IS_ENABLED(CONFIG_TEGRA_PMC_SECURE)
+       if (tegra_pmc_detect_tz_only()) {
+               struct arm_smccc_res res;
+
+               arm_smccc_smc(TEGRA_SMC_PMC, TEGRA_SMC_PMC_WRITE, offset,
+                             value, 0, 0, 0, 0, &res);
+               if (res.a0)
+                       printf("%s(): SMC failed: %lu\n", __func__, res.a0);
+
+               return;
+       }
+#endif
+
+       writel(value, NV_PA_PMC_BASE + offset);
+}
+
+void reset_cpu(ulong addr)
+{
+       u32 value;
+
+       value = tegra_pmc_readl(PMC_CNTRL);
+       value |= PMC_CNTRL_MAIN_RST;
+       tegra_pmc_writel(value, PMC_CNTRL);
+}
index e45f0961b242cdd92c344bcd186583dc80131f5b..761c9ef19e3b3abc6481bc42a5f114912bf9758e 100644 (file)
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (c) 2014-2019, NVIDIA CORPORATION.  All rights reserved.
  */
 
 #include <common.h>
@@ -11,6 +11,7 @@
 
 #include <asm/arch/powergate.h>
 #include <asm/arch/tegra.h>
+#include <asm/arch-tegra/pmc.h>
 
 #define PWRGATE_TOGGLE 0x30
 #define  PWRGATE_TOGGLE_START (1 << 8)
@@ -24,18 +25,18 @@ static int tegra_powergate_set(enum tegra_powergate id, bool state)
        u32 value, mask = state ? (1 << id) : 0, old_mask;
        unsigned long start, timeout = 25;
 
-       value = readl(NV_PA_PMC_BASE + PWRGATE_STATUS);
+       value = tegra_pmc_readl(PWRGATE_STATUS);
        old_mask = value & (1 << id);
 
        if (mask == old_mask)
                return 0;
 
-       writel(PWRGATE_TOGGLE_START | id, NV_PA_PMC_BASE + PWRGATE_TOGGLE);
+       tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
 
        start = get_timer(0);
 
        while (get_timer(start) < timeout) {
-               value = readl(NV_PA_PMC_BASE + PWRGATE_STATUS);
+               value = tegra_pmc_readl(PWRGATE_STATUS);
                if ((value & (1 << id)) == mask)
                        return 0;
        }
@@ -69,7 +70,7 @@ static int tegra_powergate_remove_clamping(enum tegra_powergate id)
        else
                value = 1 << id;
 
-       writel(value, NV_PA_PMC_BASE + REMOVE_CLAMPING);
+       tegra_pmc_writel(value, REMOVE_CLAMPING);
 
        return 0;
 }