mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
platform: tegra: introduce bwmgr kernel driver
This driver exposes EMC frequency control interface to user space via linux devfreq framework. Users could change EMC frequency to the frequency value from the available frequencies of EMC clock: $ /sys/class/devfreq/bwmgr# cat available_frequencies 665600000 2750000000 3200000000 4266000000 and update the EMC floor frequency by writing into the min_freq QoS sysfs node: $ /sys/class/devfreq/bwmgr# echo 4266000000 > min_freq and update the EMC max frequency by writing into the max_rate QoS sysfs node to cap the EMC frequency: $ /sys/class/devfreq/bwmgr# echo 4266000000 > max_freq This driver does not directly manage the EMC clock rate. Instead it just delivers the min/max frequency information to BPMP, and BPMP is still the only entity that has the full control of EMC and other related memory clocks. Bug 5483386 Bug 5196455 Change-Id: I6124eeb7411a13bde5c51582064534063abca8d3 Signed-off-by: Johnny Liu <johnliu@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3453755 GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: Rajkumar Kasirajan <rkasirajan@nvidia.com> Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
This commit is contained in:
committed by
mobile promotions
parent
b6fa67fca1
commit
099e5e1cd7
@@ -137,6 +137,7 @@ kernel_module(
|
||||
"drivers/platform/tegra/nvadsp/nvadsp.ko",
|
||||
"drivers/platform/tegra/psc/tegra23x_psc.ko",
|
||||
"drivers/platform/tegra/tegra-bootloader-debug.ko",
|
||||
"drivers/platform/tegra/tegra-bpmp-bwmgr.ko",
|
||||
"drivers/platform/tegra/tegra-cactmon-mc-all.ko",
|
||||
"drivers/platform/tegra/tegra-fsicom.ko",
|
||||
"drivers/platform/tegra/tegra-hv-xhci.ko",
|
||||
|
||||
@@ -5,6 +5,9 @@ tegra-bootloader-debug-objs := tegra_bootloader_debug.o
|
||||
obj-m += tegra-bootloader-debug.o
|
||||
|
||||
obj-m += tegra-cactmon-mc-all.o
|
||||
ifdef CONFIG_PM_DEVFREQ
|
||||
obj-m += tegra-bpmp-bwmgr.o
|
||||
endif
|
||||
obj-m += tegra-fsicom.o
|
||||
|
||||
obj-m += mce/
|
||||
|
||||
274
drivers/platform/tegra/tegra-bpmp-bwmgr.c
Normal file
274
drivers/platform/tegra/tegra-bpmp-bwmgr.c
Normal file
@@ -0,0 +1,274 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nvidia/conftest.h>
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/devfreq.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/types.h>
|
||||
#if defined(NV_TEGRA264_BWMGR_DEBUG_MACRO_PRESENT)
|
||||
#include <linux/tegra264-bwmgr.h>
|
||||
#endif
|
||||
#include <soc/tegra/bpmp.h>
|
||||
|
||||
#include <devfreq/governor.h>
|
||||
|
||||
#define DEVFREQ_GOV_BWMGR "bpmp-bwmgr"
|
||||
|
||||
struct tegra_bpmp_bwmgr {
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
struct devfreq *devfreq;
|
||||
struct devfreq_dev_profile *devfreq_profile;
|
||||
struct notifier_block max_freq_nb;
|
||||
struct tegra_bpmp *bpmp;
|
||||
};
|
||||
|
||||
static int tegra_bpmp_bwmgr_max_freq_notifier(struct notifier_block *nb, unsigned long action, void *ptr)
|
||||
{
|
||||
struct tegra_bpmp_bwmgr *bwmgr = container_of(nb, struct tegra_bpmp_bwmgr, max_freq_nb);
|
||||
struct mrq_bwmgr_int_request req = { 0 };
|
||||
struct mrq_bwmgr_int_response resp = { 0 };
|
||||
struct tegra_bpmp_message msg = { 0 };
|
||||
struct dev_pm_qos *qos = bwmgr->dev->power.qos;
|
||||
struct pm_qos_constraints *constraint = &qos->freq.max_freq;
|
||||
unsigned long max_rate = (unsigned long)constraint->target_value * 1000;
|
||||
unsigned long rate = clk_round_rate(bwmgr->clk, max_rate);
|
||||
int err;
|
||||
|
||||
req.cmd = CMD_BWMGR_INT_CAP_SET;
|
||||
req.bwmgr_cap_set_req.rate = rate;
|
||||
|
||||
memset(&msg, 0, sizeof(struct tegra_bpmp_message));
|
||||
msg.mrq = MRQ_BWMGR_INT;
|
||||
msg.tx.data = &req;
|
||||
msg.tx.size = sizeof(struct mrq_bwmgr_int_request);
|
||||
msg.rx.data = &resp;
|
||||
msg.rx.size = sizeof(struct mrq_bwmgr_int_response);
|
||||
|
||||
err = tegra_bpmp_transfer(bwmgr->bpmp, &msg);
|
||||
if (err < 0) {
|
||||
dev_err(bwmgr->dev, "BPMP transfer failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (msg.rx.ret < 0) {
|
||||
dev_err(bwmgr->dev, "failed to cap EMC frequency with %luHz: %d\n", rate, msg.rx.ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_info(bwmgr->dev, "cap EMC frequency to %luHz\n", rate);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int devfreq_gov_bwmgr_target_freq(struct devfreq *df, unsigned long *freq)
|
||||
{
|
||||
*freq = DEVFREQ_MIN_FREQ;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int devfreq_gov_bwmgr_event_handler(struct devfreq *df, unsigned int event, void *data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (event == DEVFREQ_GOV_START) {
|
||||
mutex_lock(&df->lock);
|
||||
ret = update_devfreq(df);
|
||||
mutex_unlock(&df->lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct devfreq_governor devfreq_gov_bwmgr = {
|
||||
.name = DEVFREQ_GOV_BWMGR,
|
||||
.flags = DEVFREQ_GOV_FLAG_IMMUTABLE,
|
||||
.get_target_freq = devfreq_gov_bwmgr_target_freq,
|
||||
.event_handler = devfreq_gov_bwmgr_event_handler,
|
||||
};
|
||||
|
||||
static int tegra_bpmp_bwmgr_devfreq_target(struct device *dev, unsigned long *freq, u32 flags)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct tegra_bpmp_bwmgr *bwmgr = platform_get_drvdata(pdev);
|
||||
struct mrq_bwmgr_int_request bwmgr_req = { 0 };
|
||||
struct mrq_bwmgr_int_request bwmgr_resp = { 0 };
|
||||
struct tegra_bpmp_message msg = { 0 };
|
||||
u32 khz;
|
||||
int err;
|
||||
|
||||
khz = *freq / 1000;
|
||||
bwmgr_req.cmd = CMD_BWMGR_INT_CALC_AND_SET;
|
||||
bwmgr_req.bwmgr_calc_set_req.mc_floor = khz;
|
||||
bwmgr_req.bwmgr_calc_set_req.floor_unit = BWMGR_INT_UNIT_KHZ;
|
||||
#if defined(NV_TEGRA264_BWMGR_DEBUG_MACRO_PRESENT)
|
||||
bwmgr_req.bwmgr_calc_set_req.client_id = TEGRA264_BWMGR_DEBUG;
|
||||
#endif
|
||||
|
||||
msg.mrq = MRQ_BWMGR_INT;
|
||||
msg.tx.data = &bwmgr_req;
|
||||
msg.tx.size = sizeof(bwmgr_req);
|
||||
msg.rx.data = &bwmgr_resp;
|
||||
msg.rx.size = sizeof(bwmgr_resp);
|
||||
|
||||
err = tegra_bpmp_transfer(bwmgr->bpmp, &msg);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "BPMP transfer failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (msg.rx.ret < 0) {
|
||||
dev_err(dev, "fail to set floor frequency with %uKHz\n", khz);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_bpmp_bwmgr_devfreq_register(struct tegra_bpmp_bwmgr *bwmgr)
|
||||
{
|
||||
#if defined(NV_TEGRA264_BWMGR_DEBUG_MACRO_PRESENT)
|
||||
unsigned long max_rate = clk_round_rate(bwmgr->clk, ULONG_MAX);
|
||||
unsigned long min_rate = clk_round_rate(bwmgr->clk, 0);
|
||||
unsigned long rate = min_rate;
|
||||
int ret = 0;
|
||||
|
||||
bwmgr->devfreq_profile = devm_kzalloc(bwmgr->dev, sizeof(*bwmgr->devfreq_profile), GFP_KERNEL);
|
||||
if (!bwmgr->devfreq_profile)
|
||||
return -ENOMEM;
|
||||
|
||||
do {
|
||||
dev_pm_opp_add(bwmgr->dev, rate, 0);
|
||||
if (rate == max_rate)
|
||||
break;
|
||||
|
||||
rate = clk_round_rate(bwmgr->clk, rate + 1);
|
||||
} while (rate <= max_rate);
|
||||
|
||||
bwmgr->devfreq_profile->target = tegra_bpmp_bwmgr_devfreq_target;
|
||||
bwmgr->devfreq_profile->initial_freq = clk_get_rate(bwmgr->clk);
|
||||
bwmgr->devfreq = devm_devfreq_add_device(bwmgr->dev,
|
||||
bwmgr->devfreq_profile,
|
||||
DEVFREQ_GOV_BWMGR, NULL);
|
||||
if (IS_ERR(bwmgr->devfreq)) {
|
||||
dev_pm_opp_remove_all_dynamic(bwmgr->dev);
|
||||
return PTR_ERR(bwmgr->devfreq);
|
||||
}
|
||||
|
||||
bwmgr->max_freq_nb.notifier_call = tegra_bpmp_bwmgr_max_freq_notifier;
|
||||
ret = dev_pm_qos_add_notifier(bwmgr->dev,
|
||||
&bwmgr->max_freq_nb,
|
||||
DEV_PM_QOS_MAX_FREQUENCY);
|
||||
if (ret < 0) {
|
||||
devm_devfreq_remove_device(bwmgr->dev, bwmgr->devfreq);
|
||||
dev_pm_opp_remove_all_dynamic(bwmgr->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void tegra_bpmp_bwmgr_devfreq_unregister(struct tegra_bpmp_bwmgr *bwmgr)
|
||||
{
|
||||
#if defined(NV_TEGRA264_BWMGR_DEBUG_MACRO_PRESENT)
|
||||
dev_pm_qos_remove_notifier(bwmgr->dev,
|
||||
&bwmgr->max_freq_nb,
|
||||
DEV_PM_QOS_MAX_FREQUENCY);
|
||||
devm_devfreq_remove_device(bwmgr->dev, bwmgr->devfreq);
|
||||
dev_pm_opp_remove_all_dynamic(bwmgr->dev);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int tegra_bpmp_bwmgr_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_bpmp_bwmgr *bwmgr;
|
||||
int err;
|
||||
|
||||
bwmgr = devm_kzalloc(&pdev->dev, sizeof(*bwmgr), GFP_KERNEL);
|
||||
if (!bwmgr)
|
||||
return -ENOMEM;
|
||||
|
||||
bwmgr->clk = devm_clk_get(&pdev->dev, "emc");
|
||||
if (IS_ERR(bwmgr->clk))
|
||||
return PTR_ERR(bwmgr->clk);
|
||||
|
||||
bwmgr->bpmp = tegra_bpmp_get(&pdev->dev);
|
||||
if (IS_ERR(bwmgr->bpmp))
|
||||
return PTR_ERR(bwmgr->bpmp);
|
||||
|
||||
bwmgr->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, bwmgr);
|
||||
|
||||
err = devfreq_add_governor(&devfreq_gov_bwmgr);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"fail to register devfreq governor %s: %d\n",
|
||||
DEVFREQ_GOV_BWMGR, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = tegra_bpmp_bwmgr_devfreq_register(bwmgr);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "fail to register devfreq device: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_bpmp_bwmgr_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_bpmp_bwmgr *bwmgr = platform_get_drvdata(pdev);
|
||||
|
||||
tegra_bpmp_bwmgr_devfreq_unregister(bwmgr);
|
||||
devfreq_remove_governor(&devfreq_gov_bwmgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id tegra_bpmp_bwmgr_of_match[] = {
|
||||
{ .compatible = "nvidia,tegra264-bpmp-bwmgr", .data = NULL, },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tegra_bpmp_bwmgr_of_match);
|
||||
|
||||
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
|
||||
static void tegra_bpmp_bwmgr_remove_wrapper(struct platform_device *pdev)
|
||||
{
|
||||
tegra_bpmp_bwmgr_remove(pdev);
|
||||
}
|
||||
#else
|
||||
static int tegra_bpmp_bwmgr_remove_wrapper(struct platform_device *pdev)
|
||||
{
|
||||
return tegra_bpmp_bwmgr_remove(pdev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct platform_driver tegra_bpmp_bwmgr_driver = {
|
||||
.probe = tegra_bpmp_bwmgr_probe,
|
||||
.remove = tegra_bpmp_bwmgr_remove_wrapper,
|
||||
.driver = {
|
||||
.name = "tegra-bpmp-bwmgr",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(tegra_bpmp_bwmgr_of_match),
|
||||
},
|
||||
};
|
||||
module_platform_driver(tegra_bpmp_bwmgr_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Tegra BPMP BWMGR client");
|
||||
MODULE_AUTHOR("Johnny Liu <johnliu@nvidia.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -255,6 +255,7 @@ NV_CONFTEST_GENERIC_COMPILE_TESTS += is_export_symbol_present_drm_gem_prime_hand
|
||||
NV_CONFTEST_GENERIC_COMPILE_TESTS += is_export_symbol_present_queue_limits_set
|
||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += crypto_engine_ctx_struct_removed_test
|
||||
NV_CONFTEST_MACRO_COMPILE_TESTS ?=
|
||||
NV_CONFTEST_MACRO_COMPILE_TESTS += tegra264_bwmgr_debug_macro
|
||||
NV_CONFTEST_SYMBOL_COMPILE_TESTS ?=
|
||||
|
||||
$(eval $(call NV_GENERATE_COMPILE_TEST_HEADER,functions,$(NV_CONFTEST_FUNCTION_COMPILE_TESTS)))
|
||||
|
||||
@@ -8701,6 +8701,19 @@ compile_test() {
|
||||
compile_check_conftest "$CODE" "NV_TEGRA_IVC_USE_IVC_EXT_DRIVER" "" "types"
|
||||
;;
|
||||
|
||||
tegra264_bwmgr_debug_macro)
|
||||
#
|
||||
# Determine if the 'TEGRA264_BWMGR_DEBUG' macro present or not
|
||||
#
|
||||
CODE="
|
||||
#include <linux/tegra264-bwmgr.h>
|
||||
int conftest_tegra264_bwmgr_debug_macro() {
|
||||
return TEGRA264_BWMGR_DEBUG;
|
||||
}"
|
||||
|
||||
compile_check_conftest "$CODE" "NV_TEGRA264_BWMGR_DEBUG_MACRO_PRESENT" "" "macros"
|
||||
;;
|
||||
|
||||
thermal_zone_device_priv)
|
||||
#
|
||||
# Determine if the function thermal_zone_device_priv() is present.
|
||||
|
||||
Reference in New Issue
Block a user