diff --git a/drivers/video/tegra/host/nvdla/Makefile b/drivers/video/tegra/host/nvdla/Makefile index 13f9cfb8..4df6b467 100644 --- a/drivers/video/tegra/host/nvdla/Makefile +++ b/drivers/video/tegra/host/nvdla/Makefile @@ -23,6 +23,7 @@ nvhost-nvdla-objs += \ $(NVDLA_COMMON_OBJS) \ port/device/nvdla_device_host1x.o \ port/fw/nvdla_fw_flcn.o \ + port/pm/nvdla_pm_stub.o \ port/sync/nvdla_sync_syncpt.o \ port/fw/nvdla_falcon.o diff --git a/drivers/video/tegra/host/nvdla/axi/Makefile b/drivers/video/tegra/host/nvdla/axi/Makefile index b396f8b9..13f71c35 100644 --- a/drivers/video/tegra/host/nvdla/axi/Makefile +++ b/drivers/video/tegra/host/nvdla/axi/Makefile @@ -36,6 +36,8 @@ NVDLA_OBJS := \ $(NVDLA_COMMON_OBJS) \ port/device/nvdla_device_axi.o \ port/fw/nvdla_fw_riscv.o \ + port/pm/nvdla_pm_hfrp.o \ + port/pm/nvdla_pm_hfrp_cmd_v1_2.o \ port/sync/nvdla_sync_syncpt_emu.o # Duplicate source file for forcing compilation. diff --git a/drivers/video/tegra/host/nvdla/nvdla_debug.c b/drivers/video/tegra/host/nvdla/nvdla_debug.c index 783c0c41..6db9fcd9 100644 --- a/drivers/video/tegra/host/nvdla/nvdla_debug.c +++ b/drivers/video/tegra/host/nvdla/nvdla_debug.c @@ -4,6 +4,7 @@ * NVDLA debug utils */ +#include #include #include #include "port/nvdla_host_wrapper.h" @@ -17,6 +18,7 @@ #include "nvdla_debug.h" #include "port/nvdla_fw.h" #include "port/nvdla_device.h" +#include "port/nvdla_pm.h" /* * Header in ring buffer consist (start, end) two uint32_t values. @@ -32,6 +34,35 @@ debug_set_trace_event_config(pdev, event_mask, \ DLA_SET_TRACE_EVENT_MASK); \ +static LIST_HEAD(s_debug_ctrl_list); +static DEFINE_MUTEX(s_debug_ctrl_list_lock); + +struct nvdla_debug_ctrl { + struct platform_device *pdev; + + uint32_t clock_idledelay_us; + uint32_t power_idledelay_us; + uint32_t rail_idledelay_us; + + struct list_head list; +}; + +static struct nvdla_debug_ctrl *s_nvdla_debug_ctrl_get_by_pdev( + struct platform_device *pdev) +{ + struct nvdla_debug_ctrl *ctrl = NULL; + + mutex_lock(&s_debug_ctrl_list_lock); + list_for_each_entry(ctrl, &s_debug_ctrl_list, list) { + if (ctrl->pdev == pdev) + break; + } + spec_bar(); /* break_spec_p#5_1 */ + mutex_unlock(&s_debug_ctrl_list_lock); + + return ctrl; +} + static int s_nvdla_get_pdev_from_file(struct file *file, struct platform_device **pdev) { struct seq_file *priv_data; @@ -544,7 +575,7 @@ static int nvdla_get_dvfs_statdump(struct nvdla_device *nvdla_dev) /* prepare command data */ cmd_data.method_id = DLA_CMD_GET_STATISTICS2; - /* method data is not used for this command, passing 0U */ + /* method data is not used for this command, passing 0U */ cmd_data.method_data = ALIGNED_DMA(0U); cmd_data.wait = true; @@ -628,39 +659,86 @@ static int debug_dla_ctrl_clk_enable_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + bool gated; + (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; - nvdla_dbg_info(pdev, "[CTRL/CLK] enable show"); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get pdev. err: %d", err); + goto fail; + } + nvdla_dbg_info(pdev, "[CTRL/CLK] enable show"); + + err = nvdla_pm_clock_is_gated(pdev, &gated); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get status. err: %d", err); + goto fail; + } + + seq_printf(s, "%x\n", (int) !gated); return 0; + +fail: + return err; } static int debug_dla_ctrl_clk_core_khz_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + uint32_t freq_khz; + (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch pdev\n"); + goto fail; + } nvdla_dbg_info(pdev, "[CTRL/CLK] core_khz show"); + + err = nvdla_pm_clock_get_core_freq(pdev, &freq_khz); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch frequency. err: %d\n", + err); + goto fail; + } + + seq_printf(s, "%u\n", freq_khz); return 0; + +fail: + return err; } static int debug_dla_ctrl_clk_mcu_khz_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + uint32_t freq_khz; + (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch pdev\n"); + goto fail; + } nvdla_dbg_info(pdev, "[CTRL/CLK] mcu_khz show"); + + err = nvdla_pm_clock_get_mcu_freq(pdev, &freq_khz); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch frequency. err: %d\n", + err); + goto fail; + } + + seq_printf(s, "%u\n", freq_khz); return 0; + +fail: + return err; } static int debug_dla_ctrl_clk_idlecount_show(struct seq_file *s, void *data) @@ -680,13 +758,30 @@ static int debug_dla_ctrl_clk_idledelay_us_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + struct nvdla_debug_ctrl *ctrl; (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch pdev\n"); + goto fail; + } + nvdla_dbg_info(pdev, "[CTRL/CLK] idledelay_us show"); + + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + err = -ENOMEM; + goto fail; + } + + seq_printf(s, "%x\n", (uint32_t) ctrl->clock_idledelay_us); + return 0; + +fail: + return err; } static int debug_dla_ctrl_clk_idletime_us_show(struct seq_file *s, void *data) @@ -733,13 +828,28 @@ static int debug_dla_ctrl_power_enable_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + bool gated; + (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; - nvdla_dbg_info(pdev, "[CTRL/POWER] enable show"); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get pdev. err: %d", err); + goto fail; + } + nvdla_dbg_info(pdev, "[CTRL/POWER] enable show"); + + err = nvdla_pm_power_is_gated(pdev, &gated); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get status. err: %d", err); + goto fail; + } + + seq_printf(s, "%x\n", (int) !gated); return 0; + +fail: + return err; } static int debug_dla_ctrl_power_idlecount_show(struct seq_file *s, void *data) @@ -759,13 +869,30 @@ static int debug_dla_ctrl_power_idledelay_us_show(struct seq_file *s, void *data { int err; struct platform_device *pdev; + struct nvdla_debug_ctrl *ctrl; (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch pdev\n"); + goto fail; + } + nvdla_dbg_info(pdev, "[CTRL/POWER] idledelay_us show"); + + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + err = -ENOMEM; + goto fail; + } + + seq_printf(s, "%x\n", (uint32_t) ctrl->power_idledelay_us); + return 0; + +fail: + return err; } static int debug_dla_ctrl_power_idletime_us_show(struct seq_file *s, void *data) @@ -851,13 +978,28 @@ static int debug_dla_ctrl_rail_enable_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + bool gated; + (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; - nvdla_dbg_info(pdev, "[CTRL/RAIL] enable show"); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get pdev. err: %d", err); + goto fail; + } + nvdla_dbg_info(pdev, "[CTRL/RAIL] enable show"); + + err = nvdla_pm_rail_is_gated(pdev, &gated); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get status. err: %d", err); + goto fail; + } + + seq_printf(s, "%x\n", (int) !gated); return 0; + +fail: + return err; } static int debug_dla_ctrl_rail_idlecount_show(struct seq_file *s, void *data) @@ -877,13 +1019,30 @@ static int debug_dla_ctrl_rail_idledelay_us_show(struct seq_file *s, void *data) { int err; struct platform_device *pdev; + struct nvdla_debug_ctrl *ctrl; (void) data; err = s_nvdla_get_pdev_from_seq(s, &pdev); - if (err < 0) - return -1; + if (err < 0) { + nvdla_dbg_err(pdev, "failed to fetch pdev\n"); + goto fail; + } + nvdla_dbg_info(pdev, "[CTRL/RAIL] idledelay_us show"); + + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + err = -ENOMEM; + goto fail; + } + + seq_printf(s, "%x\n", (uint32_t) ctrl->rail_idledelay_us); + return 0; + +fail: + return err; } static int debug_dla_ctrl_rail_idletime_us_show(struct seq_file *s, void *data) @@ -1145,6 +1304,7 @@ static ssize_t debug_dla_ctrl_clk_enable_write(struct file *file, int err; struct platform_device *pdev; long write_value; + struct nvdla_debug_ctrl *ctrl; /* Fetch user requested write-value. */ err = kstrtol_from_user(buffer, count, 10, &write_value); @@ -1157,6 +1317,17 @@ static ssize_t debug_dla_ctrl_clk_enable_write(struct file *file, nvdla_dbg_info(pdev, "[CTRL/CLK] enable = %u\n", (unsigned int) write_value); + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + goto fail; + } + + if (write_value > 0U) + err = nvdla_pm_clock_ungate(pdev); + else + err = nvdla_pm_clock_gate(pdev, ctrl->clock_idledelay_us, false); + return count; fail: @@ -1179,7 +1350,8 @@ static ssize_t debug_dla_ctrl_clk_core_khz_write(struct file *file, if (err < 0) goto fail; - nvdla_dbg_info(pdev, "[CTRL/CLK] core_khz = %u\n", (unsigned int) write_value); + nvdla_dbg_info(pdev, "[CTRL/CLK] core_khz = %u\n", + (unsigned int) write_value); return count; @@ -1187,44 +1359,6 @@ fail: return -1; } -static int nvdla_set_mcu_freq_khz(struct nvdla_device *nvdla_dev, uint32_t mcu_freq_khz) -{ - int err = 0; - struct nvdla_cmd_data cmd_data; - struct platform_device *pdev; - - /* prepare command data */ - cmd_data.method_id = DLA_CMD_SET_CLOCK_FREQ; - cmd_data.method_data = mcu_freq_khz; - cmd_data.wait = true; - - pdev = nvdla_dev->pdev; - if (pdev == NULL) { - err = -EFAULT; - goto fail_no_dev; - } - - /* make sure that device is powered on */ - err = nvdla_module_busy(pdev); - if (err != 0) { - nvdla_dbg_err(pdev, "failed to power on\n"); - err = -ENODEV; - goto fail_no_dev; - } - - /* pass set debug command to falcon */ - err = nvdla_fw_send_cmd(pdev, &cmd_data); - if (err != 0) { - nvdla_dbg_err(pdev, "failed to send set mcu freq command"); - goto fail_to_send_cmd; - } - -fail_to_send_cmd: - nvdla_module_idle(pdev); -fail_no_dev: - return err; -} - static ssize_t debug_dla_ctrl_clk_mcu_khz_write(struct file *file, const char __user *buffer, size_t count, loff_t *off) { @@ -1254,7 +1388,7 @@ static ssize_t debug_dla_ctrl_clk_mcu_khz_write(struct file *file, if (write_value > UINT_MAX) goto fail; - err = nvdla_set_mcu_freq_khz(nvdla_dev, write_value); + err = nvdla_pm_clock_set_mcu_freq(pdev, write_value); if (err != 0) { nvdla_dbg_err(pdev, "Failed to send set mcu freq command"); goto fail; @@ -1272,6 +1406,7 @@ static ssize_t debug_dla_ctrl_clk_idledelay_us_write(struct file *file, int err; struct platform_device *pdev; long write_value; + struct nvdla_debug_ctrl *ctrl; /* Fetch user requested write-value. */ err = kstrtol_from_user(buffer, count, 10, &write_value); @@ -1284,6 +1419,14 @@ static ssize_t debug_dla_ctrl_clk_idledelay_us_write(struct file *file, nvdla_dbg_info(pdev, "[CTRL/CLK] idledelay_us = %u\n", (unsigned int) write_value); + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + goto fail; + } + + ctrl->clock_idledelay_us = (uint32_t) write_value; + return count; fail: @@ -1297,6 +1440,7 @@ static ssize_t debug_dla_ctrl_power_enable_write(struct file *file, int err; struct platform_device *pdev; long write_value; + struct nvdla_debug_ctrl *ctrl; /* Fetch user requested write-value. */ err = kstrtol_from_user(buffer, count, 10, &write_value); @@ -1307,7 +1451,19 @@ static ssize_t debug_dla_ctrl_power_enable_write(struct file *file, if (err < 0) goto fail; - nvdla_dbg_info(pdev, "[CTRL/POWER] enable = %u\n", (unsigned int) write_value); + nvdla_dbg_info(pdev, "[CTRL/POWER] enable = %u\n", + (unsigned int) write_value); + + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + goto fail; + } + + if (write_value > 0U) + err = nvdla_pm_power_ungate(pdev); + else + err = nvdla_pm_power_gate(pdev, ctrl->power_idledelay_us, false); return count; @@ -1321,6 +1477,7 @@ static ssize_t debug_dla_ctrl_power_idledelay_us_write(struct file *file, int err; struct platform_device *pdev; long write_value; + struct nvdla_debug_ctrl *ctrl; /* Fetch user requested write-value. */ err = kstrtol_from_user(buffer, count, 10, &write_value); @@ -1333,6 +1490,14 @@ static ssize_t debug_dla_ctrl_power_idledelay_us_write(struct file *file, nvdla_dbg_info(pdev, "[CTRL/POWER] idledelay_us = %u\n", (unsigned int) write_value); + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + goto fail; + } + + ctrl->power_idledelay_us = (uint32_t) write_value; + return count; fail: @@ -1370,6 +1535,7 @@ static ssize_t debug_dla_ctrl_rail_enable_write(struct file *file, int err; struct platform_device *pdev; long write_value; + struct nvdla_debug_ctrl *ctrl; /* Fetch user requested write-value. */ err = kstrtol_from_user(buffer, count, 10, &write_value); @@ -1380,7 +1546,19 @@ static ssize_t debug_dla_ctrl_rail_enable_write(struct file *file, if (err < 0) goto fail; - nvdla_dbg_info(pdev, "[CTRL/RAIL] enable = %u\n", (unsigned int) write_value); + nvdla_dbg_info(pdev, "[CTRL/RAIL] enable = %u...\n", + (unsigned int) write_value); + + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + goto fail; + } + + if (write_value > 0U) + err = nvdla_pm_rail_ungate(pdev); + else + err = nvdla_pm_rail_gate(pdev, ctrl->rail_idledelay_us, false); return count; @@ -1394,6 +1572,7 @@ static ssize_t debug_dla_ctrl_rail_idledelay_us_write(struct file *file, int err; struct platform_device *pdev; long write_value; + struct nvdla_debug_ctrl *ctrl; /* Fetch user requested write-value. */ err = kstrtol_from_user(buffer, count, 10, &write_value); @@ -1406,6 +1585,14 @@ static ssize_t debug_dla_ctrl_rail_idledelay_us_write(struct file *file, nvdla_dbg_info(pdev, "[CTRL/RAIL] idledelay_us = %u\n", (unsigned int) write_value); + ctrl = s_nvdla_debug_ctrl_get_by_pdev(pdev); + if (ctrl == NULL) { + nvdla_dbg_err(pdev, "No ctrl node available\n"); + goto fail; + } + + ctrl->rail_idledelay_us = (uint32_t) write_value; + return count; fail: @@ -2509,11 +2696,21 @@ void nvdla_debug_init(struct platform_device *pdev) { struct nvhost_device_data *pdata = platform_get_drvdata(pdev); struct nvdla_device *nvdla_dev = pdata->private_data; + struct nvdla_debug_ctrl *ctrl; struct dentry *de = pdata->debugfs; if (!de) return; + ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return; + + ctrl->pdev = pdev; + mutex_lock(&s_debug_ctrl_list_lock); + list_add_tail(&ctrl->list, &s_debug_ctrl_list); + mutex_unlock(&s_debug_ctrl_list_lock); + debugfs_create_u32("debug_mask", 0644, de, &nvdla_dev->dbg_mask); debugfs_create_u32("bitbang", 0644, de, &nvdla_dev->bitbang); diff --git a/drivers/video/tegra/host/nvdla/port/device/nvdla_device_axi.c b/drivers/video/tegra/host/nvdla/port/device/nvdla_device_axi.c index 3ed9843b..31236299 100644 --- a/drivers/video/tegra/host/nvdla/port/device/nvdla_device_axi.c +++ b/drivers/video/tegra/host/nvdla/port/device/nvdla_device_axi.c @@ -7,6 +7,7 @@ #include #include "../nvdla_device.h" +#include "../nvdla_pm.h" #include "../../dla_queue.h" #include "../../nvdla_debug.h" @@ -277,10 +278,20 @@ int32_t nvdla_module_init(struct platform_device *pdev) goto disable_pm; } + if (pdev->num_resources > 1U) { + err = nvdla_pm_init(pdev); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to init pm. err %d\n", err); + goto destroy_device; + } + } + pdata->debugfs = debugfs_create_dir(pdata->devfs_name, NULL); return 0; +destroy_device: + s_nvdla_module_device_destroy(pdev); disable_pm: s_nvdla_module_pm_disable(pdev); fail: @@ -294,6 +305,7 @@ void nvdla_module_deinit(struct platform_device *pdev) if ((pdata != NULL) && (pdata->debugfs != NULL)) debugfs_remove_recursive(pdata->debugfs); + nvdla_pm_deinit(pdev); s_nvdla_module_device_destroy(pdev); s_nvdla_module_pm_disable(pdev); } diff --git a/drivers/video/tegra/host/nvdla/port/nvdla_pm.h b/drivers/video/tegra/host/nvdla/port/nvdla_pm.h new file mode 100644 index 00000000..042e370b --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/nvdla_pm.h @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * NVDLA Power Management + */ + +#ifndef __NVDLA_PM_H_ +#define __NVDLA_PM_H_ + +#include + +/** + * @brief Initializes power management unit + * + * @param[in] pdev Platform device with which power management is done. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise. + **/ +int32_t nvdla_pm_init(struct platform_device *pdev); + +/** + * @brief Deinitializes power management unit + * + * @param[in] pdev Platform device with which power management is done. + **/ +void nvdla_pm_deinit(struct platform_device *pdev); + +/** + * @brief Triggers request to rail gate after specified 'timeout_us'. + * + * @param[in] pdev Platform device that is to be rail gated. + * @param[in] timeout_us Timeout after which DLA must be rail gated. + * @param[in] blocking if set, waits for the gating to complete. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_rail_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking); + +/** + * @brief Power un-gating. + * + * @param[in] pdev Platform device that is to be rail ungated. + * + * @return + * - zero, up on successful rail ungating. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_rail_ungate(struct platform_device *pdev); + +/** + * @brief Checks if the rail is gated or not. + * + * @param[in] pdev Platform device. + * @param[out] gated Pointer where the status is saved. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_rail_is_gated(struct platform_device *pdev, + bool *gated); + +/** + * @brief Triggers request to power gate after specified 'timeout_us'. + * + * @param[in] pdev Platform device that is to be power gated. + * @param[in] timeout_us Timeout after which DLA must be power gated. + * @param[in] blocking if set, waits for the gating to complete. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_power_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking); + +/** + * @brief Power un-gating. + * + * @param[in] pdev Platform device that is to be power ungated. + * + * @return + * - zero, up on successful power ungating. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_power_ungate(struct platform_device *pdev); + +/** + * @brief Checks if the power is gated or not. + * + * @param[in] pdev Platform device. + * @param[out] gated Pointer where the status is saved. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_power_is_gated(struct platform_device *pdev, + bool *gated); + +/** + * @brief Triggers request to clock gate after specified 'timeout_us'. + * + * @param[in] pdev Platform device that is to be clock gated. + * @param[in] timeout_us Timeout after which DLA must be clock gated. + * @param[in] blocking if set, waits for the gating to complete. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking); + +/** + * @brief Power un-gating. + * + * @param[in] pdev Platform device that is to be clock ungated. + * + * @return + * - zero, up on successful clock ungating. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_ungate(struct platform_device *pdev); + +/** + * @brief Checks if the clock is gated or not. + * + * @param[in] pdev Platform device. + * @param[out] gated Pointer where the status is saved. + * + * @return + * - zero, up on successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_is_gated(struct platform_device *pdev, + bool *gated); + +/** + * @brief Sets DLA MCU frequency. + * + * @param[in] pdev platform device. + * @param[in] freq_khz frequency (in khz) that is to be set. + * + * @return + * - zero, with successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_set_mcu_freq(struct platform_device *pdev, + uint32_t freq_khz); + +/** + * @brief Gets current DLA MCU frequency. + * + * @param[in] pdev platform device. + * @param[out] freq_khz frequency (in khz). + * + * @return + * - zero, with successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_get_mcu_freq(struct platform_device *pdev, + uint32_t *freq_khz); + +/** + * @brief Sets DLA core frequency. + * + * @param[in] pdev Platform device. + * @param[in] freq_khz Frequency (in KHz) that is to be set. + * + * @return + * - zero, with successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_set_core_freq(struct platform_device *pdev, + uint32_t freq_khz); + +/** + * @brief Gets current DLA core frequency. + * + * @param[in] pdev platform device. + * @param[out] freq_khz frequency (in khz). + * + * @return + * - zero, with successful operation. + * - non-zero, otherwise + **/ +int32_t nvdla_pm_clock_get_core_freq(struct platform_device *pdev, + uint32_t *freq_khz); + +#endif /* __NVDLA_PM_H_ */ diff --git a/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp.c b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp.c new file mode 100644 index 00000000..55155197 --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * NVDLA Power Management - stub implementation + */ + +#include "../nvdla_pm.h" + +#include "../nvdla_device.h" +#include "../nvdla_fw.h" +#include "../nvdla_host_wrapper.h" + +#include "../../dla_os_interface.h" +#include "../../nvdla.h" +#include "../../nvdla_debug.h" + +#include "nvdla_pm_hfrp.h" +#include "nvdla_pm_hfrp_reg.h" + + +#include +#include +#include +#include +#include +#include + +#define NVDLA_HFRP_APERTURE_IDX 1U + +static LIST_HEAD(s_hfrp_list); +static DEFINE_MUTEX(s_hfrp_list_lock); + +static struct hfrp_cmd_sequence *s_hfrp_cmd_sequence_alloc(struct hfrp *hfrp) +{ + struct hfrp_cmd_sequence *sequence = NULL; + + if (list_empty(&hfrp->seq_freelist)) + goto done; + + sequence = list_first_entry(&hfrp->seq_freelist, typeof(*sequence), + list); + + /* Allocated successfully, delete it from the free list */ + list_del(&sequence->list); + +done: + return sequence; +} + +static void s_hfrp_cmd_sequence_destroy(struct hfrp_cmd_sequence *sequence) +{ + struct hfrp *hfrp = sequence->hfrp; + + /* Return the sequence back to the free list */ + list_add_tail(&sequence->list, &hfrp->seq_freelist); +} + +static struct hfrp *s_hfrp_get_by_pdev(struct platform_device *pdev) +{ + struct hfrp *hfrp = NULL; + + mutex_lock(&s_hfrp_list_lock); + list_for_each_entry(hfrp, &s_hfrp_list, list) { + if (hfrp->pdev == pdev) + break; + } + spec_bar(); /* break_spec_p#5_1 */ + mutex_unlock(&s_hfrp_list_lock); + + return hfrp; +} + +static irqreturn_t s_hfrp_isr(int irq, void *dev_id) +{ + struct platform_device *pdev; + struct hfrp *hfrp; + uint32_t intstat; + + uint32_t clientoffs; + uint32_t serveroffs; + uint32_t head_offset; + uint32_t tail_offset; + uint32_t buffer_size; + + pdev = (struct platform_device *)(dev_id); + hfrp = s_hfrp_get_by_pdev(pdev); + intstat = hfrp_reg_read(hfrp, hfrp_irq_out_set_r()); + nvdla_dbg_info(pdev, "Received interrupt: %x\n", intstat); + + if (intstat == 0U) { + nvdla_dbg_warn(pdev, "Spurious interrupt\n"); + goto done; + } + + if (hfrp_irq_out_set_reset_v(intstat) > 0) { + /* Clear the reset bit */ + hfrp_reg_write(hfrp, hfrp_irq_out_clr_reset_f(1U), + hfrp_irq_out_clr_r()); + } + + if (hfrp_irq_out_set_doorbell_v(intstat) == 0) { + /* No more action pending. */ + goto done; + } + + clientoffs = hfrp_reg_read(hfrp, hfrp_buffer_clientoffs_r()); + serveroffs = hfrp_reg_read(hfrp, hfrp_buffer_serveroffs_r()); + head_offset = hfrp_buffer_serveroffs_resp_head_v(serveroffs); + tail_offset = hfrp_buffer_clientoffs_resp_tail_v(clientoffs); + + buffer_size = DLA_HFRP_RESP_BUFFER_SIZE; + + /* Read all the pending response. */ + while (tail_offset != head_offset) { + uint32_t ii; + uint32_t header32; + uint32_t seqid; + uint32_t respid; + uint32_t size; + uint32_t payload_size; + uint32_t payload[DLA_HFRP_RESP_PAYLOAD_MAX_LEN]; + uint8_t *payload8b; + struct hfrp_cmd_sequence *sequence; + + /* read header */ + header32 = 0U; + for (ii = 0U; ii < sizeof(header32); ii++) { + uint8_t header_byte; + + header_byte = hfrp_reg_read1B(hfrp, + hfrp_buffer_resp_r(tail_offset)); + header32 |= (((uint32_t) header_byte) << (ii * 8U)); + tail_offset = ((tail_offset + 1U) % buffer_size); + } + + seqid = hfrp_buffer_resp_header_seqid_v(header32); + respid = hfrp_buffer_resp_header_seqid_v(header32); + size = hfrp_buffer_resp_header_size_v(header32); + payload_size = size - sizeof(header32); + + nvdla_dbg_info(pdev, "seqid=%x respid=%x size=%x\n", + seqid, respid, size); + + /* read payload. */ + payload8b = (uint8_t *) payload; + for (ii = 0U; ii < payload_size; ii++) { + payload8b[ii] = hfrp_reg_read1B(hfrp, + hfrp_buffer_resp_r(tail_offset)); + tail_offset = ((tail_offset + 1U) % buffer_size); + } + + /* Signal sequence ID only on success. */ + if (seqid != DLA_HFRP_SEQUENCE_ID_ASYNC) { + sequence = &hfrp->sequence_pool[seqid]; + hfrp_handle_response(hfrp, sequence->cmdid, + (uint8_t *) payload, sizeof(payload)); + complete(&sequence->completion); + s_hfrp_cmd_sequence_destroy(sequence); + } + } + + /* Update the tail offset */ + clientoffs &= ~(hfrp_buffer_clientoffs_resp_tail_m()); + clientoffs |= hfrp_buffer_clientoffs_resp_tail_f(head_offset); + hfrp_reg_write(hfrp, clientoffs, hfrp_buffer_clientoffs_r()); + + /* Clear the doorbell */ + hfrp_reg_write(hfrp, hfrp_irq_out_clr_doorbell_f(1U), + hfrp_irq_out_clr_r()); + + (void) irq; + +done: + /* For now force clear all the register. */ + hfrp_reg_write(hfrp, 0xffffffffU, + hfrp_irq_out_clr_r()); + return IRQ_HANDLED; +} + +int32_t hfrp_send_cmd(struct hfrp *hfrp, + uint32_t cmd, + uint8_t *payload, + uint32_t payload_size, + bool blocking) +{ + int32_t err; + + int32_t ii; + uint32_t header32; + uint32_t header_size; + uint8_t *header; + + uint32_t clientoffs; + uint32_t serveroffs; + uint32_t head_offset; + uint32_t tail_offset; + uint32_t buffer_size; + uint32_t buffer_used; + + uint64_t timeout; + + struct hfrp_cmd_sequence *sequence; + + header32 = 0U; + header_size = sizeof(header32); + header = (uint8_t *) &header32; + + /* Check if we have sufficient space for sending command. */ + clientoffs = hfrp_reg_read(hfrp, hfrp_buffer_clientoffs_r()); + serveroffs = hfrp_reg_read(hfrp, hfrp_buffer_serveroffs_r()); + + head_offset = hfrp_buffer_clientoffs_cmd_head_v(clientoffs); + tail_offset = hfrp_buffer_serveroffs_cmd_tail_v(serveroffs); + buffer_size = DLA_HFRP_CMD_BUFFER_SIZE; + buffer_used = ((buffer_size + head_offset - tail_offset) % buffer_size); + if ((buffer_used + header_size + payload_size) >= buffer_size) { + err = -ENOMEM; + goto fail; + } + + sequence = s_hfrp_cmd_sequence_alloc(hfrp); + if (sequence == NULL) { + err = -ENOMEM; + goto fail; + } + + sequence->cmdid = cmd; + + nvdla_dbg_info(hfrp->pdev, "seqid=%x cmdid=%x\n", + sequence->seqid, sequence->cmdid); + + /* Construct header */ + header32 |= hfrp_buffer_cmd_header_size_f((payload_size + header_size)); + header32 |= hfrp_buffer_cmd_header_seqid_f(sequence->seqid); + header32 |= hfrp_buffer_cmd_header_cmdid_f(cmd); + + /* Copy the header and payload byte-by-byte (to be safe). */ + for (ii = 0U; ii < header_size; ii++) { + hfrp_reg_write1B(hfrp, header[ii], + hfrp_buffer_cmd_r(head_offset)); + head_offset = ((head_offset + 1U) % buffer_size); + } + + for (ii = 0U; ii < payload_size; ii++) { + hfrp_reg_write1B(hfrp, payload[ii], + hfrp_buffer_cmd_r(head_offset)); + head_offset = ((head_offset + 1U) % buffer_size); + } + + /* Update the command head pointer */ + clientoffs &= ~(hfrp_buffer_clientoffs_cmd_head_m()); + clientoffs |= hfrp_buffer_clientoffs_cmd_head_f(head_offset); + hfrp_reg_write(hfrp, clientoffs, hfrp_buffer_clientoffs_r()); + + /* Ring the door bell */ + hfrp_reg_write(hfrp, hfrp_irq_in_set_doorbell_f(1U), + hfrp_irq_in_set_r()); + + /* Block if requested for response and error with 1s timeout */ + if (blocking) { + timeout = msecs_to_jiffies(1000U); + if (!wait_for_completion_timeout(&sequence->completion, + timeout)) { + nvdla_dbg_err(hfrp->pdev, + "DLA-HFRP response timedout.\n"); + err = -ETIMEDOUT; + goto fail; + } + } + return 0; + +fail: + return err; +} + +/* PM Implementation */ +int32_t nvdla_pm_init(struct platform_device *pdev) +{ + int32_t err; + int32_t irq; + struct hfrp *hfrp; + struct nvhost_device_data *pdata; + struct nvdla_device *nvdladev; + void __iomem *regs; + struct hfrp_cmd_sequence *seqpool; + uint32_t ii; + + pdata = platform_get_drvdata(pdev); + nvdladev = pdata->private_data; + regs = pdata->aperture[NVDLA_HFRP_APERTURE_IDX]; + + irq = platform_get_irq(pdev, NVDLA_HFRP_APERTURE_IDX); + if (irq < 0) { + nvdla_dbg_err(pdev, "failed to get HFRP IRQ\n"); + err = -ENXIO; + goto fail; + } + + err = devm_request_irq(&pdev->dev, irq, s_hfrp_isr, 0, + dev_name(&pdev->dev), pdev); + if (err) { + nvdla_dbg_err(pdev, "failed to request hfrp irq. err %d\n", + err); + goto fail; + } + + /* Keep the interrupt disabled */ + disable_irq(irq); + + /* Device managed allocation - for HFRP */ + hfrp = devm_kzalloc(&pdev->dev, sizeof(*hfrp), GFP_KERNEL); + if (!hfrp) { + err = -ENOMEM; + goto free_irq; + } + + /* Device managed allocation - for sequence pool */ + seqpool = devm_kzalloc(&pdev->dev, + sizeof(struct hfrp_cmd_sequence) * + DLA_HFRP_MAX_NUM_SEQ, + GFP_KERNEL); + if (!seqpool) { + err = -ENOMEM; + goto free_hfrp; + } + + hfrp->pdev = pdev; + hfrp->irq = irq; + hfrp->regs = regs; + hfrp->sequence_pool = seqpool; + hfrp->nsequences = DLA_HFRP_MAX_NUM_SEQ; + INIT_LIST_HEAD(&hfrp->seq_freelist); + mutex_init(&hfrp->cmd_lock); + for (ii = 0U; ii < DLA_HFRP_MAX_NUM_SEQ; ii++) { + struct hfrp_cmd_sequence *sequence; + + sequence = &hfrp->sequence_pool[ii]; + + sequence->hfrp = hfrp; + sequence->seqid = ii; + sequence->cmdid = 0U; + init_completion(&sequence->completion); + + list_add_tail(&sequence->list, &hfrp->seq_freelist); + } + + mutex_lock(&s_hfrp_list_lock); + list_add_tail(&hfrp->list, &s_hfrp_list); + mutex_unlock(&s_hfrp_list_lock); + + enable_irq(irq); + + /* Select circular mode */ + hfrp_reg_write(hfrp, hfrp_mailbox0_mode_circular_v(), + hfrp_mailbox0_mode_r()); + + return 0; + +free_hfrp: + devm_kfree(&pdev->dev, hfrp); +free_irq: + devm_free_irq(&pdev->dev, irq, pdev); +fail: + return err; +} + +void nvdla_pm_deinit(struct platform_device *pdev) +{ + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp != NULL) { + mutex_lock(&s_hfrp_list_lock); + list_del(&hfrp->list); + mutex_unlock(&s_hfrp_list_lock); + + mutex_destroy(&hfrp->cmd_lock); + disable_irq(hfrp->irq); + devm_free_irq(&pdev->dev, hfrp->irq, pdev); + devm_kfree(&pdev->dev, hfrp->sequence_pool); + devm_kfree(&pdev->dev, hfrp); + } +} + +int32_t nvdla_pm_rail_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking) +{ + int32_t err; + + struct nvdla_hfrp_cmd_config config_cmd; + struct nvdla_hfrp_cmd_power_ctrl cmd; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + /* Configure delay values through config command */ + if (timeout_us > 0U) { + memset(&config_cmd, 0, sizeof(config_cmd)); + config_cmd.rg_delay_ms = timeout_us >> 10; + err = nvdla_hfrp_send_cmd_config(hfrp, &config_cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Config cmd send fail. err %d", + err); + goto fail; + } + } + + /* Send power control command */ + memset(&cmd, 0, sizeof(cmd)); + if (timeout_us > 0U) + cmd.rail_delayed_off = true; + else + cmd.rail_off = true; + cmd.pps = 3U; + + err = nvdla_hfrp_send_cmd_power_ctrl(hfrp, &cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Power ctrl cmd send fail. err %d", err); + goto fail; + } + + (void) blocking; + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_rail_ungate(struct platform_device *pdev) +{ + int32_t err; + + struct nvdla_hfrp_cmd_power_ctrl cmd; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.rail_on = true; + + err = nvdla_hfrp_send_cmd_power_ctrl(hfrp, &cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Power ctrl cmd send fail. err %d", err); + goto fail; + } + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_rail_is_gated(struct platform_device *pdev, + bool *gated) +{ + int32_t err; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + mutex_lock(&hfrp->cmd_lock); + *gated = hfrp->rail_gated; + mutex_unlock(&hfrp->cmd_lock); + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_power_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking) +{ + int32_t err; + + struct nvdla_hfrp_cmd_config config_cmd; + struct nvdla_hfrp_cmd_power_ctrl cmd; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + /* Configure delay values through config command */ + if (timeout_us > 0U) { + memset(&config_cmd, 0, sizeof(config_cmd)); + config_cmd.pg_delay_ms = timeout_us >> 10; + err = nvdla_hfrp_send_cmd_config(hfrp, &config_cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Config cmd send fail. err %d", + err); + goto fail; + } + } + + memset(&cmd, 0, sizeof(cmd)); + if (timeout_us > 0U) + cmd.power_delayed_off = true; + else + cmd.power_off = true; + + err = nvdla_hfrp_send_cmd_power_ctrl(hfrp, &cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Power ctrl cmd send fail. err %d", err); + goto fail; + } + + (void) blocking; + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_power_ungate(struct platform_device *pdev) +{ + int32_t err; + + struct nvdla_hfrp_cmd_power_ctrl cmd; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.power_on = true; + + err = nvdla_hfrp_send_cmd_power_ctrl(hfrp, &cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Power ctrl cmd send fail. err %d", err); + goto fail; + } + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_power_is_gated(struct platform_device *pdev, + bool *gated) +{ + int32_t err; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + mutex_lock(&hfrp->cmd_lock); + *gated = hfrp->power_gated; + mutex_unlock(&hfrp->cmd_lock); + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_clock_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking) +{ + int32_t err; + + struct nvdla_hfrp_cmd_config config_cmd; + struct nvdla_hfrp_cmd_power_ctrl cmd; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + /* Configure delay values through config command */ + if (timeout_us > 0U) { + memset(&config_cmd, 0, sizeof(config_cmd)); + config_cmd.cg_delay_ms = timeout_us >> 10; + err = nvdla_hfrp_send_cmd_config(hfrp, &config_cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Config cmd send fail. err %d", + err); + goto fail; + } + } + + memset(&cmd, 0, sizeof(cmd)); + if (timeout_us > 0) + cmd.clock_delayed_off = true; + else + cmd.clock_off = true; + + err = nvdla_hfrp_send_cmd_power_ctrl(hfrp, &cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Power ctrl cmd send fail. err %d", err); + goto fail; + } + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_clock_ungate(struct platform_device *pdev) +{ + int32_t err; + + struct nvdla_hfrp_cmd_power_ctrl cmd; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.clock_on = true; + + err = nvdla_hfrp_send_cmd_power_ctrl(hfrp, &cmd, false); + if (err < 0) { + nvdla_dbg_err(pdev, "Power ctrl cmd send fail. err %d", err); + goto fail; + } + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_clock_is_gated(struct platform_device *pdev, + bool *gated) +{ + int32_t err; + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + mutex_lock(&hfrp->cmd_lock); + *gated = hfrp->clock_gated; + mutex_unlock(&hfrp->cmd_lock); + + return 0; +fail: + return err; +} + +int32_t nvdla_pm_clock_set_mcu_freq(struct platform_device *pdev, + uint32_t freq_khz) +{ + int32_t err = 0; + struct nvdla_cmd_data cmd_data; + + /* prepare command data */ + cmd_data.method_id = DLA_CMD_SET_CLOCK_FREQ; + cmd_data.method_data = freq_khz; + cmd_data.wait = true; + + if (pdev == NULL) { + err = -EFAULT; + goto fail_no_dev; + } + + /* make sure that device is powered on */ + err = nvdla_module_busy(pdev); + if (err != 0) { + nvdla_dbg_err(pdev, "failed to power on\n"); + err = -ENODEV; + goto fail_no_dev; + } + + /* pass set debug command to falcon */ + err = nvdla_fw_send_cmd(pdev, &cmd_data); + if (err != 0) { + nvdla_dbg_err(pdev, "failed to send set mcu freq command"); + goto fail_to_send_cmd; + } + +fail_to_send_cmd: + nvdla_module_idle(pdev); +fail_no_dev: + return err; +} + +int32_t nvdla_pm_clock_get_mcu_freq(struct platform_device *pdev, + uint32_t *freq_khz) +{ + int32_t err; + + err = nvdla_pm_clock_get_core_freq(pdev, freq_khz); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get mcu freq. err: %d\n", err); + goto fail; + } + + /* Core frequency is twice the MCU frequency. */ + *freq_khz = ((*freq_khz) >> 1); + + return 0; + +fail: + return err; +} + +int32_t nvdla_pm_clock_set_core_freq(struct platform_device *pdev, + uint32_t freq_khz) +{ + int32_t err; + + /* Core frequency is twice the MCU frequency. */ + err = nvdla_pm_clock_set_mcu_freq(pdev, (freq_khz >> 1)); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get mcu freq. err: %d\n", err); + goto fail; + } + + return 0; + +fail: + return err; +} + +int32_t nvdla_pm_clock_get_core_freq(struct platform_device *pdev, + uint32_t *freq_khz) +{ + int32_t err; + + struct hfrp *hfrp; + + hfrp = s_hfrp_get_by_pdev(pdev); + if (hfrp == NULL) { + err = -EINVAL; + goto fail; + } + + err = nvdla_hfrp_send_cmd_get_current_freq(hfrp, true); + if (err < 0) { + nvdla_dbg_err(pdev, "failed to get freq. err: %d\n", err); + goto fail; + } + + *freq_khz = hfrp->core_freq_khz; + + return 0; + +fail: + return err; +} diff --git a/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp.h b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp.h new file mode 100644 index 00000000..5114785b --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp.h @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + */ + +#ifndef __NVDLA_PM_HFRP_H_ +#define __NVDLA_PM_HFRP_H_ + +#include +#include +#include +#include + +/* Header and Payload Defines */ +#define DLA_HFRP_CMD_PAYLOAD_MAX_LEN 32U +#define DLA_HFRP_RESP_PAYLOAD_MAX_LEN 32U + +/* Circular buffer size */ +#define DLA_HFRP_CMD_BUFFER_SIZE 116U +#define DLA_HFRP_RESP_BUFFER_SIZE 116U + +/* This is reasonable number given command buffer size 116 bytes */ +#define DLA_HFRP_MAX_NUM_SEQ 64U +struct hfrp_cmd_sequence { + struct hfrp *hfrp; + +#define DLA_HFRP_SEQUENCE_ID_ASYNC 0x3ffU + uint32_t seqid; + uint32_t cmdid; + struct completion completion; + + /* Node pointer to be part of HFRP free list */ + struct list_head list; +}; + +struct hfrp { + /* General */ + struct platform_device *pdev; + void __iomem *regs; + int irq; + + /* HFRP command management */ + struct mutex cmd_lock; + + struct hfrp_cmd_sequence *sequence_pool; + uint32_t nsequences; + struct list_head seq_freelist; + + /* Cache latest response for quick accesses */ + bool rail_gated; + bool power_gated; + bool clock_gated; + + uint32_t core_freq_khz; + + /* Node pointer to be a part of a list. */ + struct list_head list; +}; + +static inline void hfrp_reg_write1B(struct hfrp *hfrp, + uint8_t value, + uint32_t offset) +{ + uint32_t write_value; + + write_value = readl(hfrp->regs + ((offset >> 2U) << 2U)); + write_value &= ~(((uint32_t) 0xffU) << ((offset % 4U) * 8U)); + write_value |= (((uint32_t) value) << ((offset % 4U) * 8U)); + + writel(write_value, hfrp->regs + ((offset >> 2U) << 2U)); +} + +static inline uint8_t hfrp_reg_read1B(struct hfrp *hfrp, + uint32_t offset) +{ + uint32_t read_value; + + read_value = readl(hfrp->regs + ((offset >> 2U) << 2U)); + read_value = read_value >> ((offset % 4U) * 8U); + read_value = read_value & 0xffU; + + return (uint8_t) read_value; +} + +static inline void hfrp_reg_write(struct hfrp *hfrp, + uint32_t value, + uint32_t offset) +{ + writel(value, hfrp->regs + offset); +} + +static inline uint32_t hfrp_reg_read(struct hfrp *hfrp, uint32_t offset) +{ + return readl(hfrp->regs + offset); +} + +/* Command and Response Headers */ +static inline uint32_t hfrp_buffer_cmd_header_size_f(uint32_t v) +{ + /* BUFFER_CMD_HEADER_SIZE 0:7 */ + return (v & 0xffU); +} + +static inline uint32_t hfrp_buffer_cmd_header_size_v(uint32_t r) +{ + /* BUFFER_CMD_HEADER_SIZE 0:7 */ + return (r & 0xffU); +} + +static inline uint32_t hfrp_buffer_cmd_header_seqid_f(uint32_t v) +{ + /* BUFFER_CMD_HEADER_SEQUENCE_ID 17:8 */ + return ((v & 0x3ffU) << 8); +} + +static inline uint32_t hfrp_buffer_cmd_header_seqid_v(uint32_t r) +{ + /* BUFFER_CMD_HEADER_SEQUENCE_ID 17:8 */ + return ((r >> 8) & 0x3ffU); +} + +static inline uint32_t hfrp_buffer_cmd_header_cmdid_f(uint32_t v) +{ + /* BUFFER_CMD_HEADER_COMMAND_ID 27:18 */ + return ((v & 0x3ffU) << 18); +} + +static inline uint32_t hfrp_buffer_cmd_header_cmdid_v(uint32_t r) +{ + /* BUFFER_CMD_HEADER_COMMAND_ID 27:18 */ + return ((r >> 18) & 0x3ffU); +} + +static inline uint32_t hfrp_buffer_resp_header_size_f(uint32_t v) +{ + /* BUFFER_RESPONSE_HEADER_SIZE 0:7 */ + return (v & 0xffU); +} + +static inline uint32_t hfrp_buffer_resp_header_size_v(uint32_t r) +{ + /* BUFFER_RESPONSE_HEADER_SIZE 0:7 */ + return (r & 0xffU); +} + +static inline uint32_t hfrp_buffer_resp_header_seqid_f(uint32_t v) +{ + /* BUFFER_RESPONSE_HEADER_SEQUENCE_ID 17:8 */ + return ((v & 0x3ffU) << 8); +} + +static inline uint32_t hfrp_buffer_resp_header_seqid_v(uint32_t r) +{ + /* BUFFER_RESPONSE_HEADER_SEQUENCE_ID 17:8 */ + return ((r >> 8) & 0x3ffU); +} + +static inline uint32_t hfrp_buffer_resp_header_respid_f(uint32_t v) +{ + /* BUFFER_RESPONSE_HEADER_RESPONSE_ID 27:18 */ + return ((v & 0x3ffU) << 18); +} + +static inline uint32_t hfrp_buffer_resp_header_respid_v(uint32_t r) +{ + /* BUFFER_RESPONSE_HEADER_RESPONSE_ID 27:18 */ + return ((r >> 18) & 0x3ffU); +} + +/** + * HFRP Communication protocol + **/ +int32_t hfrp_send_cmd(struct hfrp *hfrp, + uint32_t cmd, + uint8_t *payload, + uint32_t payload_size, + bool blocking); + +/** + * DLA-HFRP Command Request and Response Policy + **/ +void hfrp_handle_response(struct hfrp *hfrp, + uint32_t cmd, + uint8_t *payload, + uint32_t payload_size); + +/* For gating and ungating of Clock, Power, and Rail */ +struct nvdla_hfrp_cmd_power_ctrl { + bool power_on; + bool power_off; + bool power_delayed_off; + bool rail_on; + bool rail_off; + bool rail_delayed_off; + bool clock_on; + bool clock_off; + bool clock_delayed_off; + int8_t pps; +}; + +int32_t nvdla_hfrp_send_cmd_power_ctrl(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_power_ctrl *cmd, + bool blocking); + +/* For configuration of delay value in the event of delayed gating */ +struct nvdla_hfrp_cmd_config { + uint16_t cg_delay_ms; + uint16_t pg_delay_ms; + uint16_t rg_delay_ms; + + uint32_t pg_entry_freq_khz; + uint32_t pg_exit_freq_khz; +}; + +int32_t nvdla_hfrp_send_cmd_config(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_config *cmd, + bool blocking); + +/* For getting the current frequency */ +int32_t nvdla_hfrp_send_cmd_get_current_freq(struct hfrp *hfrp, + bool blocking); + +#endif /* __NVDLA_PM_HFRP_H_ */ diff --git a/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_cmd_v1_2.c b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_cmd_v1_2.c new file mode 100644 index 00000000..3488819e --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_cmd_v1_2.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * NVDLA HFRP Command Implementation + */ + +#include "nvdla_pm_hfrp.h" + +#include "../../nvdla_debug.h" + +/* Commands */ +#define DLA_HFRP_CMD_POWER_CONTROL 400 +#define DLA_HFRP_CMD_CONFIG 401 +#define DLA_HFRP_CMD_GET_CURRENT_VOLTAGE 402 +#define DLA_HFRP_CMD_GET_CURRENT_POWER_DRAW 405 +#define DLA_HFRP_CMD_SET_POWER_DRAW_CAP 406 +#define DLA_HFRP_CMD_GET_POWER_DRAW_CAP 407 +#define DLA_HFRP_CMD_SET_POWER_CONTROL_TUNING 408 +#define DLA_HFRP_CMD_RESOURCE_REQUEST 409 +#define DLA_HFRP_CMD_RESET 410 +#define DLA_HFRP_CMD_RESET_IDLE 411 +#define DLA_HFRP_CMD_SRAM_HOLD 412 +#define DLA_HFRP_CMD_CLOCK_GATE 413 +#define DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ 414 +#define DLA_HFRP_CMD_GET_AVG_CLOCK_FREQ 416 +#define DLA_HFRP_CMD_SLCG_OVERRIDE 417 +#define DLA_HFRP_CMD_GET_CURRENT_MEM_CLOCK_FREQ 418 +#define DLA_HFRP_CMD_GET_MAXIMUM_MEM_CLOCK_FREQ 419 +#define DLA_HFRP_CMD_GET_MAXIMUM_MEMORY_BW 420 +#define DLA_HFRP_CMD_CONFIG_EMI_BW_MONITOR_CTR 421 +#define DLA_HFRP_CMD_READ_EMI_BW_MONITOR_CTR 422 +#define DLA_HFRP_CMD_CONFIG_SLC_BW_MONITOR_CTR 423 +#define DLA_HFRP_CMD_READ_SLC_BW_MONITOR_CTR 424 +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE 425 +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE_LIMIT 426 +#define DLA_HFRP_CMD_GET_PERF_LIMIT_REASON 427 + +/* Command and header buffer size */ +#define SZ(cmd) cmd##_SZ + +#define DLA_HFRP_CMD_POWER_CONTROL_SZ 2U +#define DLA_HFRP_CMD_CONFIG_SZ 12U +#define DLA_HFRP_CMD_GET_CURRENT_VOLTAGE_SZ 4U +#define DLA_HFRP_CMD_GET_CURRENT_POWER_DRAW_SZ 4U +#define DLA_HFRP_CMD_SET_POWER_DRAW_CAP_SZ 4U +#define DLA_HFRP_CMD_GET_POWER_DRAW_CAP_SZ 4U +#define DLA_HFRP_CMD_SET_POWER_CONTROL_TUNING_SZ 36U +#define DLA_HFRP_CMD_RESOURCE_REQUEST_SZ 8U +#define DLA_HFRP_CMD_RESET_SZ 1U +#define DLA_HFRP_CMD_RESET_IDLE_SZ 1U +#define DLA_HFRP_CMD_SRAM_HOLD_SZ 1U +#define DLA_HFRP_CMD_CLOCK_GATE_SZ 1U +#define DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ_SZ 0U +#define DLA_HFRP_CMD_GET_AVG_CLOCK_FREQ_SZ 4U +#define DLA_HFRP_CMD_SLCG_OVERRIDE_SZ 1U +#define DLA_HFRP_CMD_GET_CURRENT_MEM_CLOCK_FREQ_SZ 4U +#define DLA_HFRP_CMD_GET_MAXIMUM_MEM_CLOCK_FREQ_SZ 4U +#define DLA_HFRP_CMD_GET_MAXIMUM_MEMORY_BW_SZ 4U +#define DLA_HFRP_CMD_CONFIG_EMI_BW_MONITOR_CTR_SZ 8U +#define DLA_HFRP_CMD_READ_EMI_BW_MONITOR_CTR_SZ 8U +#define DLA_HFRP_CMD_CONFIG_SLC_BW_MONITOR_CTR_SZ 12U +#define DLA_HFRP_CMD_READ_SLC_BW_MONITOR_CTR_SZ 8U +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE_SZ 2U +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE_LIMIT_SZ 4U +#define DLA_HFRP_CMD_GET_PERF_LIMIT_REASON_SZ 1U + +static int32_t s_nvdla_hfrp_send_cmd_clock_gate(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_power_ctrl *cmd, + bool blocking) +{ + int32_t err; + + uint32_t payload[(SZ(DLA_HFRP_CMD_CLOCK_GATE) >> 2) + 1U]; + uint32_t payload_size; + + memset(payload, 0, sizeof(payload)); + payload_size = SZ(DLA_HFRP_CMD_CLOCK_GATE); + + /** + * Core 0:0 + * MCU 1:1 + * CFG 2:2 + **/ + payload[0] |= (((uint32_t)(cmd->clock_off)) << 0); + payload[0] |= (((uint32_t)(cmd->clock_off)) << 1); + payload[0] |= (((uint32_t)(cmd->clock_off)) << 2); + + err = (hfrp_send_cmd(hfrp, DLA_HFRP_CMD_CLOCK_GATE, + (uint8_t *) payload, payload_size, blocking)); + + return err; +} + +static int32_t s_nvdla_hfrp_send_cmd_power_ctrl(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_power_ctrl *cmd, + bool blocking) +{ + int32_t err; + + uint32_t payload[(SZ(DLA_HFRP_CMD_POWER_CONTROL) >> 2) + 1U]; + uint32_t payload_size; + + memset(payload, 0, sizeof(payload)); + payload_size = SZ(DLA_HFRP_CMD_POWER_CONTROL); + + /** + * MTCMOS OFF 0:0 + * RAIL OFF 1:1 + * MTCMOS ON 2:2 + * RAIL ON 3:3 + * MTCMOS DELAYED_OFF 4:4 + * RAIL DELAYED_OFF 5:5 + * PPS 8:15 + **/ + payload[0] |= (((uint32_t)(cmd->power_off)) << 0); + payload[0] |= (((uint32_t)(cmd->rail_off)) << 1); + payload[0] |= (((uint32_t)(cmd->power_on)) << 2); + payload[0] |= (((uint32_t)(cmd->rail_on)) << 3); + payload[0] |= (((uint32_t)(cmd->power_delayed_off)) << 4); + payload[0] |= (((uint32_t)(cmd->rail_delayed_off)) << 5); + payload[0] |= (((uint32_t)(cmd->pps) & 0xffU) << 8); + + err = (hfrp_send_cmd(hfrp, DLA_HFRP_CMD_POWER_CONTROL, + (uint8_t *) payload, payload_size, blocking)); + + return err; +} + +int32_t nvdla_hfrp_send_cmd_power_ctrl(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_power_ctrl *cmd, + bool blocking) +{ + int32_t err; + struct platform_device *pdev; + + pdev = hfrp->pdev; + + if (cmd->clock_off || cmd->clock_on || cmd->clock_delayed_off) { + err = s_nvdla_hfrp_send_cmd_clock_gate(hfrp, cmd, blocking); + if (err < 0) { + nvdla_dbg_err(pdev, "Failed to send clk gate. err %d", + err); + goto fail; + } + } + + if (cmd->power_off || cmd->power_on || cmd->power_delayed_off || + cmd->rail_off || cmd->rail_on || + cmd->rail_delayed_off) { + err = s_nvdla_hfrp_send_cmd_power_ctrl(hfrp, cmd, blocking); + if (err < 0) { + nvdla_dbg_err(pdev, "Failed to send power ctrl. err %d", + err); + goto fail; + } + } + + return 0; + +fail: + return err; +} + +int32_t nvdla_hfrp_send_cmd_config(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_config *cmd, + bool blocking) +{ + int32_t err; + + uint32_t payload[(SZ(DLA_HFRP_CMD_CONFIG) >> 2) + 1U]; + uint32_t payload_size; + + memset(payload, 0, sizeof(payload)); + payload_size = SZ(DLA_HFRP_CMD_CONFIG); + + /** + * PG_DELAY 15:0 + * RG_DELAY 31:16 (15:0 byte 2) + * PG_ENTRY_FREQ 31:0 (byte 4) + * PG_EXIT_FREQ 31:0 (byte 8) + **/ + payload[0] |= (((uint32_t)(cmd->pg_delay_ms) & 0xffffU)); + payload[0] |= (((uint32_t)(cmd->rg_delay_ms) & 0xffffU) << 16); + payload[1] |= ((uint32_t)(cmd->pg_entry_freq_khz)); + payload[2] |= ((uint32_t)(cmd->pg_exit_freq_khz)); + + err = (hfrp_send_cmd(hfrp, DLA_HFRP_CMD_CONFIG, + (uint8_t *) payload, payload_size, blocking)); + + return err; +} + +int32_t nvdla_hfrp_send_cmd_get_current_freq(struct hfrp *hfrp, + bool blocking) +{ + int32_t err; + + /* No payload */ + err = hfrp_send_cmd(hfrp, DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ, NULL, + SZ(DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ), blocking); + + return err; +} + +static void s_nvdla_hfrp_handle_response_clock_gate(struct hfrp *hfrp, + uint8_t *payload, + uint32_t payload_size) +{ + uint32_t *response = (uint32_t *) payload; + + /** + * Core 0:0 + * MCU 1:1 + * CFG 2:2 + **/ + hfrp->clock_gated = ((response[0]) & 0x1U); + hfrp->clock_gated = hfrp->clock_gated || ((response[0] >> 1) & 0x1U); + hfrp->clock_gated = hfrp->clock_gated || ((response[0] >> 2) & 0x1U); +} + +static void s_nvdla_hfrp_handle_response_power_ctrl(struct hfrp *hfrp, + uint8_t *payload, + uint32_t payload_size) +{ + uint32_t *response = (uint32_t *) payload; + + /** + * MTCMOS OFF 0:0 + * RAIL OFF 1:1 + * MTCMOS ON 2:2 + * RAIL ON 3:3 + * MTCMOS DELAYED_OFF 4:4 + * RAIL DELAYED_OFF 5:5 + * PPS 8:15 + **/ + + /* Gated if off or delayed off, Ungated if on. */ + if (((response[0]) & 0x1U) || ((response[0] >> 4) & 0x1U)) + hfrp->power_gated = true; + if (((response[0] >> 2) & 0x1U)) + hfrp->power_gated = false; + + if (((response[0] >> 1) & 0x1U) || ((response[0] >> 5) & 0x1U)) + hfrp->rail_gated = true; + if (((response[0] >> 3) & 0x1U)) + hfrp->rail_gated = false; + + /* PPS is unused currently but hfrp shall be extended in the future */ +} + +static void s_nvdla_hfrp_handle_response_get_current_freq(struct hfrp *hfrp, + uint8_t *payload, + uint32_t payload_size) +{ + uint32_t *response = (uint32_t *) payload; + + /** + * FREQ 31:0 + **/ + hfrp->core_freq_khz = response[0]; +} + +void hfrp_handle_response(struct hfrp *hfrp, + uint32_t cmd, + uint8_t *payload, + uint32_t payload_size) +{ + // Expects payload to be 4byte aligned. Eases out the parsing. + BUG_ON(payload_size % 4U != 0U); + + switch (cmd) { + case DLA_HFRP_CMD_POWER_CONTROL: { + s_nvdla_hfrp_handle_response_power_ctrl(hfrp, + payload, payload_size); + break; + } + case DLA_HFRP_CMD_CLOCK_GATE: { + s_nvdla_hfrp_handle_response_clock_gate(hfrp, + payload, payload_size); + break; + } + case DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ: { + s_nvdla_hfrp_handle_response_get_current_freq(hfrp, + payload, payload_size); + break; + } + default: + /* Nothing to handle */ + break; + } +} diff --git a/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_cmd_v1_3.c b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_cmd_v1_3.c new file mode 100644 index 00000000..60e8d95f --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_cmd_v1_3.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * NVDLA HFRP Command Implementation + */ + +#include "nvdla_pm_hfrp.h" + +/* Commands */ +#define DLA_HFRP_CMD_POWER_CONTROL 400 +#define DLA_HFRP_CMD_CONFIG 401 +#define DLA_HFRP_CMD_GET_CURRENT_VOLTAGE 402 +#define DLA_HFRP_CMD_GET_CURRENT_POWER_DRAW 405 +#define DLA_HFRP_CMD_SET_POWER_DRAW_CAP 406 +#define DLA_HFRP_CMD_GET_POWER_DRAW_CAP 407 +#define DLA_HFRP_CMD_SET_POWER_CONTROL_TUNING 408 +#define DLA_HFRP_CMD_RESOURCE_REQUEST 409 +#define DLA_HFRP_CMD_RESET 410 +#define DLA_HFRP_CMD_RESET_IDLE 411 +#define DLA_HFRP_CMD_SRAM_HOLD 412 +#define DLA_HFRP_CMD_CLOCK_GATE 413 +#define DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ 414 +#define DLA_HFRP_CMD_GET_AVG_CLOCK_FREQ 416 +#define DLA_HFRP_CMD_SLCG_OVERRIDE 417 +#define DLA_HFRP_CMD_GET_CURRENT_MEM_CLOCK_FREQ 418 +#define DLA_HFRP_CMD_GET_MAXIMUM_MEM_CLOCK_FREQ 419 +#define DLA_HFRP_CMD_GET_MAXIMUM_MEMORY_BW 420 +#define DLA_HFRP_CMD_CONFIG_EMI_BW_MONITOR_CTR 421 +#define DLA_HFRP_CMD_READ_EMI_BW_MONITOR_CTR 422 +#define DLA_HFRP_CMD_CONFIG_SLC_BW_MONITOR_CTR 423 +#define DLA_HFRP_CMD_READ_SLC_BW_MONITOR_CTR 424 +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE 425 +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE_LIMIT 426 +#define DLA_HFRP_CMD_GET_PERF_LIMIT_REASON 427 + +/* Command and header buffer size */ +#define SZ(cmd) cmd##_SZ + +#define DLA_HFRP_CMD_POWER_CONTROL_SZ 3U +#define DLA_HFRP_CMD_CONFIG_SZ 14U +#define DLA_HFRP_CMD_GET_CURRENT_VOLTAGE_SZ 4U +#define DLA_HFRP_CMD_GET_CURRENT_POWER_DRAW_SZ 4U +#define DLA_HFRP_CMD_SET_POWER_DRAW_CAP_SZ 4U +#define DLA_HFRP_CMD_GET_POWER_DRAW_CAP_SZ 4U +#define DLA_HFRP_CMD_SET_POWER_CONTROL_TUNING_SZ 36U +#define DLA_HFRP_CMD_RESOURCE_REQUEST_SZ 8U +#define DLA_HFRP_CMD_RESET_SZ 1U +#define DLA_HFRP_CMD_RESET_IDLE_SZ 1U +#define DLA_HFRP_CMD_SRAM_HOLD_SZ 1U +#define DLA_HFRP_CMD_CLOCK_GATE_SZ 1U +#define DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ_SZ 0U +#define DLA_HFRP_CMD_GET_AVG_CLOCK_FREQ_SZ 4U +#define DLA_HFRP_CMD_SLCG_OVERRIDE_SZ 1U +#define DLA_HFRP_CMD_GET_CURRENT_MEM_CLOCK_FREQ_SZ 4U +#define DLA_HFRP_CMD_GET_MAXIMUM_MEM_CLOCK_FREQ_SZ 4U +#define DLA_HFRP_CMD_GET_MAXIMUM_MEMORY_BW_SZ 4U +#define DLA_HFRP_CMD_CONFIG_EMI_BW_MONITOR_CTR_SZ 8U +#define DLA_HFRP_CMD_READ_EMI_BW_MONITOR_CTR_SZ 8U +#define DLA_HFRP_CMD_CONFIG_SLC_BW_MONITOR_CTR_SZ 12U +#define DLA_HFRP_CMD_READ_SLC_BW_MONITOR_CTR_SZ 8U +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE_SZ 2U +#define DLA_HFRP_CMD_SYS_GET_TEMPERATURE_LIMIT_SZ 4U +#define DLA_HFRP_CMD_GET_PERF_LIMIT_REASON_SZ 1U + +int32_t nvdla_hfrp_send_cmd_power_ctrl(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_power_ctrl *cmd, + bool blocking) +{ + int32_t err; + + uint32_t payload[(SZ(DLA_HFRP_CMD_POWER_CONTROL) >> 2) + 1U]; + uint32_t payload_size; + + memset(payload, 0, sizeof(payload)); + payload_size = SZ(DLA_HFRP_CMD_POWER_CONTROL); + + /** + * MTCMOS OFF 0:0 + * RAIL OFF 1:1 + * MTCMOS ON 2:2 + * RAIL ON 3:3 + * MTCMOS DELAYED_OFF 4:4 + * RAIL DELAYED_OFF 5:5 + * CLOCK OFF 6:6 + * CLOCK ON 7:7 + * CLOCK_DELAYED_OFF 8:8 + * PPS 16:23 + **/ + payload[0] |= (((uint32_t)(cmd->power_off)) << 0); + payload[0] |= (((uint32_t)(cmd->rail_off)) << 1); + payload[0] |= (((uint32_t)(cmd->power_on)) << 2); + payload[0] |= (((uint32_t)(cmd->rail_on)) << 3); + payload[0] |= (((uint32_t)(cmd->power_delayed_off)) << 4); + payload[0] |= (((uint32_t)(cmd->rail_delayed_off)) << 5); + payload[0] |= (((uint32_t)(cmd->clock_off)) << 6); + payload[0] |= (((uint32_t)(cmd->clock_on)) << 7); + payload[0] |= (((uint32_t)(cmd->clock_delayed_off)) << 8); + payload[0] |= (((uint32_t)(cmd->pps) & 0xffU) << 16); + + err = (hfrp_send_cmd(hfrp, DLA_HFRP_CMD_POWER_CONTROL, + (uint8_t *) payload, payload_size, blocking)); + + return err; +} + +int32_t nvdla_hfrp_send_cmd_config(struct hfrp *hfrp, + struct nvdla_hfrp_cmd_config *cmd, + bool blocking) +{ + int32_t err; + + uint32_t payload[(SZ(DLA_HFRP_CMD_CONFIG) >> 2) + 1U]; + uint32_t payload_size; + + memset(payload, 0, sizeof(payload)); + payload_size = SZ(DLA_HFRP_CMD_CONFIG); + + /** + * PG_DELAY 15:0 + * RG_DELAY 31:16 (15:0 byte 2) + * PG_ENTRY_FREQ 31:0 (byte 4) + * PG_EXIT_FREQ 31:0 (byte 8) + * CG_DELAY 15:0 (byte 12) + **/ + payload[0] |= (((uint32_t)(cmd->pg_delay_ms) & 0xffffU)); + payload[0] |= (((uint32_t)(cmd->rg_delay_ms) & 0xffffU) << 16); + payload[1] |= ((uint32_t)(cmd->pg_entry_freq_khz)); + payload[2] |= ((uint32_t)(cmd->pg_exit_freq_khz)); + payload[3] |= (((uint32_t)(cmd->cg_delay_ms) & 0xffffU)); + + err = (hfrp_send_cmd(hfrp, DLA_HFRP_CMD_CONFIG, + (uint8_t *) payload, payload_size, blocking)); + + return err; +} + +int32_t nvdla_hfrp_send_cmd_get_current_freq(struct hfrp *hfrp, + bool blocking) +{ + int32_t err; + + /* No payload */ + err = hfrp_send_cmd(hfrp, DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ, NULL, + SZ(DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ), blocking); + + return err; +} + +static void s_nvdla_hfrp_handle_response_power_ctrl(struct hfrp *hfrp, + uint8_t *payload, + uint32_t payload_size) +{ + uint32_t *response = (uint32_t *) payload; + + /** + * MTCMOS OFF 0:0 + * RAIL OFF 1:1 + * MTCMOS ON 2:2 + * RAIL ON 3:3 + * MTCMOS DELAYED_OFF 4:4 + * RAIL DELAYED_OFF 5:5 + * CLOCK OFF 6:6 + * CLOCK ON 7:7 + * CLOCK_DELAYED_OFF 8:8 + * PPS 16:23 + **/ + + /* Gated if off or delayed off, Ungated if on. */ + if (((response[0]) & 0x1U) || ((response[0] >> 4) & 0x1U)) + hfrp->power_gated = true; + if (((response[0] >> 2) & 0x1U)) + hfrp->power_gated = false; + + if (((response[0] >> 1) & 0x1U) || ((response[0] >> 5) & 0x1U)) + hfrp->rail_gated = true; + if (((response[0] >> 3) & 0x1U)) + hfrp->rail_gated = false; + + if (((response[0] >> 6) & 0x1U) || ((response[0] >> 8) & 0x1U)) + hfrp->clock_gated = true; + if (((response[0] >> 7) & 0x1U)) + hfrp->clock_gated = false; + + /* PPS is unused currently but hfrp shall be extended in the future */ +} + +static void s_nvdla_hfrp_handle_response_get_current_freq(struct hfrp *hfrp, + uint8_t *payload, + uint32_t payload_size) +{ + uint32_t *response = (uint32_t *) payload; + + /** + * FREQ 31:0 + **/ + hfrp->core_freq_khz = response[0]; +} + +void hfrp_handle_response(struct hfrp *hfrp, + uint32_t cmd, + uint8_t *payload, + uint32_t payload_size) +{ + // Expects payload to be 4byte aligned. Eases out the parsing. + BUG_ON(payload_size % 4U != 0U); + + switch (cmd) { + case DLA_HFRP_CMD_POWER_CONTROL: { + s_nvdla_hfrp_handle_response_power_ctrl(hfrp, + payload, payload_size); + break; + } + case DLA_HFRP_CMD_GET_CURRENT_CLOCK_FREQ: { + s_nvdla_hfrp_handle_response_get_current_freq(hfrp, + payload, payload_size); + break; + } + default: + /* Nothing to handle */ + break; + } +} diff --git a/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_reg.h b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_reg.h new file mode 100644 index 00000000..3e4509a2 --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_hfrp_reg.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _o(void) : Returns the offset for element . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ + +#ifndef __NVDLA_PM_HFRP_REG_H_ +#define __NVDLA_PM_HFRP_REG_H_ + +static inline uint32_t hfrp_mailbox0_mode_r(void) +{ + /* MAILBOX0_MODE */ + return 0x00000004U; +} + +static inline uint32_t hfrp_mailbox0_mode_circular_v(void) +{ + /* MAILBOX0_MODE_CIRCULAR (0:31) */ + return 0x00000001U; +} + +static inline uint32_t hfrp_irq_in_set_r(void) +{ + /* IRQ_IN_SET */ + return 0x00000100U; +} + +static inline uint32_t hfrp_irq_in_set_doorbell_f(uint32_t v) +{ + /* IRQ_IN_SET_DOOR_BELL (1:1) */ + return ((v & 0x1) << 1); +} + +static inline uint32_t hfrp_irq_out_set_r(void) +{ + /* IRQ_OUT_SET */ + return 0x00000104U; +} + +static inline uint32_t hfrp_irq_out_set_doorbell_v(uint32_t r) +{ + /* IRQ_OUT_SET_DOOR_BELL (1:1) */ + return (r >> 1) & 0x1; +} + +static inline uint32_t hfrp_irq_out_set_reset_v(uint32_t r) +{ + /* IRQ_OUT_SET_RESET (0:0) */ + return r & 0x1; +} + + +static inline uint32_t hfrp_irq_in_clr_r(void) +{ + /* IRQ_IN_CLR */ + return 0x00000108U; +} + +static inline uint32_t hfrp_irq_out_clr_r(void) +{ + /* IRQ_OUT_CLR */ + return 0x0000010cU; +} + +static inline uint32_t hfrp_irq_out_clr_doorbell_f(uint32_t v) +{ + /* IRQ_OUT_CLR_DOOR_BELL (1:1) */ + return ((v & 0x1) << 1); +} + +static inline uint32_t hfrp_irq_out_clr_reset_f(uint32_t v) +{ + /* IRQ_OUT_CLR_RESET (0:0) */ + return ((v & 0x1) << 1); +} + +static inline uint32_t hfrp_buffer_clientoffs_r(void) +{ + /* BUFFER_CLIENTOFFS */ + return 0x00000110U; +} + +static inline uint32_t hfrp_buffer_clientoffs_cmd_head_m(void) +{ + /* BUFFER_CLIENTOFFS_CMD_HEAD 0:7 */ + return 0xffU; +} + +static inline uint32_t hfrp_buffer_clientoffs_resp_tail_m(void) +{ + /* BUFFER_CLIENTOFFS_RESPONSE_TAIL 8:15 */ + return (0xffU << 8); +} + +static inline uint32_t hfrp_buffer_clientoffs_cmd_head_f(uint32_t v) +{ + /* BUFFER_CLIENTOFFS_CMD_HEAD 0:7 */ + return (v & 0xffU); +} + +static inline uint32_t hfrp_buffer_clientoffs_cmd_head_v(uint32_t r) +{ + /* BUFFER_CLIENTOFFS_CMD_HEAD 0:7 */ + return (r & 0xffU); +} + +static inline uint32_t hfrp_buffer_clientoffs_resp_tail_f(uint32_t v) +{ + /* BUFFER_CLIENTOFFS_RESPONSE_TAIL 8:15 */ + return ((v & 0xffU) << 8); +} + +static inline uint32_t hfrp_buffer_clientoffs_resp_tail_v(uint32_t r) +{ + /* BUFFER_CLIENTOFFS_RESPONSE_TAIL 8:15 */ + return ((r >> 8) & 0xffU); +} + +static inline uint32_t hfrp_buffer_serveroffs_r(void) +{ + /* BUFFER_SERVEROFFS */ + return 0x00000188U; +} + +static inline uint32_t hfrp_buffer_serveroffs_resp_head_v(uint32_t r) +{ + /* BUFFER_SERVEROFFS_RESPONSE_HEAD 0:7 */ + return (r & 0xffU); +} + +static inline uint32_t hfrp_buffer_serveroffs_cmd_tail_v(uint32_t r) +{ + /* BUFFER_SERVEROFFS_CMD_TAIL 8:15 */ + return ((r >> 8) & 0xffU); +} + +static inline uint32_t hfrp_buffer_cmd_r(uint32_t index) +{ + /* BUFFER_CMD 116B */ + return (0x00000114U + index); +} + +static inline uint32_t hfrp_buffer_resp_r(uint32_t index) +{ + /* BUFFER_RESPONSE 116B */ + return (0x0000018cU + index); +} + +#endif /* __NVDLA_PM_HFRP_REG_H_ */ diff --git a/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_stub.c b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_stub.c new file mode 100644 index 00000000..20dbed07 --- /dev/null +++ b/drivers/video/tegra/host/nvdla/port/pm/nvdla_pm_stub.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * NVDLA Power Management - stub implementation + */ + +#include "../nvdla_pm.h" + +int32_t nvdla_pm_init(struct platform_device *pdev) +{ + (void) pdev; + return -1; +} + +void nvdla_pm_deinit(struct platform_device *pdev) +{ + (void) pdev; +} + + +int32_t nvdla_pm_rail_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking) +{ + (void) pdev; + (void) timeout_us; + (void) blocking; + + return -1; +} + +int32_t nvdla_pm_rail_ungate(struct platform_device *pdev) +{ + (void) pdev; + + return -1; +} + +int32_t nvdla_pm_rail_is_gated(struct platform_device *pdev, + bool *gated) +{ + (void) pdev; + (void) gated; + + return -1; +} + +int32_t nvdla_pm_power_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking) +{ + (void) pdev; + (void) timeout_us; + (void) blocking; + + return -1; +} + +int32_t nvdla_pm_power_ungate(struct platform_device *pdev) +{ + (void) pdev; + + return -1; +} + +int32_t nvdla_pm_power_is_gated(struct platform_device *pdev, + bool *gated) +{ + (void) pdev; + (void) gated; + + return -1; +} + +int32_t nvdla_pm_clock_gate(struct platform_device *pdev, + uint32_t timeout_us, + bool blocking) +{ + (void) pdev; + (void) timeout_us; + (void) blocking; + + return -1; +} + +int32_t nvdla_pm_clock_ungate(struct platform_device *pdev) +{ + (void) pdev; + + return -1; +} + +int32_t nvdla_pm_clock_is_gated(struct platform_device *pdev, + bool *gated) +{ + (void) pdev; + (void) gated; + + return -1; +} + +int32_t nvdla_pm_clock_set_mcu_freq(struct platform_device *pdev, + uint32_t freq_khz) +{ + (void) pdev; + (void) freq_khz; + + return -1; +} + +int32_t nvdla_pm_clock_get_mcu_freq(struct platform_device *pdev, + uint32_t *freq_khz) +{ + (void) pdev; + (void) freq_khz; + + return -1; +} + +int32_t nvdla_pm_clock_set_core_freq(struct platform_device *pdev, + uint32_t freq_khz) +{ + (void) pdev; + (void) freq_khz; + + return -1; +} + +int32_t nvdla_pm_clock_get_core_freq(struct platform_device *pdev, + uint32_t *freq_khz) +{ + (void) pdev; + (void) freq_khz; + + return -1; +}