summaryrefslogtreecommitdiffstats
path: root/drivers/pmdomain/amlogic
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/pmdomain/amlogic
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/pmdomain/amlogic')
-rw-r--r--drivers/pmdomain/amlogic/Kconfig28
-rw-r--r--drivers/pmdomain/amlogic/Makefile3
-rw-r--r--drivers/pmdomain/amlogic/meson-ee-pwrc.c652
-rw-r--r--drivers/pmdomain/amlogic/meson-secure-pwrc.c577
4 files changed, 1260 insertions, 0 deletions
diff --git a/drivers/pmdomain/amlogic/Kconfig b/drivers/pmdomain/amlogic/Kconfig
new file mode 100644
index 000000000..e72b66417
--- /dev/null
+++ b/drivers/pmdomain/amlogic/Kconfig
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "Amlogic PM Domains"
+
+config MESON_EE_PM_DOMAINS
+ tristate "Amlogic Meson Everything-Else Power Domains driver"
+ depends on ARCH_MESON || COMPILE_TEST
+ depends on PM && OF
+ default ARCH_MESON
+ select PM_GENERIC_DOMAINS
+ select PM_GENERIC_DOMAINS_OF
+ help
+ Say yes to expose Amlogic Meson Everything-Else Power Domains as
+ Generic Power Domains.
+
+config MESON_SECURE_PM_DOMAINS
+ tristate "Amlogic Meson Secure Power Domains driver"
+ depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM
+ depends on PM && OF
+ depends on HAVE_ARM_SMCCC
+ default ARCH_MESON
+ select PM_GENERIC_DOMAINS
+ select PM_GENERIC_DOMAINS_OF
+ help
+ Support for the power controller on Amlogic A1/C1 series.
+ Say yes to expose Amlogic Meson Secure Power Domains as Generic
+ Power Domains.
+
+endmenu
diff --git a/drivers/pmdomain/amlogic/Makefile b/drivers/pmdomain/amlogic/Makefile
new file mode 100644
index 000000000..99f195f09
--- /dev/null
+++ b/drivers/pmdomain/amlogic/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_MESON_EE_PM_DOMAINS) += meson-ee-pwrc.o
+obj-$(CONFIG_MESON_SECURE_PM_DOMAINS) += meson-secure-pwrc.o
diff --git a/drivers/pmdomain/amlogic/meson-ee-pwrc.c b/drivers/pmdomain/amlogic/meson-ee-pwrc.c
new file mode 100644
index 000000000..55c8c9f66
--- /dev/null
+++ b/drivers/pmdomain/amlogic/meson-ee-pwrc.c
@@ -0,0 +1,652 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2019 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/bitfield.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/reset-controller.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <dt-bindings/power/meson8-power.h>
+#include <dt-bindings/power/meson-axg-power.h>
+#include <dt-bindings/power/meson-g12a-power.h>
+#include <dt-bindings/power/meson-gxbb-power.h>
+#include <dt-bindings/power/meson-sm1-power.h>
+
+/* AO Offsets */
+
+#define GX_AO_RTI_GEN_PWR_SLEEP0 (0x3a << 2)
+#define GX_AO_RTI_GEN_PWR_ISO0 (0x3b << 2)
+
+/*
+ * Meson8/Meson8b/Meson8m2 only expose the power management registers of the
+ * AO-bus as syscon. 0x3a from GX translates to 0x02, 0x3b translates to 0x03
+ * and so on.
+ */
+#define MESON8_AO_RTI_GEN_PWR_SLEEP0 (0x02 << 2)
+#define MESON8_AO_RTI_GEN_PWR_ISO0 (0x03 << 2)
+
+/* HHI Offsets */
+
+#define HHI_MEM_PD_REG0 (0x40 << 2)
+#define HHI_VPU_MEM_PD_REG0 (0x41 << 2)
+#define HHI_VPU_MEM_PD_REG1 (0x42 << 2)
+#define HHI_VPU_MEM_PD_REG3 (0x43 << 2)
+#define HHI_VPU_MEM_PD_REG4 (0x44 << 2)
+#define HHI_AUDIO_MEM_PD_REG0 (0x45 << 2)
+#define HHI_NANOQ_MEM_PD_REG0 (0x46 << 2)
+#define HHI_NANOQ_MEM_PD_REG1 (0x47 << 2)
+#define HHI_VPU_MEM_PD_REG2 (0x4d << 2)
+
+#define G12A_HHI_NANOQ_MEM_PD_REG0 (0x43 << 2)
+#define G12A_HHI_NANOQ_MEM_PD_REG1 (0x44 << 2)
+#define G12A_HHI_ISP_MEM_PD_REG0 (0x45 << 2)
+#define G12A_HHI_ISP_MEM_PD_REG1 (0x46 << 2)
+
+struct meson_ee_pwrc;
+struct meson_ee_pwrc_domain;
+
+struct meson_ee_pwrc_mem_domain {
+ unsigned int reg;
+ unsigned int mask;
+};
+
+struct meson_ee_pwrc_top_domain {
+ unsigned int sleep_reg;
+ unsigned int sleep_mask;
+ unsigned int iso_reg;
+ unsigned int iso_mask;
+};
+
+struct meson_ee_pwrc_domain_desc {
+ char *name;
+ unsigned int reset_names_count;
+ unsigned int clk_names_count;
+ const struct meson_ee_pwrc_top_domain *top_pd;
+ unsigned int mem_pd_count;
+ const struct meson_ee_pwrc_mem_domain *mem_pd;
+ bool (*is_powered_off)(struct meson_ee_pwrc_domain *pwrc_domain);
+};
+
+struct meson_ee_pwrc_domain_data {
+ unsigned int count;
+ const struct meson_ee_pwrc_domain_desc *domains;
+};
+
+/* TOP Power Domains */
+
+static const struct meson_ee_pwrc_top_domain gx_pwrc_vpu = {
+ .sleep_reg = GX_AO_RTI_GEN_PWR_SLEEP0,
+ .sleep_mask = BIT(8),
+ .iso_reg = GX_AO_RTI_GEN_PWR_SLEEP0,
+ .iso_mask = BIT(9),
+};
+
+static const struct meson_ee_pwrc_top_domain meson8_pwrc_vpu = {
+ .sleep_reg = MESON8_AO_RTI_GEN_PWR_SLEEP0,
+ .sleep_mask = BIT(8),
+ .iso_reg = MESON8_AO_RTI_GEN_PWR_SLEEP0,
+ .iso_mask = BIT(9),
+};
+
+#define SM1_EE_PD(__bit) \
+ { \
+ .sleep_reg = GX_AO_RTI_GEN_PWR_SLEEP0, \
+ .sleep_mask = BIT(__bit), \
+ .iso_reg = GX_AO_RTI_GEN_PWR_ISO0, \
+ .iso_mask = BIT(__bit), \
+ }
+
+static const struct meson_ee_pwrc_top_domain sm1_pwrc_vpu = SM1_EE_PD(8);
+static const struct meson_ee_pwrc_top_domain sm1_pwrc_nna = SM1_EE_PD(16);
+static const struct meson_ee_pwrc_top_domain sm1_pwrc_usb = SM1_EE_PD(17);
+static const struct meson_ee_pwrc_top_domain sm1_pwrc_pci = SM1_EE_PD(18);
+static const struct meson_ee_pwrc_top_domain sm1_pwrc_ge2d = SM1_EE_PD(19);
+
+static const struct meson_ee_pwrc_top_domain g12a_pwrc_nna = {
+ .sleep_reg = GX_AO_RTI_GEN_PWR_SLEEP0,
+ .sleep_mask = BIT(16) | BIT(17),
+ .iso_reg = GX_AO_RTI_GEN_PWR_ISO0,
+ .iso_mask = BIT(16) | BIT(17),
+};
+
+static const struct meson_ee_pwrc_top_domain g12a_pwrc_isp = {
+ .sleep_reg = GX_AO_RTI_GEN_PWR_SLEEP0,
+ .sleep_mask = BIT(18) | BIT(19),
+ .iso_reg = GX_AO_RTI_GEN_PWR_ISO0,
+ .iso_mask = BIT(18) | BIT(19),
+};
+
+/* Memory PD Domains */
+
+#define VPU_MEMPD(__reg) \
+ { __reg, GENMASK(1, 0) }, \
+ { __reg, GENMASK(3, 2) }, \
+ { __reg, GENMASK(5, 4) }, \
+ { __reg, GENMASK(7, 6) }, \
+ { __reg, GENMASK(9, 8) }, \
+ { __reg, GENMASK(11, 10) }, \
+ { __reg, GENMASK(13, 12) }, \
+ { __reg, GENMASK(15, 14) }, \
+ { __reg, GENMASK(17, 16) }, \
+ { __reg, GENMASK(19, 18) }, \
+ { __reg, GENMASK(21, 20) }, \
+ { __reg, GENMASK(23, 22) }, \
+ { __reg, GENMASK(25, 24) }, \
+ { __reg, GENMASK(27, 26) }, \
+ { __reg, GENMASK(29, 28) }, \
+ { __reg, GENMASK(31, 30) }
+
+#define VPU_HHI_MEMPD(__reg) \
+ { __reg, BIT(8) }, \
+ { __reg, BIT(9) }, \
+ { __reg, BIT(10) }, \
+ { __reg, BIT(11) }, \
+ { __reg, BIT(12) }, \
+ { __reg, BIT(13) }, \
+ { __reg, BIT(14) }, \
+ { __reg, BIT(15) }
+
+static const struct meson_ee_pwrc_mem_domain axg_pwrc_mem_vpu[] = {
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
+ VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
+};
+
+static const struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_vpu[] = {
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG2),
+ VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
+};
+
+static const struct meson_ee_pwrc_mem_domain gxbb_pwrc_mem_vpu[] = {
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
+ VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
+};
+
+static const struct meson_ee_pwrc_mem_domain meson_pwrc_mem_eth[] = {
+ { HHI_MEM_PD_REG0, GENMASK(3, 2) },
+};
+
+static const struct meson_ee_pwrc_mem_domain meson8_pwrc_audio_dsp_mem[] = {
+ { HHI_MEM_PD_REG0, GENMASK(1, 0) },
+};
+
+static const struct meson_ee_pwrc_mem_domain meson8_pwrc_mem_vpu[] = {
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
+ VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
+};
+
+static const struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_vpu[] = {
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG0),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG1),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG2),
+ VPU_MEMPD(HHI_VPU_MEM_PD_REG3),
+ { HHI_VPU_MEM_PD_REG4, GENMASK(1, 0) },
+ { HHI_VPU_MEM_PD_REG4, GENMASK(3, 2) },
+ { HHI_VPU_MEM_PD_REG4, GENMASK(5, 4) },
+ { HHI_VPU_MEM_PD_REG4, GENMASK(7, 6) },
+ VPU_HHI_MEMPD(HHI_MEM_PD_REG0),
+};
+
+static const struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_nna[] = {
+ { HHI_NANOQ_MEM_PD_REG0, 0xff },
+ { HHI_NANOQ_MEM_PD_REG1, 0xff },
+};
+
+static const struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_usb[] = {
+ { HHI_MEM_PD_REG0, GENMASK(31, 30) },
+};
+
+static const struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_pcie[] = {
+ { HHI_MEM_PD_REG0, GENMASK(29, 26) },
+};
+
+static const struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_ge2d[] = {
+ { HHI_MEM_PD_REG0, GENMASK(25, 18) },
+};
+
+static const struct meson_ee_pwrc_mem_domain axg_pwrc_mem_audio[] = {
+ { HHI_MEM_PD_REG0, GENMASK(5, 4) },
+};
+
+static const struct meson_ee_pwrc_mem_domain sm1_pwrc_mem_audio[] = {
+ { HHI_MEM_PD_REG0, GENMASK(5, 4) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(1, 0) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(3, 2) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(5, 4) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(7, 6) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(13, 12) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(15, 14) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(17, 16) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(19, 18) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(21, 20) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(23, 22) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(25, 24) },
+ { HHI_AUDIO_MEM_PD_REG0, GENMASK(27, 26) },
+};
+
+static const struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_nna[] = {
+ { G12A_HHI_NANOQ_MEM_PD_REG0, GENMASK(31, 0) },
+ { G12A_HHI_NANOQ_MEM_PD_REG1, GENMASK(31, 0) },
+};
+
+static const struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_isp[] = {
+ { G12A_HHI_ISP_MEM_PD_REG0, GENMASK(31, 0) },
+ { G12A_HHI_ISP_MEM_PD_REG1, GENMASK(31, 0) },
+};
+
+#define VPU_PD(__name, __top_pd, __mem, __is_pwr_off, __resets, __clks) \
+ { \
+ .name = __name, \
+ .reset_names_count = __resets, \
+ .clk_names_count = __clks, \
+ .top_pd = __top_pd, \
+ .mem_pd_count = ARRAY_SIZE(__mem), \
+ .mem_pd = __mem, \
+ .is_powered_off = __is_pwr_off, \
+ }
+
+#define TOP_PD(__name, __top_pd, __mem, __is_pwr_off) \
+ { \
+ .name = __name, \
+ .top_pd = __top_pd, \
+ .mem_pd_count = ARRAY_SIZE(__mem), \
+ .mem_pd = __mem, \
+ .is_powered_off = __is_pwr_off, \
+ }
+
+#define MEM_PD(__name, __mem) \
+ TOP_PD(__name, NULL, __mem, NULL)
+
+static bool pwrc_ee_is_powered_off(struct meson_ee_pwrc_domain *pwrc_domain);
+
+static const struct meson_ee_pwrc_domain_desc axg_pwrc_domains[] = {
+ [PWRC_AXG_VPU_ID] = VPU_PD("VPU", &gx_pwrc_vpu, axg_pwrc_mem_vpu,
+ pwrc_ee_is_powered_off, 5, 2),
+ [PWRC_AXG_ETHERNET_MEM_ID] = MEM_PD("ETH", meson_pwrc_mem_eth),
+ [PWRC_AXG_AUDIO_ID] = MEM_PD("AUDIO", axg_pwrc_mem_audio),
+};
+
+static const struct meson_ee_pwrc_domain_desc g12a_pwrc_domains[] = {
+ [PWRC_G12A_VPU_ID] = VPU_PD("VPU", &gx_pwrc_vpu, g12a_pwrc_mem_vpu,
+ pwrc_ee_is_powered_off, 11, 2),
+ [PWRC_G12A_ETH_ID] = MEM_PD("ETH", meson_pwrc_mem_eth),
+ [PWRC_G12A_NNA_ID] = TOP_PD("NNA", &g12a_pwrc_nna, g12a_pwrc_mem_nna,
+ pwrc_ee_is_powered_off),
+ [PWRC_G12A_ISP_ID] = TOP_PD("ISP", &g12a_pwrc_isp, g12a_pwrc_mem_isp,
+ pwrc_ee_is_powered_off),
+};
+
+static const struct meson_ee_pwrc_domain_desc gxbb_pwrc_domains[] = {
+ [PWRC_GXBB_VPU_ID] = VPU_PD("VPU", &gx_pwrc_vpu, gxbb_pwrc_mem_vpu,
+ pwrc_ee_is_powered_off, 12, 2),
+ [PWRC_GXBB_ETHERNET_MEM_ID] = MEM_PD("ETH", meson_pwrc_mem_eth),
+};
+
+static const struct meson_ee_pwrc_domain_desc meson8_pwrc_domains[] = {
+ [PWRC_MESON8_VPU_ID] = VPU_PD("VPU", &meson8_pwrc_vpu,
+ meson8_pwrc_mem_vpu,
+ pwrc_ee_is_powered_off, 0, 1),
+ [PWRC_MESON8_ETHERNET_MEM_ID] = MEM_PD("ETHERNET_MEM",
+ meson_pwrc_mem_eth),
+ [PWRC_MESON8_AUDIO_DSP_MEM_ID] = MEM_PD("AUDIO_DSP_MEM",
+ meson8_pwrc_audio_dsp_mem),
+};
+
+static const struct meson_ee_pwrc_domain_desc meson8b_pwrc_domains[] = {
+ [PWRC_MESON8_VPU_ID] = VPU_PD("VPU", &meson8_pwrc_vpu,
+ meson8_pwrc_mem_vpu,
+ pwrc_ee_is_powered_off, 11, 1),
+ [PWRC_MESON8_ETHERNET_MEM_ID] = MEM_PD("ETHERNET_MEM",
+ meson_pwrc_mem_eth),
+ [PWRC_MESON8_AUDIO_DSP_MEM_ID] = MEM_PD("AUDIO_DSP_MEM",
+ meson8_pwrc_audio_dsp_mem),
+};
+
+static const struct meson_ee_pwrc_domain_desc sm1_pwrc_domains[] = {
+ [PWRC_SM1_VPU_ID] = VPU_PD("VPU", &sm1_pwrc_vpu, sm1_pwrc_mem_vpu,
+ pwrc_ee_is_powered_off, 11, 2),
+ [PWRC_SM1_NNA_ID] = TOP_PD("NNA", &sm1_pwrc_nna, sm1_pwrc_mem_nna,
+ pwrc_ee_is_powered_off),
+ [PWRC_SM1_USB_ID] = TOP_PD("USB", &sm1_pwrc_usb, sm1_pwrc_mem_usb,
+ pwrc_ee_is_powered_off),
+ [PWRC_SM1_PCIE_ID] = TOP_PD("PCI", &sm1_pwrc_pci, sm1_pwrc_mem_pcie,
+ pwrc_ee_is_powered_off),
+ [PWRC_SM1_GE2D_ID] = TOP_PD("GE2D", &sm1_pwrc_ge2d, sm1_pwrc_mem_ge2d,
+ pwrc_ee_is_powered_off),
+ [PWRC_SM1_AUDIO_ID] = MEM_PD("AUDIO", sm1_pwrc_mem_audio),
+ [PWRC_SM1_ETH_ID] = MEM_PD("ETH", meson_pwrc_mem_eth),
+};
+
+struct meson_ee_pwrc_domain {
+ struct generic_pm_domain base;
+ bool enabled;
+ struct meson_ee_pwrc *pwrc;
+ struct meson_ee_pwrc_domain_desc desc;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct reset_control *rstc;
+ int num_rstc;
+};
+
+struct meson_ee_pwrc {
+ struct regmap *regmap_ao;
+ struct regmap *regmap_hhi;
+ struct meson_ee_pwrc_domain *domains;
+ struct genpd_onecell_data xlate;
+};
+
+static bool pwrc_ee_is_powered_off(struct meson_ee_pwrc_domain *pwrc_domain)
+{
+ u32 reg;
+
+ regmap_read(pwrc_domain->pwrc->regmap_ao,
+ pwrc_domain->desc.top_pd->sleep_reg, &reg);
+
+ return (reg & pwrc_domain->desc.top_pd->sleep_mask);
+}
+
+static int meson_ee_pwrc_off(struct generic_pm_domain *domain)
+{
+ struct meson_ee_pwrc_domain *pwrc_domain =
+ container_of(domain, struct meson_ee_pwrc_domain, base);
+ int i;
+
+ if (pwrc_domain->desc.top_pd)
+ regmap_update_bits(pwrc_domain->pwrc->regmap_ao,
+ pwrc_domain->desc.top_pd->sleep_reg,
+ pwrc_domain->desc.top_pd->sleep_mask,
+ pwrc_domain->desc.top_pd->sleep_mask);
+ udelay(20);
+
+ for (i = 0 ; i < pwrc_domain->desc.mem_pd_count ; ++i)
+ regmap_update_bits(pwrc_domain->pwrc->regmap_hhi,
+ pwrc_domain->desc.mem_pd[i].reg,
+ pwrc_domain->desc.mem_pd[i].mask,
+ pwrc_domain->desc.mem_pd[i].mask);
+
+ udelay(20);
+
+ if (pwrc_domain->desc.top_pd)
+ regmap_update_bits(pwrc_domain->pwrc->regmap_ao,
+ pwrc_domain->desc.top_pd->iso_reg,
+ pwrc_domain->desc.top_pd->iso_mask,
+ pwrc_domain->desc.top_pd->iso_mask);
+
+ if (pwrc_domain->num_clks) {
+ msleep(20);
+ clk_bulk_disable_unprepare(pwrc_domain->num_clks,
+ pwrc_domain->clks);
+ }
+
+ return 0;
+}
+
+static int meson_ee_pwrc_on(struct generic_pm_domain *domain)
+{
+ struct meson_ee_pwrc_domain *pwrc_domain =
+ container_of(domain, struct meson_ee_pwrc_domain, base);
+ int i, ret;
+
+ if (pwrc_domain->desc.top_pd)
+ regmap_update_bits(pwrc_domain->pwrc->regmap_ao,
+ pwrc_domain->desc.top_pd->sleep_reg,
+ pwrc_domain->desc.top_pd->sleep_mask, 0);
+ udelay(20);
+
+ for (i = 0 ; i < pwrc_domain->desc.mem_pd_count ; ++i)
+ regmap_update_bits(pwrc_domain->pwrc->regmap_hhi,
+ pwrc_domain->desc.mem_pd[i].reg,
+ pwrc_domain->desc.mem_pd[i].mask, 0);
+
+ udelay(20);
+
+ ret = reset_control_assert(pwrc_domain->rstc);
+ if (ret)
+ return ret;
+
+ if (pwrc_domain->desc.top_pd)
+ regmap_update_bits(pwrc_domain->pwrc->regmap_ao,
+ pwrc_domain->desc.top_pd->iso_reg,
+ pwrc_domain->desc.top_pd->iso_mask, 0);
+
+ ret = reset_control_deassert(pwrc_domain->rstc);
+ if (ret)
+ return ret;
+
+ return clk_bulk_prepare_enable(pwrc_domain->num_clks,
+ pwrc_domain->clks);
+}
+
+static int meson_ee_pwrc_init_domain(struct platform_device *pdev,
+ struct meson_ee_pwrc *pwrc,
+ struct meson_ee_pwrc_domain *dom)
+{
+ int ret;
+
+ dom->pwrc = pwrc;
+ dom->num_rstc = dom->desc.reset_names_count;
+ dom->num_clks = dom->desc.clk_names_count;
+
+ if (dom->num_rstc) {
+ int count = reset_control_get_count(&pdev->dev);
+
+ if (count != dom->num_rstc)
+ dev_warn(&pdev->dev, "Invalid resets count %d for domain %s\n",
+ count, dom->desc.name);
+
+ dom->rstc = devm_reset_control_array_get_exclusive(&pdev->dev);
+ if (IS_ERR(dom->rstc))
+ return PTR_ERR(dom->rstc);
+ }
+
+ if (dom->num_clks) {
+ int ret = devm_clk_bulk_get_all(&pdev->dev, &dom->clks);
+ if (ret < 0)
+ return ret;
+
+ if (dom->num_clks != ret) {
+ dev_warn(&pdev->dev, "Invalid clocks count %d for domain %s\n",
+ ret, dom->desc.name);
+ dom->num_clks = ret;
+ }
+ }
+
+ dom->base.name = dom->desc.name;
+ dom->base.power_on = meson_ee_pwrc_on;
+ dom->base.power_off = meson_ee_pwrc_off;
+
+ /*
+ * TOFIX: This is a special case for the VPU power domain, which can
+ * be enabled previously by the bootloader. In this case the VPU
+ * pipeline may be functional but no driver maybe never attach
+ * to this power domain, and if the domain is disabled it could
+ * cause system errors. This is why the pm_domain_always_on_gov
+ * is used here.
+ * For the same reason, the clocks should be enabled in case
+ * we need to power the domain off, otherwise the internal clocks
+ * prepare/enable counters won't be in sync.
+ */
+ if (dom->num_clks && dom->desc.is_powered_off && !dom->desc.is_powered_off(dom)) {
+ ret = clk_bulk_prepare_enable(dom->num_clks, dom->clks);
+ if (ret)
+ return ret;
+
+ dom->base.flags = GENPD_FLAG_ALWAYS_ON;
+ ret = pm_genpd_init(&dom->base, NULL, false);
+ if (ret)
+ return ret;
+ } else {
+ ret = pm_genpd_init(&dom->base, NULL,
+ (dom->desc.is_powered_off ?
+ dom->desc.is_powered_off(dom) : true));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int meson_ee_pwrc_probe(struct platform_device *pdev)
+{
+ const struct meson_ee_pwrc_domain_data *match;
+ struct regmap *regmap_ao, *regmap_hhi;
+ struct device_node *parent_np;
+ struct meson_ee_pwrc *pwrc;
+ int i, ret;
+
+ match = of_device_get_match_data(&pdev->dev);
+ if (!match) {
+ dev_err(&pdev->dev, "failed to get match data\n");
+ return -ENODEV;
+ }
+
+ pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL);
+ if (!pwrc)
+ return -ENOMEM;
+
+ pwrc->xlate.domains = devm_kcalloc(&pdev->dev, match->count,
+ sizeof(*pwrc->xlate.domains),
+ GFP_KERNEL);
+ if (!pwrc->xlate.domains)
+ return -ENOMEM;
+
+ pwrc->domains = devm_kcalloc(&pdev->dev, match->count,
+ sizeof(*pwrc->domains), GFP_KERNEL);
+ if (!pwrc->domains)
+ return -ENOMEM;
+
+ pwrc->xlate.num_domains = match->count;
+
+ parent_np = of_get_parent(pdev->dev.of_node);
+ regmap_hhi = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap_hhi)) {
+ dev_err(&pdev->dev, "failed to get HHI regmap\n");
+ return PTR_ERR(regmap_hhi);
+ }
+
+ regmap_ao = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "amlogic,ao-sysctrl");
+ if (IS_ERR(regmap_ao)) {
+ dev_err(&pdev->dev, "failed to get AO regmap\n");
+ return PTR_ERR(regmap_ao);
+ }
+
+ pwrc->regmap_ao = regmap_ao;
+ pwrc->regmap_hhi = regmap_hhi;
+
+ platform_set_drvdata(pdev, pwrc);
+
+ for (i = 0 ; i < match->count ; ++i) {
+ struct meson_ee_pwrc_domain *dom = &pwrc->domains[i];
+
+ memcpy(&dom->desc, &match->domains[i], sizeof(dom->desc));
+
+ ret = meson_ee_pwrc_init_domain(pdev, pwrc, dom);
+ if (ret)
+ return ret;
+
+ pwrc->xlate.domains[i] = &dom->base;
+ }
+
+ return of_genpd_add_provider_onecell(pdev->dev.of_node, &pwrc->xlate);
+}
+
+static void meson_ee_pwrc_shutdown(struct platform_device *pdev)
+{
+ struct meson_ee_pwrc *pwrc = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0 ; i < pwrc->xlate.num_domains ; ++i) {
+ struct meson_ee_pwrc_domain *dom = &pwrc->domains[i];
+
+ if (dom->desc.is_powered_off && !dom->desc.is_powered_off(dom))
+ meson_ee_pwrc_off(&dom->base);
+ }
+}
+
+static const struct meson_ee_pwrc_domain_data meson_ee_g12a_pwrc_data = {
+ .count = ARRAY_SIZE(g12a_pwrc_domains),
+ .domains = g12a_pwrc_domains,
+};
+
+static const struct meson_ee_pwrc_domain_data meson_ee_axg_pwrc_data = {
+ .count = ARRAY_SIZE(axg_pwrc_domains),
+ .domains = axg_pwrc_domains,
+};
+
+static const struct meson_ee_pwrc_domain_data meson_ee_gxbb_pwrc_data = {
+ .count = ARRAY_SIZE(gxbb_pwrc_domains),
+ .domains = gxbb_pwrc_domains,
+};
+
+static const struct meson_ee_pwrc_domain_data meson_ee_m8_pwrc_data = {
+ .count = ARRAY_SIZE(meson8_pwrc_domains),
+ .domains = meson8_pwrc_domains,
+};
+
+static const struct meson_ee_pwrc_domain_data meson_ee_m8b_pwrc_data = {
+ .count = ARRAY_SIZE(meson8b_pwrc_domains),
+ .domains = meson8b_pwrc_domains,
+};
+
+static const struct meson_ee_pwrc_domain_data meson_ee_sm1_pwrc_data = {
+ .count = ARRAY_SIZE(sm1_pwrc_domains),
+ .domains = sm1_pwrc_domains,
+};
+
+static const struct of_device_id meson_ee_pwrc_match_table[] = {
+ {
+ .compatible = "amlogic,meson8-pwrc",
+ .data = &meson_ee_m8_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson8b-pwrc",
+ .data = &meson_ee_m8b_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson8m2-pwrc",
+ .data = &meson_ee_m8b_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson-axg-pwrc",
+ .data = &meson_ee_axg_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson-gxbb-pwrc",
+ .data = &meson_ee_gxbb_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson-g12a-pwrc",
+ .data = &meson_ee_g12a_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson-sm1-pwrc",
+ .data = &meson_ee_sm1_pwrc_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, meson_ee_pwrc_match_table);
+
+static struct platform_driver meson_ee_pwrc_driver = {
+ .probe = meson_ee_pwrc_probe,
+ .shutdown = meson_ee_pwrc_shutdown,
+ .driver = {
+ .name = "meson_ee_pwrc",
+ .of_match_table = meson_ee_pwrc_match_table,
+ },
+};
+module_platform_driver(meson_ee_pwrc_driver);
+MODULE_DESCRIPTION("Amlogic Meson Everything-Else Power Domains driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pmdomain/amlogic/meson-secure-pwrc.c b/drivers/pmdomain/amlogic/meson-secure-pwrc.c
new file mode 100644
index 000000000..133549956
--- /dev/null
+++ b/drivers/pmdomain/amlogic/meson-secure-pwrc.c
@@ -0,0 +1,577 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2019 Amlogic, Inc.
+ * Author: Jianxin Pan <jianxin.pan@amlogic.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <dt-bindings/power/meson-a1-power.h>
+#include <dt-bindings/power/amlogic,c3-pwrc.h>
+#include <dt-bindings/power/meson-s4-power.h>
+#include <dt-bindings/power/amlogic,t7-pwrc.h>
+#include <dt-bindings/power/amlogic,a4-pwrc.h>
+#include <dt-bindings/power/amlogic,a5-pwrc.h>
+#include <dt-bindings/power/amlogic,a9-pwrc.h>
+#include <dt-bindings/power/amlogic,s6-pwrc.h>
+#include <dt-bindings/power/amlogic,s7-pwrc.h>
+#include <dt-bindings/power/amlogic,s7d-pwrc.h>
+#include <linux/arm-smccc.h>
+#include <linux/firmware/meson/meson_sm.h>
+#include <linux/module.h>
+
+#define PWRC_ON 1
+#define PWRC_OFF 0
+#define PWRC_NO_PARENT UINT_MAX
+
+struct meson_secure_pwrc_domain {
+ struct generic_pm_domain base;
+ unsigned int index;
+ unsigned int parent;
+ struct meson_secure_pwrc *pwrc;
+};
+
+struct meson_secure_pwrc {
+ struct meson_secure_pwrc_domain *domains;
+ struct genpd_onecell_data xlate;
+ struct meson_sm_firmware *fw;
+};
+
+struct meson_secure_pwrc_domain_desc {
+ unsigned int index;
+ unsigned int parent;
+ unsigned int flags;
+ char *name;
+ bool (*is_off)(struct meson_secure_pwrc_domain *pwrc_domain);
+};
+
+struct meson_secure_pwrc_domain_data {
+ unsigned int count;
+ const struct meson_secure_pwrc_domain_desc *domains;
+};
+
+static bool pwrc_secure_is_off(struct meson_secure_pwrc_domain *pwrc_domain)
+{
+ int is_off = 1;
+
+ if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_GET, &is_off,
+ pwrc_domain->index, 0, 0, 0, 0) < 0)
+ pr_err("failed to get power domain status\n");
+
+ return is_off;
+}
+
+static int meson_secure_pwrc_off(struct generic_pm_domain *domain)
+{
+ int ret = 0;
+ struct meson_secure_pwrc_domain *pwrc_domain =
+ container_of(domain, struct meson_secure_pwrc_domain, base);
+
+ if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL,
+ pwrc_domain->index, PWRC_OFF, 0, 0, 0) < 0) {
+ pr_err("failed to set power domain off\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int meson_secure_pwrc_on(struct generic_pm_domain *domain)
+{
+ int ret = 0;
+ struct meson_secure_pwrc_domain *pwrc_domain =
+ container_of(domain, struct meson_secure_pwrc_domain, base);
+
+ if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL,
+ pwrc_domain->index, PWRC_ON, 0, 0, 0) < 0) {
+ pr_err("failed to set power domain on\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+#define SEC_PD(__name, __flag) \
+[PWRC_##__name##_ID] = \
+{ \
+ .name = #__name, \
+ .index = PWRC_##__name##_ID, \
+ .is_off = pwrc_secure_is_off, \
+ .flags = __flag, \
+ .parent = PWRC_NO_PARENT, \
+}
+
+#define TOP_PD(__name, __flag, __parent) \
+[PWRC_##__name##_ID] = \
+{ \
+ .name = #__name, \
+ .index = PWRC_##__name##_ID, \
+ .is_off = pwrc_secure_is_off, \
+ .flags = __flag, \
+ .parent = __parent, \
+}
+
+static const struct meson_secure_pwrc_domain_desc a1_pwrc_domains[] = {
+ SEC_PD(DSPA, 0),
+ SEC_PD(DSPB, 0),
+ /* UART should keep working in ATF after suspend and before resume */
+ SEC_PD(UART, GENPD_FLAG_ALWAYS_ON),
+ /* DMC is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(DMC, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(I2C, 0),
+ SEC_PD(PSRAM, 0),
+ SEC_PD(ACODEC, 0),
+ SEC_PD(AUDIO, 0),
+ SEC_PD(OTP, 0),
+ SEC_PD(DMA, GENPD_FLAG_ALWAYS_ON | GENPD_FLAG_IRQ_SAFE),
+ SEC_PD(SD_EMMC, 0),
+ SEC_PD(RAMA, 0),
+ /* SRAMB is used as ATF runtime memory, and should be always on */
+ SEC_PD(RAMB, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(IR, 0),
+ SEC_PD(SPICC, 0),
+ SEC_PD(SPIFC, 0),
+ SEC_PD(USB, 0),
+ /* NIC is for the Arm NIC-400 interconnect, and should be always on */
+ SEC_PD(NIC, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(PDMIN, 0),
+ SEC_PD(RSA, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc a4_pwrc_domains[] = {
+ SEC_PD(A4_AUDIO, 0),
+ SEC_PD(A4_SDIOA, 0),
+ SEC_PD(A4_EMMC, 0),
+ SEC_PD(A4_USB_COMB, 0),
+ SEC_PD(A4_ETH, 0),
+ SEC_PD(A4_VOUT, 0),
+ SEC_PD(A4_AUDIO_PDM, 0),
+ /* DMC is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(A4_DMC, GENPD_FLAG_ALWAYS_ON),
+ /* WRAP is secure_top, a lot of modules are included, and should be always on */
+ SEC_PD(A4_SYS_WRAP, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(A4_AO_I2C_S, 0),
+ SEC_PD(A4_AO_UART, 0),
+ /* IR is wake up trigger source, and should be always on */
+ SEC_PD(A4_AO_IR, GENPD_FLAG_ALWAYS_ON),
+};
+
+static const struct meson_secure_pwrc_domain_desc a5_pwrc_domains[] = {
+ SEC_PD(A5_NNA, 0),
+ SEC_PD(A5_AUDIO, 0),
+ SEC_PD(A5_SDIOA, 0),
+ SEC_PD(A5_EMMC, 0),
+ SEC_PD(A5_USB_COMB, 0),
+ SEC_PD(A5_ETH, 0),
+ SEC_PD(A5_RSA, 0),
+ SEC_PD(A5_AUDIO_PDM, 0),
+ /* DMC is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(A5_DMC, GENPD_FLAG_ALWAYS_ON),
+ /* WRAP is secure_top, a lot of modules are included, and should be always on */
+ SEC_PD(A5_SYS_WRAP, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(A5_DSPA, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc a9_pwrc_domains[] = {
+ SEC_PD(A9_DSPA, 0),
+ SEC_PD(A9_U3HSG_U3, 0),
+ SEC_PD(A9_DP, 0),
+ SEC_PD(A9_DOS_HCODEC, 0),
+ SEC_PD(A9_TAHOE, 0),
+ SEC_PD(A9_DOS_HEVC, 0),
+ SEC_PD(A9_U2H, 0),
+ SEC_PD(A9_U3DRD_B, 0),
+ SEC_PD(A9_VPU_HDMI, 0),
+ SEC_PD(A9_U2DRD, 0),
+ SEC_PD(A9_U3DRD_A, 0),
+ SEC_PD(A9_SD_EMMC_C, 0),
+ SEC_PD(A9_GE2D, 0),
+ SEC_PD(A9_AMFC, 0),
+ SEC_PD(A9_EDPTX, 0),
+ SEC_PD(A9_OPP, 0),
+ SEC_PD(A9_VICP, 0),
+ SEC_PD(A9_SD_EMMC_A, 0),
+ SEC_PD(A9_SD_EMMC_B, 0),
+ SEC_PD(A9_ETH, 0),
+ SEC_PD(A9_PCIE_A, 0),
+ SEC_PD(A9_PCIE_B, 0),
+ SEC_PD(A9_NNA_4T, 0),
+ SEC_PD(A9_HDMIRX, 0),
+ SEC_PD(A9_CVE, 0),
+ SEC_PD(A9_ISP, 0),
+ SEC_PD(A9_ETH_1G, 0),
+ SEC_PD(A9_U3HSG_HSG, 0),
+ SEC_PD(A9_U3DPPHY_U3, 0),
+ SEC_PD(A9_U3DPPHY_DP, 0),
+ SEC_PD(A9_PCIE3PHY, 0),
+ SEC_PD(A9_U3HSG_PCIE2, 0),
+ SEC_PD(A9_MALI_TOP, 0),
+ SEC_PD(A9_AO_SED, 0),
+ SEC_PD(A9_AO_IR, 0),
+ /* UART_B is for console log, and should be always on */
+ SEC_PD(A9_AO_UART_B, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(A9_AO_UART_C, 0),
+ SEC_PD(A9_AO_UART_D, 0),
+ SEC_PD(A9_AO_SPISG, 0),
+ SEC_PD(A9_AO_UART_E, 0),
+ SEC_PD(A9_AO_CEC, 0),
+ /* SRAMA is used as ATF runtime memory, and should be always on */
+ SEC_PD(A9_EE_SRAMA, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(A9_AUDIO, 0),
+ /* DMC0 is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(A9_DMC0, GENPD_FLAG_ALWAYS_ON),
+ /*GIC needs to be used consistently, and should be always on */
+ SEC_PD(A9_GIC, GENPD_FLAG_ALWAYS_ON),
+ /* DDRPHY is for DDR Work, and should be always on */
+ SEC_PD(A9_DDRPHY, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(A9_AUCPU, 0),
+ SEC_PD(A9_DSI0, 0),
+ SEC_PD(A9_DSI1, 0),
+ SEC_PD(A9_CAN0, 0),
+ SEC_PD(A9_CAN1, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc c3_pwrc_domains[] = {
+ SEC_PD(C3_NNA, 0),
+ SEC_PD(C3_AUDIO, 0),
+ SEC_PD(C3_SDIOA, 0),
+ SEC_PD(C3_EMMC, 0),
+ SEC_PD(C3_USB_COMB, 0),
+ SEC_PD(C3_SDCARD, 0),
+ /* ETH is for ethernet online wakeup, and should be always on */
+ SEC_PD(C3_ETH, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(C3_GE2D, 0),
+ SEC_PD(C3_CVE, 0),
+ SEC_PD(C3_GDC_WRAP, 0),
+ SEC_PD(C3_ISP_TOP, 0),
+ SEC_PD(C3_MIPI_ISP_WRAP, 0),
+ SEC_PD(C3_VCODEC, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc s4_pwrc_domains[] = {
+ SEC_PD(S4_DOS_HEVC, 0),
+ SEC_PD(S4_DOS_VDEC, 0),
+ SEC_PD(S4_VPU_HDMI, 0),
+ SEC_PD(S4_USB_COMB, 0),
+ SEC_PD(S4_GE2D, 0),
+ /* ETH is for ethernet online wakeup, and should be always on */
+ SEC_PD(S4_ETH, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(S4_DEMOD, 0),
+ SEC_PD(S4_AUDIO, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc s6_pwrc_domains[] = {
+ SEC_PD(S6_DSPA, 0),
+ SEC_PD(S6_DOS_HEVC, 0),
+ SEC_PD(S6_DOS_VDEC, 0),
+ SEC_PD(S6_VPU_HDMI, 0),
+ SEC_PD(S6_U2DRD, 0),
+ SEC_PD(S6_U3DRD, 0),
+ SEC_PD(S6_SD_EMMC_C, 0),
+ SEC_PD(S6_GE2D, 0),
+ SEC_PD(S6_AMFC, 0),
+ SEC_PD(S6_VC9000E, 0),
+ SEC_PD(S6_DEWARP, 0),
+ SEC_PD(S6_VICP, 0),
+ SEC_PD(S6_SD_EMMC_A, 0),
+ SEC_PD(S6_SD_EMMC_B, 0),
+ /* ETH is for ethernet online wakeup, and should be always on */
+ SEC_PD(S6_ETH, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(S6_PCIE, 0),
+ SEC_PD(S6_NNA_4T, 0),
+ SEC_PD(S6_AUDIO, 0),
+ SEC_PD(S6_AUCPU, 0),
+ SEC_PD(S6_ADAPT, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc s7_pwrc_domains[] = {
+ SEC_PD(S7_DOS_HEVC, 0),
+ SEC_PD(S7_DOS_VDEC, 0),
+ SEC_PD(S7_VPU_HDMI, 0),
+ SEC_PD(S7_USB_COMB, 0),
+ SEC_PD(S7_SD_EMMC_C, 0),
+ SEC_PD(S7_GE2D, 0),
+ SEC_PD(S7_SD_EMMC_A, 0),
+ SEC_PD(S7_SD_EMMC_B, 0),
+ /* ETH is for ethernet online wakeup, and should be always on */
+ SEC_PD(S7_ETH, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(S7_AUCPU, 0),
+ SEC_PD(S7_AUDIO, 0),
+};
+
+static const struct meson_secure_pwrc_domain_desc s7d_pwrc_domains[] = {
+ SEC_PD(S7D_DOS_HCODEC, 0),
+ SEC_PD(S7D_DOS_HEVC, 0),
+ SEC_PD(S7D_DOS_VDEC, 0),
+ SEC_PD(S7D_VPU_HDMI, 0),
+ SEC_PD(S7D_USB_U2DRD, 0),
+ SEC_PD(S7D_USB_U2H, 0),
+ SEC_PD(S7D_SSD_EMMC_C, 0),
+ SEC_PD(S7D_GE2D, 0),
+ SEC_PD(S7D_AMFC, 0),
+ SEC_PD(S7D_EMMC_A, 0),
+ SEC_PD(S7D_EMMC_B, 0),
+ /* ETH is for ethernet online wakeup, and should be always on */
+ SEC_PD(S7D_ETH, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(S7D_AUCPU, 0),
+ SEC_PD(S7D_AUDIO, 0),
+ /* SRAMA is used as ATF runtime memory, and should be always on */
+ SEC_PD(S7D_SRAMA, GENPD_FLAG_ALWAYS_ON),
+ /* DMC0 is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(S7D_DMC0, GENPD_FLAG_ALWAYS_ON),
+ /* DMC1 is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(S7D_DMC1, GENPD_FLAG_ALWAYS_ON),
+ /* DDR should be always on */
+ SEC_PD(S7D_DDR, GENPD_FLAG_ALWAYS_ON),
+};
+
+static const struct meson_secure_pwrc_domain_desc t7_pwrc_domains[] = {
+ SEC_PD(T7_DSPA, 0),
+ SEC_PD(T7_DSPB, 0),
+ TOP_PD(T7_DOS_HCODEC, 0, PWRC_T7_NIC3_ID),
+ TOP_PD(T7_DOS_HEVC, 0, PWRC_T7_NIC3_ID),
+ TOP_PD(T7_DOS_VDEC, 0, PWRC_T7_NIC3_ID),
+ TOP_PD(T7_DOS_WAVE, 0, PWRC_T7_NIC3_ID),
+ SEC_PD(T7_VPU_HDMI, 0),
+ SEC_PD(T7_USB_COMB, 0),
+ SEC_PD(T7_PCIE, 0),
+ TOP_PD(T7_GE2D, 0, PWRC_T7_NIC3_ID),
+ /* SRAMA is used as ATF runtime memory, and should be always on */
+ SEC_PD(T7_SRAMA, GENPD_FLAG_ALWAYS_ON),
+ /* SRAMB is used as ATF runtime memory, and should be always on */
+ SEC_PD(T7_SRAMB, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(T7_HDMIRX, 0),
+ SEC_PD(T7_VI_CLK1, 0),
+ SEC_PD(T7_VI_CLK2, 0),
+ /* ETH is for ethernet online wakeup, and should be always on */
+ SEC_PD(T7_ETH, GENPD_FLAG_ALWAYS_ON),
+ TOP_PD(T7_ISP, 0, PWRC_T7_MIPI_ISP_ID),
+ SEC_PD(T7_MIPI_ISP, 0),
+ TOP_PD(T7_GDC, 0, PWRC_T7_NIC3_ID),
+ TOP_PD(T7_DEWARP, 0, PWRC_T7_NIC3_ID),
+ SEC_PD(T7_SDIO_A, 0),
+ SEC_PD(T7_SDIO_B, 0),
+ SEC_PD(T7_EMMC, 0),
+ TOP_PD(T7_MALI_SC0, 0, PWRC_T7_NNA_TOP_ID),
+ TOP_PD(T7_MALI_SC1, 0, PWRC_T7_NNA_TOP_ID),
+ TOP_PD(T7_MALI_SC2, 0, PWRC_T7_NNA_TOP_ID),
+ TOP_PD(T7_MALI_SC3, 0, PWRC_T7_NNA_TOP_ID),
+ SEC_PD(T7_MALI_TOP, 0),
+ TOP_PD(T7_NNA_CORE0, 0, PWRC_T7_NNA_TOP_ID),
+ TOP_PD(T7_NNA_CORE1, 0, PWRC_T7_NNA_TOP_ID),
+ TOP_PD(T7_NNA_CORE2, 0, PWRC_T7_NNA_TOP_ID),
+ TOP_PD(T7_NNA_CORE3, 0, PWRC_T7_NNA_TOP_ID),
+ SEC_PD(T7_NNA_TOP, 0),
+ SEC_PD(T7_DDR0, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(T7_DDR1, GENPD_FLAG_ALWAYS_ON),
+ /* DMC0 is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(T7_DMC0, GENPD_FLAG_ALWAYS_ON),
+ /* DMC1 is for DDR PHY ana/dig and DMC, and should be always on */
+ SEC_PD(T7_DMC1, GENPD_FLAG_ALWAYS_ON),
+ /* NOC is related to clk bus, and should be always on */
+ SEC_PD(T7_NOC, GENPD_FLAG_ALWAYS_ON),
+ /* NIC is for the Arm NIC-400 interconnect, and should be always on */
+ SEC_PD(T7_NIC2, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(T7_NIC3, 0),
+ /* CPU accesses the interleave data to the ddr need cci, and should be always on */
+ SEC_PD(T7_CCI, GENPD_FLAG_ALWAYS_ON),
+ SEC_PD(T7_MIPI_DSI0, 0),
+ SEC_PD(T7_SPICC0, 0),
+ SEC_PD(T7_SPICC1, 0),
+ SEC_PD(T7_SPICC2, 0),
+ SEC_PD(T7_SPICC3, 0),
+ SEC_PD(T7_SPICC4, 0),
+ SEC_PD(T7_SPICC5, 0),
+ SEC_PD(T7_EDP0, 0),
+ SEC_PD(T7_EDP1, 0),
+ SEC_PD(T7_MIPI_DSI1, 0),
+ SEC_PD(T7_AUDIO, 0),
+};
+
+static int meson_secure_pwrc_probe(struct platform_device *pdev)
+{
+ int i;
+ struct device_node *sm_np;
+ struct meson_secure_pwrc *pwrc;
+ const struct meson_secure_pwrc_domain_data *match;
+
+ match = of_device_get_match_data(&pdev->dev);
+ if (!match) {
+ dev_err(&pdev->dev, "failed to get match data\n");
+ return -ENODEV;
+ }
+
+ sm_np = of_find_compatible_node(NULL, NULL, "amlogic,meson-gxbb-sm");
+ if (!sm_np) {
+ dev_err(&pdev->dev, "no secure-monitor node\n");
+ return -ENODEV;
+ }
+
+ pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL);
+ if (!pwrc) {
+ of_node_put(sm_np);
+ return -ENOMEM;
+ }
+
+ pwrc->fw = meson_sm_get(sm_np);
+ of_node_put(sm_np);
+ if (!pwrc->fw)
+ return -EPROBE_DEFER;
+
+ pwrc->xlate.domains = devm_kcalloc(&pdev->dev, match->count,
+ sizeof(*pwrc->xlate.domains),
+ GFP_KERNEL);
+ if (!pwrc->xlate.domains)
+ return -ENOMEM;
+
+ pwrc->domains = devm_kcalloc(&pdev->dev, match->count,
+ sizeof(*pwrc->domains), GFP_KERNEL);
+ if (!pwrc->domains)
+ return -ENOMEM;
+
+ pwrc->xlate.num_domains = match->count;
+ platform_set_drvdata(pdev, pwrc);
+
+ for (i = 0 ; i < match->count ; ++i) {
+ struct meson_secure_pwrc_domain *dom = &pwrc->domains[i];
+
+ if (!match->domains[i].name)
+ continue;
+
+ dom->pwrc = pwrc;
+ dom->index = match->domains[i].index;
+ dom->parent = match->domains[i].parent;
+ dom->base.name = match->domains[i].name;
+ dom->base.flags = match->domains[i].flags;
+ dom->base.power_on = meson_secure_pwrc_on;
+ dom->base.power_off = meson_secure_pwrc_off;
+
+ if (match->domains[i].is_off(dom) && (dom->base.flags & GENPD_FLAG_ALWAYS_ON))
+ meson_secure_pwrc_on(&dom->base);
+
+ pm_genpd_init(&dom->base, NULL, match->domains[i].is_off(dom));
+
+ pwrc->xlate.domains[i] = &dom->base;
+ }
+
+ for (i = 0; i < match->count; i++) {
+ struct meson_secure_pwrc_domain *dom = pwrc->domains;
+
+ if (!match->domains[i].name || match->domains[i].parent == PWRC_NO_PARENT)
+ continue;
+
+ pm_genpd_add_subdomain(&dom[dom[i].parent].base, &dom[i].base);
+ }
+
+ return of_genpd_add_provider_onecell(pdev->dev.of_node, &pwrc->xlate);
+}
+
+static const struct meson_secure_pwrc_domain_data meson_secure_a1_pwrc_data = {
+ .domains = a1_pwrc_domains,
+ .count = ARRAY_SIZE(a1_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_a4_pwrc_data = {
+ .domains = a4_pwrc_domains,
+ .count = ARRAY_SIZE(a4_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_a5_pwrc_data = {
+ .domains = a5_pwrc_domains,
+ .count = ARRAY_SIZE(a5_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_a9_pwrc_data = {
+ .domains = a9_pwrc_domains,
+ .count = ARRAY_SIZE(a9_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_c3_pwrc_data = {
+ .domains = c3_pwrc_domains,
+ .count = ARRAY_SIZE(c3_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data meson_secure_s4_pwrc_data = {
+ .domains = s4_pwrc_domains,
+ .count = ARRAY_SIZE(s4_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_s6_pwrc_data = {
+ .domains = s6_pwrc_domains,
+ .count = ARRAY_SIZE(s6_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_s7_pwrc_data = {
+ .domains = s7_pwrc_domains,
+ .count = ARRAY_SIZE(s7_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_s7d_pwrc_data = {
+ .domains = s7d_pwrc_domains,
+ .count = ARRAY_SIZE(s7d_pwrc_domains),
+};
+
+static const struct meson_secure_pwrc_domain_data amlogic_secure_t7_pwrc_data = {
+ .domains = t7_pwrc_domains,
+ .count = ARRAY_SIZE(t7_pwrc_domains),
+};
+
+static const struct of_device_id meson_secure_pwrc_match_table[] = {
+ {
+ .compatible = "amlogic,meson-a1-pwrc",
+ .data = &meson_secure_a1_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,a4-pwrc",
+ .data = &amlogic_secure_a4_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,a5-pwrc",
+ .data = &amlogic_secure_a5_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,a9-pwrc",
+ .data = &amlogic_secure_a9_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,c3-pwrc",
+ .data = &amlogic_secure_c3_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,meson-s4-pwrc",
+ .data = &meson_secure_s4_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,s6-pwrc",
+ .data = &amlogic_secure_s6_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,s7-pwrc",
+ .data = &amlogic_secure_s7_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,s7d-pwrc",
+ .data = &amlogic_secure_s7d_pwrc_data,
+ },
+ {
+ .compatible = "amlogic,t7-pwrc",
+ .data = &amlogic_secure_t7_pwrc_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, meson_secure_pwrc_match_table);
+
+static struct platform_driver meson_secure_pwrc_driver = {
+ .probe = meson_secure_pwrc_probe,
+ .driver = {
+ .name = "meson_secure_pwrc",
+ .of_match_table = meson_secure_pwrc_match_table,
+ },
+};
+module_platform_driver(meson_secure_pwrc_driver);
+MODULE_DESCRIPTION("Amlogic Meson Secure Power Domains driver");
+MODULE_LICENSE("Dual MIT/GPL");