From 263000591f1fd6a614a5ee8e668b0cbad2fd61e3 Mon Sep 17 00:00:00 2001 From: azkali Date: Mon, 28 Aug 2023 13:50:20 +0200 Subject: [PATCH] soc: tegra: pmc: add r2p support --- drivers/soc/tegra/Kconfig | 22 ++ drivers/soc/tegra/Makefile | 1 + drivers/soc/tegra/pmc-r2p.c | 409 ++++++++++++++++++++++++++++++++++++ drivers/soc/tegra/pmc.c | 41 +++- include/soc/tegra/pmc.h | 2 + 5 files changed, 466 insertions(+), 9 deletions(-) create mode 100644 drivers/soc/tegra/pmc-r2p.c diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig index b5f674869fdb..441213470e8a 100644 --- a/drivers/soc/tegra/Kconfig +++ b/drivers/soc/tegra/Kconfig @@ -176,3 +176,25 @@ config SOC_TEGRA_CBB Support for handling error from Tegra Control Backbone(CBB). This driver handles the errors from CBB and prints debug information about the failed transactions. + +config TEGRA_124_R2P + bool "Tegra124 Reboot 2 Payload support" + depends on ARCH_TEGRA_124_SOC + select TEGRA_R2P + default n + help + This enables a special reboot path which allows the board to reboot + to a specified bootloader other than the one in boot storage. + + If in doubt, say N. + +config TEGRA_210_R2P + bool "Tegra210 Reboot 2 Payload support" + depends on ARCH_TEGRA_210_SOC + select TEGRA_R2P + default n + help + This enables a special reboot path which allows the board to reboot + to a specified bootloader other than the one in boot storage. + + If in doubt, say N. diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile index 01059619e764..16dc861fd237 100644 --- a/drivers/soc/tegra/Makefile +++ b/drivers/soc/tegra/Makefile @@ -3,6 +3,7 @@ obj-y += fuse/ obj-y += cbb/ obj-y += common.o +obj-$(CONFIG_ARCH_TEGRA_210_SOC) += pmc-r2p.o obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o diff --git a/drivers/soc/tegra/pmc-r2p.c b/drivers/soc/tegra/pmc-r2p.c new file mode 100644 index 000000000000..dcd40f1c74ae --- /dev/null +++ b/drivers/soc/tegra/pmc-r2p.c @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2019, Ezekiel Bethel + * Copyright (c) 2021-2022, CTCaer + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEGRA_SIP_R2P_DO_REBOOT 0xC2FFFE03u +#define TEGRA_SIP_R2P_SET_BIN_CFG 0xC2FFFE04u + +#define IRAM_PAYLOAD_BASE 0x40010000u +#define IRAM_CHUNK_SIZE 0x4000 + +#define RTC_REBOOT_REASON_MAGIC 0x77 + +/* hekate/Nyx */ +#define BOOT_CFG_AUTOBOOT_EN BIT(0) +#define BOOT_CFG_FROM_ID BIT(2) + +#define EXTRA_CFG_NYX_UMS BIT(5) +#define EXTRA_CFG_NYX_RELOAD BIT(6) + +enum { + NYX_UMS_SD_CARD = 0, + NYX_UMS_EMMC_BOOT0, + NYX_UMS_EMMC_BOOT1, + NYX_UMS_EMMC_GPP, + NYX_UMS_EMUMMC_BOOT0, + NYX_UMS_EMUMMC_BOOT1, + NYX_UMS_EMUMMC_GPP +}; + +enum { + REBOOT_REASON_NOP = 0, /* Use [config]. */ + REBOOT_REASON_SELF = 1, /* Use autoboot_idx/autoboot_list. */ + REBOOT_REASON_MENU = 2, /* Force menu. */ + REBOOT_REASON_UMS = 3, /* Force selected UMS partition. */ + REBOOT_REASON_REC = 4, /* Set PMC_SCRATCH0_MODE_RECOVERY and reboot to self. */ + REBOOT_REASON_PANIC = 5 /* Inform bootloader that panic occured if T210B01. */ +}; + +struct __attribute__((__packed__)) hekate_boot_cfg +{ + u8 boot_cfg; + u8 autoboot; + u8 autoboot_list; + u8 extra_cfg; + union { + struct { + char id[8]; + char emummc_path[0x78]; + }; + u8 ums; + u8 xt_str[0x80]; + }; +}; + +struct rtc_rr_dec +{ + u16 reason:4; + u16 autoboot_idx:4; + u16 autoboot_list:1; + u16 ums_idx:3; +}; + +struct rtc_rr_enc +{ + u16 val1:6; /* 6-bit reg. */ + u16 val2:6; /* 6-bit reg. */ +}; + +struct rtc_reboot_reason +{ + union { + struct rtc_rr_dec dec; + struct rtc_rr_enc enc; + }; +}; + +struct reboot_driver_state +{ + char action[16]; + int param1; + int param2; + char entry_id[8]; +}; + +static int enabled = 0; +static int param1 = 0; +static int param2 = 0; +static char* action = NULL; +static char* entry_id = NULL; + +module_param(enabled, int, 0664); +module_param(action, charp, 0664); +module_param(param1, int, 0664); +module_param(param2, int, 0664); +module_param(entry_id, charp, 0664); + +MODULE_PARM_DESC(enabled, "Enable Reboot 2 Payload function"); +MODULE_PARM_DESC(action, "Reboot action to take"); +MODULE_PARM_DESC(param1, "Autoboot entry or UMS device index"); +MODULE_PARM_DESC(param2, "Autooboot entry is ini list"); +MODULE_PARM_DESC(entry_id, "Autoboot entry id"); + +struct platform_device *r2p_device = NULL; + +static void tegra_pmc_r2p_set_cfg(struct hekate_boot_cfg *hekate_bcfg) +{ + struct arm_smccc_res res; + u64 hekate_boot_smc_cfg[4]; + + memcpy(hekate_boot_smc_cfg, hekate_bcfg, sizeof(hekate_boot_smc_cfg)); + + arm_smccc_smc(TEGRA_SIP_R2P_SET_BIN_CFG, + hekate_boot_smc_cfg[0], + hekate_boot_smc_cfg[1], + hekate_boot_smc_cfg[2], + hekate_boot_smc_cfg[3], + 0, 0, 0, &res); + + if (res.a0) { + pr_err("%s: SMC failed (ret=%ld)\n", __func__, res.a0); + WARN_ON(1); + } +} + +void tegra_pmc_r2p_setup(const char *cmd, bool panic_occurred) +{ + struct rtc_reboot_reason rr = {}; + struct hekate_boot_cfg hekate_bcfg = {}; + struct reboot_driver_state *state; + + uint32_t reboot_reason = REBOOT_REASON_NOP; + + if (r2p_device == NULL) + return; + + state = dev_get_drvdata(&r2p_device->dev); + + /* Linux reboot reason */ + if (cmd) { + if (!strcmp(cmd, "recovery")) { + reboot_reason = REBOOT_REASON_REC; + } else if (!strcmp(cmd, "bootloader")) { + reboot_reason = REBOOT_REASON_MENU; + } else { + dev_err(&r2p_device->dev, + "Command '%s' not recognized\n", cmd); + } + } + + /* Missing reboot reason or not matching, use r2p action. */ + if (reboot_reason == REBOOT_REASON_NOP && strlen(state->action)) { + if (!strcmp(state->action, "self") || + !strcmp(state->action, "via-payload")) { /* Deprecated */ + reboot_reason = REBOOT_REASON_SELF; + } else if (!strcmp(state->action, "bootloader")) { + reboot_reason = REBOOT_REASON_MENU; + } else if (!strcmp(state->action, "ums")) { + reboot_reason = REBOOT_REASON_UMS; + } else if (!strcmp(state->action, "normal")) { + reboot_reason = REBOOT_REASON_NOP; + } else { + dev_err(&r2p_device->dev, + "Action '%s' not recognized\n", state->action); + } + } + + /* Prepare boot config data */ + switch (reboot_reason) { + case REBOOT_REASON_NOP: + break; + case REBOOT_REASON_REC: + case REBOOT_REASON_SELF: + hekate_bcfg.boot_cfg = BOOT_CFG_AUTOBOOT_EN; + if (strlen(state->entry_id) != 0) { + hekate_bcfg.boot_cfg |= BOOT_CFG_FROM_ID; + strcpy(hekate_bcfg.id, state->entry_id); + } + hekate_bcfg.autoboot = state->param1; + hekate_bcfg.autoboot_list = state->param2; + + rr.dec.reason = reboot_reason; + rr.dec.autoboot_idx = state->param1; + rr.dec.autoboot_list = state->param2; + break; + case REBOOT_REASON_MENU: + hekate_bcfg.boot_cfg = BOOT_CFG_AUTOBOOT_EN; + + rr.dec.reason = reboot_reason; + break; + case REBOOT_REASON_UMS: + hekate_bcfg.boot_cfg = BOOT_CFG_AUTOBOOT_EN; + hekate_bcfg.extra_cfg = EXTRA_CFG_NYX_UMS; + hekate_bcfg.ums = state->param1; + + rr.dec.reason = reboot_reason; + rr.dec.ums_idx = state->param1; + break; + case REBOOT_REASON_PANIC: + rr.dec.reason = reboot_reason; + break; + } + + /* Notify TZ to use its preloaded payload */ + /* Set hekate boot config. */ + tegra_pmc_r2p_set_cfg(&hekate_bcfg); +} + +static ssize_t action_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + int res; + + if (strlen(state->action)) + res = sprintf(buf, "%s\n", state->action); + else + res = sprintf(buf, "Not-set\n"); + + return res; +} + +static ssize_t action_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + + if (count > sizeof(state->action) - 1) + return -EINVAL; + + strncpy(state->action, buf, sizeof(state->action) - 1); + state->action[sizeof(state->action) - 1] = 0; + + /* Strip newline if it exists */ + if (state->action[count - 1] == '\n') + state->action[count - 1] = 0; + + return count; +} + +static ssize_t entry_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + int res; + + if (strlen(state->entry_id)) + res = sprintf(buf, "%s\n", state->entry_id); + else + res = sprintf(buf, "Not-set\n"); + + return res; +} + +static ssize_t entry_id_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + + if (count > sizeof(state->entry_id) - 1) + return -EINVAL; + + strncpy(state->entry_id, buf, sizeof(state->entry_id) - 1); + state->entry_id[sizeof(state->entry_id) - 1] = 0; + + /* Strip newline if it exists */ + if (state->entry_id[count - 1] == '\n') + state->entry_id[count - 1] = 0; + + return count; +} + +static ssize_t param1_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + + return sprintf(buf, "%d\n", (int)state->param1); +} + +static ssize_t param1_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + ssize_t res; + long value; + + res = kstrtol(buf, 0, &value); + if (!res) + state->param1 = value; + + return res ? res : count; +} + +static ssize_t param2_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + + return sprintf(buf, "%d\n", (int)state->param2); +} + +static ssize_t param2_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct reboot_driver_state *state = dev_get_drvdata(&r2p_device->dev); + ssize_t res; + long value; + + res = kstrtol(buf, 0, &value); + if (!res) + state->param2 = value; + + return res ? res : count; +} + +static DEVICE_ATTR_RW(action); +static DEVICE_ATTR_RW(entry_id); +static DEVICE_ATTR_RW(param1); +static DEVICE_ATTR_RW(param2); + +static struct attribute *r2p_sysfs_attrs[] = { + &dev_attr_action.attr, + &dev_attr_entry_id.attr, + &dev_attr_param1.attr, + &dev_attr_param2.attr, + NULL, +}; +ATTRIBUTE_GROUPS(r2p_sysfs); + +static int tegra_pmc_r2p_driver_probe(struct platform_device *pdev) +{ + struct reboot_driver_state *state; + + if (!enabled) + return 0; + + r2p_device = pdev; + + state = devm_kzalloc(&pdev->dev, sizeof(struct reboot_driver_state), GFP_KERNEL); + + /* Copy reboot action if valid */ + if (action) { + strncpy(state->action, action, sizeof(state->action) - 1); + state->action[sizeof(state->action) - 1] = 0; + } + + /* Copy entry ID if valid */ + if (entry_id) { + strncpy(state->entry_id, entry_id, sizeof(state->entry_id) - 1); + state->entry_id[sizeof(state->entry_id) - 1] = 0; + } + + /* Set reboot reason parameters. */ + state->param1 = param1; + state->param2 = param2; + + dev_set_drvdata(&pdev->dev, state); + + if (sysfs_create_groups(&pdev->dev.kobj, r2p_sysfs_groups)) + dev_err(&pdev->dev, "sysfs creation failed\n"); + + return 0; +} + +static const struct of_device_id tegra_pmc_r2p_match[] = { + { .compatible = "tegra-r2p", }, + { } +}; + +static struct platform_driver tegra_pmc_r2p_driver = { + .probe = tegra_pmc_r2p_driver_probe, + .driver = { + .name = "tegra-r2p", + .owner = THIS_MODULE, + .of_match_table = tegra_pmc_r2p_match + }, +}; + +builtin_platform_driver(tegra_pmc_r2p_driver); diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 1cb474028ec5..93433846336b 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -94,12 +94,13 @@ #define PMC_PWR_DET 0x48 -#define PMC_SCRATCH0_MODE_RECOVERY BIT(31) -#define PMC_SCRATCH0_MODE_BOOTLOADER BIT(30) -#define PMC_SCRATCH0_MODE_RCM BIT(1) -#define PMC_SCRATCH0_MODE_MASK (PMC_SCRATCH0_MODE_RECOVERY | \ - PMC_SCRATCH0_MODE_BOOTLOADER | \ - PMC_SCRATCH0_MODE_RCM) +#define PMC_SCRATCH0_MODE_RECOVERY (1 << 31) +#define PMC_SCRATCH0_MODE_BOOTLOADER (1 << 30) +#define PMC_SCRATCH0_MODE_PAYLOAD (1 << 29) /* R2P custom mode. Deprecated */ +#define PMC_SCRATCH0_MODE_RCM (1 << 1) +#define PMC_SCRATCH0_MODE_MASK \ + (PMC_SCRATCH0_MODE_RECOVERY | PMC_SCRATCH0_MODE_BOOTLOADER | \ + PMC_SCRATCH0_MODE_RCM | PMC_SCRATCH0_MODE_PAYLOAD) #define PMC_CPUPWRGOOD_TIMER 0xc8 #define PMC_CPUPWROFF_TIMER 0xcc @@ -195,10 +196,12 @@ #define SW_WAKE_ID 83 /* wake83 */ /* for secure PMC */ -#define TEGRA_SMC_PMC 0xc2fffe00 +#define TEGRA_SMC_PMC 0xc2fffe00 #define TEGRA_SMC_PMC_READ 0xaa #define TEGRA_SMC_PMC_WRITE 0xbb +#define KERNEL_PANIC_MAGIC 0x4E415054 + struct pmc_clk { struct clk_hw hw; unsigned long offs; @@ -385,6 +388,7 @@ struct tegra_pmc_soc { bool has_usb_sleepwalk; bool supports_core_domain; bool has_single_mmio_aperture; + bool supports_r2p; }; /** @@ -1102,6 +1106,7 @@ int tegra_pmc_cpu_remove_clamping(unsigned int cpuid) static void tegra_pmc_program_reboot_reason(const char *cmd) { u32 value; + u32 panic_code = tegra_pmc_scratch_readl(pmc, PMC_SCRATCH37); value = tegra_pmc_scratch_readl(pmc, pmc->soc->regs->scratch0); value &= ~PMC_SCRATCH0_MODE_MASK; @@ -1110,13 +1115,21 @@ static void tegra_pmc_program_reboot_reason(const char *cmd) if (strcmp(cmd, "recovery") == 0) value |= PMC_SCRATCH0_MODE_RECOVERY; - if (strcmp(cmd, "bootloader") == 0) + else if (strcmp(cmd, "bootloader") == 0) value |= PMC_SCRATCH0_MODE_BOOTLOADER; - if (strcmp(cmd, "forced-recovery") == 0) + else if (strcmp(cmd, "forced-recovery") == 0) value |= PMC_SCRATCH0_MODE_RCM; } + /* Setup R2P for T124/T210/T210B01 */ + if (pmc->soc->supports_r2p) { + tegra_pmc_r2p_setup(cmd, panic_code == KERNEL_PANIC_MAGIC); + + /* Deprecated: Old TZ support. */ + value |= PMC_SCRATCH0_MODE_PAYLOAD; + } + tegra_pmc_scratch_writel(pmc, value, pmc->soc->regs->scratch0); } @@ -3564,6 +3577,9 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, +#ifdef CONFIG_TEGRA_124_R2P + .supports_r2p = true, +#endif }; static const char * const tegra210_powergates[] = { @@ -3728,6 +3744,10 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, +#ifdef CONFIG_TEGRA_210_R2P + .supports_r2p = true, +#endif + }; static const struct tegra_io_pad_soc tegra210b01_io_pads[] = { @@ -3844,6 +3864,9 @@ static const struct tegra_pmc_soc tegra210b01_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, +#ifdef CONFIG_TEGRA_210_R2P + .supports_r2p = true, +#endif }; static const struct tegra_io_pad_soc tegra186_io_pads[] = { diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index c545875d0ff1..12eca475c6b6 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -227,4 +227,6 @@ static inline enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) } #endif +void tegra_pmc_r2p_setup(const char *cmd, bool panic_occurred); + #endif /* __SOC_TEGRA_PMC_H__ */