soc: tegra: pmc: add bootrom command support

This commit is contained in:
Azkali Manad
2025-02-16 17:31:19 +07:00
committed by Thomas Makin
parent 263000591f
commit 904fd8aaf2
2 changed files with 394 additions and 7 deletions

View File

@@ -133,6 +133,22 @@
#define PMC_RST_STATUS_LP0 4
#define PMC_RST_STATUS_AOTAG 5
/* Bootrom comand register */
#define PMC_REG_8bit_MASK 0xFF
#define PMC_REG_16bit_MASK 0xFFFF
#define PMC_BR_COMMAND_I2C_ADD_MASK 0x7F
#define PMC_BR_COMMAND_WR_COMMANDS_MASK 0x3F
#define PMC_BR_COMMAND_WR_COMMANDS_SHIFT 8
#define PMC_BR_COMMAND_OPERAND_SHIFT 15
#define PMC_BR_COMMAND_CSUM_MASK 0xFF
#define PMC_BR_COMMAND_CSUM_SHIFT 16
#define PMC_BR_COMMAND_PMUX_MASK 0x7
#define PMC_BR_COMMAND_PMUX_SHIFT 24
#define PMC_BR_COMMAND_CTRL_ID_MASK 0x7
#define PMC_BR_COMMAND_CTRL_ID_SHIFT 27
#define PMC_BR_COMMAND_CTRL_TYPE_SHIFT 30
#define PMC_BR_COMMAND_RST_EN_SHIFT 31
#define IO_DPD_REQ 0x1b8
#define IO_DPD_REQ_CODE_IDLE (0U << 30)
#define IO_DPD_REQ_CODE_OFF (1U << 30)
@@ -163,6 +179,9 @@
#define PMC_SCRATCH55_CHECKSUM_SHIFT 16
#define PMC_SCRATCH55_I2CSLV1_SHIFT 0
/* Scratch 250: Bootrom i2c command base */
#define PMC_BR_COMMAND_BASE 0x908
#define PMC_UTMIP_UHSIC_LINE_WAKEUP 0x26c
#define PMC_UTMIP_BIAS_MASTER_CNTRL 0x270
@@ -346,6 +365,7 @@ struct tegra_pmc_soc {
bool has_tsense_reset;
bool has_gpu_clamps;
bool has_bootrom_command;
bool needs_mbist_war;
bool has_impl_33v_pwr;
bool maybe_tz_only;
@@ -485,13 +505,37 @@ to_powergate(struct generic_pm_domain *domain)
return container_of(domain, struct tegra_powergate, genpd);
}
/* Bootrom commands structures */
struct tegra_bootrom_block {
const char *name;
int address;
bool reg_8bits;
bool data_8bits;
bool i2c_controller;
int controller_id;
bool enable_reset;
int ncommands;
u32 *commands;
};
struct tegra_bootrom_commands {
u32 command_retry_count;
u32 delay_between_commands;
u32 wait_before_bus_clear;
struct tegra_bootrom_block *blocks;
int nblocks;
};
static struct tegra_bootrom_commands *br_rst_commands;
static struct tegra_bootrom_commands *br_off_commands;
static u32 tegra_pmc_readl(struct tegra_pmc *pmc, unsigned long offset)
{
struct arm_smccc_res res;
if (pmc->tz_only) {
arm_smccc_smc(TEGRA_SMC_PMC, TEGRA_SMC_PMC_READ, offset, 0, 0,
0, 0, 0, &res);
arm_smccc_smc(TEGRA_SMC_PMC, TEGRA_SMC_PMC_READ, offset,
0, 0, 0, 0, 0, &res);
if (res.a0) {
if (pmc->dev)
dev_warn(pmc->dev, "%s(): SMC failed: %lu\n",
@@ -501,7 +545,7 @@ static u32 tegra_pmc_readl(struct tegra_pmc *pmc, unsigned long offset)
res.a0);
}
return res.a1;
return (u32)res.a1;
}
return readl(pmc->base + offset);
@@ -1133,6 +1177,324 @@ static void tegra_pmc_program_reboot_reason(const char *cmd)
tegra_pmc_scratch_writel(pmc, value, pmc->soc->regs->scratch0);
}
/* PMC Bootrom commands */
static int tegra_pmc_parse_bootrom_cmd(struct device *dev,
struct device_node *np,
struct tegra_bootrom_commands **br_cmds)
{
struct device_node *child;
struct tegra_bootrom_commands *bcommands;
int *command_ptr;
struct tegra_bootrom_block *block;
int nblocks;
u32 reg, data, pval;
u32 *wr_commands;
int count, nblock, ncommands, i, data_shift;
int ret;
int sz_bcommand, sz_blocks;
nblocks = of_get_available_child_count(np);
if (!nblocks) {
dev_info(dev, "PMC: No Bootrom Command\n");
return -ENOENT;
}
count = 0;
for_each_available_child_of_node(np, child) {
ret = of_property_count_u32_elems(child,
"nvidia,write-commands");
if (ret < 0) {
dev_err(dev, "PMC: Node %s does not have write-commnds\n",
child->full_name);
return -EINVAL;
}
count += ret / 2;
}
sz_bcommand = (sizeof(*bcommands) + 0x3) & ~0x3;
sz_blocks = (sizeof(*block) + 0x3) & ~0x3;
bcommands = devm_kzalloc(dev, sz_bcommand + nblocks * sz_blocks +
count * sizeof(u32), GFP_KERNEL);
if (!bcommands)
return -ENOMEM;
bcommands->nblocks = nblocks;
bcommands->blocks = (void *)bcommands + sz_bcommand;
command_ptr = (void *)bcommands->blocks + nblocks * sz_blocks;
of_property_read_u32(np, "nvidia,command-retries-count",
&bcommands->command_retry_count);
of_property_read_u32(np, "nvidia,delay-between-commands-us",
&bcommands->delay_between_commands);
ret = of_property_read_u32(np, "nvidia,wait-before-start-bus-clear-us",
&bcommands->wait_before_bus_clear);
if (ret < 0)
of_property_read_u32(np, "nvidia,wait-start-bus-clear-us",
&bcommands->wait_before_bus_clear);
nblock = 0;
for_each_available_child_of_node(np, child) {
block = &bcommands->blocks[nblock];
ret = of_property_read_u32(child, "reg", &pval);
if (ret) {
dev_err(dev, "PMC: Reg property missing on block %s\n",
child->name);
return ret;
}
block->address = pval;
of_property_read_string(child, "nvidia,command-names",
&block->name);
block->reg_8bits = !of_property_read_bool(child,
"nvidia,enable-16bit-register");
block->data_8bits = !of_property_read_bool(child,
"nvidia,enable-16bit-data");
block->i2c_controller = of_property_read_bool(child,
"nvidia,controller-type-i2c");
block->enable_reset = of_property_read_bool(child,
"nvidia,enable-controller-reset");
count = of_property_count_u32_elems(child,
"nvidia,write-commands");
ncommands = count / 2;
block->commands = command_ptr;
command_ptr += ncommands;
wr_commands = block->commands;
data_shift = (block->data_8bits) ? 8 : 16;
for (i = 0; i < ncommands; ++i) {
of_property_read_u32_index(child,
"nvidia,write-commands",
i * 2, &reg);
of_property_read_u32_index(child,
"nvidia,write-commands",
i * 2 + 1, &data);
wr_commands[i] = (data << data_shift) | reg;
}
block->ncommands = ncommands;
nblock++;
}
*br_cmds = bcommands;
return 0;
}
static void tegra_pmc_write_bootrom_command(struct tegra_pmc *pmc,
u32 command_offset, unsigned long val)
{
tegra_pmc_writel(pmc, val, command_offset + PMC_BR_COMMAND_BASE);
}
static int tegra_pmc_read_bootrom_cmd(struct device *dev,
struct tegra_bootrom_commands **br_rst_cmds,
struct tegra_bootrom_commands **br_off_cmds)
{
struct device_node *np = dev->of_node;
struct device_node *br_np, *rst_np, *off_np;
int ret;
*br_rst_cmds = NULL;
*br_off_cmds = NULL;
br_np = of_find_node_by_name(np, "bootrom-commands");
if (!br_np) {
dev_info(dev, "PMC: Bootrom commmands not found\n");
return -ENOENT;
}
rst_np = of_find_node_by_name(br_np, "reset-commands");
if (!rst_np) {
dev_info(dev, "PMC: bootrom-commands used for reset\n");
rst_np = br_np;
}
ret = tegra_pmc_parse_bootrom_cmd(dev, rst_np, br_rst_cmds);
if (ret < 0)
return ret;
if (rst_np == br_np)
return 0;
off_np = of_find_node_by_name(br_np, "power-off-commands");
if (!off_np)
return 0;
ret = tegra_pmc_parse_bootrom_cmd(dev, off_np, br_off_cmds);
if (ret < 0)
return ret;
return 0;
}
static int tegra_pmc_configure_bootrom_scratch(
struct device *dev,
struct tegra_bootrom_commands *br_commands,
struct tegra_br_cmd_cfg *bcfg, u32 bcfg_size)
{
struct tegra_pmc *pmc = dev_get_drvdata(dev);
struct tegra_bootrom_block *block;
int i, j, k;
u32 cmd, tmp_cmd;
int reg_offset = 1;
int bcfg_idx = 0;
u32 reg_data_mask, edit_data_mask;
int cmd_pw;
u32 block_add, block_val, csum;
for (i = 0; i < br_commands->nblocks; ++i) {
block = &br_commands->blocks[i];
cmd = block->address & PMC_BR_COMMAND_I2C_ADD_MASK;
cmd |= block->ncommands << PMC_BR_COMMAND_WR_COMMANDS_SHIFT;
if (!block->reg_8bits || !block->data_8bits)
cmd |= BIT(PMC_BR_COMMAND_OPERAND_SHIFT);
if (block->enable_reset)
cmd |= BIT(PMC_BR_COMMAND_RST_EN_SHIFT);
cmd |= (block->controller_id & PMC_BR_COMMAND_CTRL_ID_MASK) <<
PMC_BR_COMMAND_CTRL_ID_SHIFT;
/* Checksum will be added after parsing from reg/data */
tegra_pmc_write_bootrom_command(pmc, reg_offset * 4, cmd);
block_add = reg_offset * 4;
block_val = cmd;
reg_offset++;
cmd_pw = (block->reg_8bits && block->data_8bits) ? 2 : 1;
reg_data_mask = (cmd_pw == 2) ? 0xFFFF : 0xFFFFFFFFUL;
csum = 0;
for (j = 0; j < block->ncommands; j++) {
tmp_cmd = block->commands[j] & reg_data_mask;
if (bcfg_idx < bcfg_size &&
bcfg[bcfg_idx].dev == i &&
bcfg[bcfg_idx].idx == j) {
edit_data_mask = (cmd_pw == 2) ?
0xFF00UL : 0xFFFF0000UL;
tmp_cmd &= ~edit_data_mask;
tmp_cmd |= (bcfg[bcfg_idx].val <<
(cmd_pw == 2 ? 8 : 16)) & edit_data_mask;
bcfg_idx++;
}
cmd = tmp_cmd;
if (cmd_pw == 2) {
j++;
if (j == block->ncommands)
goto reg_update;
tmp_cmd = (block->commands[j] & reg_data_mask) << 16;
if (bcfg_idx < bcfg_size &&
bcfg[bcfg_idx].dev == i &&
bcfg[bcfg_idx].idx == j) {
edit_data_mask = 0xFF000000UL;
tmp_cmd &= ~edit_data_mask;
tmp_cmd |= (bcfg[bcfg_idx].val << 24) &
edit_data_mask;
bcfg_idx++;
}
cmd |= tmp_cmd;
}
reg_update:
tegra_pmc_write_bootrom_command(pmc, reg_offset * 4, cmd);
for (k = 0; k < 4; ++k)
csum += (cmd >> (k * 8)) & 0xFF;
reg_offset++;
}
for (k = 0; k < 4; ++k)
csum += (block_val >> (k * 8)) & 0xFF;
csum = 0x100 - csum;
block_val = (block_val & 0xFF00FFFF) | ((csum & 0xFF) << 16);
tegra_pmc_write_bootrom_command(pmc, block_add, block_val);
}
cmd = br_commands->command_retry_count & 0x7;
cmd |= (br_commands->delay_between_commands & 0x1F) << 3;
cmd |= (br_commands->nblocks & 0x7) << 8;
cmd |= (br_commands->wait_before_bus_clear & 0x1F) << 11;
tegra_pmc_write_bootrom_command(pmc, 0, cmd);
return 0;
}
int tegra_pmc_edit_bootrom_scratch_poff(struct device *dev,
struct tegra_br_cmd_cfg *bcfg,
u32 bcfg_size)
{
if (br_off_commands) {
tegra_pmc_configure_bootrom_scratch(dev, br_off_commands,
bcfg, bcfg_size);
return 0;
}
return -EINVAL;
}
int tegra_pmc_edit_bootrom_scratch_reset(struct device *dev,
struct tegra_br_cmd_cfg *bcfg,
u32 bcfg_size)
{
if (br_rst_commands) {
tegra_pmc_configure_bootrom_scratch(dev, br_rst_commands,
bcfg, bcfg_size);
return 0;
}
return -EINVAL;
}
static int tegra_pmc_init_bootrom_power_off_cmd(struct device *dev)
{
int ret;
if (!br_off_commands) {
dev_info(dev, "PMC: Power Off Command not available\n");
return 0;
}
ret = tegra_pmc_configure_bootrom_scratch(dev, br_off_commands, NULL, 0);
if (ret < 0) {
dev_err(dev, "PMC: Failed to configure power-off command: %d\n",
ret);
return ret;
}
dev_info(dev, "PMC: Successfully configure power-off commands\n");
return 0;
}
static int tegra_pmc_init_bootrom_cmds(struct device *dev)
{
int ret;
ret = tegra_pmc_read_bootrom_cmd(dev, &br_rst_commands,
&br_off_commands);
if (ret < 0) {
if (ret == -ENOENT)
ret = 0;
else
dev_info(dev,
"PMC: Failed to read bootrom cmd: %d\n", ret);
return ret;
}
/* if (br_off_commands)
set_soc_specific_power_off(tegra_pmc_soc_power_off); */
ret = tegra_pmc_configure_bootrom_scratch(dev, br_rst_commands, NULL, 0);
if (ret < 0) {
dev_info(dev, "PMC: Failed to write bootrom scratch register: %d\n",
ret);
return ret;
}
dev_info(dev, "PMC: Successfully configure bootrom reset commands\n");
return 0;
}
static int tegra_pmc_reboot_notify(struct notifier_block *this,
unsigned long action, void *data)
{
@@ -1165,16 +1527,24 @@ static int tegra_pmc_restart_handler(struct sys_off_data *data)
static int tegra_pmc_power_off_handler(struct sys_off_data *data)
{
/*
* Reboot Nexus 7 into special bootloader mode if USB cable is
* connected in order to display battery status and power off.
*/
/* Handle special case devices */
if (of_machine_is_compatible("asus,grouper") &&
power_supply_is_system_supplied()) {
/**
* Reboot Nexus 7 into special bootloader mode if USB cable is
* connected in order to display battery status and power off.
*/
const u32 go_to_charger_mode = 0xa5a55a5a;
tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37);
tegra_pmc_restart();
} else if (pmc->soc->has_bootrom_command) {
/**
* Configure PMC power off cmds for special-case devices with
* brom commands for reset.
*/
tegra_pmc_init_bootrom_power_off_cmd(pmc->dev);
tegra_pmc_restart();
}
return NOTIFY_DONE;
@@ -3010,6 +3380,9 @@ static int tegra_pmc_probe(struct platform_device *pdev)
if (err < 0)
goto cleanup_powergates;
if (pmc->soc->has_bootrom_command)
tegra_pmc_init_bootrom_cmds(&pdev->dev);
mutex_lock(&pmc->powergates_lock);
iounmap(pmc->base);
pmc->base = base;
@@ -3723,6 +4096,7 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = {
.needs_mbist_war = true,
.has_impl_33v_pwr = false,
.maybe_tz_only = true,
.has_bootrom_command = false,
.num_io_pads = ARRAY_SIZE(tegra210_io_pads),
.io_pads = tegra210_io_pads,
.num_pin_descs = ARRAY_SIZE(tegra210_pin_descs),
@@ -3843,6 +4217,7 @@ static const struct tegra_pmc_soc tegra210b01_pmc_soc = {
.needs_mbist_war = true,
.has_impl_33v_pwr = false,
.maybe_tz_only = true,
.has_bootrom_command = true,
.num_io_pads = ARRAY_SIZE(tegra210b01_io_pads),
.io_pads = tegra210b01_io_pads,
.num_pin_descs = ARRAY_SIZE(tegra210b01_pin_descs),

View File

@@ -227,6 +227,18 @@ static inline enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void)
}
#endif
struct tegra_br_cmd_cfg {
u32 dev;
u32 idx;
u32 val;
};
int tegra_pmc_edit_bootrom_scratch_poff(struct device *dev,
struct tegra_br_cmd_cfg *bcfg,
u32 bcfg_size);
int tegra_pmc_edit_bootrom_scratch_reset(struct device *dev,
struct tegra_br_cmd_cfg *bcfg,
u32 bcfg_size);
void tegra_pmc_r2p_setup(const char *cmd, bool panic_occurred);
#endif /* __SOC_TEGRA_PMC_H__ */