diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 11d200e3eb7e..cdaacdcafe98 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -180,6 +180,26 @@ #define PMC_UTMIP_PAD_CFG0 0x4c0 #define PMC_UTMIP_UHSIC_SLEEP_CFG1 0x4d0 #define PMC_UTMIP_SLEEPWALK_P3 0x4e0 + +/* Scratch 250: Bootrom i2c command base */ +#define PMC_BR_COMMAND_BASE 0x908 + +/* 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 + /* Tegra186 and later */ #define WAKE_AOWAKE_CNTRL(x) (0x000 + ((x) << 2)) #define WAKE_AOWAKE_CNTRL_LEVEL (1 << 3) @@ -295,6 +315,29 @@ struct tegra_powergate { struct reset_control *reset; }; +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; + struct tegra_io_pad_soc { enum tegra_io_pad id; unsigned int dpd; @@ -381,6 +424,7 @@ struct tegra_pmc_soc { bool needs_mbist_war; bool has_io_pad_wren; bool maybe_tz_only; + bool has_bootrom_command; const struct tegra_io_pad_soc *io_pads; unsigned int num_io_pads; @@ -1322,25 +1366,6 @@ static int tegra_pmc_panic_handler(struct notifier_block *this, return NOTIFY_DONE; } -static int tegra_pmc_power_off_handler(struct sys_off_data *data) -{ - struct tegra_pmc *pmc = data->cb_data; - - /* - * Reboot Nexus 7 into special bootloader mode if USB cable is - * connected in order to display battery status and power off. - */ - if (of_machine_is_compatible("asus,grouper") && - power_supply_is_system_supplied()) { - const u32 go_to_charger_mode = 0xa5a55a5a; - - tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37); - tegra_pmc_restart(pmc); - } - - return NOTIFY_DONE; -} - static int powergate_show(struct seq_file *s, void *data) { struct tegra_pmc *pmc = data; @@ -1710,6 +1735,330 @@ static void tegra_powergate_remove_all(struct device_node *parent) } } +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, ®); + 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 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 tegra_pmc *pmc, + struct tegra_bootrom_commands *br_commands, + struct tegra_br_cmd_cfg *bcfg, u32 bcfg_size) +{ + 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_writel(pmc, cmd, PMC_BR_COMMAND_BASE + (reg_offset * 4)); + 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_writel(pmc, cmd, PMC_BR_COMMAND_BASE + (reg_offset * 4)); + 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_writel(pmc, block_val, PMC_BR_COMMAND_BASE + block_add); + } + + 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_writel(pmc, cmd, PMC_BR_COMMAND_BASE); + + return 0; +} + +/** + * tegra_pmc_edit_bootrom_scratch_reset() - configure brom reset commands + * @pmc: power management controller + * @bcfg: bootrom command config structure + * @bcfg_size: size of bcfg + * + * Returns: 0 on success or a negative error code on failure. + */ +int tegra_pmc_edit_bootrom_scratch_reset(struct tegra_pmc *pmc, + struct tegra_br_cmd_cfg *bcfg, u32 bcfg_size) +{ + if (br_rst_commands) { + tegra_pmc_configure_bootrom_scratch(pmc, br_rst_commands, + bcfg, bcfg_size); + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL(tegra_pmc_edit_bootrom_scratch_reset); + +static int tegra_pmc_init_bootrom_power_off_cmd(struct tegra_pmc *pmc) +{ + int ret; + + if (!br_off_commands) { + dev_info(pmc->dev, "PMC: Power Off Command not available\n"); + return 0; + } + + ret = tegra_pmc_configure_bootrom_scratch(pmc, br_off_commands, NULL, 0); + if (ret < 0) { + dev_err(pmc->dev, "PMC: Failed to configure power-off command: %d\n", + ret); + return ret; + } + + dev_info(pmc->dev, "PMC: Successfully configure power-off commands\n"); + + return 0; +} + +static int tegra_pmc_init_bootrom_cmds(struct tegra_pmc *pmc, 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; + } + + ret = tegra_pmc_configure_bootrom_scratch(pmc, 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_power_off_handler(struct sys_off_data *data) +{ + struct tegra_pmc *pmc = data->cb_data; + + if (br_off_commands) { + tegra_pmc_init_bootrom_power_off_cmd(pmc); + tegra_pmc_restart(pmc); + } + + /* + * Reboot Nexus 7 into special bootloader mode if USB cable is + * connected in order to display battery status and power off. + */ + if (of_machine_is_compatible("asus,grouper") && + power_supply_is_system_supplied()) { + const u32 go_to_charger_mode = 0xa5a55a5a; + + tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37); + tegra_pmc_restart(pmc); + } + + return NOTIFY_DONE; +} + static const struct tegra_io_pad_soc * tegra_io_pad_find(struct tegra_pmc *pmc, enum tegra_io_pad id) { @@ -3194,6 +3543,9 @@ static int tegra_pmc_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(pmc->clk), "failed to get pclk\n"); + if (pmc->soc->has_bootrom_command) + tegra_pmc_init_bootrom_cmds(pmc, &pdev->dev); + /* * PMC should be last resort for restarting since it soft-resets * CPU without resetting everything else. @@ -3592,6 +3944,7 @@ static const struct tegra_pmc_soc tegra20_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, + .has_bootrom_command = false, }; static const char * const tegra30_powergates[] = { @@ -3654,6 +4007,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, + .has_bootrom_command = false, }; static const char * const tegra114_powergates[] = { @@ -3712,6 +4066,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, + .has_bootrom_command = false, }; static const char * const tegra124_powergates[] = { @@ -3864,6 +4219,7 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = { .has_blink_output = true, .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, + .has_bootrom_command = false, }; static const char * const tegra210_powergates[] = { @@ -4045,6 +4401,7 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, .has_panic_notifier = true, + .has_bootrom_command = true, }; static const char * const tegra210b01_powergates[] = { @@ -4107,6 +4464,7 @@ static const struct tegra_pmc_soc tegra210b01_pmc_soc = { .has_usb_sleepwalk = true, .has_single_mmio_aperture = true, .has_panic_notifier = true, + .has_bootrom_command = true, }; 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 1fd21be02577..a6fcacee2414 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -149,6 +149,12 @@ enum tegra_io_pad { TEGRA_IO_PAD_AO_HV, }; +struct tegra_br_cmd_cfg { + u32 dev; + u32 idx; + u32 val; +}; + #ifdef CONFIG_SOC_TEGRA_PMC struct tegra_pmc *devm_tegra_pmc_get(struct device *dev); @@ -156,6 +162,10 @@ int tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id); int tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id); int tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id); +int tegra_pmc_edit_bootrom_scratch_reset(struct tegra_pmc *pmc, + struct tegra_br_cmd_cfg *bcfg, u32 bcfg_size); +u32 tegra_pmc_readl(struct tegra_pmc *pmc, unsigned long offset); + /* Must be called with clk disabled, and returns with clk enabled */ int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, unsigned int id, struct clk *clk, @@ -212,6 +222,19 @@ tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, unsigned int id, return -ENOSYS; } +static inline int +tegra_pmc_edit_bootrom_scratch_reset(struct tegra_pmc *pmc, + struct tegra_br_cmd_cfg *bcfg, u32 bcfg_size) +{ + return -ENOSYS; +} + +static inline u32 +tegra_pmc_readl(struct tegra_pmc *pmc, unsigned long offset) +{ + return -ENOSYS; +} + static inline int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id) {