soc: tegra: pmc: add r2p support

This commit is contained in:
azkali
2023-08-28 13:50:20 +02:00
committed by Thomas Makin
parent 718ac118de
commit 263000591f
5 changed files with 466 additions and 9 deletions

View File

@@ -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.

View File

@@ -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

409
drivers/soc/tegra/pmc-r2p.c Normal file
View File

@@ -0,0 +1,409 @@
/*
* Copyright (c) 2019, Ezekiel Bethel <zek@9net.org>
* Copyright (c) 2021-2022, CTCaer <ctcaer@gmail.com>
*
* 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/arm-smccc.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <soc/tegra/pmc.h>
#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);

View File

@@ -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[] = {

View File

@@ -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__ */