From 099e5e1cd7aeb8f1747c8e6411b57cd1c5d6ed36 Mon Sep 17 00:00:00 2001 From: Johnny Liu Date: Wed, 3 Sep 2025 09:28:46 +0000 Subject: [PATCH] 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 Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3453755 GVS: buildbot_gerritrpt Reviewed-by: mobile promotions Reviewed-by: Rajkumar Kasirajan Tested-by: mobile promotions --- BUILD.bazel | 1 + drivers/platform/tegra/Makefile | 3 + drivers/platform/tegra/tegra-bpmp-bwmgr.c | 274 ++++++++++++++++++++++ scripts/conftest/Makefile | 1 + scripts/conftest/conftest.sh | 13 + 5 files changed, 292 insertions(+) create mode 100644 drivers/platform/tegra/tegra-bpmp-bwmgr.c diff --git a/BUILD.bazel b/BUILD.bazel index 004ef524..4f7272d0 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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", diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index ae454230..10354cbb 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -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/ diff --git a/drivers/platform/tegra/tegra-bpmp-bwmgr.c b/drivers/platform/tegra/tegra-bpmp-bwmgr.c new file mode 100644 index 00000000..0134ebfb --- /dev/null +++ b/drivers/platform/tegra/tegra-bpmp-bwmgr.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION. All rights reserved. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(NV_TEGRA264_BWMGR_DEBUG_MACRO_PRESENT) +#include +#endif +#include + +#include + +#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 "); +MODULE_LICENSE("GPL v2"); diff --git a/scripts/conftest/Makefile b/scripts/conftest/Makefile index 91a881cf..fd5b2171 100644 --- a/scripts/conftest/Makefile +++ b/scripts/conftest/Makefile @@ -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))) diff --git a/scripts/conftest/conftest.sh b/scripts/conftest/conftest.sh index 911a2f84..74077901 100755 --- a/scripts/conftest/conftest.sh +++ b/scripts/conftest/conftest.sh @@ -8699,6 +8699,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 + 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)