summaryrefslogtreecommitdiff
path: root/drivers/pmdomain
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pmdomain')
-rw-r--r--drivers/pmdomain/bcm/bcm2835-power.c17
-rw-r--r--drivers/pmdomain/core.c6
-rw-r--r--drivers/pmdomain/governor.c20
-rw-r--r--drivers/pmdomain/mediatek/Kconfig17
-rw-r--r--drivers/pmdomain/mediatek/Makefile1
-rw-r--r--drivers/pmdomain/mediatek/mt8196-pm-domains.h625
-rw-r--r--drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c1044
-rw-r--r--drivers/pmdomain/mediatek/mtk-pm-domains.c306
-rw-r--r--drivers/pmdomain/mediatek/mtk-pm-domains.h49
-rw-r--r--drivers/pmdomain/qcom/rpmhpd.c28
-rw-r--r--drivers/pmdomain/rockchip/pm-domains.c41
11 files changed, 2111 insertions, 43 deletions
diff --git a/drivers/pmdomain/bcm/bcm2835-power.c b/drivers/pmdomain/bcm/bcm2835-power.c
index f5289fd184d0..1d29addfe036 100644
--- a/drivers/pmdomain/bcm/bcm2835-power.c
+++ b/drivers/pmdomain/bcm/bcm2835-power.c
@@ -79,6 +79,7 @@
#define PM_IMAGE 0x108
#define PM_GRAFX 0x10c
#define PM_PROC 0x110
+#define PM_GRAFX_2712 0x304
#define PM_ENAB BIT(12)
#define PM_ISPRSTN BIT(8)
#define PM_H264RSTN BIT(7)
@@ -381,6 +382,9 @@ static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain)
return bcm2835_power_power_on(pd, PM_GRAFX);
case BCM2835_POWER_DOMAIN_GRAFX_V3D:
+ if (!power->asb)
+ return bcm2835_asb_power_on(pd, PM_GRAFX_2712,
+ 0, 0, PM_V3DRSTN);
return bcm2835_asb_power_on(pd, PM_GRAFX,
ASB_V3D_M_CTRL, ASB_V3D_S_CTRL,
PM_V3DRSTN);
@@ -447,6 +451,9 @@ static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain)
return bcm2835_power_power_off(pd, PM_GRAFX);
case BCM2835_POWER_DOMAIN_GRAFX_V3D:
+ if (!power->asb)
+ return bcm2835_asb_power_off(pd, PM_GRAFX_2712,
+ 0, 0, PM_V3DRSTN);
return bcm2835_asb_power_off(pd, PM_GRAFX,
ASB_V3D_M_CTRL, ASB_V3D_S_CTRL,
PM_V3DRSTN);
@@ -635,10 +642,12 @@ static int bcm2835_power_probe(struct platform_device *pdev)
power->asb = pm->asb;
power->rpivid_asb = pm->rpivid_asb;
- id = readl(power->asb + ASB_AXI_BRDG_ID);
- if (id != BCM2835_BRDG_ID /* "BRDG" */) {
- dev_err(dev, "ASB register ID returned 0x%08x\n", id);
- return -ENODEV;
+ if (power->asb) {
+ id = readl(power->asb + ASB_AXI_BRDG_ID);
+ if (id != BCM2835_BRDG_ID /* "BRDG" */) {
+ dev_err(dev, "ASB register ID returned 0x%08x\n", id);
+ return -ENODEV;
+ }
}
if (power->rpivid_asb) {
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 4fd546ef0448..bf82775f6a67 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -1551,7 +1551,8 @@ static int genpd_finish_suspend(struct device *dev,
if (ret)
return ret;
- if (device_awake_path(dev) && genpd_is_active_wakeup(genpd))
+ if (device_awake_path(dev) && genpd_is_active_wakeup(genpd) &&
+ !device_out_band_wakeup(dev))
return 0;
if (genpd->dev_ops.stop && genpd->dev_ops.start &&
@@ -1606,7 +1607,8 @@ static int genpd_finish_resume(struct device *dev,
if (IS_ERR(genpd))
return -EINVAL;
- if (device_awake_path(dev) && genpd_is_active_wakeup(genpd))
+ if (device_awake_path(dev) && genpd_is_active_wakeup(genpd) &&
+ !device_out_band_wakeup(dev))
return resume_noirq(dev);
genpd_lock(genpd);
diff --git a/drivers/pmdomain/governor.c b/drivers/pmdomain/governor.c
index 05e68680f34b..96737abbb496 100644
--- a/drivers/pmdomain/governor.c
+++ b/drivers/pmdomain/governor.c
@@ -408,15 +408,21 @@ static bool cpu_power_down_ok(struct dev_pm_domain *pd)
if ((idle_duration_ns >= (genpd->states[i].residency_ns +
genpd->states[i].power_off_latency_ns)) &&
(global_constraint >= (genpd->states[i].power_on_latency_ns +
- genpd->states[i].power_off_latency_ns))) {
- genpd->state_idx = i;
- genpd->gd->last_enter = now;
- genpd->gd->reflect_residency = true;
- return true;
- }
+ genpd->states[i].power_off_latency_ns)))
+ break;
+
} while (--i >= 0);
- return false;
+ if (i < 0)
+ return false;
+
+ if (cpus_peek_for_pending_ipi(genpd->cpus))
+ return false;
+
+ genpd->state_idx = i;
+ genpd->gd->last_enter = now;
+ genpd->gd->reflect_residency = true;
+ return true;
}
static bool cpu_system_power_down_ok(struct dev_pm_domain *pd)
diff --git a/drivers/pmdomain/mediatek/Kconfig b/drivers/pmdomain/mediatek/Kconfig
index 0e34a517ab7d..8923e6516441 100644
--- a/drivers/pmdomain/mediatek/Kconfig
+++ b/drivers/pmdomain/mediatek/Kconfig
@@ -26,6 +26,23 @@ config MTK_SCPSYS_PM_DOMAINS
Control Processor System (SCPSYS) has several power management related
tasks in the system.
+config MTK_MFG_PM_DOMAIN
+ bool "MediaTek MFlexGraphics power domain"
+ default ARCH_MEDIATEK
+ depends on PM
+ depends on OF
+ depends on COMMON_CLK
+ select MAILBOX
+ select PM_GENERIC_DOMAINS
+ imply MTK_GPUEB_MBOX
+ help
+ Say y or m here to enable the power domains driver for MediaTek
+ MFlexGraphics. This driver allows for power and frequency control of
+ GPUs on MediaTek SoCs such as the MT8196 or MT6991.
+
+ This driver is required for the Mali GPU to work at all on MT8196 and
+ MT6991.
+
config AIROHA_CPU_PM_DOMAIN
tristate "Airoha CPU power domain"
default ARCH_AIROHA
diff --git a/drivers/pmdomain/mediatek/Makefile b/drivers/pmdomain/mediatek/Makefile
index 18ba92e3c418..b424f1ed8676 100644
--- a/drivers/pmdomain/mediatek/Makefile
+++ b/drivers/pmdomain/mediatek/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_MTK_MFG_PM_DOMAIN) += mtk-mfg-pmdomain.o
obj-$(CONFIG_MTK_SCPSYS) += mtk-scpsys.o
obj-$(CONFIG_MTK_SCPSYS_PM_DOMAINS) += mtk-pm-domains.o
obj-$(CONFIG_AIROHA_CPU_PM_DOMAIN) += airoha-cpu-pmdomain.o
diff --git a/drivers/pmdomain/mediatek/mt8196-pm-domains.h b/drivers/pmdomain/mediatek/mt8196-pm-domains.h
new file mode 100644
index 000000000000..2e4b28720659
--- /dev/null
+++ b/drivers/pmdomain/mediatek/mt8196-pm-domains.h
@@ -0,0 +1,625 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Collabora Ltd
+ * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+ */
+
+#ifndef __SOC_MEDIATEK_MT8196_PM_DOMAINS_H
+#define __SOC_MEDIATEK_MT8196_PM_DOMAINS_H
+
+#include "mtk-pm-domains.h"
+#include <dt-bindings/power/mediatek,mt8196-power.h>
+
+/*
+ * MT8196 and MT6991 power domain support
+ */
+
+/* INFRA TOP_AXI registers */
+#define MT8196_TOP_AXI_PROT_EN_SET 0x4
+#define MT8196_TOP_AXI_PROT_EN_CLR 0x8
+#define MT8196_TOP_AXI_PROT_EN_STA 0xc
+ #define MT8196_TOP_AXI_PROT_EN_SLEEP0_MD BIT(29)
+
+#define MT8196_TOP_AXI_PROT_EN_1_SET 0x24
+#define MT8196_TOP_AXI_PROT_EN_1_CLR 0x28
+#define MT8196_TOP_AXI_PROT_EN_1_STA 0x2c
+ #define MT8196_TOP_AXI_PROT_EN_1_SLEEP1_MD BIT(0)
+
+/* SPM BUS_PROTECT registers */
+#define MT8196_SPM_BUS_PROTECT_CON_SET 0xdc
+#define MT8196_SPM_BUS_PROTECT_CON_CLR 0xe0
+#define MT8196_SPM_BUS_PROTECT_RDY 0x208
+ #define MT8196_SPM_PROT_EN_BUS_CONN BIT(1)
+ #define MT8196_SPM_PROT_EN_BUS_SSUSB_DP_PHY_P0 BIT(6)
+ #define MT8196_SPM_PROT_EN_BUS_SSUSB_P0 BIT(7)
+ #define MT8196_SPM_PROT_EN_BUS_SSUSB_P1 BIT(8)
+ #define MT8196_SPM_PROT_EN_BUS_SSUSB_P23 BIT(9)
+ #define MT8196_SPM_PROT_EN_BUS_SSUSB_PHY_P2 BIT(10)
+ #define MT8196_SPM_PROT_EN_BUS_PEXTP_MAC0 BIT(13)
+ #define MT8196_SPM_PROT_EN_BUS_PEXTP_MAC1 BIT(14)
+ #define MT8196_SPM_PROT_EN_BUS_PEXTP_MAC2 BIT(15)
+ #define MT8196_SPM_PROT_EN_BUS_PEXTP_PHY0 BIT(16)
+ #define MT8196_SPM_PROT_EN_BUS_PEXTP_PHY1 BIT(17)
+ #define MT8196_SPM_PROT_EN_BUS_PEXTP_PHY2 BIT(18)
+ #define MT8196_SPM_PROT_EN_BUS_AUDIO BIT(19)
+ #define MT8196_SPM_PROT_EN_BUS_ADSP_TOP BIT(21)
+ #define MT8196_SPM_PROT_EN_BUS_ADSP_INFRA BIT(22)
+ #define MT8196_SPM_PROT_EN_BUS_ADSP_AO BIT(23)
+ #define MT8196_SPM_PROT_EN_BUS_MM_PROC BIT(24)
+
+/* PWR_CON registers */
+#define MT8196_PWR_ACK BIT(30)
+#define MT8196_PWR_ACK_2ND BIT(31)
+
+static enum scpsys_bus_prot_block scpsys_bus_prot_blocks_mt8196[] = {
+ BUS_PROT_BLOCK_INFRA, BUS_PROT_BLOCK_SPM
+};
+
+static const struct scpsys_domain_data scpsys_domain_data_mt8196[] = {
+ [MT8196_POWER_DOMAIN_MD] = {
+ .name = "md",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe00,
+ .pwr_sta_offs = 0xe00,
+ .pwr_sta2nd_offs = 0xe00,
+ .ext_buck_iso_offs = 0xefc,
+ .ext_buck_iso_mask = GENMASK(1, 0),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(INFRA, MT8196_TOP_AXI_PROT_EN_SLEEP0_MD,
+ MT8196_TOP_AXI_PROT_EN_SET,
+ MT8196_TOP_AXI_PROT_EN_CLR,
+ MT8196_TOP_AXI_PROT_EN_STA),
+ BUS_PROT_WR_IGN(INFRA, MT8196_TOP_AXI_PROT_EN_1_SLEEP1_MD,
+ MT8196_TOP_AXI_PROT_EN_1_SET,
+ MT8196_TOP_AXI_PROT_EN_1_CLR,
+ MT8196_TOP_AXI_PROT_EN_1_STA),
+ },
+ .caps = MTK_SCPD_MODEM_PWRSEQ | MTK_SCPD_EXT_BUCK_ISO |
+ MTK_SCPD_SKIP_RESET_B | MTK_SCPD_KEEP_DEFAULT_OFF,
+ },
+ [MT8196_POWER_DOMAIN_CONN] = {
+ .name = "conn",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe04,
+ .pwr_sta_offs = 0xe04,
+ .pwr_sta2nd_offs = 0xe04,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_CONN,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_KEEP_DEFAULT_OFF,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_SSUSB_DP_PHY_P0] = {
+ .name = "ssusb-dp-phy-p0",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe18,
+ .pwr_sta_offs = 0xe18,
+ .pwr_sta2nd_offs = 0xe18,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_DP_PHY_P0,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_ALWAYS_ON,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_SSUSB_P0] = {
+ .name = "ssusb-p0",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe1c,
+ .pwr_sta_offs = 0xe1c,
+ .pwr_sta2nd_offs = 0xe1c,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_P0,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_ALWAYS_ON,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_SSUSB_P1] = {
+ .name = "ssusb-p1",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe20,
+ .pwr_sta_offs = 0xe20,
+ .pwr_sta2nd_offs = 0xe20,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_P1,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_ALWAYS_ON,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_SSUSB_P23] = {
+ .name = "ssusb-p23",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe24,
+ .pwr_sta_offs = 0xe24,
+ .pwr_sta2nd_offs = 0xe24,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_P23,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_KEEP_DEFAULT_OFF,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_SSUSB_PHY_P2] = {
+ .name = "ssusb-phy-p2",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe28,
+ .pwr_sta_offs = 0xe28,
+ .pwr_sta2nd_offs = 0xe28,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_PHY_P2,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_KEEP_DEFAULT_OFF,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_PEXTP_MAC0] = {
+ .name = "pextp-mac0",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe34,
+ .pwr_sta_offs = 0xe34,
+ .pwr_sta2nd_offs = 0xe34,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_MAC0,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY,
+ },
+ [MT8196_POWER_DOMAIN_PEXTP_MAC1] = {
+ .name = "pextp-mac1",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe38,
+ .pwr_sta_offs = 0xe38,
+ .pwr_sta2nd_offs = 0xe38,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_MAC1,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY,
+ },
+ [MT8196_POWER_DOMAIN_PEXTP_MAC2] = {
+ .name = "pextp-mac2",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe3c,
+ .pwr_sta_offs = 0xe3c,
+ .pwr_sta2nd_offs = 0xe3c,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_MAC2,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY,
+ },
+ [MT8196_POWER_DOMAIN_PEXTP_PHY0] = {
+ .name = "pextp-phy0",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe40,
+ .pwr_sta_offs = 0xe40,
+ .pwr_sta2nd_offs = 0xe40,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_PHY0,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY,
+ },
+ [MT8196_POWER_DOMAIN_PEXTP_PHY1] = {
+ .name = "pextp-phy1",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe44,
+ .pwr_sta_offs = 0xe44,
+ .pwr_sta2nd_offs = 0xe44,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_PHY1,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY,
+ },
+ [MT8196_POWER_DOMAIN_PEXTP_PHY2] = {
+ .name = "pextp-phy2",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe48,
+ .pwr_sta_offs = 0xe48,
+ .pwr_sta2nd_offs = 0xe48,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_PHY2,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY,
+ },
+ [MT8196_POWER_DOMAIN_AUDIO] = {
+ .name = "audio",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe4c,
+ .pwr_sta_offs = 0xe4c,
+ .pwr_sta2nd_offs = 0xe4c,
+ .sram_pdn_bits = BIT(8),
+ .sram_pdn_ack_bits = BIT(12),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_AUDIO,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_ADSP_TOP_DORMANT] = {
+ .name = "adsp-top-dormant",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe54,
+ .pwr_sta_offs = 0xe54,
+ .pwr_sta2nd_offs = 0xe54,
+ /* Note: This is not managing powerdown (pdn), but sleep instead (slp) */
+ .sram_pdn_bits = BIT(9),
+ .sram_pdn_ack_bits = BIT(13),
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_ADSP_TOP,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_SRAM_ISO | MTK_SCPD_SRAM_PDN_INVERTED,
+ },
+ [MT8196_POWER_DOMAIN_ADSP_INFRA] = {
+ .name = "adsp-infra",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe58,
+ .pwr_sta_offs = 0xe58,
+ .pwr_sta2nd_offs = 0xe58,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_ADSP_INFRA,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_ALWAYS_ON,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+ [MT8196_POWER_DOMAIN_ADSP_AO] = {
+ .name = "adsp-ao",
+ .sta_mask = MT8196_PWR_ACK,
+ .sta2nd_mask = MT8196_PWR_ACK_2ND,
+ .ctl_offs = 0xe5c,
+ .pwr_sta_offs = 0xe5c,
+ .pwr_sta2nd_offs = 0xe5c,
+ .bp_cfg = {
+ BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_ADSP_AO,
+ MT8196_SPM_BUS_PROTECT_CON_SET,
+ MT8196_SPM_BUS_PROTECT_CON_CLR,
+ MT8196_SPM_BUS_PROTECT_RDY),
+ },
+ .caps = MTK_SCPD_ALWAYS_ON,
+ .rtff_type = SCPSYS_RTFF_TYPE_GENERIC,
+ },
+};
+
+static const struct scpsys_hwv_domain_data scpsys_hwv_domain_data_mt8196[] = {
+ [MT8196_POWER_DOMAIN_MM_PROC_DORMANT] = {
+ .name = "mm-proc-dormant",
+ .set = 0x0218,
+ .clr = 0x021c,
+ .done = 0x141c,
+ .en = 0x1410,
+ .set_sta = 0x146c,
+ .clr_sta = 0x1470,
+ .setclr_bit = 0,
+ .caps = MTK_SCPD_ALWAYS_ON,
+ },
+ [MT8196_POWER_DOMAIN_SSR] = {
+ .name = "ssrsys",
+ .set = 0x0218,
+ .clr = 0x021c,
+ .done = 0x141c,
+ .en = 0x1410,
+ .set_sta = 0x146c,
+ .clr_sta = 0x1470,
+ .setclr_bit = 1,
+ },
+};
+
+static const struct scpsys_hwv_domain_data hfrpsys_hwv_domain_data_mt8196[] = {
+ [MT8196_POWER_DOMAIN_VDE0] = {
+ .name = "vde0",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 7,
+ },
+ [MT8196_POWER_DOMAIN_VDE1] = {
+ .name = "vde1",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 8,
+ },
+ [MT8196_POWER_DOMAIN_VDE_VCORE0] = {
+ .name = "vde-vcore0",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 9,
+ },
+ [MT8196_POWER_DOMAIN_VEN0] = {
+ .name = "ven0",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 10,
+ },
+ [MT8196_POWER_DOMAIN_VEN1] = {
+ .name = "ven1",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 11,
+ },
+ [MT8196_POWER_DOMAIN_VEN2] = {
+ .name = "ven2",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 12,
+ },
+ [MT8196_POWER_DOMAIN_DISP_VCORE] = {
+ .name = "disp-vcore",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 24,
+ },
+ [MT8196_POWER_DOMAIN_DIS0_DORMANT] = {
+ .name = "dis0-dormant",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 25,
+ },
+ [MT8196_POWER_DOMAIN_DIS1_DORMANT] = {
+ .name = "dis1-dormant",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 26,
+ },
+ [MT8196_POWER_DOMAIN_OVL0_DORMANT] = {
+ .name = "ovl0-dormant",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 27,
+ },
+ [MT8196_POWER_DOMAIN_OVL1_DORMANT] = {
+ .name = "ovl1-dormant",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 28,
+ },
+ [MT8196_POWER_DOMAIN_DISP_EDPTX_DORMANT] = {
+ .name = "disp-edptx-dormant",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 29,
+ },
+ [MT8196_POWER_DOMAIN_DISP_DPTX_DORMANT] = {
+ .name = "disp-dptx-dormant",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 30,
+ },
+ [MT8196_POWER_DOMAIN_MML0_SHUTDOWN] = {
+ .name = "mml0-shutdown",
+ .set = 0x0218,
+ .clr = 0x021C,
+ .done = 0x141C,
+ .en = 0x1410,
+ .set_sta = 0x146C,
+ .clr_sta = 0x1470,
+ .setclr_bit = 31,
+ },
+ [MT8196_POWER_DOMAIN_MML1_SHUTDOWN] = {
+ .name = "mml1-shutdown",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 0,
+ },
+ [MT8196_POWER_DOMAIN_MM_INFRA0] = {
+ .name = "mm-infra0",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 1,
+ },
+ [MT8196_POWER_DOMAIN_MM_INFRA1] = {
+ .name = "mm-infra1",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 2,
+ },
+ [MT8196_POWER_DOMAIN_MM_INFRA_AO] = {
+ .name = "mm-infra-ao",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 3,
+ },
+ [MT8196_POWER_DOMAIN_CSI_BS_RX] = {
+ .name = "csi-bs-rx",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 5,
+ },
+ [MT8196_POWER_DOMAIN_CSI_LS_RX] = {
+ .name = "csi-ls-rx",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 6,
+ },
+ [MT8196_POWER_DOMAIN_DSI_PHY0] = {
+ .name = "dsi-phy0",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 7,
+ },
+ [MT8196_POWER_DOMAIN_DSI_PHY1] = {
+ .name = "dsi-phy1",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 8,
+ },
+ [MT8196_POWER_DOMAIN_DSI_PHY2] = {
+ .name = "dsi-phy2",
+ .set = 0x0220,
+ .clr = 0x0224,
+ .done = 0x142C,
+ .en = 0x1420,
+ .set_sta = 0x1474,
+ .clr_sta = 0x1478,
+ .setclr_bit = 9,
+ },
+};
+
+static const struct scpsys_soc_data mt8196_scpsys_data = {
+ .domains_data = scpsys_domain_data_mt8196,
+ .num_domains = ARRAY_SIZE(scpsys_domain_data_mt8196),
+ .bus_prot_blocks = scpsys_bus_prot_blocks_mt8196,
+ .num_bus_prot_blocks = ARRAY_SIZE(scpsys_bus_prot_blocks_mt8196),
+ .type = SCPSYS_MTCMOS_TYPE_DIRECT_CTL,
+};
+
+static const struct scpsys_soc_data mt8196_scpsys_hwv_data = {
+ .hwv_domains_data = scpsys_hwv_domain_data_mt8196,
+ .num_hwv_domains = ARRAY_SIZE(scpsys_hwv_domain_data_mt8196),
+ .type = SCPSYS_MTCMOS_TYPE_HW_VOTER,
+};
+
+static const struct scpsys_soc_data mt8196_hfrpsys_hwv_data = {
+ .hwv_domains_data = hfrpsys_hwv_domain_data_mt8196,
+ .num_hwv_domains = ARRAY_SIZE(hfrpsys_hwv_domain_data_mt8196),
+ .type = SCPSYS_MTCMOS_TYPE_HW_VOTER,
+};
+
+#endif /* __SOC_MEDIATEK_MT8196_PM_DOMAINS_H */
diff --git a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
new file mode 100644
index 000000000000..9bad577b3ae4
--- /dev/null
+++ b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
@@ -0,0 +1,1044 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for MediaTek MFlexGraphics Devices
+ *
+ * Copyright (C) 2025, Collabora Ltd.
+ */
+
+#include <linux/completion.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/container_of.h>
+#include <linux/iopoll.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/overflow.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+
+#define GPR_LP_STATE 0x0028
+#define EB_ON_SUSPEND 0x0
+#define EB_ON_RESUME 0x1
+#define GPR_IPI_MAGIC 0x34
+
+#define RPC_PWR_CON 0x0504
+#define PWR_ACK_M GENMASK(31, 30)
+#define RPC_DUMMY_REG_2 0x0658
+#define RPC_GHPM_CFG0_CON 0x0800
+#define GHPM_ENABLE_M BIT(0)
+#define GHPM_ON_SEQ_M BIT(2)
+#define RPC_GHPM_RO0_CON 0x09A4
+#define GHPM_STATE_M GENMASK(7, 0)
+#define GHPM_PWR_STATE_M BIT(16)
+
+#define GF_REG_MAGIC 0x0000
+#define GF_REG_GPU_OPP_IDX 0x0004
+#define GF_REG_STK_OPP_IDX 0x0008
+#define GF_REG_GPU_OPP_NUM 0x000c
+#define GF_REG_STK_OPP_NUM 0x0010
+#define GF_REG_GPU_OPP_SNUM 0x0014
+#define GF_REG_STK_OPP_SNUM 0x0018
+#define GF_REG_POWER_COUNT 0x001c
+#define GF_REG_BUCK_COUNT 0x0020
+#define GF_REG_MTCMOS_COUNT 0x0024
+#define GF_REG_CG_COUNT 0x0028 /* CG = Clock Gate? */
+#define GF_REG_ACTIVE_COUNT 0x002C
+#define GF_REG_TEMP_RAW 0x0030
+#define GF_REG_TEMP_NORM_GPU 0x0034
+#define GF_REG_TEMP_HIGH_GPU 0x0038
+#define GF_REG_TEMP_NORM_STK 0x003C
+#define GF_REG_TEMP_HIGH_STK 0x0040
+#define GF_REG_FREQ_CUR_GPU 0x0044
+#define GF_REG_FREQ_CUR_STK 0x0048
+#define GF_REG_FREQ_OUT_GPU 0x004C /* Guess: actual achieved freq */
+#define GF_REG_FREQ_OUT_STK 0x0050 /* Guess: actual achieved freq */
+#define GF_REG_FREQ_METER_GPU 0x0054 /* Seems unused, always 0 */
+#define GF_REG_FREQ_METER_STK 0x0058 /* Seems unused, always 0 */
+#define GF_REG_VOLT_CUR_GPU 0x005C /* in tens of microvolts */
+#define GF_REG_VOLT_CUR_STK 0x0060 /* in tens of microvolts */
+#define GF_REG_VOLT_CUR_GPU_SRAM 0x0064
+#define GF_REG_VOLT_CUR_STK_SRAM 0x0068
+#define GF_REG_VOLT_CUR_GPU_REG 0x006C /* Seems unused, always 0 */
+#define GF_REG_VOLT_CUR_STK_REG 0x0070 /* Seems unused, always 0 */
+#define GF_REG_VOLT_CUR_GPU_REG_SRAM 0x0074
+#define GF_REG_VOLT_CUR_STK_REG_SRAM 0x0078
+#define GF_REG_PWR_CUR_GPU 0x007C /* in milliwatts */
+#define GF_REG_PWR_CUR_STK 0x0080 /* in milliwatts */
+#define GF_REG_PWR_MAX_GPU 0x0084 /* in milliwatts */
+#define GF_REG_PWR_MAX_STK 0x0088 /* in milliwatts */
+#define GF_REG_PWR_MIN_GPU 0x008C /* in milliwatts */
+#define GF_REG_PWR_MIN_STK 0x0090 /* in milliwatts */
+#define GF_REG_LEAKAGE_RT_GPU 0x0094 /* Unknown */
+#define GF_REG_LEAKAGE_RT_STK 0x0098 /* Unknown */
+#define GF_REG_LEAKAGE_RT_SRAM 0x009C /* Unknown */
+#define GF_REG_LEAKAGE_HT_GPU 0x00A0 /* Unknown */
+#define GF_REG_LEAKAGE_HT_STK 0x00A4 /* Unknown */
+#define GF_REG_LEAKAGE_HT_SRAM 0x00A8 /* Unknown */
+#define GF_REG_VOLT_DAC_LOW_GPU 0x00AC /* Seems unused, always 0 */
+#define GF_REG_VOLT_DAC_LOW_STK 0x00B0 /* Seems unused, always 0 */
+#define GF_REG_OPP_CUR_CEIL 0x00B4
+#define GF_REG_OPP_CUR_FLOOR 0x00B8
+#define GF_REG_OPP_CUR_LIMITER_CEIL 0x00BC
+#define GF_REG_OPP_CUR_LIMITER_FLOOR 0x00C0
+#define GF_REG_OPP_PRIORITY_CEIL 0x00C4
+#define GF_REG_OPP_PRIORITY_FLOOR 0x00C8
+#define GF_REG_PWR_CTL 0x00CC
+#define GF_REG_ACTIVE_SLEEP_CTL 0x00D0
+#define GF_REG_DVFS_STATE 0x00D4
+#define GF_REG_SHADER_PRESENT 0x00D8
+#define GF_REG_ASENSOR_ENABLE 0x00DC
+#define GF_REG_AGING_LOAD 0x00E0
+#define GF_REG_AGING_MARGIN 0x00E4
+#define GF_REG_AVS_ENABLE 0x00E8
+#define GF_REG_AVS_MARGIN 0x00EC
+#define GF_REG_CHIP_TYPE 0x00F0
+#define GF_REG_SB_VERSION 0x00F4
+#define GF_REG_PTP_VERSION 0x00F8
+#define GF_REG_DBG_VERSION 0x00FC
+#define GF_REG_KDBG_VERSION 0x0100
+#define GF_REG_GPM1_MODE 0x0104
+#define GF_REG_GPM3_MODE 0x0108
+#define GF_REG_DFD_MODE 0x010C
+#define GF_REG_DUAL_BUCK 0x0110
+#define GF_REG_SEGMENT_ID 0x0114
+#define GF_REG_POWER_TIME_H 0x0118
+#define GF_REG_POWER_TIME_L 0x011C
+#define GF_REG_PWR_STATUS 0x0120
+#define GF_REG_STRESS_TEST 0x0124
+#define GF_REG_TEST_MODE 0x0128
+#define GF_REG_IPS_MODE 0x012C
+#define GF_REG_TEMP_COMP_MODE 0x0130
+#define GF_REG_HT_TEMP_COMP_MODE 0x0134
+#define GF_REG_PWR_TRACKER_MODE 0x0138
+#define GF_REG_OPP_TABLE_GPU 0x0314
+#define GF_REG_OPP_TABLE_STK 0x09A4
+#define GF_REG_OPP_TABLE_GPU_S 0x1034
+#define GF_REG_OPP_TABLE_STK_S 0x16c4
+#define GF_REG_LIMIT_TABLE 0x1d54
+#define GF_REG_GPM3_TABLE 0x223C
+
+#define MFG_MT8196_E2_ID 0x101
+#define GPUEB_SLEEP_MAGIC 0x55667788UL
+#define GPUEB_MEM_MAGIC 0xBABADADAUL
+
+#define GPUEB_TIMEOUT_US 10000UL
+#define GPUEB_POLL_US 50
+
+#define MAX_OPP_NUM 70
+
+#define GPUEB_MBOX_MAX_RX_SIZE 32 /* in bytes */
+
+/*
+ * This enum is part of the ABI of the GPUEB firmware. Don't change the
+ * numbering, as you would wreak havoc.
+ */
+enum mtk_mfg_ipi_cmd {
+ CMD_INIT_SHARED_MEM = 0,
+ CMD_GET_FREQ_BY_IDX = 1,
+ CMD_GET_POWER_BY_IDX = 2,
+ CMD_GET_OPPIDX_BY_FREQ = 3,
+ CMD_GET_LEAKAGE_POWER = 4,
+ CMD_SET_LIMIT = 5,
+ CMD_POWER_CONTROL = 6,
+ CMD_ACTIVE_SLEEP_CONTROL = 7,
+ CMD_COMMIT = 8,
+ CMD_DUAL_COMMIT = 9,
+ CMD_PDCA_CONFIG = 10,
+ CMD_UPDATE_DEBUG_OPP_INFO = 11,
+ CMD_SWITCH_LIMIT = 12,
+ CMD_FIX_TARGET_OPPIDX = 13,
+ CMD_FIX_DUAL_TARGET_OPPIDX = 14,
+ CMD_FIX_CUSTOM_FREQ_VOLT = 15,
+ CMD_FIX_DUAL_CUSTOM_FREQ_VOLT = 16,
+ CMD_SET_MFGSYS_CONFIG = 17,
+ CMD_MSSV_COMMIT = 18,
+ CMD_NUM = 19,
+};
+
+/*
+ * This struct is part of the ABI of the GPUEB firmware. Changing it, or
+ * reordering fields in it, will break things, so don't do it. Thank you.
+ */
+struct __packed mtk_mfg_ipi_msg {
+ __le32 magic;
+ __le32 cmd;
+ __le32 target;
+ /*
+ * Downstream relies on the compiler to implicitly add the following
+ * padding, as it declares the struct as non-packed.
+ */
+ __le32 reserved;
+ union {
+ s32 __bitwise oppidx;
+ s32 __bitwise return_value;
+ __le32 freq;
+ __le32 volt;
+ __le32 power;
+ __le32 power_state;
+ __le32 mode;
+ __le32 value;
+ struct {
+ __le64 base;
+ __le32 size;
+ } shared_mem;
+ struct {
+ __le32 freq;
+ __le32 volt;
+ } custom;
+ struct {
+ __le32 limiter;
+ s32 __bitwise ceiling_info;
+ s32 __bitwise floor_info;
+ } set_limit;
+ struct {
+ __le32 target;
+ __le32 val;
+ } mfg_cfg;
+ struct {
+ __le32 target;
+ __le32 val;
+ } mssv;
+ struct {
+ s32 __bitwise gpu_oppidx;
+ s32 __bitwise stack_oppidx;
+ } dual_commit;
+ struct {
+ __le32 fgpu;
+ __le32 vgpu;
+ __le32 fstack;
+ __le32 vstack;
+ } dual_custom;
+ } u;
+};
+
+struct __packed mtk_mfg_ipi_sleep_msg {
+ __le32 event;
+ __le32 state;
+ __le32 magic;
+};
+
+/**
+ * struct mtk_mfg_opp_entry - OPP table entry from firmware
+ * @freq_khz: The operating point's frequency in kilohertz
+ * @voltage_core: The operating point's core voltage in tens of microvolts
+ * @voltage_sram: The operating point's SRAM voltage in tens of microvolts
+ * @posdiv: exponent of base 2 for PLL frequency divisor used for this OPP
+ * @voltage_margin: Number of tens of microvolts the voltage can be undershot
+ * @power_mw: estimate of power usage at this operating point, in milliwatts
+ *
+ * This struct is part of the ABI with the EB firmware. Do not change it.
+ */
+struct __packed mtk_mfg_opp_entry {
+ __le32 freq_khz;
+ __le32 voltage_core;
+ __le32 voltage_sram;
+ __le32 posdiv;
+ __le32 voltage_margin;
+ __le32 power_mw;
+};
+
+struct mtk_mfg_mbox {
+ struct mbox_client cl;
+ struct completion rx_done;
+ struct mtk_mfg *mfg;
+ struct mbox_chan *ch;
+ void *rx_data;
+};
+
+struct mtk_mfg {
+ struct generic_pm_domain pd;
+ struct platform_device *pdev;
+ struct clk *clk_eb;
+ struct clk_bulk_data *gpu_clks;
+ struct clk_hw clk_core_hw;
+ struct clk_hw clk_stack_hw;
+ struct regulator_bulk_data *gpu_regs;
+ void __iomem *rpc;
+ void __iomem *gpr;
+ void __iomem *shared_mem;
+ phys_addr_t shared_mem_phys;
+ unsigned int shared_mem_size;
+ u16 ghpm_en_reg;
+ u32 ipi_magic;
+ unsigned short num_gpu_opps;
+ unsigned short num_stack_opps;
+ struct dev_pm_opp_data *gpu_opps;
+ struct dev_pm_opp_data *stack_opps;
+ struct mtk_mfg_mbox *gf_mbox;
+ struct mtk_mfg_mbox *slp_mbox;
+ const struct mtk_mfg_variant *variant;
+};
+
+struct mtk_mfg_variant {
+ const char *const *clk_names;
+ unsigned int num_clks;
+ const char *const *regulator_names;
+ unsigned int num_regulators;
+ /** @turbo_below: opp indices below this value are considered turbo */
+ unsigned int turbo_below;
+ int (*init)(struct mtk_mfg *mfg);
+};
+
+static inline struct mtk_mfg *mtk_mfg_from_genpd(struct generic_pm_domain *pd)
+{
+ return container_of(pd, struct mtk_mfg, pd);
+}
+
+static inline void mtk_mfg_update_reg_bits(void __iomem *addr, u32 mask, u32 val)
+{
+ writel((readl(addr) & ~mask) | (val & mask), addr);
+}
+
+static inline bool mtk_mfg_is_powered_on(struct mtk_mfg *mfg)
+{
+ return (readl(mfg->rpc + RPC_PWR_CON) & PWR_ACK_M) == PWR_ACK_M;
+}
+
+static unsigned long mtk_mfg_recalc_rate_gpu(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct mtk_mfg *mfg = container_of(hw, struct mtk_mfg, clk_core_hw);
+
+ return readl(mfg->shared_mem + GF_REG_FREQ_OUT_GPU) * HZ_PER_KHZ;
+}
+
+static int mtk_mfg_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ /*
+ * The determine_rate callback needs to be implemented to avoid returning
+ * the current clock frequency, rather than something even remotely
+ * close to the frequency that was asked for.
+ *
+ * Instead of writing considerable amounts of possibly slow code just to
+ * somehow figure out which of the three PLLs to round for, or even to
+ * do a search through one of two OPP tables in order to find the closest
+ * OPP of a frequency, just return the rate as-is. This avoids devfreq
+ * "rounding" a request for the lowest frequency to the possibly very
+ * high current frequency, breaking the powersave governor in the process.
+ */
+
+ return 0;
+}
+
+static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct mtk_mfg *mfg = container_of(hw, struct mtk_mfg, clk_stack_hw);
+
+ return readl(mfg->shared_mem + GF_REG_FREQ_OUT_STK) * HZ_PER_KHZ;
+}
+
+static const struct clk_ops mtk_mfg_clk_gpu_ops = {
+ .recalc_rate = mtk_mfg_recalc_rate_gpu,
+ .determine_rate = mtk_mfg_determine_rate,
+};
+
+static const struct clk_ops mtk_mfg_clk_stack_ops = {
+ .recalc_rate = mtk_mfg_recalc_rate_stack,
+ .determine_rate = mtk_mfg_determine_rate,
+};
+
+static const struct clk_init_data mtk_mfg_clk_gpu_init = {
+ .name = "gpu-core",
+ .ops = &mtk_mfg_clk_gpu_ops,
+ .flags = CLK_GET_RATE_NOCACHE,
+};
+
+static const struct clk_init_data mtk_mfg_clk_stack_init = {
+ .name = "gpu-stack",
+ .ops = &mtk_mfg_clk_stack_ops,
+ .flags = CLK_GET_RATE_NOCACHE,
+};
+
+static int mtk_mfg_eb_on(struct mtk_mfg *mfg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ u32 val;
+ int ret;
+
+ /*
+ * If MFG is already on from e.g. the bootloader, skip doing the
+ * power-on sequence, as it wouldn't work without powering it off first.
+ */
+ if (mtk_mfg_is_powered_on(mfg))
+ return 0;
+
+ ret = readl_poll_timeout(mfg->rpc + RPC_GHPM_RO0_CON, val,
+ !(val & (GHPM_PWR_STATE_M | GHPM_STATE_M)),
+ GPUEB_POLL_US, GPUEB_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "timed out waiting for EB to power on\n");
+ return ret;
+ }
+
+ mtk_mfg_update_reg_bits(mfg->rpc + mfg->ghpm_en_reg, GHPM_ENABLE_M,
+ GHPM_ENABLE_M);
+
+ mtk_mfg_update_reg_bits(mfg->rpc + RPC_GHPM_CFG0_CON, GHPM_ON_SEQ_M, 0);
+ mtk_mfg_update_reg_bits(mfg->rpc + RPC_GHPM_CFG0_CON, GHPM_ON_SEQ_M,
+ GHPM_ON_SEQ_M);
+
+ mtk_mfg_update_reg_bits(mfg->rpc + mfg->ghpm_en_reg, GHPM_ENABLE_M, 0);
+
+
+ ret = readl_poll_timeout(mfg->rpc + RPC_PWR_CON, val,
+ (val & PWR_ACK_M) == PWR_ACK_M,
+ GPUEB_POLL_US, GPUEB_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "timed out waiting for EB power ack, val = 0x%X\n",
+ val);
+ return ret;
+ }
+
+ ret = readl_poll_timeout(mfg->gpr + GPR_LP_STATE, val,
+ (val == EB_ON_RESUME),
+ GPUEB_POLL_US, GPUEB_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "timed out waiting for EB to resume, status = 0x%X\n", val);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_mfg_eb_off(struct mtk_mfg *mfg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ struct mtk_mfg_ipi_sleep_msg msg = {
+ .event = 0,
+ .state = 0,
+ .magic = GPUEB_SLEEP_MAGIC
+ };
+ u32 val;
+ int ret;
+
+ ret = mbox_send_message(mfg->slp_mbox->ch, &msg);
+ if (ret < 0) {
+ dev_err(dev, "Cannot send sleep command: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = readl_poll_timeout(mfg->rpc + RPC_PWR_CON, val,
+ !(val & PWR_ACK_M), GPUEB_POLL_US,
+ GPUEB_TIMEOUT_US);
+
+ if (ret) {
+ dev_err(dev, "Timed out waiting for EB to power off, val=0x%08X\n", val);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mtk_mfg_send_ipi - synchronously send an IPI message on the gpufreq channel
+ * @mfg: pointer to this driver instance's private &struct mtk_mfg
+ * @msg: pointer to a message to send; will have magic filled and response assigned
+ *
+ * Send an IPI message on the gpufreq channel, and wait for a response. Once a
+ * response is received, assign a pointer to the response buffer (valid until
+ * next response is received) to @msg.
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+static int mtk_mfg_send_ipi(struct mtk_mfg *mfg, struct mtk_mfg_ipi_msg *msg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ unsigned long wait;
+ int ret;
+
+ msg->magic = mfg->ipi_magic;
+
+ ret = mbox_send_message(mfg->gf_mbox->ch, msg);
+ if (ret < 0) {
+ dev_err(dev, "Cannot send GPUFreq IPI command: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ wait = wait_for_completion_timeout(&mfg->gf_mbox->rx_done, msecs_to_jiffies(500));
+ if (!wait)
+ return -ETIMEDOUT;
+
+ msg = mfg->gf_mbox->rx_data;
+
+ if (msg->u.return_value < 0) {
+ dev_err(dev, "IPI return: %d\n", msg->u.return_value);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int mtk_mfg_init_shared_mem(struct mtk_mfg *mfg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ struct mtk_mfg_ipi_msg msg = {};
+ int ret;
+
+ dev_dbg(dev, "clearing GPUEB shared memory, 0x%X bytes\n", mfg->shared_mem_size);
+ memset_io(mfg->shared_mem, 0, mfg->shared_mem_size);
+
+ msg.cmd = CMD_INIT_SHARED_MEM;
+ msg.u.shared_mem.base = mfg->shared_mem_phys;
+ msg.u.shared_mem.size = mfg->shared_mem_size;
+
+ ret = mtk_mfg_send_ipi(mfg, &msg);
+ if (ret)
+ return ret;
+
+ if (readl(mfg->shared_mem + GF_REG_MAGIC) != GPUEB_MEM_MAGIC) {
+ dev_err(dev, "EB did not initialise shared memory correctly\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mtk_mfg_power_control(struct mtk_mfg *mfg, bool enabled)
+{
+ struct mtk_mfg_ipi_msg msg = {};
+
+ msg.cmd = CMD_POWER_CONTROL;
+ msg.u.power_state = enabled ? 1 : 0;
+
+ return mtk_mfg_send_ipi(mfg, &msg);
+}
+
+static int mtk_mfg_set_oppidx(struct mtk_mfg *mfg, unsigned int opp_idx)
+{
+ struct mtk_mfg_ipi_msg msg = {};
+ int ret;
+
+ if (opp_idx >= mfg->num_gpu_opps)
+ return -EINVAL;
+
+ msg.cmd = CMD_FIX_DUAL_TARGET_OPPIDX;
+ msg.u.dual_commit.gpu_oppidx = opp_idx;
+ msg.u.dual_commit.stack_oppidx = opp_idx;
+
+ ret = mtk_mfg_send_ipi(mfg, &msg);
+ if (ret) {
+ dev_err(&mfg->pdev->dev, "Failed to set OPP %u: %pe\n",
+ opp_idx, ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_mfg_read_opp_tables(struct mtk_mfg *mfg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ struct mtk_mfg_opp_entry e = {};
+ unsigned int i;
+
+ mfg->num_gpu_opps = readl(mfg->shared_mem + GF_REG_GPU_OPP_NUM);
+ mfg->num_stack_opps = readl(mfg->shared_mem + GF_REG_STK_OPP_NUM);
+
+ if (mfg->num_gpu_opps > MAX_OPP_NUM || mfg->num_gpu_opps == 0) {
+ dev_err(dev, "GPU OPP count (%u) out of range %u >= count > 0\n",
+ mfg->num_gpu_opps, MAX_OPP_NUM);
+ return -EINVAL;
+ }
+
+ if (mfg->num_stack_opps && mfg->num_stack_opps > MAX_OPP_NUM) {
+ dev_err(dev, "Stack OPP count (%u) out of range %u >= count >= 0\n",
+ mfg->num_stack_opps, MAX_OPP_NUM);
+ return -EINVAL;
+ }
+
+ mfg->gpu_opps = devm_kcalloc(dev, mfg->num_gpu_opps,
+ sizeof(struct dev_pm_opp_data), GFP_KERNEL);
+ if (!mfg->gpu_opps)
+ return -ENOMEM;
+
+ if (mfg->num_stack_opps) {
+ mfg->stack_opps = devm_kcalloc(dev, mfg->num_stack_opps,
+ sizeof(struct dev_pm_opp_data), GFP_KERNEL);
+ if (!mfg->stack_opps)
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < mfg->num_gpu_opps; i++) {
+ memcpy_fromio(&e, mfg->shared_mem + GF_REG_OPP_TABLE_GPU + i * sizeof(e),
+ sizeof(e));
+ if (mem_is_zero(&e, sizeof(e))) {
+ dev_err(dev, "ran into an empty GPU OPP at index %u\n",
+ i);
+ return -EINVAL;
+ }
+ mfg->gpu_opps[i].freq = e.freq_khz * HZ_PER_KHZ;
+ mfg->gpu_opps[i].u_volt = e.voltage_core * 10;
+ mfg->gpu_opps[i].level = i;
+ if (i < mfg->variant->turbo_below)
+ mfg->gpu_opps[i].turbo = true;
+ }
+
+ for (i = 0; i < mfg->num_stack_opps; i++) {
+ memcpy_fromio(&e, mfg->shared_mem + GF_REG_OPP_TABLE_STK + i * sizeof(e),
+ sizeof(e));
+ if (mem_is_zero(&e, sizeof(e))) {
+ dev_err(dev, "ran into an empty Stack OPP at index %u\n",
+ i);
+ return -EINVAL;
+ }
+ mfg->stack_opps[i].freq = e.freq_khz * HZ_PER_KHZ;
+ mfg->stack_opps[i].u_volt = e.voltage_core * 10;
+ mfg->stack_opps[i].level = i;
+ if (i < mfg->variant->turbo_below)
+ mfg->stack_opps[i].turbo = true;
+ }
+
+ return 0;
+}
+
+static const char *const mtk_mfg_mt8196_clk_names[] = {
+ "core",
+ "stack0",
+ "stack1",
+};
+
+static const char *const mtk_mfg_mt8196_regulators[] = {
+ "core",
+ "stack",
+ "sram",
+};
+
+static int mtk_mfg_mt8196_init(struct mtk_mfg *mfg)
+{
+ void __iomem *e2_base;
+
+ e2_base = devm_platform_ioremap_resource_byname(mfg->pdev, "hw-revision");
+ if (IS_ERR(e2_base))
+ return dev_err_probe(&mfg->pdev->dev, PTR_ERR(e2_base),
+ "Couldn't get hw-revision register\n");
+
+ clk_prepare_enable(mfg->clk_eb);
+
+ if (readl(e2_base) == MFG_MT8196_E2_ID)
+ mfg->ghpm_en_reg = RPC_DUMMY_REG_2;
+ else
+ mfg->ghpm_en_reg = RPC_GHPM_CFG0_CON;
+
+ clk_disable_unprepare(mfg->clk_eb);
+
+ return 0;
+}
+
+static const struct mtk_mfg_variant mtk_mfg_mt8196_variant = {
+ .clk_names = mtk_mfg_mt8196_clk_names,
+ .num_clks = ARRAY_SIZE(mtk_mfg_mt8196_clk_names),
+ .regulator_names = mtk_mfg_mt8196_regulators,
+ .num_regulators = ARRAY_SIZE(mtk_mfg_mt8196_regulators),
+ .turbo_below = 7,
+ .init = mtk_mfg_mt8196_init,
+};
+
+static void mtk_mfg_mbox_rx_callback(struct mbox_client *cl, void *mssg)
+{
+ struct mtk_mfg_mbox *mb = container_of(cl, struct mtk_mfg_mbox, cl);
+
+ if (mb->rx_data)
+ mb->rx_data = memcpy(mb->rx_data, mssg, GPUEB_MBOX_MAX_RX_SIZE);
+ complete(&mb->rx_done);
+}
+
+static int mtk_mfg_attach_dev(struct generic_pm_domain *pd, struct device *dev)
+{
+ struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
+ struct dev_pm_opp_data *so = mfg->stack_opps;
+ struct dev_pm_opp_data *go = mfg->gpu_opps;
+ struct dev_pm_opp_data *prev_o;
+ struct dev_pm_opp_data *o;
+ int i, ret;
+
+ for (i = mfg->num_gpu_opps - 1; i >= 0; i--) {
+ /*
+ * Adding the lower of the two OPPs avoids gaps of indices in
+ * situations where the GPU OPPs are duplicated a couple of
+ * times when only the Stack OPP is being lowered at that index.
+ */
+ if (i >= mfg->num_stack_opps || go[i].freq < so[i].freq)
+ o = &go[i];
+ else
+ o = &so[i];
+
+ /*
+ * Skip indices where both GPU and Stack OPPs are equal. Nominally,
+ * OPP core shouldn't care about dupes, but not doing so will cause
+ * dev_pm_opp_find_freq_ceil_indexed to -ERANGE later down the line.
+ */
+ if (prev_o && prev_o->freq == o->freq)
+ continue;
+
+ ret = dev_pm_opp_add_dynamic(dev, o);
+ if (ret) {
+ dev_err(dev, "Failed to add OPP level %u from PD %s: %pe\n",
+ o->level, pd->name, ERR_PTR(ret));
+ dev_pm_opp_remove_all_dynamic(dev);
+ return ret;
+ }
+ prev_o = o;
+ }
+
+ return 0;
+}
+
+static void mtk_mfg_detach_dev(struct generic_pm_domain *pd, struct device *dev)
+{
+ dev_pm_opp_remove_all_dynamic(dev);
+}
+
+static int mtk_mfg_set_performance(struct generic_pm_domain *pd,
+ unsigned int state)
+{
+ struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
+
+ /*
+ * pmdomain core intentionally sets a performance state before turning
+ * a domain on, and after turning it off. For the GPUEB however, it's
+ * only possible to act on performance requests when the GPUEB is
+ * powered on. To do this, return cleanly without taking action, and
+ * defer setting what pmdomain core set in mtk_mfg_power_on.
+ */
+ if (mfg->pd.status != GENPD_STATE_ON)
+ return 0;
+
+ return mtk_mfg_set_oppidx(mfg, state);
+}
+
+static int mtk_mfg_power_on(struct generic_pm_domain *pd)
+{
+ struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
+ int ret;
+
+ ret = regulator_bulk_enable(mfg->variant->num_regulators,
+ mfg->gpu_regs);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(mfg->clk_eb);
+ if (ret)
+ goto err_disable_regulators;
+
+ ret = clk_bulk_prepare_enable(mfg->variant->num_clks, mfg->gpu_clks);
+ if (ret)
+ goto err_disable_eb_clk;
+
+ ret = mtk_mfg_eb_on(mfg);
+ if (ret)
+ goto err_disable_clks;
+
+ mfg->ipi_magic = readl(mfg->gpr + GPR_IPI_MAGIC);
+
+ ret = mtk_mfg_power_control(mfg, true);
+ if (ret)
+ goto err_eb_off;
+
+ /* Don't try to set a OPP in probe before OPPs have been read from EB */
+ if (mfg->gpu_opps) {
+ /* The aforementioned deferred setting of pmdomain's state */
+ ret = mtk_mfg_set_oppidx(mfg, pd->performance_state);
+ if (ret)
+ dev_warn(&mfg->pdev->dev, "Failed to set oppidx in %s\n", __func__);
+ }
+
+ return 0;
+
+err_eb_off:
+ mtk_mfg_eb_off(mfg);
+err_disable_clks:
+ clk_bulk_disable_unprepare(mfg->variant->num_clks, mfg->gpu_clks);
+err_disable_eb_clk:
+ clk_disable_unprepare(mfg->clk_eb);
+err_disable_regulators:
+ regulator_bulk_disable(mfg->variant->num_regulators, mfg->gpu_regs);
+
+ return ret;
+}
+
+static int mtk_mfg_power_off(struct generic_pm_domain *pd)
+{
+ struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
+ struct device *dev = &mfg->pdev->dev;
+ int ret;
+
+ ret = mtk_mfg_power_control(mfg, false);
+ if (ret) {
+ dev_err(dev, "power_control failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = mtk_mfg_eb_off(mfg);
+ if (ret) {
+ dev_err(dev, "eb_off failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ clk_bulk_disable_unprepare(mfg->variant->num_clks, mfg->gpu_clks);
+ clk_disable_unprepare(mfg->clk_eb);
+ ret = regulator_bulk_disable(mfg->variant->num_regulators, mfg->gpu_regs);
+ if (ret) {
+ dev_err(dev, "Disabling regulators failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_mfg_init_mbox(struct mtk_mfg *mfg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ struct mtk_mfg_mbox *gf;
+ struct mtk_mfg_mbox *slp;
+
+ gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL);
+ if (!gf)
+ return -ENOMEM;
+
+ gf->rx_data = devm_kzalloc(dev, GPUEB_MBOX_MAX_RX_SIZE, GFP_KERNEL);
+ if (!gf->rx_data)
+ return -ENOMEM;
+
+ gf->mfg = mfg;
+ init_completion(&gf->rx_done);
+ gf->cl.dev = dev;
+ gf->cl.rx_callback = mtk_mfg_mbox_rx_callback;
+ gf->cl.tx_tout = GPUEB_TIMEOUT_US / USEC_PER_MSEC;
+ gf->ch = mbox_request_channel_byname(&gf->cl, "gpufreq");
+ if (IS_ERR(gf->ch))
+ return PTR_ERR(gf->ch);
+
+ mfg->gf_mbox = gf;
+
+ slp = devm_kzalloc(dev, sizeof(*slp), GFP_KERNEL);
+ if (!slp)
+ return -ENOMEM;
+
+ slp->mfg = mfg;
+ init_completion(&slp->rx_done);
+ slp->cl.dev = dev;
+ slp->cl.tx_tout = GPUEB_TIMEOUT_US / USEC_PER_MSEC;
+ slp->cl.tx_block = true;
+ slp->ch = mbox_request_channel_byname(&slp->cl, "sleep");
+ if (IS_ERR(slp->ch)) {
+ mbox_free_channel(gf->ch);
+ return PTR_ERR(slp->ch);
+ }
+
+ mfg->slp_mbox = slp;
+
+ return 0;
+}
+
+static int mtk_mfg_init_clk_provider(struct mtk_mfg *mfg)
+{
+ struct device *dev = &mfg->pdev->dev;
+ struct clk_hw_onecell_data *clk_data;
+ int ret;
+
+ clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, 2), GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->num = 2;
+
+ mfg->clk_core_hw.init = &mtk_mfg_clk_gpu_init;
+ mfg->clk_stack_hw.init = &mtk_mfg_clk_stack_init;
+
+ ret = devm_clk_hw_register(dev, &mfg->clk_core_hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't register GPU core clock\n");
+
+ ret = devm_clk_hw_register(dev, &mfg->clk_stack_hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't register GPU stack clock\n");
+
+ clk_data->hws[0] = &mfg->clk_core_hw;
+ clk_data->hws[1] = &mfg->clk_stack_hw;
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't register clock provider\n");
+
+ return 0;
+}
+
+static int mtk_mfg_probe(struct platform_device *pdev)
+{
+ struct mtk_mfg *mfg;
+ struct device *dev = &pdev->dev;
+ const struct mtk_mfg_variant *data = of_device_get_match_data(dev);
+ struct resource res;
+ int ret, i;
+
+ mfg = devm_kzalloc(dev, sizeof(*mfg), GFP_KERNEL);
+ if (!mfg)
+ return -ENOMEM;
+
+ mfg->pdev = pdev;
+ mfg->variant = data;
+
+ dev_set_drvdata(dev, mfg);
+
+ mfg->gpr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mfg->gpr))
+ return dev_err_probe(dev, PTR_ERR(mfg->gpr),
+ "Couldn't retrieve GPR MMIO registers\n");
+
+ mfg->rpc = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(mfg->rpc))
+ return dev_err_probe(dev, PTR_ERR(mfg->rpc),
+ "Couldn't retrieve RPC MMIO registers\n");
+
+ mfg->clk_eb = devm_clk_get(dev, "eb");
+ if (IS_ERR(mfg->clk_eb))
+ return dev_err_probe(dev, PTR_ERR(mfg->clk_eb),
+ "Couldn't get 'eb' clock\n");
+
+ mfg->gpu_clks = devm_kcalloc(dev, data->num_clks, sizeof(*mfg->gpu_clks),
+ GFP_KERNEL);
+ if (!mfg->gpu_clks)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_clks; i++)
+ mfg->gpu_clks[i].id = data->clk_names[i];
+
+ ret = devm_clk_bulk_get(dev, data->num_clks, mfg->gpu_clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't get GPU clocks\n");
+
+ mfg->gpu_regs = devm_kcalloc(dev, data->num_regulators,
+ sizeof(*mfg->gpu_regs), GFP_KERNEL);
+ if (!mfg->gpu_regs)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_regulators; i++)
+ mfg->gpu_regs[i].supply = data->regulator_names[i];
+
+ ret = devm_regulator_bulk_get(dev, data->num_regulators, mfg->gpu_regs);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't get GPU regulators\n");
+
+ ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't get GPUEB shared memory\n");
+
+ mfg->shared_mem = devm_ioremap(dev, res.start, resource_size(&res));
+ if (!mfg->shared_mem)
+ return dev_err_probe(dev, -ENOMEM, "Can't ioremap GPUEB shared memory\n");
+ mfg->shared_mem_size = resource_size(&res);
+ mfg->shared_mem_phys = res.start;
+
+ if (data->init) {
+ ret = data->init(mfg);
+ if (ret)
+ return dev_err_probe(dev, ret, "Variant init failed\n");
+ }
+
+ mfg->pd.name = dev_name(dev);
+ mfg->pd.attach_dev = mtk_mfg_attach_dev;
+ mfg->pd.detach_dev = mtk_mfg_detach_dev;
+ mfg->pd.power_off = mtk_mfg_power_off;
+ mfg->pd.power_on = mtk_mfg_power_on;
+ mfg->pd.set_performance_state = mtk_mfg_set_performance;
+ mfg->pd.flags = GENPD_FLAG_OPP_TABLE_FW;
+
+ ret = pm_genpd_init(&mfg->pd, NULL, false);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialise power domain\n");
+
+ ret = mtk_mfg_init_mbox(mfg);
+ if (ret) {
+ dev_err_probe(dev, ret, "Couldn't initialise mailbox\n");
+ goto err_remove_genpd;
+ }
+
+ ret = mtk_mfg_power_on(&mfg->pd);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to power on MFG\n");
+ goto err_free_mbox;
+ }
+
+ ret = mtk_mfg_init_shared_mem(mfg);
+ if (ret) {
+ dev_err_probe(dev, ret, "Couldn't initialize EB shared memory\n");
+ goto err_power_off;
+ }
+
+ ret = mtk_mfg_read_opp_tables(mfg);
+ if (ret) {
+ dev_err_probe(dev, ret, "Error reading OPP tables from EB\n");
+ goto err_power_off;
+ }
+
+ ret = mtk_mfg_init_clk_provider(mfg);
+ if (ret)
+ goto err_power_off;
+
+ ret = of_genpd_add_provider_simple(dev->of_node, &mfg->pd);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to add pmdomain provider\n");
+ goto err_power_off;
+ }
+
+ return 0;
+
+err_power_off:
+ mtk_mfg_power_off(&mfg->pd);
+err_free_mbox:
+ mbox_free_channel(mfg->slp_mbox->ch);
+ mfg->slp_mbox->ch = NULL;
+ mbox_free_channel(mfg->gf_mbox->ch);
+ mfg->gf_mbox->ch = NULL;
+err_remove_genpd:
+ pm_genpd_remove(&mfg->pd);
+
+ return ret;
+}
+
+static const struct of_device_id mtk_mfg_of_match[] = {
+ { .compatible = "mediatek,mt8196-gpufreq", .data = &mtk_mfg_mt8196_variant },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mtk_mfg_of_match);
+
+static void mtk_mfg_remove(struct platform_device *pdev)
+{
+ struct mtk_mfg *mfg = dev_get_drvdata(&pdev->dev);
+
+ if (mtk_mfg_is_powered_on(mfg))
+ mtk_mfg_power_off(&mfg->pd);
+
+ of_genpd_del_provider(pdev->dev.of_node);
+ pm_genpd_remove(&mfg->pd);
+
+ mbox_free_channel(mfg->gf_mbox->ch);
+ mfg->gf_mbox->ch = NULL;
+
+ mbox_free_channel(mfg->slp_mbox->ch);
+ mfg->slp_mbox->ch = NULL;
+}
+
+static struct platform_driver mtk_mfg_driver = {
+ .driver = {
+ .name = "mtk-mfg-pmdomain",
+ .of_match_table = mtk_mfg_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = mtk_mfg_probe,
+ .remove = mtk_mfg_remove,
+};
+module_platform_driver(mtk_mfg_driver);
+
+MODULE_AUTHOR("Nicolas Frattaroli <nicolas.frattaroli@collabora.com>");
+MODULE_DESCRIPTION("MediaTek MFlexGraphics Power Domain Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c
index 9c9323c8c93a..80561d27f2b2 100644
--- a/drivers/pmdomain/mediatek/mtk-pm-domains.c
+++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c
@@ -2,6 +2,7 @@
/*
* Copyright (c) 2020 Collabora Ltd.
*/
+#include <linux/arm-smccc.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/init.h>
@@ -15,6 +16,7 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/soc/mediatek/infracfg.h>
+#include <linux/soc/mediatek/mtk_sip_svc.h>
#include "mt6735-pm-domains.h"
#include "mt6795-pm-domains.h"
@@ -26,11 +28,18 @@
#include "mt8188-pm-domains.h"
#include "mt8192-pm-domains.h"
#include "mt8195-pm-domains.h"
+#include "mt8196-pm-domains.h"
#include "mt8365-pm-domains.h"
#define MTK_POLL_DELAY_US 10
#define MTK_POLL_TIMEOUT USEC_PER_SEC
+#define MTK_HWV_POLL_DELAY_US 5
+#define MTK_HWV_POLL_TIMEOUT (300 * USEC_PER_MSEC)
+
+#define MTK_HWV_PREPARE_DELAY_US 1
+#define MTK_HWV_PREPARE_TIMEOUT (3 * USEC_PER_MSEC)
+
#define PWR_RST_B_BIT BIT(0)
#define PWR_ISO_BIT BIT(1)
#define PWR_ON_BIT BIT(2)
@@ -45,9 +54,12 @@
#define PWR_RTFF_SAVE_FLAG BIT(27)
#define PWR_RTFF_UFS_CLK_DIS BIT(28)
+#define MTK_SIP_KERNEL_HWCCF_CONTROL MTK_SIP_SMC_CMD(0x540)
+
struct scpsys_domain {
struct generic_pm_domain genpd;
const struct scpsys_domain_data *data;
+ const struct scpsys_hwv_domain_data *hwv_data;
struct scpsys *scpsys;
int num_clks;
struct clk_bulk_data *clks;
@@ -71,18 +83,56 @@ struct scpsys {
static bool scpsys_domain_is_on(struct scpsys_domain *pd)
{
struct scpsys *scpsys = pd->scpsys;
- u32 status, status2;
+ u32 mask = pd->data->sta_mask;
+ u32 status, status2, mask2;
+
+ mask2 = pd->data->sta2nd_mask ? pd->data->sta2nd_mask : mask;
regmap_read(scpsys->base, pd->data->pwr_sta_offs, &status);
- status &= pd->data->sta_mask;
+ status &= mask;
regmap_read(scpsys->base, pd->data->pwr_sta2nd_offs, &status2);
- status2 &= pd->data->sta_mask;
+ status2 &= mask2;
/* A domain is on when both status bits are set. */
return status && status2;
}
+static bool scpsys_hwv_domain_is_disable_done(struct scpsys_domain *pd)
+{
+ const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+ u32 regs[2] = { hwv->done, hwv->clr_sta };
+ u32 val[2];
+ u32 mask = BIT(hwv->setclr_bit);
+
+ regmap_multi_reg_read(pd->scpsys->base, regs, val, 2);
+
+ /* Disable is done when the bit is set in DONE, cleared in CLR_STA */
+ return (val[0] & mask) && !(val[1] & mask);
+}
+
+static bool scpsys_hwv_domain_is_enable_done(struct scpsys_domain *pd)
+{
+ const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+ u32 regs[3] = { hwv->done, hwv->en, hwv->set_sta };
+ u32 val[3];
+ u32 mask = BIT(hwv->setclr_bit);
+
+ regmap_multi_reg_read(pd->scpsys->base, regs, val, 3);
+
+ /* Enable is done when the bit is set in DONE and EN, cleared in SET_STA */
+ return (val[0] & mask) && (val[1] & mask) && !(val[2] & mask);
+}
+
+static int scpsys_sec_infra_power_on(bool on)
+{
+ struct arm_smccc_res res;
+ unsigned long cmd = on ? 1 : 0;
+
+ arm_smccc_smc(MTK_SIP_KERNEL_HWCCF_CONTROL, cmd, 0, 0, 0, 0, 0, 0, &res);
+ return res.a0;
+}
+
static int scpsys_sram_enable(struct scpsys_domain *pd)
{
u32 expected_ack, pdn_ack = pd->data->sram_pdn_ack_bits;
@@ -250,6 +300,161 @@ static int scpsys_regulator_disable(struct regulator *supply)
return supply ? regulator_disable(supply) : 0;
}
+static int scpsys_hwv_power_on(struct generic_pm_domain *genpd)
+{
+ struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
+ const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+ struct scpsys *scpsys = pd->scpsys;
+ u32 val;
+ int ret;
+
+ if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) {
+ ret = scpsys_sec_infra_power_on(true);
+ if (ret)
+ return ret;
+ }
+
+ ret = scpsys_regulator_enable(pd->supply);
+ if (ret)
+ goto err_infra;
+
+ ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks);
+ if (ret)
+ goto err_reg;
+
+ /* For HWV the subsys clocks refer to the HWV low power subsystem */
+ ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks);
+ if (ret)
+ goto err_disable_clks;
+
+ /* Make sure the HW Voter is idle and able to accept commands */
+ ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->done, val,
+ val & BIT(hwv->setclr_bit),
+ MTK_HWV_POLL_DELAY_US,
+ MTK_HWV_POLL_TIMEOUT);
+ if (ret) {
+ dev_err(scpsys->dev, "Failed to power on: HW Voter busy.\n");
+ goto err_disable_subsys_clks;
+ }
+
+ /*
+ * Instruct the HWV to power on the MTCMOS (power domain): after that,
+ * the same bit will be unset immediately by the hardware.
+ */
+ regmap_write(scpsys->base, hwv->set, BIT(hwv->setclr_bit));
+
+ /*
+ * Wait until the HWV sets the bit again, signalling that its internal
+ * state machine was started and it now processing the vote command.
+ */
+ ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->set, val,
+ val & BIT(hwv->setclr_bit),
+ MTK_HWV_PREPARE_DELAY_US,
+ MTK_HWV_PREPARE_TIMEOUT);
+ if (ret) {
+ dev_err(scpsys->dev, "Failed to power on: HW Voter not starting.\n");
+ goto err_disable_subsys_clks;
+ }
+
+ /* Wait for ACK, signalling that the MTCMOS was enabled */
+ ret = readx_poll_timeout_atomic(scpsys_hwv_domain_is_enable_done, pd, val, val,
+ MTK_HWV_POLL_DELAY_US, MTK_HWV_POLL_TIMEOUT);
+ if (ret) {
+ dev_err(scpsys->dev, "Failed to power on: HW Voter ACK timeout.\n");
+ goto err_disable_subsys_clks;
+ }
+
+ /* It's done! Disable the HWV low power subsystem clocks */
+ clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+
+ if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL))
+ scpsys_sec_infra_power_on(false);
+
+ return 0;
+
+err_disable_subsys_clks:
+ clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+err_disable_clks:
+ clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
+err_reg:
+ scpsys_regulator_disable(pd->supply);
+err_infra:
+ if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL))
+ scpsys_sec_infra_power_on(false);
+ return ret;
+};
+
+static int scpsys_hwv_power_off(struct generic_pm_domain *genpd)
+{
+ struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
+ const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+ struct scpsys *scpsys = pd->scpsys;
+ u32 val;
+ int ret;
+
+ if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) {
+ ret = scpsys_sec_infra_power_on(true);
+ if (ret)
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks);
+ if (ret)
+ goto err_infra;
+
+ /* Make sure the HW Voter is idle and able to accept commands */
+ ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->done, val,
+ val & BIT(hwv->setclr_bit),
+ MTK_HWV_POLL_DELAY_US,
+ MTK_HWV_POLL_TIMEOUT);
+ if (ret)
+ goto err_disable_subsys_clks;
+
+
+ /*
+ * Instruct the HWV to power off the MTCMOS (power domain): differently
+ * from poweron, the bit will be kept set.
+ */
+ regmap_write(scpsys->base, hwv->clr, BIT(hwv->setclr_bit));
+
+ /*
+ * Wait until the HWV clears the bit, signalling that its internal
+ * state machine was started and it now processing the clear command.
+ */
+ ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->clr, val,
+ !(val & BIT(hwv->setclr_bit)),
+ MTK_HWV_PREPARE_DELAY_US,
+ MTK_HWV_PREPARE_TIMEOUT);
+ if (ret)
+ goto err_disable_subsys_clks;
+
+ /* Poweroff needs 100us for the HW to stabilize */
+ udelay(100);
+
+ /* Wait for ACK, signalling that the MTCMOS was disabled */
+ ret = readx_poll_timeout_atomic(scpsys_hwv_domain_is_disable_done, pd, val, val,
+ MTK_HWV_POLL_DELAY_US, MTK_HWV_POLL_TIMEOUT);
+ if (ret)
+ goto err_disable_subsys_clks;
+
+ clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+ clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
+
+ scpsys_regulator_disable(pd->supply);
+
+ if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL))
+ scpsys_sec_infra_power_on(false);
+
+ return 0;
+
+err_disable_subsys_clks:
+ clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+err_infra:
+ if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL))
+ scpsys_sec_infra_power_on(false);
+ return ret;
+};
+
static int scpsys_ctl_pwrseq_on(struct scpsys_domain *pd)
{
struct scpsys *scpsys = pd->scpsys;
@@ -514,6 +719,7 @@ static struct
generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node)
{
const struct scpsys_domain_data *domain_data;
+ const struct scpsys_hwv_domain_data *hwv_domain_data;
struct scpsys_domain *pd;
struct property *prop;
const char *clk_name;
@@ -529,14 +735,33 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
return ERR_PTR(-EINVAL);
}
- if (id >= scpsys->soc_data->num_domains) {
- dev_err(scpsys->dev, "%pOF: invalid domain id %d\n", node, id);
- return ERR_PTR(-EINVAL);
- }
+ switch (scpsys->soc_data->type) {
+ case SCPSYS_MTCMOS_TYPE_DIRECT_CTL:
+ if (id >= scpsys->soc_data->num_domains) {
+ dev_err(scpsys->dev, "%pOF: invalid domain id %d\n", node, id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ domain_data = &scpsys->soc_data->domains_data[id];
+ hwv_domain_data = NULL;
+
+ if (domain_data->sta_mask == 0) {
+ dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ break;
+ case SCPSYS_MTCMOS_TYPE_HW_VOTER:
+ if (id >= scpsys->soc_data->num_hwv_domains) {
+ dev_err(scpsys->dev, "%pOF: invalid HWV domain id %d\n", node, id);
+ return ERR_PTR(-EINVAL);
+ }
- domain_data = &scpsys->soc_data->domains_data[id];
- if (domain_data->sta_mask == 0) {
- dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id);
+ domain_data = NULL;
+ hwv_domain_data = &scpsys->soc_data->hwv_domains_data[id];
+
+ break;
+ default:
return ERR_PTR(-EINVAL);
}
@@ -545,6 +770,7 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
return ERR_PTR(-ENOMEM);
pd->data = domain_data;
+ pd->hwv_data = hwv_domain_data;
pd->scpsys = scpsys;
if (MTK_SCPD_CAPS(pd, MTK_SCPD_DOMAIN_SUPPLY)) {
@@ -604,6 +830,31 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
pd->subsys_clks[i].clk = clk;
}
+ if (scpsys->domains[id]) {
+ ret = -EINVAL;
+ dev_err(scpsys->dev,
+ "power domain with id %d already exists, check your device-tree\n", id);
+ goto err_put_subsys_clocks;
+ }
+
+ if (pd->data && pd->data->name)
+ pd->genpd.name = pd->data->name;
+ else if (pd->hwv_data && pd->hwv_data->name)
+ pd->genpd.name = pd->hwv_data->name;
+ else
+ pd->genpd.name = node->name;
+
+ if (scpsys->soc_data->type == SCPSYS_MTCMOS_TYPE_DIRECT_CTL) {
+ pd->genpd.power_off = scpsys_power_off;
+ pd->genpd.power_on = scpsys_power_on;
+ } else {
+ pd->genpd.power_off = scpsys_hwv_power_off;
+ pd->genpd.power_on = scpsys_hwv_power_on;
+
+ /* HW-Voter code can be invoked in atomic context */
+ pd->genpd.flags |= GENPD_FLAG_IRQ_SAFE;
+ }
+
/*
* Initially turn on all domains to make the domains usable
* with !CONFIG_PM and to get the hardware in sync with the
@@ -615,7 +866,7 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
dev_warn(scpsys->dev,
"%pOF: A default off power domain has been ON\n", node);
} else {
- ret = scpsys_power_on(&pd->genpd);
+ ret = pd->genpd.power_on(&pd->genpd);
if (ret < 0) {
dev_err(scpsys->dev, "%pOF: failed to power on domain: %d\n", node, ret);
goto err_put_subsys_clocks;
@@ -625,21 +876,6 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
}
- if (scpsys->domains[id]) {
- ret = -EINVAL;
- dev_err(scpsys->dev,
- "power domain with id %d already exists, check your device-tree\n", id);
- goto err_put_subsys_clocks;
- }
-
- if (!pd->data->name)
- pd->genpd.name = node->name;
- else
- pd->genpd.name = pd->data->name;
-
- pd->genpd.power_off = scpsys_power_off;
- pd->genpd.power_on = scpsys_power_on;
-
if (MTK_SCPD_CAPS(pd, MTK_SCPD_ACTIVE_WAKEUP))
pd->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
@@ -932,6 +1168,18 @@ static const struct of_device_id scpsys_of_match[] = {
.data = &mt8195_scpsys_data,
},
{
+ .compatible = "mediatek,mt8196-power-controller",
+ .data = &mt8196_scpsys_data,
+ },
+ {
+ .compatible = "mediatek,mt8196-hwv-hfrp-power-controller",
+ .data = &mt8196_hfrpsys_hwv_data,
+ },
+ {
+ .compatible = "mediatek,mt8196-hwv-scp-power-controller",
+ .data = &mt8196_scpsys_hwv_data,
+ },
+ {
.compatible = "mediatek,mt8365-power-controller",
.data = &mt8365_scpsys_data,
},
@@ -946,7 +1194,7 @@ static int scpsys_probe(struct platform_device *pdev)
struct device_node *node;
struct device *parent;
struct scpsys *scpsys;
- int ret;
+ int num_domains, ret;
soc = of_device_get_match_data(&pdev->dev);
if (!soc) {
@@ -954,7 +1202,9 @@ static int scpsys_probe(struct platform_device *pdev)
return -EINVAL;
}
- scpsys = devm_kzalloc(dev, struct_size(scpsys, domains, soc->num_domains), GFP_KERNEL);
+ num_domains = soc->num_domains + soc->num_hwv_domains;
+
+ scpsys = devm_kzalloc(dev, struct_size(scpsys, domains, num_domains), GFP_KERNEL);
if (!scpsys)
return -ENOMEM;
diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.h b/drivers/pmdomain/mediatek/mtk-pm-domains.h
index b2e3dee03831..f608e6ec4744 100644
--- a/drivers/pmdomain/mediatek/mtk-pm-domains.h
+++ b/drivers/pmdomain/mediatek/mtk-pm-domains.h
@@ -16,7 +16,10 @@
#define MTK_SCPD_SRAM_PDN_INVERTED BIT(9)
#define MTK_SCPD_MODEM_PWRSEQ BIT(10)
#define MTK_SCPD_SKIP_RESET_B BIT(11)
-#define MTK_SCPD_CAPS(_scpd, _x) ((_scpd)->data->caps & (_x))
+#define MTK_SCPD_INFRA_PWR_CTL BIT(12)
+#define MTK_SCPD_CAPS(_scpd, _x) ((_scpd)->data ? \
+ (_scpd)->data->caps & (_x) : \
+ (_scpd)->hwv_data->caps & (_x))
#define SPM_VDE_PWR_CON 0x0210
#define SPM_MFG_PWR_CON 0x0214
@@ -59,6 +62,7 @@ enum scpsys_bus_prot_block {
BUS_PROT_BLOCK_INFRA,
BUS_PROT_BLOCK_INFRA_NAO,
BUS_PROT_BLOCK_SMI,
+ BUS_PROT_BLOCK_SPM,
BUS_PROT_BLOCK_COUNT,
};
@@ -125,9 +129,22 @@ enum scpsys_rtff_type {
};
/**
+ * enum scpsys_mtcmos_type - Type of power domain controller
+ * @SCPSYS_MTCMOS_TYPE_DIRECT_CTL: Power domains are controlled with direct access
+ * @SCPSYS_MTCMOS_TYPE_HW_VOTER: Hardware-assisted voted power domain control
+ * @SCPSYS_MTCMOS_TYPE_MAX: Number of supported power domain types
+ */
+enum scpsys_mtcmos_type {
+ SCPSYS_MTCMOS_TYPE_DIRECT_CTL = 0,
+ SCPSYS_MTCMOS_TYPE_HW_VOTER,
+ SCPSYS_MTCMOS_TYPE_MAX
+};
+
+/**
* struct scpsys_domain_data - scp domain data for power on/off flow
* @name: The name of the power domain.
* @sta_mask: The mask for power on/off status bit.
+ * @sta2nd_mask: The mask for second power on/off status bit.
* @ctl_offs: The offset for main power control register.
* @sram_pdn_bits: The mask for sram power control bits.
* @sram_pdn_ack_bits: The mask for sram power control acked bits.
@@ -140,6 +157,7 @@ enum scpsys_rtff_type {
struct scpsys_domain_data {
const char *name;
u32 sta_mask;
+ u32 sta2nd_mask;
int ctl_offs;
u32 sram_pdn_bits;
u32 sram_pdn_ack_bits;
@@ -152,11 +170,40 @@ struct scpsys_domain_data {
int pwr_sta2nd_offs;
};
+/**
+ * struct scpsys_hwv_domain_data - Hardware Voter power domain data
+ * @name: Name of the power domain
+ * @set: Offset of the HWV SET register
+ * @clr: Offset of the HWV CLEAR register
+ * @done: Offset of the HWV DONE register
+ * @en: Offset of the HWV ENABLE register
+ * @set_sta: Offset of the HWV SET STATUS register
+ * @clr_sta: Offset of the HWV CLEAR STATUS register
+ * @setclr_bit: The SET/CLR bit to enable/disable the power domain
+ * @sta_bit: The SET/CLR STA bit to check for on/off ACK
+ * @caps: The flag for active wake-up action
+ */
+struct scpsys_hwv_domain_data {
+ const char *name;
+ u16 set;
+ u16 clr;
+ u16 done;
+ u16 en;
+ u16 set_sta;
+ u16 clr_sta;
+ u8 setclr_bit;
+ u8 sta_bit;
+ u16 caps;
+};
+
struct scpsys_soc_data {
const struct scpsys_domain_data *domains_data;
int num_domains;
+ const struct scpsys_hwv_domain_data *hwv_domains_data;
+ int num_hwv_domains;
enum scpsys_bus_prot_block *bus_prot_blocks;
int num_bus_prot_blocks;
+ enum scpsys_mtcmos_type type;
};
#endif /* __SOC_MEDIATEK_MTK_PM_DOMAINS_H */
diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c
index 4faa8a256186..a8b37037c6fe 100644
--- a/drivers/pmdomain/qcom/rpmhpd.c
+++ b/drivers/pmdomain/qcom/rpmhpd.c
@@ -19,7 +19,7 @@
#define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd)
-#define RPMH_ARC_MAX_LEVELS 16
+#define RPMH_ARC_MAX_LEVELS 32
/**
* struct rpmhpd - top level RPMh power domain resource data structure
@@ -595,6 +595,31 @@ static const struct rpmhpd_desc sm8750_desc = {
.num_pds = ARRAY_SIZE(sm8750_rpmhpds),
};
+/* KAANAPALI RPMH powerdomains */
+static struct rpmhpd *kaanapali_rpmhpds[] = {
+ [RPMHPD_CX] = &cx,
+ [RPMHPD_CX_AO] = &cx_ao,
+ [RPMHPD_EBI] = &ebi,
+ [RPMHPD_GFX] = &gfx,
+ [RPMHPD_GMXC] = &gmxc,
+ [RPMHPD_LCX] = &lcx,
+ [RPMHPD_LMX] = &lmx,
+ [RPMHPD_MX] = &mx,
+ [RPMHPD_MX_AO] = &mx_ao,
+ [RPMHPD_MMCX] = &mmcx,
+ [RPMHPD_MMCX_AO] = &mmcx_ao,
+ [RPMHPD_MSS] = &mss,
+ [RPMHPD_MXC] = &mxc,
+ [RPMHPD_MXC_AO] = &mxc_ao,
+ [RPMHPD_NSP] = &nsp,
+ [RPMHPD_NSP2] = &nsp2,
+};
+
+static const struct rpmhpd_desc kaanapali_desc = {
+ .rpmhpds = kaanapali_rpmhpds,
+ .num_pds = ARRAY_SIZE(kaanapali_rpmhpds),
+};
+
/* QDU1000/QRU1000 RPMH powerdomains */
static struct rpmhpd *qdu1000_rpmhpds[] = {
[QDU1000_CX] = &cx,
@@ -767,6 +792,7 @@ static const struct rpmhpd_desc qcs615_desc = {
static const struct of_device_id rpmhpd_match_table[] = {
{ .compatible = "qcom,glymur-rpmhpd", .data = &glymur_desc },
+ { .compatible = "qcom,kaanapali-rpmhpd", .data = &kaanapali_desc },
{ .compatible = "qcom,milos-rpmhpd", .data = &milos_desc },
{ .compatible = "qcom,qcs615-rpmhpd", .data = &qcs615_desc },
{ .compatible = "qcom,qcs8300-rpmhpd", .data = &qcs8300_desc },
diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index 1955c6d453e4..4f1336a0f49a 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -25,6 +25,7 @@
#include <soc/rockchip/rockchip_sip.h>
#include <dt-bindings/power/px30-power.h>
#include <dt-bindings/power/rockchip,rv1126-power.h>
+#include <dt-bindings/power/rockchip,rv1126b-power-controller.h>
#include <dt-bindings/power/rk3036-power.h>
#include <dt-bindings/power/rk3066-power.h>
#include <dt-bindings/power/rk3128-power.h>
@@ -137,6 +138,20 @@ struct rockchip_pmu {
.active_wakeup = wakeup, \
}
+#define DOMAIN_M_G(_name, pwr, status, req, idle, ack, g_mask, wakeup, keepon) \
+{ \
+ .name = _name, \
+ .pwr_w_mask = (pwr) << 16, \
+ .pwr_mask = (pwr), \
+ .status_mask = (status), \
+ .req_w_mask = (req) << 16, \
+ .req_mask = (req), \
+ .idle_mask = (idle), \
+ .ack_mask = (ack), \
+ .clk_ungate_mask = (g_mask), \
+ .active_wakeup = wakeup, \
+}
+
#define DOMAIN_M_G_SD(_name, pwr, status, req, idle, ack, g_mask, mem, wakeup, keepon) \
{ \
.name = _name, \
@@ -205,6 +220,9 @@ struct rockchip_pmu {
#define DOMAIN_RV1126(name, pwr, req, idle, wakeup) \
DOMAIN_M(name, pwr, pwr, req, idle, idle, wakeup)
+#define DOMAIN_RV1126B(name, pwr, req, wakeup) \
+ DOMAIN_M_G(name, pwr, pwr, req, req, req, req, wakeup, true)
+
#define DOMAIN_RK3288(name, pwr, status, req, wakeup) \
DOMAIN(name, pwr, status, req, req, (req) << 16, wakeup)
@@ -1104,6 +1122,13 @@ static const struct rockchip_domain_info rv1126_pm_domains[] = {
[RV1126_PD_USB] = DOMAIN_RV1126("usb", BIT(9), BIT(15), BIT(15), false),
};
+static const struct rockchip_domain_info rv1126b_pm_domains[] = {
+ /* name pwr req wakeup */
+ [RV1126B_PD_NPU] = DOMAIN_RV1126B("npu", BIT(0), BIT(8), false),
+ [RV1126B_PD_VDO] = DOMAIN_RV1126B("vdo", BIT(1), BIT(9), false),
+ [RV1126B_PD_AIISP] = DOMAIN_RV1126B("aiisp", BIT(2), BIT(10), false),
+};
+
static const struct rockchip_domain_info rk3036_pm_domains[] = {
[RK3036_PD_MSCH] = DOMAIN_RK3036("msch", BIT(14), BIT(23), BIT(30), true),
[RK3036_PD_CORE] = DOMAIN_RK3036("core", BIT(13), BIT(17), BIT(24), false),
@@ -1516,6 +1541,18 @@ static const struct rockchip_pmu_info rv1126_pmu = {
.domain_info = rv1126_pm_domains,
};
+static const struct rockchip_pmu_info rv1126b_pmu = {
+ .pwr_offset = 0x210,
+ .status_offset = 0x230,
+ .req_offset = 0x110,
+ .idle_offset = 0x128,
+ .ack_offset = 0x120,
+ .clk_ungate_offset = 0x140,
+
+ .num_domains = ARRAY_SIZE(rv1126b_pm_domains),
+ .domain_info = rv1126b_pm_domains,
+};
+
static const struct of_device_id rockchip_pm_domain_dt_match[] = {
{
.compatible = "rockchip,px30-power-controller",
@@ -1585,6 +1622,10 @@ static const struct of_device_id rockchip_pm_domain_dt_match[] = {
.compatible = "rockchip,rv1126-power-controller",
.data = (void *)&rv1126_pmu,
},
+ {
+ .compatible = "rockchip,rv1126b-power-controller",
+ .data = (void *)&rv1126b_pmu,
+ },
{ /* sentinel */ },
};