mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
Since the offsets for the tsec engine registers have changed with t264, this change adds the support to use SoC specific register offsets. Jira TSEC-14 Change-Id: I37afc076809008b0948239f5e9555dfa5c763ba8 Signed-off-by: spatki <spatki@nvidia.com> Signed-off-by: Mayuresh Kulkarni <mkulkarni@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3360321 Reviewed-by: Bitan Biswas <bbiswas@nvidia.com> GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: Nikesh Oswal <noswal@nvidia.com> Reviewed-by: Sachin Nikam <snikam@nvidia.com>
520 lines
13 KiB
C
520 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 register offsets
|
|
*/
|
|
extern struct tsec_reg_offsets_t t23x_reg_offsets;
|
|
extern struct tsec_reg_offsets_t t264_reg_offsets;
|
|
|
|
/*
|
|
* 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",
|
|
.dma_mask_bits = 39,
|
|
.soc = TSEC_ON_T23x,
|
|
.tsec_reg_offsets = &t23x_reg_offsets
|
|
};
|
|
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",
|
|
.dma_mask_bits = 39,
|
|
.soc = TSEC_ON_T239,
|
|
.tsec_reg_offsets = &t23x_reg_offsets
|
|
};
|
|
|
|
static struct tsec_device_data t264_tsec_data = {
|
|
.rate = {192000000, 0, 204000000},
|
|
.riscv_desc_bin = "tegra264/nvhost_tsec_desc.fw",
|
|
.riscv_image_bin = "tegra264/nvhost_tsec_riscv.fw",
|
|
.dma_mask_bits = 48,
|
|
.soc = TSEC_ON_T26x,
|
|
.tsec_reg_offsets = &t264_reg_offsets
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
struct tsec_reg_offsets_t *reg_off = pdata->tsec_reg_offsets;
|
|
/* 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, reg_off->THI_STREAMID0_0, streamid);
|
|
tsec_writel(pdata, reg_off->THI_STREAMID1_0, streamid);
|
|
}
|
|
|
|
static void tsec_set_cg_regs(struct tsec_device_data *pdata)
|
|
{
|
|
struct tsec_reg_offsets_t *reg_off = pdata->tsec_reg_offsets;
|
|
|
|
tsec_writel(pdata, reg_off->PRIV_BLOCKER_CTRL_CG1, 0x0);
|
|
tsec_writel(pdata, reg_off->RISCV_CG, 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;
|
|
struct tsec_reg_offsets_t *reg_off = pdata->tsec_reg_offsets;
|
|
|
|
/* 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 = reg_off->DMEM_LOGBUF_OFFSET;
|
|
u32 dmemC = tsec_falcon_dmemc_r(DMEM_PORT, reg_off->FALCON_DMEMC_0);
|
|
u32 dmemD = tsec_falcon_dmemd_r(DMEM_PORT, reg_off->FALCON_DMEMD_0);
|
|
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(pdata->dma_mask_bits));
|
|
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);
|
|
|
|
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 },
|
|
{ .compatible = "nvidia,tegra264-tsec",
|
|
.data = (struct tsec_device_data *)&t264_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");
|