mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-25 10:42:21 +03:00
During SC7 tsec driver is unable to find the desc file for the tsec fw and thus tsec boot fails. This might result into system hang. Hence this change adds the tsec fw desc file path as the module firmware so that the firmware is available to it during SC7. Bug 5053273 Change-Id: I4f2c9bb5288dac6973f574159fdebc48009edf2a Signed-off-by: spatki <spatki@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3308515 Reviewed-by: Nikesh Oswal <noswal@nvidia.com> Reviewed-by: Jon Hunter <jonathanh@nvidia.com> GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
496 lines
13 KiB
C
496 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
*
|
|
* Tegra TSEC Module Support
|
|
*/
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include "tsec_linux.h"
|
|
#include "tsec.h"
|
|
#include "tsec_boot.h"
|
|
#include "tsec_regs.h"
|
|
|
|
/*
|
|
* TSEC Device Data
|
|
*/
|
|
|
|
static struct tsec_device_data t23x_tsec_data = {
|
|
.rate = {192000000, 0, 204000000},
|
|
.riscv_desc_bin = "tegra23x/nvhost_tsec_desc.fw",
|
|
.riscv_image_bin = "tegra23x/nvhost_tsec_riscv.fw",
|
|
};
|
|
MODULE_FIRMWARE("tegra23x/nvhost_tsec_riscv.fw");
|
|
MODULE_FIRMWARE("tegra23x/nvhost_tsec_desc.fw");
|
|
|
|
static struct tsec_device_data t239_tsec_data = {
|
|
.rate = {192000000, 0, 204000000},
|
|
.riscv_desc_bin = "tegra239/nvhost_tsec_desc.fw",
|
|
.riscv_image_bin = "tegra239/nvhost_tsec_riscv.fw",
|
|
};
|
|
|
|
/*
|
|
* TSEC Register Access APIs
|
|
*/
|
|
|
|
void tsec_writel(struct tsec_device_data *pdata, u32 r, u32 v)
|
|
{
|
|
void __iomem *addr = pdata->reg_aperture + r;
|
|
|
|
writel(v, addr);
|
|
}
|
|
|
|
u32 tsec_readl(struct tsec_device_data *pdata, u32 r)
|
|
{
|
|
void __iomem *addr = pdata->reg_aperture + r;
|
|
|
|
return readl(addr);
|
|
}
|
|
|
|
/*
|
|
* TSEC helpers for clock, reset and register initialisation
|
|
*/
|
|
|
|
static int tsec_enable_clks(struct tsec_device_data *pdata)
|
|
{
|
|
int err = 0, index = 0;
|
|
|
|
for (index = 0; index < TSEC_NUM_OF_CLKS; index++) {
|
|
err = clk_prepare_enable(pdata->clk[index]);
|
|
if (err) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static void tsec_disable_clk(struct tsec_device_data *pdata)
|
|
{
|
|
int index = 0;
|
|
|
|
for (index = 0; index < TSEC_NUM_OF_CLKS; index++)
|
|
clk_disable_unprepare(pdata->clk[index]);
|
|
}
|
|
|
|
static void tsec_deassert_reset(struct tsec_device_data *pdata)
|
|
{
|
|
reset_control_acquire(pdata->reset_control);
|
|
/* Does assert and then deassert */
|
|
reset_control_reset(pdata->reset_control);
|
|
reset_control_release(pdata->reset_control);
|
|
}
|
|
|
|
static void tsec_assert_reset(struct tsec_device_data *pdata)
|
|
{
|
|
reset_control_acquire(pdata->reset_control);
|
|
reset_control_assert(pdata->reset_control);
|
|
reset_control_release(pdata->reset_control);
|
|
}
|
|
|
|
static void tsec_set_streamid_regs(struct device *dev,
|
|
struct tsec_device_data *pdata)
|
|
{
|
|
struct iommu_fwspec *fwspec;
|
|
int streamid;
|
|
|
|
/* Get the StreamID value */
|
|
fwspec = dev_iommu_fwspec_get(dev);
|
|
if (fwspec && fwspec->num_ids)
|
|
streamid = fwspec->ids[0] & 0xffff;
|
|
else
|
|
streamid = 0x7F; /* bypass hwid */
|
|
|
|
/* Update the StreamID value */
|
|
tsec_writel(pdata, tsec_thi_streamid0_r(), streamid);
|
|
tsec_writel(pdata, tsec_thi_streamid1_r(), streamid);
|
|
}
|
|
|
|
static void tsec_set_cg_regs(struct tsec_device_data *pdata)
|
|
{
|
|
tsec_writel(pdata, tsec_priv_blocker_ctrl_cg1_r(), 0x0);
|
|
tsec_writel(pdata, tsec_riscv_cg_r(), 0x3);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
|
struct nvriscv_log_buffer {
|
|
/* read offset updated by client RM */
|
|
uint32_t read_offset;
|
|
/* write offset updated by firmware RM */
|
|
uint32_t write_offset;
|
|
/* buffer size configured by client RM */
|
|
uint32_t buffer_size;
|
|
/* magic number for header validation in nvwatch */
|
|
uint32_t magic;
|
|
};
|
|
|
|
#define LOG_BUF_SIZE ((4 * SZ_1K) - sizeof(struct nvriscv_log_buffer))
|
|
#define INFO_BUF_SIZE (SZ_128)
|
|
static u8 log_buf[LOG_BUF_SIZE]; /* Read from DMEM into local buffer */
|
|
static u8 info_buf[INFO_BUF_SIZE];
|
|
|
|
static int tsec_debug_show(struct seq_file *s, void *unused)
|
|
{
|
|
int info_len = 0;
|
|
struct tsec_device_data *pdata = (struct tsec_device_data *)s->private;
|
|
|
|
/* Do not attempt to read DMEM if TSEC is power-down */
|
|
if (pdata->power_on) {
|
|
#define DMEM_PORT (0)
|
|
/* Offset of Log Buffer in DMEM */
|
|
u32 log_bug_off = tsec_dmem_logbuf_offset_f();
|
|
u32 dmemC = tsec_falcon_dmemc_r(DMEM_PORT);
|
|
u32 dmemD = tsec_falcon_dmemd_r(DMEM_PORT);
|
|
struct nvriscv_log_buffer log_buf_info;
|
|
|
|
/* Auto Increment Read */
|
|
u32 loop_index = 0;
|
|
|
|
tsec_writel(pdata, dmemC, log_bug_off | 0x02000000);
|
|
while (loop_index < LOG_BUF_SIZE) {
|
|
u32 reg_val;
|
|
|
|
reg_val = tsec_readl(pdata, dmemD);
|
|
log_buf[loop_index++] = (u8)((reg_val >> 0) & 0xFF);
|
|
log_buf[loop_index++] = (u8)((reg_val >> 8) & 0xFF);
|
|
log_buf[loop_index++] = (u8)((reg_val >> 16) & 0xFF);
|
|
log_buf[loop_index++] = (u8)((reg_val >> 24) & 0xFF);
|
|
}
|
|
log_buf_info.read_offset = tsec_readl(pdata, dmemD);
|
|
log_buf_info.write_offset = tsec_readl(pdata, dmemD);
|
|
log_buf_info.buffer_size = tsec_readl(pdata, dmemD);
|
|
log_buf_info.magic = tsec_readl(pdata, dmemD);
|
|
|
|
/*
|
|
* Replace any null charecter with new line because tsec ucode
|
|
* does the reverse before dumping the logs in the buffer
|
|
*/
|
|
for (loop_index = 0; loop_index < LOG_BUF_SIZE; loop_index++) {
|
|
if (log_buf[loop_index] == 0)
|
|
log_buf[loop_index] = 0xA;
|
|
}
|
|
|
|
/* Insert null charecter at end of log buffer */
|
|
log_buf[log_buf_info.write_offset] = 0;
|
|
|
|
/* Print log_buf */
|
|
info_len = sprintf(info_buf,
|
|
"Tsec Logs Start: read_offset=%d write_offset=%d buffer_size=%d magic=0x%x\n",
|
|
log_buf_info.read_offset, log_buf_info.write_offset,
|
|
log_buf_info.buffer_size, log_buf_info.magic);
|
|
if (info_len > 0)
|
|
seq_write(s, info_buf, info_len);
|
|
|
|
seq_write(s, log_buf,
|
|
(log_buf_info.write_offset - log_buf_info.read_offset));
|
|
|
|
info_len = strlen("Tsec Logs End\n");
|
|
strcpy(info_buf, "Tsec Logs End\n");
|
|
info_buf[info_len] = '\0';
|
|
seq_write(s, info_buf, info_len);
|
|
} else {
|
|
info_len = strlen("Tsec power-down\n");
|
|
strcpy(info_buf, "Tsec power-down\n");
|
|
info_buf[info_len] = '\0';
|
|
seq_write(s, info_buf, info_len);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsec_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, tsec_debug_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations tsec_debug_fops = {
|
|
.open = tsec_debug_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
/*
|
|
* TSEC debugfs interface
|
|
* create: /sys/kernel/debug/tegra_tsec/fw_logs.
|
|
* cat /sys/kernel/debug/tegra_tsec/fw_logs will read the DMEM reserved for
|
|
* debug messages and print it on console.
|
|
*/
|
|
|
|
static int tsec_module_init_debugfs(struct platform_device *dev)
|
|
{
|
|
struct tsec_device_data *pdata = platform_get_drvdata(dev);
|
|
|
|
pdata->debug_root = debugfs_create_dir("tegra_tsec", NULL);
|
|
if (!pdata->debug_root)
|
|
return -ENOMEM;
|
|
|
|
debugfs_create_file("fw_logs", 0444, pdata->debug_root,
|
|
pdata, &tsec_debug_fops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tsec_module_deinit_debugfs(struct platform_device *dev)
|
|
{
|
|
struct tsec_device_data *pdata = platform_get_drvdata(dev);
|
|
|
|
debugfs_remove_recursive(pdata->debug_root);
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
/*
|
|
* TSEC Power Management Operations
|
|
*/
|
|
|
|
int tsec_poweron(struct device *dev)
|
|
{
|
|
struct tsec_device_data *pdata;
|
|
int err = 0, tsec_clks_enabled = 0;
|
|
|
|
pdata = dev_get_drvdata(dev);
|
|
|
|
err = tsec_enable_clks(pdata);
|
|
if (err) {
|
|
dev_err(dev, "Cannot enable tsec clocks %d\n", err);
|
|
goto out;
|
|
}
|
|
tsec_clks_enabled = 1;
|
|
|
|
tsec_deassert_reset(pdata);
|
|
tsec_set_cg_regs(pdata);
|
|
tsec_set_streamid_regs(dev, pdata);
|
|
|
|
err = tsec_finalize_poweron(to_platform_device(dev));
|
|
/* Failed to start the device */
|
|
if (err) {
|
|
dev_err(dev, "tsec_finalize_poweron error %d\n", err);
|
|
goto out;
|
|
}
|
|
|
|
pdata->power_on = true;
|
|
|
|
out:
|
|
if (err && tsec_clks_enabled)
|
|
tsec_disable_clk(pdata);
|
|
return err;
|
|
}
|
|
|
|
int tsec_poweroff(struct device *dev)
|
|
{
|
|
struct tsec_device_data *pdata;
|
|
|
|
pdata = dev_get_drvdata(dev);
|
|
|
|
if (pdata->power_on) {
|
|
tsec_assert_reset(pdata);
|
|
tsec_disable_clk(pdata);
|
|
tsec_prepare_poweroff(to_platform_device(dev));
|
|
pdata->power_on = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsec_module_suspend(struct device *dev)
|
|
{
|
|
return tsec_poweroff(dev);
|
|
}
|
|
|
|
static int tsec_module_resume(struct device *dev)
|
|
{
|
|
return tsec_poweron(dev);
|
|
}
|
|
|
|
/*
|
|
* TSEC Probe/Remove and Module Init
|
|
*/
|
|
|
|
static int tsec_module_init(struct platform_device *dev)
|
|
{
|
|
struct tsec_device_data *pdata = platform_get_drvdata(dev);
|
|
struct resource *res = NULL;
|
|
void __iomem *regs = NULL;
|
|
|
|
/* Initialize dma parameters */
|
|
dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(39));
|
|
dev->dev.dma_parms = &pdata->dma_parms;
|
|
dma_set_max_seg_size(&dev->dev, UINT_MAX);
|
|
|
|
/* Get register aperture */
|
|
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
|
if (!res)
|
|
return -EINVAL;
|
|
regs = devm_ioremap_resource(&dev->dev, res);
|
|
if (IS_ERR(regs)) {
|
|
int err = PTR_ERR(regs);
|
|
|
|
dev_err(&dev->dev, "failed to get register memory %d\n", err);
|
|
return err;
|
|
}
|
|
pdata->reg_aperture = regs;
|
|
|
|
/* Get interrupt */
|
|
pdata->irq = platform_get_irq(dev, 0);
|
|
if (pdata->irq < 0) {
|
|
dev_err(&dev->dev, "failed to get irq %d\n", -pdata->irq);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* get TSEC_CLK and enable it */
|
|
pdata->clk[TSEC_CLK_INDEX] = devm_clk_get(&dev->dev, TSEC_CLK_NAME);
|
|
if (IS_ERR(pdata->clk[TSEC_CLK_INDEX])) {
|
|
dev_err(&dev->dev, "failed to get %s clk", TSEC_CLK_NAME);
|
|
return -ENXIO;
|
|
}
|
|
clk_set_rate(pdata->clk[TSEC_CLK_INDEX],
|
|
clk_round_rate(pdata->clk[TSEC_CLK_INDEX],
|
|
pdata->rate[TSEC_CLK_INDEX]));
|
|
if (clk_prepare_enable(pdata->clk[TSEC_CLK_INDEX]) != 0) {
|
|
dev_err(&dev->dev, "failed to enable %s clk", TSEC_CLK_NAME);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* get EFUSE_CLK and enable it */
|
|
pdata->clk[EFUSE_CLK_INDEX] = devm_clk_get(&dev->dev, EFUSE_CLK_NAME);
|
|
if (IS_ERR(pdata->clk[EFUSE_CLK_INDEX])) {
|
|
dev_err(&dev->dev, "failed to get %s clk", EFUSE_CLK_NAME);
|
|
clk_disable_unprepare(pdata->clk[TSEC_CLK_INDEX]);
|
|
return -ENXIO;
|
|
}
|
|
clk_set_rate(pdata->clk[EFUSE_CLK_INDEX],
|
|
clk_round_rate(pdata->clk[EFUSE_CLK_INDEX],
|
|
pdata->rate[EFUSE_CLK_INDEX]));
|
|
if (clk_prepare_enable(pdata->clk[EFUSE_CLK_INDEX]) != 0) {
|
|
dev_err(&dev->dev, "failed to enable %s clk", EFUSE_CLK_NAME);
|
|
clk_disable_unprepare(pdata->clk[TSEC_CLK_INDEX]);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* get TSEC_PKA_CLK and enable it */
|
|
pdata->clk[TSEC_PKA_CLK_INDEX] = devm_clk_get(&dev->dev, TSEC_PKA_CLK_NAME);
|
|
if (IS_ERR(pdata->clk[TSEC_PKA_CLK_INDEX])) {
|
|
dev_err(&dev->dev, "failed to get %s clk", TSEC_PKA_CLK_NAME);
|
|
clk_disable_unprepare(pdata->clk[EFUSE_CLK_INDEX]);
|
|
clk_disable_unprepare(pdata->clk[TSEC_CLK_INDEX]);
|
|
return -ENXIO;
|
|
}
|
|
clk_set_rate(pdata->clk[TSEC_PKA_CLK_INDEX],
|
|
clk_round_rate(pdata->clk[TSEC_PKA_CLK_INDEX],
|
|
pdata->rate[TSEC_PKA_CLK_INDEX]));
|
|
if (clk_prepare_enable(pdata->clk[TSEC_PKA_CLK_INDEX]) != 0) {
|
|
dev_err(&dev->dev, "failed to enable %s clk", TSEC_PKA_CLK_NAME);
|
|
clk_disable_unprepare(pdata->clk[EFUSE_CLK_INDEX]);
|
|
clk_disable_unprepare(pdata->clk[TSEC_CLK_INDEX]);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* get reset_control and reset the module */
|
|
pdata->reset_control = devm_reset_control_get_exclusive_released(
|
|
&dev->dev, NULL);
|
|
if (IS_ERR(pdata->reset_control))
|
|
pdata->reset_control = NULL;
|
|
tsec_deassert_reset(pdata);
|
|
|
|
/* disable the clocks after resetting the module */
|
|
tsec_disable_clk(pdata);
|
|
|
|
/* read the support_comms property*/
|
|
pdata->is_comms_supported = of_property_read_bool((&dev->dev)->of_node, "support_comms");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct dev_pm_ops tsec_module_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(tsec_module_suspend, tsec_module_resume)
|
|
};
|
|
|
|
static const struct of_device_id tsec_of_match[] = {
|
|
{ .compatible = "nvidia,tegra234-tsec",
|
|
.data = (struct tsec_device_data *)&t23x_tsec_data },
|
|
{ .compatible = "nvidia,tegra239-tsec",
|
|
.data = (struct tsec_device_data *)&t239_tsec_data },
|
|
{ },
|
|
};
|
|
|
|
static int tsec_probe(struct platform_device *dev)
|
|
{
|
|
int err;
|
|
struct tsec_device_data *pdata = NULL;
|
|
|
|
/* Get device platform data */
|
|
if (dev->dev.of_node) {
|
|
const struct of_device_id *match;
|
|
|
|
match = of_match_device(tsec_of_match, &dev->dev);
|
|
if (match)
|
|
pdata = (struct tsec_device_data *)match->data;
|
|
} else {
|
|
pdata = (struct tsec_device_data *)dev->dev.platform_data;
|
|
}
|
|
pdata->pdev = dev;
|
|
platform_set_drvdata(dev, pdata);
|
|
|
|
err = tsec_module_init(dev);
|
|
if (err) {
|
|
dev_err(&dev->dev, "error %d in tsec_module_init\n", err);
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
if (debugfs_initialized()) {
|
|
err = tsec_module_init_debugfs(dev);
|
|
if (err) {
|
|
dev_err(&dev->dev, "error %d in tsec_module_init_debugfs\n", err);
|
|
return err;
|
|
}
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
return tsec_kickoff_boot(dev);
|
|
}
|
|
|
|
static int tsec_remove(struct platform_device *dev)
|
|
{
|
|
#ifdef CONFIG_DEBUG_FS
|
|
tsec_module_deinit_debugfs(dev);
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
return tsec_poweroff(&dev->dev);
|
|
}
|
|
|
|
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
|
|
static void tsec_remove_wrapper(struct platform_device *pdev)
|
|
{
|
|
tsec_remove(pdev);
|
|
}
|
|
#else
|
|
static int tsec_remove_wrapper(struct platform_device *pdev)
|
|
{
|
|
return tsec_remove(pdev);
|
|
}
|
|
#endif
|
|
|
|
static struct platform_driver tsec_driver = {
|
|
.probe = tsec_probe,
|
|
.remove = tsec_remove_wrapper,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "tsec",
|
|
.pm = &tsec_module_pm_ops,
|
|
.of_match_table = tsec_of_match,
|
|
}
|
|
};
|
|
|
|
module_platform_driver(tsec_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Nikesh Oswal <noswal@nvidia.com>");
|
|
MODULE_DEVICE_TABLE(of, tsec_of_match);
|
|
MODULE_DESCRIPTION("TSEC Driver");
|