From 6259094ee806fb813ca95894c65fb80e2ec98bf1 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Sun, 28 Dec 2025 16:26:36 +0000 Subject: [PATCH 01/18] soc: mediatek: svs: Fix memory leak in svs_enable_debug_write() In svs_enable_debug_write(), the buf allocated by memdup_user_nul() is leaked if kstrtoint() fails. Fix this by using __free(kfree) to automatically free buf, eliminating the need for explicit kfree() calls and preventing leaks. Fixes: 13f1bbcfb582 ("soc: mediatek: SVS: add debug commands") Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan [Angelo: Added missing cleanup.h inclusion] Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-svs.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/soc/mediatek/mtk-svs.c b/drivers/soc/mediatek/mtk-svs.c index f45537546553..99edecb204f2 100644 --- a/drivers/soc/mediatek/mtk-svs.c +++ b/drivers/soc/mediatek/mtk-svs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -789,7 +790,7 @@ static ssize_t svs_enable_debug_write(struct file *filp, struct svs_bank *svsb = file_inode(filp)->i_private; struct svs_platform *svsp = dev_get_drvdata(svsb->dev); int enabled, ret; - char *buf = NULL; + char *buf __free(kfree) = NULL; if (count >= PAGE_SIZE) return -EINVAL; @@ -807,8 +808,6 @@ static ssize_t svs_enable_debug_write(struct file *filp, svsb->mode_support = SVSB_MODE_ALL_DISABLE; } - kfree(buf); - return count; } From db6dcaeeb60f97ce9765d50035be23c0901d7348 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Tue, 9 Dec 2025 10:33:20 +0100 Subject: [PATCH 02/18] soc: mediatek: mtk-socinfo: Add entry for MT8371AV/AZA Genio 520 Add an entry for the MT8371 SoC with commercial name Genio 520. Signed-off-by: Louis-Alexis Eyraud Reviewed-by: Macpaul Lin Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-socinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/mediatek/mtk-socinfo.c b/drivers/soc/mediatek/mtk-socinfo.c index 978c43e9115a..424a1eb82c20 100644 --- a/drivers/soc/mediatek/mtk-socinfo.c +++ b/drivers/soc/mediatek/mtk-socinfo.c @@ -59,6 +59,7 @@ static struct socinfo_data socinfo_data_table[] = { MTK_SOCINFO_ENTRY("MT8195", "MT8195TV/EZA", "Kompanio 1380", 0x81950400, CELL_NOT_USED), MTK_SOCINFO_ENTRY("MT8195", "MT8195TV/EHZA", "Kompanio 1380", 0x81950404, CELL_NOT_USED), MTK_SOCINFO_ENTRY("MT8370", "MT8370AV/AZA", "Genio 510", 0x83700000, 0x00000081), + MTK_SOCINFO_ENTRY("MT8371", "MT8371AV/AZA", "Genio 520", 0x83710000, 0x00000081), MTK_SOCINFO_ENTRY("MT8390", "MT8390AV/AZA", "Genio 700", 0x83900000, 0x00000080), MTK_SOCINFO_ENTRY("MT8391", "MT8391AV/AZA", "Genio 720", 0x83910000, 0x00000080), MTK_SOCINFO_ENTRY("MT8395", "MT8395AV/ZA", "Genio 1200", 0x83950100, CELL_NOT_USED), From 831ee17036e259da23a6313e28a3cbdda221a88c Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:06:51 +0100 Subject: [PATCH 03/18] dt-bindings: soc: mediatek: dvfsrc: Document clock The DVFSRC hardware has a clock on all platforms. Instead or proliferating the culture of omitting clock descriptions in the clock controller drivers or marking them critical instead of declaring these types of relationships, add this one to the binding. Any device that wishes to use this binding should figure out their incomplete or incorrect clock situation first before piling more features on top. Acked-by: Rob Herring (Arm) Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- .../bindings/soc/mediatek/mediatek,mt8183-dvfsrc.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt8183-dvfsrc.yaml b/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt8183-dvfsrc.yaml index 4c96d4917967..27cce748e0ca 100644 --- a/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt8183-dvfsrc.yaml +++ b/Documentation/devicetree/bindings/soc/mediatek/mediatek,mt8183-dvfsrc.yaml @@ -34,6 +34,10 @@ properties: maxItems: 1 description: DVFSRC common register address and length. + clocks: + items: + - description: Clock that drives the DVFSRC MCU + regulators: type: object $ref: /schemas/regulator/mediatek,mt6873-dvfsrc-regulator.yaml# @@ -50,6 +54,7 @@ additionalProperties: false examples: - | + #include soc { #address-cells = <2>; #size-cells = <2>; @@ -57,6 +62,7 @@ examples: system-controller@10012000 { compatible = "mediatek,mt8195-dvfsrc"; reg = <0 0x10012000 0 0x1000>; + clocks = <&topckgen CLK_TOP_DVFSRC>; regulators { compatible = "mediatek,mt8195-dvfsrc-regulator"; From 23f1b4922a9135515e37d3bbad766e311845071f Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:06:53 +0100 Subject: [PATCH 04/18] soc: mediatek: mtk-dvfsrc: Change error check for DVFSRCv4 START cmd In preparation for adding support for DVFSRC Version 4, change the error check for the MTK_SIP_DVFSRC_START command in the probe function to error out only if BIT(0) is set: this is still valid for the previous DVFSRC versions, as those always set this bit in a fail reply anyway. Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index 41add5636b03..7708b07ab2d6 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -440,7 +440,7 @@ static int mtk_dvfsrc_probe(struct platform_device *pdev) /* Everything is set up - make it run! */ arm_smccc_smc(MTK_SIP_DVFSRC_VCOREFS_CONTROL, MTK_SIP_DVFSRC_START, 0, 0, 0, 0, 0, 0, &ares); - if (ares.a0) + if (ares.a0 & BIT(0)) return dev_err_probe(&pdev->dev, -EINVAL, "Cannot start DVFSRC: %lu\n", ares.a0); return 0; From c2488fecba681d632a3dbb6b2f33c39df2cb7be9 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:06:54 +0100 Subject: [PATCH 05/18] soc: mediatek: mtk-dvfsrc: Add and propagate DVFSRC bandwidth type In preparation for adding support for DVFSRC Version 4, add a new mtk_dvfsrc_bw_type enumeration, and propagate it from specific bw setting callbacks to __dvfsrc_set_dram_bw_v1(), which will use it to choose calculation multipliers and dividers in v4 callbacks. Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index 7708b07ab2d6..a684e405daf7 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -36,6 +36,13 @@ #define MTK_SIP_DVFSRC_INIT 0x0 #define MTK_SIP_DVFSRC_START 0x1 +enum mtk_dvfsrc_bw_type { + DVFSRC_BW_AVG, + DVFSRC_BW_PEAK, + DVFSRC_BW_HRT, + DVFSRC_BW_MAX, +}; + struct dvfsrc_bw_constraints { u16 max_dram_nom_bw; u16 max_dram_peak_bw; @@ -268,7 +275,7 @@ static void dvfsrc_set_vscp_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level) } static void __dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u32 reg, - u16 max_bw, u16 min_bw, u64 bw) + int type, u16 max_bw, u16 min_bw, u64 bw) { u32 new_bw = (u32)div_u64(bw, 100 * 1000); @@ -285,21 +292,21 @@ static void dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) { u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_nom_bw; - __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_BW, max_bw, 0, bw); + __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_BW, DVFSRC_BW_AVG, max_bw, 0, bw); }; static void dvfsrc_set_dram_peak_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) { u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_peak_bw; - __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_PEAK_BW, max_bw, 0, bw); + __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_PEAK_BW, DVFSRC_BW_PEAK, max_bw, 0, bw); } static void dvfsrc_set_dram_hrt_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) { u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_hrt_bw; - __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_HRT_BW, max_bw, 0, bw); + __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_HRT_BW, DVFSRC_BW_HRT, max_bw, 0, bw); } static void dvfsrc_set_opp_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level) From ddb5862a43b1f40ca0a5cc16882277d8d07b966a Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:06:55 +0100 Subject: [PATCH 06/18] soc: mediatek: mtk-dvfsrc: Add a new callback for calc_dram_bw In preparation for adding support for DVFSRC Version 4, add a new callback for calculating the dram bandwidth, assign the current calculation algo to all of the currently supported SoCs, and use this in __dvfsrc_set_dram_bw_v1(). Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index a684e405daf7..3cbccbb7469a 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -73,6 +73,7 @@ struct mtk_dvfsrc { struct dvfsrc_soc_data { const int *regs; const struct dvfsrc_opp_desc *opps_desc; + u32 (*calc_dram_bw)(struct mtk_dvfsrc *dvfsrc, int type, u64 bw); u32 (*get_target_level)(struct mtk_dvfsrc *dvfsrc); u32 (*get_current_level)(struct mtk_dvfsrc *dvfsrc); u32 (*get_vcore_level)(struct mtk_dvfsrc *dvfsrc); @@ -274,10 +275,15 @@ static void dvfsrc_set_vscp_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level) dvfsrc_writel(dvfsrc, DVFSRC_VCORE, val); } +static u32 dvfsrc_calc_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, int type, u64 bw) +{ + return (u32)div_u64(bw, 100 * 1000); +} + static void __dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u32 reg, int type, u16 max_bw, u16 min_bw, u64 bw) { - u32 new_bw = (u32)div_u64(bw, 100 * 1000); + u32 new_bw = dvfsrc->dvd->calc_dram_bw(dvfsrc, type, bw); /* If bw constraints (in mbps) are defined make sure to respect them */ if (max_bw) @@ -519,6 +525,7 @@ static const struct dvfsrc_opp_desc dvfsrc_opp_mt8183_desc[] = { static const struct dvfsrc_soc_data mt8183_data = { .opps_desc = dvfsrc_opp_mt8183_desc, .regs = dvfsrc_mt8183_regs, + .calc_dram_bw = dvfsrc_calc_dram_bw_v1, .get_target_level = dvfsrc_get_target_level_v1, .get_current_level = dvfsrc_get_current_level_v1, .get_vcore_level = dvfsrc_get_vcore_level_v1, @@ -549,6 +556,7 @@ static const struct dvfsrc_opp_desc dvfsrc_opp_mt8195_desc[] = { static const struct dvfsrc_soc_data mt8195_data = { .opps_desc = dvfsrc_opp_mt8195_desc, .regs = dvfsrc_mt8195_regs, + .calc_dram_bw = dvfsrc_calc_dram_bw_v1, .get_target_level = dvfsrc_get_target_level_v2, .get_current_level = dvfsrc_get_current_level_v2, .get_vcore_level = dvfsrc_get_vcore_level_v2, From 7cf9db2aca552f5f537d46f1e52e0ab08ddc2d64 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:06:56 +0100 Subject: [PATCH 07/18] soc: mediatek: mtk-dvfsrc: Write bandwidth to EMI DDR if present In preparation for adding support for DVFSRC Version 4, add a new `has_emi_ddr` member to struct dvfsrc_soc_data: if true, write the DRAM bandwidth both to the BW_AVG and to the newly defined EMI_BW register, present only on DVFSRC v4. Currently supported SoCs will not use this, as has_emi_ddr is left out from their platform data, hence reading false. Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index 3cbccbb7469a..bf0e7b01d255 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -72,6 +72,7 @@ struct mtk_dvfsrc { struct dvfsrc_soc_data { const int *regs; + const bool has_emi_ddr; const struct dvfsrc_opp_desc *opps_desc; u32 (*calc_dram_bw)(struct mtk_dvfsrc *dvfsrc, int type, u64 bw); u32 (*get_target_level)(struct mtk_dvfsrc *dvfsrc); @@ -107,6 +108,7 @@ enum dvfsrc_regs { DVFSRC_SW_BW, DVFSRC_SW_PEAK_BW, DVFSRC_SW_HRT_BW, + DVFSRC_SW_EMI_BW, DVFSRC_VCORE, DVFSRC_REGS_MAX, }; @@ -292,6 +294,9 @@ static void __dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u32 reg, new_bw = max(new_bw, min_bw); dvfsrc_writel(dvfsrc, reg, new_bw); + + if (type == DVFSRC_BW_AVG && dvfsrc->dvd->has_emi_ddr) + dvfsrc_writel(dvfsrc, DVFSRC_SW_EMI_BW, bw); } static void dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) From 75cf308fee7e4b3038741f96fd90afc3bd871e64 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:06:57 +0100 Subject: [PATCH 08/18] soc: mediatek: mtk-dvfsrc: Add support for DVFSRCv4 and MT8196 Add support for the DVFSRC Version 4 by adding new functions for vcore/dram levels (in v4, called gears instead), and for readout of pre-programmed dvfsrc_opp entries, corresponding to each gear. In the probe function, for v4, the curr_opps is initialized from the get_hw_opps() function instead of platform data. In order to make use of the new DVFSRCv4 code, also add support for the MediaTek MT8196 SoC. Co-developed-by: Nicolas Frattaroli Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 248 +++++++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 1 deletion(-) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index bf0e7b01d255..3a83fd4baf54 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -15,11 +15,17 @@ #include #include +/* DVFSRC_BASIC_CONTROL */ +#define DVFSRC_V4_BASIC_CTRL_OPP_COUNT GENMASK(26, 20) + /* DVFSRC_LEVEL */ #define DVFSRC_V1_LEVEL_TARGET_LEVEL GENMASK(15, 0) #define DVFSRC_TGT_LEVEL_IDLE 0x00 #define DVFSRC_V1_LEVEL_CURRENT_LEVEL GENMASK(31, 16) +#define DVFSRC_V4_LEVEL_TARGET_LEVEL GENMASK(15, 8) +#define DVFSRC_V4_LEVEL_TARGET_PRESENT BIT(16) + /* DVFSRC_SW_REQ, DVFSRC_SW_REQ2 */ #define DVFSRC_V1_SW_REQ2_DRAM_LEVEL GENMASK(1, 0) #define DVFSRC_V1_SW_REQ2_VCORE_LEVEL GENMASK(3, 2) @@ -27,9 +33,23 @@ #define DVFSRC_V2_SW_REQ_DRAM_LEVEL GENMASK(3, 0) #define DVFSRC_V2_SW_REQ_VCORE_LEVEL GENMASK(6, 4) +#define DVFSRC_V4_SW_REQ_EMI_LEVEL GENMASK(3, 0) +#define DVFSRC_V4_SW_REQ_DRAM_LEVEL GENMASK(15, 12) + /* DVFSRC_VCORE */ #define DVFSRC_V2_VCORE_REQ_VSCP_LEVEL GENMASK(14, 12) +/* DVFSRC_TARGET_GEAR */ +#define DVFSRC_V4_GEAR_TARGET_DRAM GENMASK(7, 0) +#define DVFSRC_V4_GEAR_TARGET_VCORE GENMASK(15, 8) + +/* DVFSRC_GEAR_INFO */ +#define DVFSRC_V4_GEAR_INFO_REG_WIDTH 0x4 +#define DVFSRC_V4_GEAR_INFO_REG_LEVELS 64 +#define DVFSRC_V4_GEAR_INFO_VCORE GENMASK(3, 0) +#define DVFSRC_V4_GEAR_INFO_EMI GENMASK(7, 4) +#define DVFSRC_V4_GEAR_INFO_DRAM GENMASK(15, 12) + #define DVFSRC_POLL_TIMEOUT_US 1000 #define STARTUP_TIME_US 1 @@ -52,6 +72,7 @@ struct dvfsrc_bw_constraints { struct dvfsrc_opp { u32 vcore_opp; u32 dram_opp; + u32 emi_opp; }; struct dvfsrc_opp_desc { @@ -72,6 +93,7 @@ struct mtk_dvfsrc { struct dvfsrc_soc_data { const int *regs; + const u8 *bw_units; const bool has_emi_ddr; const struct dvfsrc_opp_desc *opps_desc; u32 (*calc_dram_bw)(struct mtk_dvfsrc *dvfsrc, int type, u64 bw); @@ -79,6 +101,8 @@ struct dvfsrc_soc_data { u32 (*get_current_level)(struct mtk_dvfsrc *dvfsrc); u32 (*get_vcore_level)(struct mtk_dvfsrc *dvfsrc); u32 (*get_vscp_level)(struct mtk_dvfsrc *dvfsrc); + u32 (*get_opp_count)(struct mtk_dvfsrc *dvfsrc); + int (*get_hw_opps)(struct mtk_dvfsrc *dvfsrc); void (*set_dram_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw); void (*set_dram_peak_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw); void (*set_dram_hrt_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw); @@ -101,6 +125,7 @@ static void dvfsrc_writel(struct mtk_dvfsrc *dvfs, u32 offset, u32 val) } enum dvfsrc_regs { + DVFSRC_BASIC_CONTROL, DVFSRC_SW_REQ, DVFSRC_SW_REQ2, DVFSRC_LEVEL, @@ -110,6 +135,9 @@ enum dvfsrc_regs { DVFSRC_SW_HRT_BW, DVFSRC_SW_EMI_BW, DVFSRC_VCORE, + DVFSRC_TARGET_GEAR, + DVFSRC_GEAR_INFO_L, + DVFSRC_GEAR_INFO_H, DVFSRC_REGS_MAX, }; @@ -130,6 +158,22 @@ static const int dvfsrc_mt8195_regs[] = { [DVFSRC_TARGET_LEVEL] = 0xd48, }; +static const int dvfsrc_mt8196_regs[] = { + [DVFSRC_BASIC_CONTROL] = 0x0, + [DVFSRC_SW_REQ] = 0x18, + [DVFSRC_VCORE] = 0x80, + [DVFSRC_GEAR_INFO_L] = 0xfc, + [DVFSRC_SW_BW] = 0x1e8, + [DVFSRC_SW_PEAK_BW] = 0x1f4, + [DVFSRC_SW_HRT_BW] = 0x20c, + [DVFSRC_LEVEL] = 0x5f0, + [DVFSRC_TARGET_LEVEL] = 0x5f0, + [DVFSRC_SW_REQ2] = 0x604, + [DVFSRC_SW_EMI_BW] = 0x60c, + [DVFSRC_TARGET_GEAR] = 0x6ac, + [DVFSRC_GEAR_INFO_H] = 0x6b0, +}; + static const struct dvfsrc_opp *dvfsrc_get_current_opp(struct mtk_dvfsrc *dvfsrc) { u32 level = dvfsrc->dvd->get_current_level(dvfsrc); @@ -137,6 +181,20 @@ static const struct dvfsrc_opp *dvfsrc_get_current_opp(struct mtk_dvfsrc *dvfsrc return &dvfsrc->curr_opps->opps[level]; } +static u32 dvfsrc_get_current_target_vcore_gear(struct mtk_dvfsrc *dvfsrc) +{ + u32 val = dvfsrc_readl(dvfsrc, DVFSRC_TARGET_GEAR); + + return FIELD_GET(DVFSRC_V4_GEAR_TARGET_VCORE, val); +} + +static u32 dvfsrc_get_current_target_dram_gear(struct mtk_dvfsrc *dvfsrc) +{ + u32 val = dvfsrc_readl(dvfsrc, DVFSRC_TARGET_GEAR); + + return FIELD_GET(DVFSRC_V4_GEAR_TARGET_DRAM, val); +} + static bool dvfsrc_is_idle(struct mtk_dvfsrc *dvfsrc) { if (!dvfsrc->dvd->get_target_level) @@ -193,6 +251,24 @@ static int dvfsrc_wait_for_opp_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level) return 0; } +static int dvfsrc_wait_for_vcore_level_v4(struct mtk_dvfsrc *dvfsrc, u32 level) +{ + u32 val; + + return readx_poll_timeout_atomic(dvfsrc_get_current_target_vcore_gear, + dvfsrc, val, val >= level, + STARTUP_TIME_US, DVFSRC_POLL_TIMEOUT_US); +} + +static int dvfsrc_wait_for_opp_level_v4(struct mtk_dvfsrc *dvfsrc, u32 level) +{ + u32 val; + + return readx_poll_timeout_atomic(dvfsrc_get_current_target_dram_gear, + dvfsrc, val, val >= level, + STARTUP_TIME_US, DVFSRC_POLL_TIMEOUT_US); +} + static u32 dvfsrc_get_target_level_v1(struct mtk_dvfsrc *dvfsrc) { u32 val = dvfsrc_readl(dvfsrc, DVFSRC_LEVEL); @@ -226,6 +302,27 @@ static u32 dvfsrc_get_current_level_v2(struct mtk_dvfsrc *dvfsrc) return 0; } +static u32 dvfsrc_get_target_level_v4(struct mtk_dvfsrc *dvfsrc) +{ + u32 val = dvfsrc_readl(dvfsrc, DVFSRC_TARGET_LEVEL); + + if (val & DVFSRC_V4_LEVEL_TARGET_PRESENT) + return FIELD_GET(DVFSRC_V4_LEVEL_TARGET_LEVEL, val) + 1; + return 0; +} + +static u32 dvfsrc_get_current_level_v4(struct mtk_dvfsrc *dvfsrc) +{ + u32 level = dvfsrc_readl(dvfsrc, DVFSRC_LEVEL) + 1; + + /* Valid levels */ + if (level < dvfsrc->curr_opps->num_opp) + return dvfsrc->curr_opps->num_opp - level; + + /* Zero for level 0 or invalid level */ + return 0; +} + static u32 dvfsrc_get_vcore_level_v1(struct mtk_dvfsrc *dvfsrc) { u32 val = dvfsrc_readl(dvfsrc, DVFSRC_SW_REQ2); @@ -277,11 +374,30 @@ static void dvfsrc_set_vscp_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level) dvfsrc_writel(dvfsrc, DVFSRC_VCORE, val); } +static u32 dvfsrc_get_opp_count_v4(struct mtk_dvfsrc *dvfsrc) +{ + u32 val = dvfsrc_readl(dvfsrc, DVFSRC_BASIC_CONTROL); + + return FIELD_GET(DVFSRC_V4_BASIC_CTRL_OPP_COUNT, val) + 1; +} + static u32 dvfsrc_calc_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, int type, u64 bw) { return (u32)div_u64(bw, 100 * 1000); } +static u32 dvfsrc_calc_dram_bw_v4(struct mtk_dvfsrc *dvfsrc, int type, u64 bw) +{ + u8 bw_unit = dvfsrc->dvd->bw_units[type]; + u64 bw_mbps; + + if (type < DVFSRC_BW_AVG || type >= DVFSRC_BW_MAX) + return 0; + + bw_mbps = div_u64(bw, 1000); + return (u32)div_u64((bw_mbps + bw_unit - 1), bw_unit); +} + static void __dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u32 reg, int type, u16 max_bw, u16 min_bw, u64 bw) { @@ -333,6 +449,100 @@ static void dvfsrc_set_opp_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level) dvfsrc_writel(dvfsrc, DVFSRC_SW_REQ, val); } +static u32 dvfsrc_get_opp_gear(struct mtk_dvfsrc *dvfsrc, u8 level) +{ + u32 reg_ofst, val; + u8 idx; + + /* Calculate register offset and index for requested gear */ + if (level < DVFSRC_V4_GEAR_INFO_REG_LEVELS) { + reg_ofst = dvfsrc->dvd->regs[DVFSRC_GEAR_INFO_L]; + idx = level; + } else { + reg_ofst = dvfsrc->dvd->regs[DVFSRC_GEAR_INFO_H]; + idx = level - DVFSRC_V4_GEAR_INFO_REG_LEVELS; + } + reg_ofst += DVFSRC_V4_GEAR_INFO_REG_WIDTH * (level / 2); + + /* Read the corresponding gear register */ + val = readl(dvfsrc->regs + reg_ofst); + + /* Each register contains two sets of data, 16 bits per gear */ + val >>= 16 * (idx % 2); + + return val; +} + +static int dvfsrc_get_hw_opps_v4(struct mtk_dvfsrc *dvfsrc) +{ + struct dvfsrc_opp *dvfsrc_opps; + struct dvfsrc_opp_desc *desc; + u32 num_opps, gear_info; + u8 num_vcore, num_dram; + u8 num_emi; + int i; + + num_opps = dvfsrc_get_opp_count_v4(dvfsrc); + if (num_opps == 0) { + dev_err(dvfsrc->dev, "No OPPs programmed in DVFSRC MCU.\n"); + return -EINVAL; + } + + /* + * The first 16 bits set in the gear info table says how many OPPs + * and how many vcore, dram and emi table entries are available. + */ + gear_info = dvfsrc_readl(dvfsrc, DVFSRC_GEAR_INFO_L); + if (gear_info == 0) { + dev_err(dvfsrc->dev, "No gear info in DVFSRC MCU.\n"); + return -EINVAL; + } + + num_vcore = FIELD_GET(DVFSRC_V4_GEAR_INFO_VCORE, gear_info) + 1; + num_dram = FIELD_GET(DVFSRC_V4_GEAR_INFO_DRAM, gear_info) + 1; + num_emi = FIELD_GET(DVFSRC_V4_GEAR_INFO_EMI, gear_info) + 1; + dev_info(dvfsrc->dev, + "Discovered %u gears and %u vcore, %u dram, %u emi table entries.\n", + num_opps, num_vcore, num_dram, num_emi); + + /* Allocate everything now as anything else after that cannot fail */ + desc = devm_kzalloc(dvfsrc->dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + dvfsrc_opps = devm_kcalloc(dvfsrc->dev, num_opps + 1, + sizeof(*dvfsrc_opps), GFP_KERNEL); + if (!dvfsrc_opps) + return -ENOMEM; + + /* Read the OPP table gear indices */ + for (i = 0; i <= num_opps; i++) { + gear_info = dvfsrc_get_opp_gear(dvfsrc, num_opps - i); + dvfsrc_opps[i].vcore_opp = FIELD_GET(DVFSRC_V4_GEAR_INFO_VCORE, gear_info); + dvfsrc_opps[i].dram_opp = FIELD_GET(DVFSRC_V4_GEAR_INFO_DRAM, gear_info); + dvfsrc_opps[i].emi_opp = FIELD_GET(DVFSRC_V4_GEAR_INFO_EMI, gear_info); + }; + desc->num_opp = num_opps + 1; + desc->opps = dvfsrc_opps; + + /* Assign to main structure now that everything is done! */ + dvfsrc->curr_opps = desc; + + return 0; +} + +static void dvfsrc_set_dram_level_v4(struct mtk_dvfsrc *dvfsrc, u32 level) +{ + u32 val = dvfsrc_readl(dvfsrc, DVFSRC_SW_REQ); + + val &= ~DVFSRC_V4_SW_REQ_DRAM_LEVEL; + val |= FIELD_PREP(DVFSRC_V4_SW_REQ_DRAM_LEVEL, level); + + dev_dbg(dvfsrc->dev, "%s level=%u\n", __func__, level); + + dvfsrc_writel(dvfsrc, DVFSRC_SW_REQ, val); +} + int mtk_dvfsrc_send_request(const struct device *dev, u32 cmd, u64 data) { struct mtk_dvfsrc *dvfsrc = dev_get_drvdata(dev); @@ -448,7 +658,14 @@ static int mtk_dvfsrc_probe(struct platform_device *pdev) dvfsrc->dram_type = ares.a1; dev_dbg(&pdev->dev, "DRAM Type: %d\n", dvfsrc->dram_type); - dvfsrc->curr_opps = &dvfsrc->dvd->opps_desc[dvfsrc->dram_type]; + /* Newer versions of the DVFSRC MCU have pre-programmed gear tables */ + if (dvfsrc->dvd->get_hw_opps) { + ret = dvfsrc->dvd->get_hw_opps(dvfsrc); + if (ret) + return ret; + } else { + dvfsrc->curr_opps = &dvfsrc->dvd->opps_desc[dvfsrc->dram_type]; + } platform_set_drvdata(pdev, dvfsrc); ret = devm_of_platform_populate(&pdev->dev); @@ -576,10 +793,39 @@ static const struct dvfsrc_soc_data mt8195_data = { .bw_constraints = &dvfsrc_bw_constr_v2, }; +static const u8 mt8196_bw_units[] = { + [DVFSRC_BW_AVG] = 64, + [DVFSRC_BW_PEAK] = 64, + [DVFSRC_BW_HRT] = 30, +}; + +static const struct dvfsrc_soc_data mt8196_data = { + .regs = dvfsrc_mt8196_regs, + .bw_units = mt8196_bw_units, + .has_emi_ddr = true, + .get_target_level = dvfsrc_get_target_level_v4, + .get_current_level = dvfsrc_get_current_level_v4, + .get_vcore_level = dvfsrc_get_vcore_level_v2, + .get_vscp_level = dvfsrc_get_vscp_level_v2, + .get_opp_count = dvfsrc_get_opp_count_v4, + .get_hw_opps = dvfsrc_get_hw_opps_v4, + .calc_dram_bw = dvfsrc_calc_dram_bw_v4, + .set_dram_bw = dvfsrc_set_dram_bw_v1, + .set_dram_peak_bw = dvfsrc_set_dram_peak_bw_v1, + .set_dram_hrt_bw = dvfsrc_set_dram_hrt_bw_v1, + .set_opp_level = dvfsrc_set_dram_level_v4, + .set_vcore_level = dvfsrc_set_vcore_level_v2, + .set_vscp_level = dvfsrc_set_vscp_level_v2, + .wait_for_opp_level = dvfsrc_wait_for_opp_level_v4, + .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v4, + .bw_constraints = &dvfsrc_bw_constr_v1, +}; + static const struct of_device_id mtk_dvfsrc_of_match[] = { { .compatible = "mediatek,mt6893-dvfsrc", .data = &mt6893_data }, { .compatible = "mediatek,mt8183-dvfsrc", .data = &mt8183_data }, { .compatible = "mediatek,mt8195-dvfsrc", .data = &mt8195_data }, + { .compatible = "mediatek,mt8196-dvfsrc", .data = &mt8196_data }, { /* sentinel */ } }; From 39aa8c4e762ea9b00d66cc55957527167ed89435 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:06:58 +0100 Subject: [PATCH 09/18] soc: mediatek: mtk-dvfsrc: Get and Enable DVFSRC clock The DVFSRC has a clock on all platforms. Get and enable it in the probe function, so that Linux's common clock framework knows we're a user of it. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index 3a83fd4baf54..a43d6f913914 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -83,6 +84,7 @@ struct dvfsrc_opp_desc { struct dvfsrc_soc_data; struct mtk_dvfsrc { struct device *dev; + struct clk *clk; struct platform_device *icc; struct platform_device *regulator; const struct dvfsrc_soc_data *dvd; @@ -650,6 +652,11 @@ static int mtk_dvfsrc_probe(struct platform_device *pdev) if (IS_ERR(dvfsrc->regs)) return PTR_ERR(dvfsrc->regs); + dvfsrc->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(dvfsrc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(dvfsrc->clk), + "Couldn't get and enable DVFSRC clock\n"); + arm_smccc_smc(MTK_SIP_DVFSRC_VCOREFS_CONTROL, MTK_SIP_DVFSRC_INIT, 0, 0, 0, 0, 0, 0, &ares); if (ares.a0) From 3da293d70005496317d1ff3a49b89c29dd7c21e8 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:06:59 +0100 Subject: [PATCH 10/18] soc: mediatek: mtk-dvfsrc: Rework bandwidth calculations The code, as it is, plays fast and loose with bandwidth units. It also doesn't specify its constraints in the actual maximum hardware value, but as some roundabout thing that then ends up multiplied into the actual hardware value constraint after some indirections. In part, this is due to the use of individual members for storing each limit, instead of making it possible to index them by type. Rework all of this by adding const array members indexed by the bandwidth type enum to the soc_data struct. This array expresses the actual hardware value limitations, not a factor thereof. Use the clamp function macro to clamp the values between the minimum and maximum constraints after all the calculations, which also means the code doesn't write nonsense to a hardware register when the math is wrong, as it'll constrain after all the calculations. Pass the type as the actual enum type as well, and not as an int. If there's some type checking that can be extracted from the function signature, then we may as well use it. Don't needlessly explicitly cast return values to the return type either; this is both unnecessary and makes it harder to spot type safety issues. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-dvfsrc.c | 107 +++++++++++++++++++----------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/drivers/soc/mediatek/mtk-dvfsrc.c b/drivers/soc/mediatek/mtk-dvfsrc.c index a43d6f913914..548a28f50242 100644 --- a/drivers/soc/mediatek/mtk-dvfsrc.c +++ b/drivers/soc/mediatek/mtk-dvfsrc.c @@ -64,12 +64,6 @@ enum mtk_dvfsrc_bw_type { DVFSRC_BW_MAX, }; -struct dvfsrc_bw_constraints { - u16 max_dram_nom_bw; - u16 max_dram_peak_bw; - u16 max_dram_hrt_bw; -}; - struct dvfsrc_opp { u32 vcore_opp; u32 dram_opp; @@ -98,7 +92,7 @@ struct dvfsrc_soc_data { const u8 *bw_units; const bool has_emi_ddr; const struct dvfsrc_opp_desc *opps_desc; - u32 (*calc_dram_bw)(struct mtk_dvfsrc *dvfsrc, int type, u64 bw); + u32 (*calc_dram_bw)(struct mtk_dvfsrc *dvfsrc, enum mtk_dvfsrc_bw_type type, u64 bw); u32 (*get_target_level)(struct mtk_dvfsrc *dvfsrc); u32 (*get_current_level)(struct mtk_dvfsrc *dvfsrc); u32 (*get_vcore_level)(struct mtk_dvfsrc *dvfsrc); @@ -113,7 +107,22 @@ struct dvfsrc_soc_data { void (*set_vscp_level)(struct mtk_dvfsrc *dvfsrc, u32 level); int (*wait_for_opp_level)(struct mtk_dvfsrc *dvfsrc, u32 level); int (*wait_for_vcore_level)(struct mtk_dvfsrc *dvfsrc, u32 level); - const struct dvfsrc_bw_constraints *bw_constraints; + + /** + * @bw_max_constraints - array of maximum bandwidth for this hardware + * + * indexed by &enum mtk_dvfsrc_bw_type, storing the maximum permissible + * hardware value for each bandwidth type. + */ + const u32 *const bw_max_constraints; + + /** + * @bw_min_constraints - array of minimum bandwidth for this hardware + * + * indexed by &enum mtk_dvfsrc_bw_type, storing the minimum permissible + * hardware value for each bandwidth type. + */ + const u32 *const bw_min_constraints; }; static u32 dvfsrc_readl(struct mtk_dvfsrc *dvfs, u32 offset) @@ -383,59 +392,62 @@ static u32 dvfsrc_get_opp_count_v4(struct mtk_dvfsrc *dvfsrc) return FIELD_GET(DVFSRC_V4_BASIC_CTRL_OPP_COUNT, val) + 1; } -static u32 dvfsrc_calc_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, int type, u64 bw) +static u32 +dvfsrc_calc_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, enum mtk_dvfsrc_bw_type type, u64 bw) { - return (u32)div_u64(bw, 100 * 1000); + return clamp_val(div_u64(bw, 100 * 1000), dvfsrc->dvd->bw_min_constraints[type], + dvfsrc->dvd->bw_max_constraints[type]); } -static u32 dvfsrc_calc_dram_bw_v4(struct mtk_dvfsrc *dvfsrc, int type, u64 bw) +/** + * dvfsrc_calc_dram_bw_v4 - convert kbps to hardware register bandwidth value + * @dvfsrc: pointer to the &struct mtk_dvfsrc of this driver instance + * @type: one of %DVFSRC_BW_AVG, %DVFSRC_BW_PEAK, or %DVFSRC_BW_HRT + * @bw: the bandwidth in kilobits per second + * + * Returns the hardware register value appropriate for expressing @bw, clamped + * to hardware limits. + */ +static u32 +dvfsrc_calc_dram_bw_v4(struct mtk_dvfsrc *dvfsrc, enum mtk_dvfsrc_bw_type type, u64 bw) { u8 bw_unit = dvfsrc->dvd->bw_units[type]; u64 bw_mbps; + u32 bw_hw; if (type < DVFSRC_BW_AVG || type >= DVFSRC_BW_MAX) return 0; bw_mbps = div_u64(bw, 1000); - return (u32)div_u64((bw_mbps + bw_unit - 1), bw_unit); + bw_hw = div_u64((bw_mbps + bw_unit - 1), bw_unit); + return clamp_val(bw_hw, dvfsrc->dvd->bw_min_constraints[type], + dvfsrc->dvd->bw_max_constraints[type]); } static void __dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u32 reg, - int type, u16 max_bw, u16 min_bw, u64 bw) + enum mtk_dvfsrc_bw_type type, u64 bw) { - u32 new_bw = dvfsrc->dvd->calc_dram_bw(dvfsrc, type, bw); + u32 bw_hw = dvfsrc->dvd->calc_dram_bw(dvfsrc, type, bw); - /* If bw constraints (in mbps) are defined make sure to respect them */ - if (max_bw) - new_bw = min(new_bw, max_bw); - if (min_bw && new_bw > 0) - new_bw = max(new_bw, min_bw); - - dvfsrc_writel(dvfsrc, reg, new_bw); + dvfsrc_writel(dvfsrc, reg, bw_hw); if (type == DVFSRC_BW_AVG && dvfsrc->dvd->has_emi_ddr) - dvfsrc_writel(dvfsrc, DVFSRC_SW_EMI_BW, bw); + dvfsrc_writel(dvfsrc, DVFSRC_SW_EMI_BW, bw_hw); } static void dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) { - u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_nom_bw; - - __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_BW, DVFSRC_BW_AVG, max_bw, 0, bw); + __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_BW, DVFSRC_BW_AVG, bw); }; static void dvfsrc_set_dram_peak_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) { - u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_peak_bw; - - __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_PEAK_BW, DVFSRC_BW_PEAK, max_bw, 0, bw); + __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_PEAK_BW, DVFSRC_BW_PEAK, bw); } static void dvfsrc_set_dram_hrt_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw) { - u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_hrt_bw; - - __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_HRT_BW, DVFSRC_BW_HRT, max_bw, 0, bw); + __dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_HRT_BW, DVFSRC_BW_HRT, bw); } static void dvfsrc_set_opp_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level) @@ -688,11 +700,22 @@ static int mtk_dvfsrc_probe(struct platform_device *pdev) return 0; } -static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_v1 = { 0, 0, 0 }; -static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_v2 = { - .max_dram_nom_bw = 255, - .max_dram_peak_bw = 255, - .max_dram_hrt_bw = 1023, +static const u32 dvfsrc_bw_min_constr_none[DVFSRC_BW_MAX] = { + [DVFSRC_BW_AVG] = 0, + [DVFSRC_BW_PEAK] = 0, + [DVFSRC_BW_HRT] = 0, +}; + +static const u32 dvfsrc_bw_max_constr_v1[DVFSRC_BW_MAX] = { + [DVFSRC_BW_AVG] = U32_MAX, + [DVFSRC_BW_PEAK] = U32_MAX, + [DVFSRC_BW_HRT] = U32_MAX, +}; + +static const u32 dvfsrc_bw_max_constr_v2[DVFSRC_BW_MAX] = { + [DVFSRC_BW_AVG] = 65535, + [DVFSRC_BW_PEAK] = 65535, + [DVFSRC_BW_HRT] = 1023, }; static const struct dvfsrc_opp dvfsrc_opp_mt6893_lp4[] = { @@ -725,7 +748,8 @@ static const struct dvfsrc_soc_data mt6893_data = { .set_vscp_level = dvfsrc_set_vscp_level_v2, .wait_for_opp_level = dvfsrc_wait_for_opp_level_v2, .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1, - .bw_constraints = &dvfsrc_bw_constr_v2, + .bw_max_constraints = dvfsrc_bw_max_constr_v2, + .bw_min_constraints = dvfsrc_bw_min_constr_none, }; static const struct dvfsrc_opp dvfsrc_opp_mt8183_lp4[] = { @@ -763,7 +787,8 @@ static const struct dvfsrc_soc_data mt8183_data = { .set_vcore_level = dvfsrc_set_vcore_level_v1, .wait_for_opp_level = dvfsrc_wait_for_opp_level_v1, .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1, - .bw_constraints = &dvfsrc_bw_constr_v1, + .bw_max_constraints = dvfsrc_bw_max_constr_v1, + .bw_min_constraints = dvfsrc_bw_min_constr_none, }; static const struct dvfsrc_opp dvfsrc_opp_mt8195_lp4[] = { @@ -797,7 +822,8 @@ static const struct dvfsrc_soc_data mt8195_data = { .set_vscp_level = dvfsrc_set_vscp_level_v2, .wait_for_opp_level = dvfsrc_wait_for_opp_level_v2, .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1, - .bw_constraints = &dvfsrc_bw_constr_v2, + .bw_max_constraints = dvfsrc_bw_max_constr_v2, + .bw_min_constraints = dvfsrc_bw_min_constr_none, }; static const u8 mt8196_bw_units[] = { @@ -825,7 +851,8 @@ static const struct dvfsrc_soc_data mt8196_data = { .set_vscp_level = dvfsrc_set_vscp_level_v2, .wait_for_opp_level = dvfsrc_wait_for_opp_level_v4, .wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v4, - .bw_constraints = &dvfsrc_bw_constr_v1, + .bw_max_constraints = dvfsrc_bw_max_constr_v2, + .bw_min_constraints = dvfsrc_bw_min_constr_none, }; static const struct of_device_id mtk_dvfsrc_of_match[] = { From 266f35701b6f7ddd9521310eb5add01001d4a614 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:30 +0800 Subject: [PATCH 11/18] mailbox: mtk-cmdq: Add cmdq private data to cmdq_pkt for generating instruction Add the cmdq_mbox_priv structure to store the private data of GCE, such as the shift bits of the physical address. Then, include the cmdq_mbox_priv structure within the cmdq_pkt structure. This allows CMDQ users to utilize the private data in cmdq_pkt to generate GCE instructions when needed. Additionally, having cmdq_mbox_priv makes it easier to expand and reference other GCE private data in the future. Add cmdq_get_mbox_priv() for CMDQ users to get all the private data into the cmdq_mbox_priv of the cmdq_pkt. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/mailbox/mtk-cmdq-mailbox.c | 8 ++++++++ include/linux/mailbox/mtk-cmdq-mailbox.h | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index 5791f80f995a..95e8a5331b7c 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -104,6 +104,14 @@ static inline dma_addr_t cmdq_revert_gce_addr(u32 addr, const struct gce_plat *p return (dma_addr_t)addr << pdata->shift; } +void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv) +{ + struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox); + + priv->shift_pa = cmdq->pdata->shift; +} +EXPORT_SYMBOL(cmdq_get_mbox_priv); + u8 cmdq_get_shift_pa(struct mbox_chan *chan) { struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox); diff --git a/include/linux/mailbox/mtk-cmdq-mailbox.h b/include/linux/mailbox/mtk-cmdq-mailbox.h index e1555e06e7e5..73b70be4a8a7 100644 --- a/include/linux/mailbox/mtk-cmdq-mailbox.h +++ b/include/linux/mailbox/mtk-cmdq-mailbox.h @@ -70,13 +70,31 @@ struct cmdq_cb_data { struct cmdq_pkt *pkt; }; +struct cmdq_mbox_priv { + u8 shift_pa; +}; + struct cmdq_pkt { void *va_base; dma_addr_t pa_base; size_t cmd_buf_size; /* command occupied size */ size_t buf_size; /* real buffer size */ + struct cmdq_mbox_priv priv; /* for generating instruction */ }; +/** + * cmdq_get_mbox_priv() - get the private data of mailbox channel + * @chan: mailbox channel + * @priv: pointer to store the private data of mailbox channel + * + * While generating the GCE instruction to command buffer, the private data + * of GCE hardware may need to be referenced, such as the shift bits of + * physical address. + * + * This function should be called before generating the GCE instruction. + */ +void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv); + /** * cmdq_get_shift_pa() - get the shift bits of physical address * @chan: mailbox channel From 7005b7cb2fff9081a6b1738b84a8ea12a6781fb3 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:31 +0800 Subject: [PATCH 12/18] mailbox: mtk-cmdq: Add GCE hardware virtualization configuration The GCE hardware virtualization configuration supports the isolation of GCE hardware resources across different OS environments. Each OS is treated as a virtual machine (VM) for GCE purposes. There are 6 VMs and 1 host VM. The host VM has main control over the GCE virtualization settings for all VMs. To properly access the GCE thread registers, it is necessary to configure access permissions for specific GCE threads assigned to different VMs. Currently, since only the host VM is being used, it is required to enable access permissions for all GCE threads for the host VM. There are 2 VM configurations: 1. VM_ID_MAP There are 4 registers to allocate 32 GCE threads across different VMs: VM_ID_MAP0 for threads 0-9, VM_ID_MAP1 for threads 10-19, VM_ID_MAP2 for threads 20-29, and VM_ID_MAP3 for threads 30-31. Each thread has a 3-bit configuration, where setting all bits to 1 configures the thread for the host VM. 2. VM_CPR_GSIZE It is used to allocate the CPR SRAM size to each VM. Each VM has 4-bit configuration, where setting bit 0-3 to configures the size of host VM. This setting must be configured before the VM configuration to prevent resource leakage. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/mailbox/mtk-cmdq-mailbox.c | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index 95e8a5331b7c..a544108ddae7 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -43,6 +43,13 @@ #define GCE_CTRL_BY_SW GENMASK(2, 0) #define GCE_DDR_EN GENMASK(18, 16) +#define GCE_VM_ID_MAP(n) (0x5018 + (n) / 10 * 4) +#define GCE_VM_ID_MAP_THR_FLD_SHIFT(n) ((n) % 10 * 3) +#define GCE_VM_ID_MAP_HOST_VM GENMASK(2, 0) +#define GCE_VM_CPR_GSIZE 0x50c4 +#define GCE_VM_CPR_GSIZE_FLD_SHIFT(vm_id) ((vm_id) * 4) +#define GCE_VM_CPR_GSIZE_MAX GENMASK(3, 0) + #define CMDQ_THR_ACTIVE_SLOT_CYCLES 0x3200 #define CMDQ_THR_ENABLED 0x1 #define CMDQ_THR_DISABLED 0x0 @@ -89,6 +96,7 @@ struct gce_plat { u8 shift; bool control_by_sw; bool sw_ddr_en; + bool gce_vm; u32 gce_num; }; @@ -120,6 +128,45 @@ u8 cmdq_get_shift_pa(struct mbox_chan *chan) } EXPORT_SYMBOL(cmdq_get_shift_pa); +static void cmdq_vm_init(struct cmdq *cmdq) +{ + int i; + u32 vm_cpr_gsize = 0, vm_id_map = 0; + u32 *vm_map = NULL; + + if (!cmdq->pdata->gce_vm) + return; + + vm_map = kcalloc(cmdq->pdata->thread_nr, sizeof(*vm_map), GFP_KERNEL); + if (!vm_map) + return; + + /* only configure the max CPR SRAM size to host vm (vm_id = 0) currently */ + vm_cpr_gsize = GCE_VM_CPR_GSIZE_MAX << GCE_VM_CPR_GSIZE_FLD_SHIFT(0); + + /* set all thread mapping to host vm currently */ + for (i = 0; i < cmdq->pdata->thread_nr; i++) + vm_map[i] = GCE_VM_ID_MAP_HOST_VM << GCE_VM_ID_MAP_THR_FLD_SHIFT(i); + + /* set the amount of CPR SRAM to allocate to each VM */ + writel(vm_cpr_gsize, cmdq->base + GCE_VM_CPR_GSIZE); + + /* config CPR_GSIZE before setting VM_ID_MAP to avoid data leakage */ + for (i = 0; i < cmdq->pdata->thread_nr; i++) { + vm_id_map |= vm_map[i]; + /* config every 10 threads, e.g., thread id=0~9, 10~19, ..., into one register */ + if ((i + 1) % 10 == 0) { + writel(vm_id_map, cmdq->base + GCE_VM_ID_MAP(i)); + vm_id_map = 0; + } + } + /* config remaining threads settings */ + if (cmdq->pdata->thread_nr % 10 != 0) + writel(vm_id_map, cmdq->base + GCE_VM_ID_MAP(cmdq->pdata->thread_nr - 1)); + + kfree(vm_map); +} + static void cmdq_gctl_value_toggle(struct cmdq *cmdq, bool ddr_enable) { u32 val = cmdq->pdata->control_by_sw ? GCE_CTRL_BY_SW : 0; @@ -164,6 +211,7 @@ static void cmdq_init(struct cmdq *cmdq) WARN_ON(clk_bulk_enable(cmdq->pdata->gce_num, cmdq->clocks)); + cmdq_vm_init(cmdq); cmdq_gctl_value_toggle(cmdq, true); writel(CMDQ_THR_ACTIVE_SLOT_CYCLES, cmdq->base + CMDQ_THR_SLOT_CYCLES); From 1c1874843bc43d9f333d441af00f61ece2373e5d Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:32 +0800 Subject: [PATCH 13/18] mailbox: mtk-cmdq: Add mminfra_offset configuration for DRAM transaction The GCE in MT8196 is placed in MMINFRA and requires all addresses in GCE instructions for DRAM transactions to be IOVA. Due to MMIO, if the GCE needs to access a hardware register at 0x1000_0000, but the SMMU is also mapping a DRAM block at 0x1000_0000, the MMINFRA will not know whether to write to the hardware register or the DRAM. To solve this, MMINFRA treats addresses greater than 2G as data paths and those less than 2G as config paths because the DRAM start address is currently at 2G (0x8000_0000). On the data path, MMINFRA remaps DRAM addresses by subtracting 2G, allowing SMMU to map DRAM addresses less than 2G. For example, if the DRAM start address 0x8000_0000 is mapped to IOVA=0x0, when GCE accesses IOVA=0x0, it must add a 2G offset to the address in the GCE instruction. MMINFRA will then see it as a data path (IOVA >= 2G) and subtract 2G, allowing GCE to access IOVA=0x0. Since the MMINFRA remap subtracting 2G is done in hardware and cannot be configured by software, the address of DRAM in GCE instruction must always add 2G to ensure proper access. After that, the shift functions do more than just shift addresses, so the APIs were renamed to cmdq_convert_gce_addr() and cmdq_revert_gce_addr(). This 2G adjustment is referred to as mminfra_offset in the CMDQ driver. CMDQ helper can get the mminfra_offset from the cmdq_mbox_priv of cmdq_pkt and add the mminfra_offset to the DRAM address in GCE instructions. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/mailbox/mtk-cmdq-mailbox.c | 6 ++++-- include/linux/mailbox/mtk-cmdq-mailbox.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index a544108ddae7..a9c06e4bbad4 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -94,6 +94,7 @@ struct cmdq { struct gce_plat { u32 thread_nr; u8 shift; + dma_addr_t mminfra_offset; bool control_by_sw; bool sw_ddr_en; bool gce_vm; @@ -103,13 +104,13 @@ struct gce_plat { static inline u32 cmdq_convert_gce_addr(dma_addr_t addr, const struct gce_plat *pdata) { /* Convert DMA addr (PA or IOVA) to GCE readable addr */ - return addr >> pdata->shift; + return (addr + pdata->mminfra_offset) >> pdata->shift; } static inline dma_addr_t cmdq_revert_gce_addr(u32 addr, const struct gce_plat *pdata) { /* Revert GCE readable addr to DMA addr (PA or IOVA) */ - return (dma_addr_t)addr << pdata->shift; + return ((dma_addr_t)addr << pdata->shift) - pdata->mminfra_offset; } void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv) @@ -117,6 +118,7 @@ void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv) struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox); priv->shift_pa = cmdq->pdata->shift; + priv->mminfra_offset = cmdq->pdata->mminfra_offset; } EXPORT_SYMBOL(cmdq_get_mbox_priv); diff --git a/include/linux/mailbox/mtk-cmdq-mailbox.h b/include/linux/mailbox/mtk-cmdq-mailbox.h index 73b70be4a8a7..07c1bfbdb8c4 100644 --- a/include/linux/mailbox/mtk-cmdq-mailbox.h +++ b/include/linux/mailbox/mtk-cmdq-mailbox.h @@ -72,6 +72,7 @@ struct cmdq_cb_data { struct cmdq_mbox_priv { u8 shift_pa; + dma_addr_t mminfra_offset; }; struct cmdq_pkt { From 5ea617e818333a2078dadc11e5734886e39901d0 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:33 +0800 Subject: [PATCH 14/18] mailbox: mtk-cmdq: Add driver data to support for MT8196 MT8196 has 2 new hardware configuration compared with the previous SoC, which correspond to the 2 new driver data: 1. mminfra_offset: For GCE data path control Since GCE has been moved into mminfra, GCE needs to append the mminfra offset to the DRAM address when accessing the DRAM. 2. gce_vm: For GCE hardware virtualization control Currently, the first version of the mt8196 mailbox controller only requires setting the VM-related registers to enable the permissions of a host VM. Signed-off-by: Jason-JH Lin Reviewed-by: CK Hu Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/mailbox/mtk-cmdq-mailbox.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index a9c06e4bbad4..1bf6984948ef 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -840,6 +841,16 @@ static const struct gce_plat gce_plat_mt8195 = { .gce_num = 2 }; +static const struct gce_plat gce_plat_mt8196 = { + .thread_nr = 32, + .shift = 3, + .mminfra_offset = SZ_2G, + .control_by_sw = true, + .sw_ddr_en = true, + .gce_vm = true, + .gce_num = 2 +}; + static const struct of_device_id cmdq_of_ids[] = { {.compatible = "mediatek,mt6779-gce", .data = (void *)&gce_plat_mt6779}, {.compatible = "mediatek,mt8173-gce", .data = (void *)&gce_plat_mt8173}, @@ -848,6 +859,7 @@ static const struct of_device_id cmdq_of_ids[] = { {.compatible = "mediatek,mt8188-gce", .data = (void *)&gce_plat_mt8188}, {.compatible = "mediatek,mt8192-gce", .data = (void *)&gce_plat_mt8192}, {.compatible = "mediatek,mt8195-gce", .data = (void *)&gce_plat_mt8195}, + {.compatible = "mediatek,mt8196-gce", .data = (void *)&gce_plat_mt8196}, {} }; MODULE_DEVICE_TABLE(of, cmdq_of_ids); From c775b23b1f78626daca804bd26f1460368f20406 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:34 +0800 Subject: [PATCH 15/18] soc: mediatek: mtk-cmdq: Add cmdq_get_mbox_priv() in cmdq_pkt_create() Add cmdq_get_mbox_priv() in cmdq_pkt_create() to ensure getting private data before generating GCE instructions. Signed-off-by: Jason-JH Lin Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-cmdq-helper.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/mediatek/mtk-cmdq-helper.c b/drivers/soc/mediatek/mtk-cmdq-helper.c index 455221e8de24..8feeaa320359 100644 --- a/drivers/soc/mediatek/mtk-cmdq-helper.c +++ b/drivers/soc/mediatek/mtk-cmdq-helper.c @@ -140,6 +140,7 @@ int cmdq_pkt_create(struct cmdq_client *client, struct cmdq_pkt *pkt, size_t siz } pkt->pa_base = dma_addr; + cmdq_get_mbox_priv(client->chan, &pkt->priv); return 0; } From 4bf783d8415cc397334b375a05f0b2321fc6c319 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:35 +0800 Subject: [PATCH 16/18] soc: mediatek: mtk-cmdq: Add pa_base parsing for hardware without subsys ID support When GCE executes instructions, it typically locates the corresponding hardware register using the subsys ID. For hardware that does not support subsys ID, the subsys ID is set to an invalid value, and the physical address must be used to generate GCE instructions. The main advantage of using subsys ID is to reduce the number of instructions. Without subsys ID, an additional `ASSIGN` instruction is needed to assign the high bytes of the physical address, which can impact performance if too many instructions are required. However, if the hardware does not support subsys ID, using the physical address is the only option to achieve the same functionality. This commit adds a pa_base parsing flow to the cmdq_client_reg structure to handle hardware without subsys ID support. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-cmdq-helper.c | 16 ++++++++++++++-- include/linux/soc/mediatek/mtk-cmdq.h | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/soc/mediatek/mtk-cmdq-helper.c b/drivers/soc/mediatek/mtk-cmdq-helper.c index 8feeaa320359..80806fbeba91 100644 --- a/drivers/soc/mediatek/mtk-cmdq-helper.c +++ b/drivers/soc/mediatek/mtk-cmdq-helper.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #define CMDQ_WRITE_ENABLE_MASK BIT(0) @@ -60,20 +61,31 @@ int cmdq_dev_get_client_reg(struct device *dev, struct cmdq_client_reg *client_reg, int idx) { struct of_phandle_args spec; + struct resource res; int err; if (!client_reg) return -ENOENT; + err = of_address_to_resource(dev->of_node, 0, &res); + if (err) { + dev_err(dev, "Missing reg in %s node\n", dev->of_node->full_name); + return -EINVAL; + } + client_reg->pa_base = res.start; + err = of_parse_phandle_with_fixed_args(dev->of_node, "mediatek,gce-client-reg", 3, idx, &spec); if (err < 0) { - dev_warn(dev, + dev_dbg(dev, "error %d can't parse gce-client-reg property (%d)", err, idx); - return err; + /* make subsys invalid */ + client_reg->subsys = CMDQ_SUBSYS_INVALID; + + return 0; } client_reg->subsys = (u8)spec.args[0]; diff --git a/include/linux/soc/mediatek/mtk-cmdq.h b/include/linux/soc/mediatek/mtk-cmdq.h index 0c3906e8ad19..3578cc9200e9 100644 --- a/include/linux/soc/mediatek/mtk-cmdq.h +++ b/include/linux/soc/mediatek/mtk-cmdq.h @@ -23,6 +23,8 @@ #define CMDQ_THR_SPR_IDX2 (2) #define CMDQ_THR_SPR_IDX3 (3) +#define CMDQ_SUBSYS_INVALID (U8_MAX) + struct cmdq_pkt; enum cmdq_logic_op { @@ -52,6 +54,7 @@ struct cmdq_operand { struct cmdq_client_reg { u8 subsys; + phys_addr_t pa_base; u16 offset; u16 size; }; From 40dc5bbad63b5f60dd2e69a32def1a2673cba09e Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:36 +0800 Subject: [PATCH 17/18] soc: mediatek: mtk-cmdq: Extend cmdq_pkt_write API for SoCs without subsys ID This patch extends the cmdq_pkt_write API to support SoCs that do not have subsys ID mapping by introducing new register write APIs: - cmdq_pkt_write_pa() and cmdq_pkt_write_subsys() replace cmdq_pkt_write() - cmdq_pkt_write_mask_pa() and cmdq_pkt_write_mask_subsys() replace cmdq_pkt_write_mask() To ensure consistent function pointer interfaces, both cmdq_pkt_write_pa() and cmdq_pkt_write_subsys() provide subsys and pa_base parameters. This unifies how register writes are invoked, regardless of whether subsys ID is supported by the device. All GCEs support writing registers by PA (with mask) without subsys, but this requires extra GCE instructions to convert the PA into a GCE readable format, reducing performance compared to using subsys directly. Therefore, subsys is preferred for register writes when available. API documentation and function pointer declarations in cmdq_client_reg have been updated. The original write APIs will be removed after all CMDQ users transition to the new interfaces. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-cmdq-helper.c | 54 ++++++++++++++++ include/linux/soc/mediatek/mtk-cmdq.h | 90 ++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/drivers/soc/mediatek/mtk-cmdq-helper.c b/drivers/soc/mediatek/mtk-cmdq-helper.c index 80806fbeba91..b30715ef0d6f 100644 --- a/drivers/soc/mediatek/mtk-cmdq-helper.c +++ b/drivers/soc/mediatek/mtk-cmdq-helper.c @@ -85,6 +85,16 @@ int cmdq_dev_get_client_reg(struct device *dev, /* make subsys invalid */ client_reg->subsys = CMDQ_SUBSYS_INVALID; + /* + * All GCEs support writing register PA with mask without subsys, + * but this requires extra GCE instructions to convert the PA into + * a format that GCE can handle, which is less performance than + * directly using subsys. Therefore, when subsys is available, + * we prefer to use subsys for writing register PA. + */ + client_reg->pkt_write = cmdq_pkt_write_pa; + client_reg->pkt_write_mask = cmdq_pkt_write_mask_pa; + return 0; } @@ -93,6 +103,9 @@ int cmdq_dev_get_client_reg(struct device *dev, client_reg->size = (u16)spec.args[2]; of_node_put(spec.np); + client_reg->pkt_write = cmdq_pkt_write_subsys; + client_reg->pkt_write_mask = cmdq_pkt_write_mask_subsys; + return 0; } EXPORT_SYMBOL(cmdq_dev_get_client_reg); @@ -214,6 +227,26 @@ int cmdq_pkt_write(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value) } EXPORT_SYMBOL(cmdq_pkt_write); +int cmdq_pkt_write_pa(struct cmdq_pkt *pkt, u8 subsys /*unused*/, u32 pa_base, + u16 offset, u32 value) +{ + int err; + + err = cmdq_pkt_assign(pkt, CMDQ_THR_SPR_IDX0, CMDQ_ADDR_HIGH(pa_base)); + if (err < 0) + return err; + + return cmdq_pkt_write_s_value(pkt, CMDQ_THR_SPR_IDX0, CMDQ_ADDR_LOW(offset), value); +} +EXPORT_SYMBOL(cmdq_pkt_write_pa); + +int cmdq_pkt_write_subsys(struct cmdq_pkt *pkt, u8 subsys, u32 pa_base /*unused*/, + u16 offset, u32 value) +{ + return cmdq_pkt_write(pkt, subsys, offset, value); +} +EXPORT_SYMBOL(cmdq_pkt_write_subsys); + int cmdq_pkt_write_mask(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value, u32 mask) { @@ -231,6 +264,27 @@ int cmdq_pkt_write_mask(struct cmdq_pkt *pkt, u8 subsys, } EXPORT_SYMBOL(cmdq_pkt_write_mask); +int cmdq_pkt_write_mask_pa(struct cmdq_pkt *pkt, u8 subsys /*unused*/, u32 pa_base, + u16 offset, u32 value, u32 mask) +{ + int err; + + err = cmdq_pkt_assign(pkt, CMDQ_THR_SPR_IDX0, CMDQ_ADDR_HIGH(pa_base)); + if (err < 0) + return err; + + return cmdq_pkt_write_s_mask_value(pkt, CMDQ_THR_SPR_IDX0, + CMDQ_ADDR_LOW(offset), value, mask); +} +EXPORT_SYMBOL(cmdq_pkt_write_mask_pa); + +int cmdq_pkt_write_mask_subsys(struct cmdq_pkt *pkt, u8 subsys, u32 pa_base /*unused*/, + u16 offset, u32 value, u32 mask) +{ + return cmdq_pkt_write_mask(pkt, subsys, offset, value, mask); +} +EXPORT_SYMBOL(cmdq_pkt_write_mask_subsys); + int cmdq_pkt_read_s(struct cmdq_pkt *pkt, u16 high_addr_reg_idx, u16 addr_low, u16 reg_idx) { diff --git a/include/linux/soc/mediatek/mtk-cmdq.h b/include/linux/soc/mediatek/mtk-cmdq.h index 3578cc9200e9..a06b5a61f337 100644 --- a/include/linux/soc/mediatek/mtk-cmdq.h +++ b/include/linux/soc/mediatek/mtk-cmdq.h @@ -57,6 +57,17 @@ struct cmdq_client_reg { phys_addr_t pa_base; u16 offset; u16 size; + + /* + * Client only uses these functions for MMIO access, + * so doesn't need to handle the mminfra_offset. + * The mminfra_offset is used for DRAM access and + * is handled internally by CMDQ APIs. + */ + int (*pkt_write)(struct cmdq_pkt *pkt, u8 subsys, u32 pa_base, + u16 offset, u32 value); + int (*pkt_write_mask)(struct cmdq_pkt *pkt, u8 subsys, u32 pa_base, + u16 offset, u32 value, u32 mask); }; struct cmdq_client { @@ -124,6 +135,32 @@ void cmdq_pkt_destroy(struct cmdq_client *client, struct cmdq_pkt *pkt); */ int cmdq_pkt_write(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value); +/** + * cmdq_pkt_write_pa() - append write command to the CMDQ packet with pa_base + * @pkt: the CMDQ packet + * @subsys: unused parameter + * @pa_base: the physical address base of the hardware register + * @offset: register offset from CMDQ sub system + * @value: the specified target register value + * + * Return: 0 for success; else the error code is returned + */ +int cmdq_pkt_write_pa(struct cmdq_pkt *pkt, u8 subsys /*unused*/, + u32 pa_base, u16 offset, u32 value); + +/** + * cmdq_pkt_write_subsys() - append write command to the CMDQ packet with subsys + * @pkt: the CMDQ packet + * @subsys: the CMDQ sub system code + * @pa_base: unused parameter + * @offset: register offset from CMDQ sub system + * @value: the specified target register value + * + * Return: 0 for success; else the error code is returned + */ +int cmdq_pkt_write_subsys(struct cmdq_pkt *pkt, u8 subsys, + u32 pa_base /*unused*/, u16 offset, u32 value); + /** * cmdq_pkt_write_mask() - append write command with mask to the CMDQ packet * @pkt: the CMDQ packet @@ -137,6 +174,34 @@ int cmdq_pkt_write(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value); int cmdq_pkt_write_mask(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value, u32 mask); +/** + * cmdq_pkt_write_mask_pa() - append write command with mask to the CMDQ packet with pa + * @pkt: the CMDQ packet + * @subsys: unused parameter + * @pa_base: the physical address base of the hardware register + * @offset: register offset from CMDQ sub system + * @value: the specified target register value + * @mask: the specified target register mask + * + * Return: 0 for success; else the error code is returned + */ +int cmdq_pkt_write_mask_pa(struct cmdq_pkt *pkt, u8 subsys /*unused*/, + u32 pa_base, u16 offset, u32 value, u32 mask); + +/** + * cmdq_pkt_write_mask_subsys() - append write command with mask to the CMDQ packet with subsys + * @pkt: the CMDQ packet + * @subsys: the CMDQ sub system code + * @pa_base: unused parameter + * @offset: register offset from CMDQ sub system + * @value: the specified target register value + * @mask: the specified target register mask + * + * Return: 0 for success; else the error code is returned + */ +int cmdq_pkt_write_mask_subsys(struct cmdq_pkt *pkt, u8 subsys, + u32 pa_base /*unused*/, u16 offset, u32 value, u32 mask); + /* * cmdq_pkt_read_s() - append read_s command to the CMDQ packet * @pkt: the CMDQ packet @@ -421,12 +486,37 @@ static inline int cmdq_pkt_write(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u3 return -ENOENT; } +static inline int cmdq_pkt_write_pa(struct cmdq_pkt *pkt, u8 subsys /*unused*/, + u32 pa_base, u16 offset, u32 value) +{ + return -ENOENT; +} + +static inline int cmdq_pkt_write_subsys(struct cmdq_pkt *pkt, u8 subsys, + u32 pa_base /*unused*/, u16 offset, u32 value) +{ + return -ENOENT; +} + static inline int cmdq_pkt_write_mask(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value, u32 mask) { return -ENOENT; } +static inline int cmdq_pkt_write_mask_pa(struct cmdq_pkt *pkt, u8 subsys /*unused*/, + u32 pa_base, u16 offset, u32 value, u32 mask) +{ + return -ENOENT; +} + +static inline int cmdq_pkt_write_mask_subsys(struct cmdq_pkt *pkt, u8 subsys, + u32 pa_base /*unused*/, u16 offset, + u32 value, u32 mask) +{ + return -ENOENT; +} + static inline int cmdq_pkt_read_s(struct cmdq_pkt *pkt, u16 high_addr_reg_idx, u16 addr_low, u16 reg_idx) { From 22ce09ce1af574747fce072c3f62c29c440538d7 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Fri, 31 Oct 2025 23:56:37 +0800 Subject: [PATCH 18/18] soc: mediatek: mtk-cmdq: Add mminfra_offset adjustment for DRAM addresses Since GCE has been moved to MMINFRA in MT8196, all transactions from MMINFRA to DRAM will have their addresses adjusted by subtracting a mminfra_offset. Therefore, the CMDQ helper driver needs to get the mminfra_offset value of the SoC from cmdq_mbox_priv of cmdq_pkt and then add it to the DRAM address when generating instructions to ensure GCE accesses the correct DRAM address. CMDQ users can then call CMDQ helper APIs as usual. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Acked-by: Jassi Brar Acked-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-cmdq-helper.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/soc/mediatek/mtk-cmdq-helper.c b/drivers/soc/mediatek/mtk-cmdq-helper.c index b30715ef0d6f..67e5879374ac 100644 --- a/drivers/soc/mediatek/mtk-cmdq-helper.c +++ b/drivers/soc/mediatek/mtk-cmdq-helper.c @@ -372,6 +372,7 @@ int cmdq_pkt_mem_move(struct cmdq_pkt *pkt, dma_addr_t src_addr, dma_addr_t dst_ int ret; /* read the value of src_addr into high_addr_reg_idx */ + src_addr += pkt->priv.mminfra_offset; ret = cmdq_pkt_assign(pkt, high_addr_reg_idx, CMDQ_ADDR_HIGH(src_addr)); if (ret < 0) return ret; @@ -380,6 +381,7 @@ int cmdq_pkt_mem_move(struct cmdq_pkt *pkt, dma_addr_t src_addr, dma_addr_t dst_ return ret; /* write the value of value_reg_idx into dst_addr */ + dst_addr += pkt->priv.mminfra_offset; ret = cmdq_pkt_assign(pkt, high_addr_reg_idx, CMDQ_ADDR_HIGH(dst_addr)); if (ret < 0) return ret; @@ -505,7 +507,7 @@ int cmdq_pkt_poll_addr(struct cmdq_pkt *pkt, dma_addr_t addr, u32 value, u32 mas inst.op = CMDQ_CODE_MASK; inst.dst_t = CMDQ_REG_TYPE; inst.sop = CMDQ_POLL_ADDR_GPR; - inst.value = addr; + inst.value = addr + pkt->priv.mminfra_offset; ret = cmdq_pkt_append_command(pkt, inst); if (ret < 0) return ret; @@ -565,7 +567,7 @@ int cmdq_pkt_jump_abs(struct cmdq_pkt *pkt, dma_addr_t addr, u8 shift_pa) struct cmdq_instruction inst = { .op = CMDQ_CODE_JUMP, .offset = CMDQ_JUMP_ABSOLUTE, - .value = addr >> shift_pa + .value = (addr + pkt->priv.mminfra_offset) >> pkt->priv.shift_pa }; return cmdq_pkt_append_command(pkt, inst); }