summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2025-03-25 20:40:51 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2025-03-25 20:40:51 -0700
commit2a2274e90a76c24a03287e386f1ad128a8cc3b67 (patch)
treeec40e7ddbe9b7c7263f90e50c29949c994934596 /drivers
parent0163c8864bee0df6459fdb18ef9c0944368639e3 (diff)
parent51f0b8911ec4355cea07b180f6569cc52f65aaa8 (diff)
Merge tag 'pmdomain-v6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm
Pull pmdomain updates from Ulf Hansson: "pmdomain core: - Add dev_pm_genpd_rpm_always_on() to support more fine-grained PM pmdomain providers: - arm: Remove redundant state verification for the SCMI PM domain - bcm: Add system-wakeup support for bcm2835 via GENPD_FLAG_ACTIVE_WAKEUP - rockchip: Add support for regulators - rockchip: Use SMC call to properly inform firmware - sunxi: Add V853 ppu support - thead: Add support for RISC-V TH1520 power-domains firmware: - Add support for the AON firmware protocol for RISC-V THEAD cpuidle-psci: - Update section in MAINTAINERS for cpuidle-psci - Add trace support for PSCI domain-idlestates" * tag 'pmdomain-v6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm: (29 commits) firmware: thead: add CONFIG_MAILBOX dependency firmware: thead,th1520-aon: Fix use after free in th1520_aon_init() pmdomain: arm: scmi_pm_domain: Remove redundant state verification pmdomain: thead: fix TH1520_AON_PROTOCOL dependency pmdomain: thead: Add power-domain driver for TH1520 dt-bindings: power: Add TH1520 SoC power domains firmware: thead: Add AON firmware protocol driver dt-bindings: firmware: thead,th1520: Add support for firmware node pmdomain: rockchip: add regulator dependency pmdomain: rockchip: add regulator support pmdomain: rockchip: fix rockchip_pd_power error handling pmdomain: rockchip: reduce indentation in rockchip_pd_power pmdomain: rockchip: forward rockchip_do_pmu_set_power_domain errors pmdomain: rockchip: cleanup mutex handling in rockchip_pd_power dt-bindings: power: rockchip: add regulator support pmdomain: rockchip: Fix build error pmdomain: imx: gpcv2: use proper helper for property detection MAINTAINERS: Update section for cpuidle-psci pmdomain: rockchip: Check if SMC could be handled by TA cpuidle: psci: Add trace for PSCI domain idle ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/cpuidle/cpuidle-psci.c3
-rw-r--r--drivers/firmware/Kconfig10
-rw-r--r--drivers/firmware/Makefile1
-rw-r--r--drivers/firmware/thead,th1520-aon.c250
-rw-r--r--drivers/pmdomain/Kconfig1
-rw-r--r--drivers/pmdomain/Makefile1
-rw-r--r--drivers/pmdomain/arm/scmi_pm_domain.c11
-rw-r--r--drivers/pmdomain/bcm/bcm2835-power.c1
-rw-r--r--drivers/pmdomain/core.c35
-rw-r--r--drivers/pmdomain/imx/gpcv2.c2
-rw-r--r--drivers/pmdomain/renesas/rcar-sysc.c2
-rw-r--r--drivers/pmdomain/rockchip/Kconfig2
-rw-r--r--drivers/pmdomain/rockchip/pm-domains.c205
-rw-r--r--drivers/pmdomain/sunxi/sun20i-ppu.c15
-rw-r--r--drivers/pmdomain/thead/Kconfig12
-rw-r--r--drivers/pmdomain/thead/Makefile2
-rw-r--r--drivers/pmdomain/thead/th1520-pm-domains.c218
-rw-r--r--drivers/pmdomain/ti/omap_prm.c2
18 files changed, 691 insertions, 82 deletions
diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c
index a4594c3d6562..b46a83f5ffe4 100644
--- a/drivers/cpuidle/cpuidle-psci.c
+++ b/drivers/cpuidle/cpuidle-psci.c
@@ -25,6 +25,7 @@
#include <linux/syscore_ops.h>
#include <asm/cpuidle.h>
+#include <trace/events/power.h>
#include "cpuidle-psci.h"
#include "dt_idle_states.h"
@@ -74,7 +75,9 @@ static __cpuidle int __psci_enter_domain_idle_state(struct cpuidle_device *dev,
if (!state)
state = states[idx];
+ trace_psci_domain_idle_enter(dev->cpu, state, s2idle);
ret = psci_cpu_suspend_enter(state) ? -1 : idx;
+ trace_psci_domain_idle_exit(dev->cpu, state, s2idle);
if (s2idle)
dev_pm_genpd_resume(pd_dev);
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 9f35f69e0f9e..ebdd972e6f19 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -212,6 +212,16 @@ config SYSFB_SIMPLEFB
If unsure, say Y.
+config TH1520_AON_PROTOCOL
+ tristate "Always-On firmware protocol"
+ depends on ARCH_THEAD || COMPILE_TEST
+ depends on MAILBOX
+ help
+ Power, clock, and resource management capabilities on the TH1520 SoC are
+ managed by the E902 core. Firmware running on this core communicates with
+ the kernel through the Always-On protocol, using hardware mailbox as a medium.
+ Say yes if you need such capabilities.
+
config TI_SCI_PROTOCOL
tristate "TI System Control Interface (TISCI) Message Protocol"
depends on TI_MESSAGE_MANAGER
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 7a8d486e718f..5db9c042430c 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
obj-$(CONFIG_SYSFB) += sysfb.o
obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o
+obj-$(CONFIG_TH1520_AON_PROTOCOL) += thead,th1520-aon.o
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
obj-$(CONFIG_TRUSTED_FOUNDATIONS) += trusted_foundations.o
obj-$(CONFIG_TURRIS_MOX_RWTM) += turris-mox-rwtm.o
diff --git a/drivers/firmware/thead,th1520-aon.c b/drivers/firmware/thead,th1520-aon.c
new file mode 100644
index 000000000000..38f812ac9920
--- /dev/null
+++ b/drivers/firmware/thead,th1520-aon.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * Author: Michal Wilczynski <m.wilczynski@samsung.com>
+ */
+
+#include <linux/device.h>
+#include <linux/firmware/thead/thead,th1520-aon.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox_controller.h>
+#include <linux/slab.h>
+
+#define MAX_RX_TIMEOUT (msecs_to_jiffies(3000))
+#define MAX_TX_TIMEOUT 500
+
+struct th1520_aon_chan {
+ struct mbox_chan *ch;
+ struct th1520_aon_rpc_ack_common ack_msg;
+ struct mbox_client cl;
+ struct completion done;
+
+ /* make sure only one RPC is performed at a time */
+ struct mutex transaction_lock;
+};
+
+struct th1520_aon_msg_req_set_resource_power_mode {
+ struct th1520_aon_rpc_msg_hdr hdr;
+ u16 resource;
+ u16 mode;
+ u16 reserved[10];
+} __packed __aligned(1);
+
+/*
+ * This type is used to indicate error response for most functions.
+ */
+enum th1520_aon_error_codes {
+ LIGHT_AON_ERR_NONE = 0, /* Success */
+ LIGHT_AON_ERR_VERSION = 1, /* Incompatible API version */
+ LIGHT_AON_ERR_CONFIG = 2, /* Configuration error */
+ LIGHT_AON_ERR_PARM = 3, /* Bad parameter */
+ LIGHT_AON_ERR_NOACCESS = 4, /* Permission error (no access) */
+ LIGHT_AON_ERR_LOCKED = 5, /* Permission error (locked) */
+ LIGHT_AON_ERR_UNAVAILABLE = 6, /* Unavailable (out of resources) */
+ LIGHT_AON_ERR_NOTFOUND = 7, /* Not found */
+ LIGHT_AON_ERR_NOPOWER = 8, /* No power */
+ LIGHT_AON_ERR_IPC = 9, /* Generic IPC error */
+ LIGHT_AON_ERR_BUSY = 10, /* Resource is currently busy/active */
+ LIGHT_AON_ERR_FAIL = 11, /* General I/O failure */
+ LIGHT_AON_ERR_LAST
+};
+
+static int th1520_aon_linux_errmap[LIGHT_AON_ERR_LAST] = {
+ 0, /* LIGHT_AON_ERR_NONE */
+ -EINVAL, /* LIGHT_AON_ERR_VERSION */
+ -EINVAL, /* LIGHT_AON_ERR_CONFIG */
+ -EINVAL, /* LIGHT_AON_ERR_PARM */
+ -EACCES, /* LIGHT_AON_ERR_NOACCESS */
+ -EACCES, /* LIGHT_AON_ERR_LOCKED */
+ -ERANGE, /* LIGHT_AON_ERR_UNAVAILABLE */
+ -EEXIST, /* LIGHT_AON_ERR_NOTFOUND */
+ -EPERM, /* LIGHT_AON_ERR_NOPOWER */
+ -EPIPE, /* LIGHT_AON_ERR_IPC */
+ -EBUSY, /* LIGHT_AON_ERR_BUSY */
+ -EIO, /* LIGHT_AON_ERR_FAIL */
+};
+
+static inline int th1520_aon_to_linux_errno(int errno)
+{
+ if (errno >= LIGHT_AON_ERR_NONE && errno < LIGHT_AON_ERR_LAST)
+ return th1520_aon_linux_errmap[errno];
+
+ return -EIO;
+}
+
+static void th1520_aon_rx_callback(struct mbox_client *c, void *rx_msg)
+{
+ struct th1520_aon_chan *aon_chan =
+ container_of(c, struct th1520_aon_chan, cl);
+ struct th1520_aon_rpc_msg_hdr *hdr =
+ (struct th1520_aon_rpc_msg_hdr *)rx_msg;
+ u8 recv_size = sizeof(struct th1520_aon_rpc_msg_hdr) + hdr->size;
+
+ if (recv_size != sizeof(struct th1520_aon_rpc_ack_common)) {
+ dev_err(c->dev, "Invalid ack size, not completing\n");
+ return;
+ }
+
+ memcpy(&aon_chan->ack_msg, rx_msg, recv_size);
+ complete(&aon_chan->done);
+}
+
+/**
+ * th1520_aon_call_rpc() - Send an RPC request to the TH1520 AON subsystem
+ * @aon_chan: Pointer to the AON channel structure
+ * @msg: Pointer to the message (RPC payload) that will be sent
+ *
+ * This function sends an RPC message to the TH1520 AON subsystem via mailbox.
+ * It takes the provided @msg buffer, formats it with version and service flags,
+ * then blocks until the RPC completes or times out. The completion is signaled
+ * by the `aon_chan->done` completion, which is waited upon for a duration
+ * defined by `MAX_RX_TIMEOUT`.
+ *
+ * Return:
+ * * 0 on success
+ * * -ETIMEDOUT if the RPC call times out
+ * * A negative error code if the mailbox send fails or if AON responds with
+ * a non-zero error code (converted via th1520_aon_to_linux_errno()).
+ */
+int th1520_aon_call_rpc(struct th1520_aon_chan *aon_chan, void *msg)
+{
+ struct th1520_aon_rpc_msg_hdr *hdr = msg;
+ int ret;
+
+ mutex_lock(&aon_chan->transaction_lock);
+ reinit_completion(&aon_chan->done);
+
+ RPC_SET_VER(hdr, TH1520_AON_RPC_VERSION);
+ RPC_SET_SVC_ID(hdr, hdr->svc);
+ RPC_SET_SVC_FLAG_MSG_TYPE(hdr, RPC_SVC_MSG_TYPE_DATA);
+ RPC_SET_SVC_FLAG_ACK_TYPE(hdr, RPC_SVC_MSG_NEED_ACK);
+
+ ret = mbox_send_message(aon_chan->ch, msg);
+ if (ret < 0) {
+ dev_err(aon_chan->cl.dev, "RPC send msg failed: %d\n", ret);
+ goto out;
+ }
+
+ if (!wait_for_completion_timeout(&aon_chan->done, MAX_RX_TIMEOUT)) {
+ dev_err(aon_chan->cl.dev, "RPC send msg timeout\n");
+ mutex_unlock(&aon_chan->transaction_lock);
+ return -ETIMEDOUT;
+ }
+
+ ret = aon_chan->ack_msg.err_code;
+
+out:
+ mutex_unlock(&aon_chan->transaction_lock);
+
+ return th1520_aon_to_linux_errno(ret);
+}
+EXPORT_SYMBOL_GPL(th1520_aon_call_rpc);
+
+/**
+ * th1520_aon_power_update() - Change power state of a resource via TH1520 AON
+ * @aon_chan: Pointer to the AON channel structure
+ * @rsrc: Resource ID whose power state needs to be updated
+ * @power_on: Boolean indicating whether the resource should be powered on (true)
+ * or powered off (false)
+ *
+ * This function requests the TH1520 AON subsystem to set the power mode of the
+ * given resource (@rsrc) to either on or off. It constructs the message in
+ * `struct th1520_aon_msg_req_set_resource_power_mode` and then invokes
+ * th1520_aon_call_rpc() to make the request. If the AON call fails, an error
+ * message is logged along with the specific return code.
+ *
+ * Return:
+ * * 0 on success
+ * * A negative error code in case of failures (propagated from
+ * th1520_aon_call_rpc()).
+ */
+int th1520_aon_power_update(struct th1520_aon_chan *aon_chan, u16 rsrc,
+ bool power_on)
+{
+ struct th1520_aon_msg_req_set_resource_power_mode msg = {};
+ struct th1520_aon_rpc_msg_hdr *hdr = &msg.hdr;
+ int ret;
+
+ hdr->svc = TH1520_AON_RPC_SVC_PM;
+ hdr->func = TH1520_AON_PM_FUNC_SET_RESOURCE_POWER_MODE;
+ hdr->size = TH1520_AON_RPC_MSG_NUM;
+
+ RPC_SET_BE16(&msg.resource, 0, rsrc);
+ RPC_SET_BE16(&msg.resource, 2,
+ (power_on ? TH1520_AON_PM_PW_MODE_ON :
+ TH1520_AON_PM_PW_MODE_OFF));
+
+ ret = th1520_aon_call_rpc(aon_chan, &msg);
+ if (ret)
+ dev_err(aon_chan->cl.dev, "failed to power %s resource %d ret %d\n",
+ power_on ? "up" : "off", rsrc, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(th1520_aon_power_update);
+
+/**
+ * th1520_aon_init() - Initialize TH1520 AON firmware protocol interface
+ * @dev: Device pointer for the AON subsystem
+ *
+ * This function initializes the TH1520 AON firmware protocol interface by:
+ * - Allocating and initializing the AON channel structure
+ * - Setting up the mailbox client
+ * - Requesting the AON mailbox channel
+ * - Initializing synchronization primitives
+ *
+ * Return:
+ * * Valid pointer to th1520_aon_chan structure on success
+ * * ERR_PTR(-ENOMEM) if memory allocation fails
+ * * ERR_PTR() with other negative error codes from mailbox operations
+ */
+struct th1520_aon_chan *th1520_aon_init(struct device *dev)
+{
+ struct th1520_aon_chan *aon_chan;
+ struct mbox_client *cl;
+ int ret;
+
+ aon_chan = kzalloc(sizeof(*aon_chan), GFP_KERNEL);
+ if (!aon_chan)
+ return ERR_PTR(-ENOMEM);
+
+ cl = &aon_chan->cl;
+ cl->dev = dev;
+ cl->tx_block = true;
+ cl->tx_tout = MAX_TX_TIMEOUT;
+ cl->rx_callback = th1520_aon_rx_callback;
+
+ aon_chan->ch = mbox_request_channel_byname(cl, "aon");
+ if (IS_ERR(aon_chan->ch)) {
+ dev_err(dev, "Failed to request aon mbox chan\n");
+ ret = PTR_ERR(aon_chan->ch);
+ kfree(aon_chan);
+ return ERR_PTR(ret);
+ }
+
+ mutex_init(&aon_chan->transaction_lock);
+ init_completion(&aon_chan->done);
+
+ return aon_chan;
+}
+EXPORT_SYMBOL_GPL(th1520_aon_init);
+
+/**
+ * th1520_aon_deinit() - Clean up TH1520 AON firmware protocol interface
+ * @aon_chan: Pointer to the AON channel structure to clean up
+ *
+ * This function cleans up resources allocated by th1520_aon_init():
+ * - Frees the mailbox channel
+ * - Frees the AON channel
+ */
+void th1520_aon_deinit(struct th1520_aon_chan *aon_chan)
+{
+ mbox_free_channel(aon_chan->ch);
+ kfree(aon_chan);
+}
+EXPORT_SYMBOL_GPL(th1520_aon_deinit);
+
+MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
+MODULE_DESCRIPTION("T-HEAD TH1520 Always-On firmware protocol library");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig
index 23c64851a5b0..91f04ace35d4 100644
--- a/drivers/pmdomain/Kconfig
+++ b/drivers/pmdomain/Kconfig
@@ -16,6 +16,7 @@ source "drivers/pmdomain/st/Kconfig"
source "drivers/pmdomain/starfive/Kconfig"
source "drivers/pmdomain/sunxi/Kconfig"
source "drivers/pmdomain/tegra/Kconfig"
+source "drivers/pmdomain/thead/Kconfig"
source "drivers/pmdomain/ti/Kconfig"
source "drivers/pmdomain/xilinx/Kconfig"
diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile
index a68ece2f4c68..7030f44a49df 100644
--- a/drivers/pmdomain/Makefile
+++ b/drivers/pmdomain/Makefile
@@ -14,6 +14,7 @@ obj-y += st/
obj-y += starfive/
obj-y += sunxi/
obj-y += tegra/
+obj-y += thead/
obj-y += ti/
obj-y += xilinx/
obj-y += core.o governor.o
diff --git a/drivers/pmdomain/arm/scmi_pm_domain.c b/drivers/pmdomain/arm/scmi_pm_domain.c
index 86b531e15b85..2a213c218126 100644
--- a/drivers/pmdomain/arm/scmi_pm_domain.c
+++ b/drivers/pmdomain/arm/scmi_pm_domain.c
@@ -24,8 +24,7 @@ struct scmi_pm_domain {
static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on)
{
- int ret;
- u32 state, ret_state;
+ u32 state;
struct scmi_pm_domain *pd = to_scmi_pd(domain);
if (power_on)
@@ -33,13 +32,7 @@ static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on)
else
state = SCMI_POWER_STATE_GENERIC_OFF;
- ret = power_ops->state_set(pd->ph, pd->domain, state);
- if (!ret)
- ret = power_ops->state_get(pd->ph, pd->domain, &ret_state);
- if (!ret && state != ret_state)
- return -EIO;
-
- return ret;
+ return power_ops->state_set(pd->ph, pd->domain, state);
}
static int scmi_pd_power_on(struct generic_pm_domain *domain)
diff --git a/drivers/pmdomain/bcm/bcm2835-power.c b/drivers/pmdomain/bcm/bcm2835-power.c
index d2f0233cb620..d3cd816979ac 100644
--- a/drivers/pmdomain/bcm/bcm2835-power.c
+++ b/drivers/pmdomain/bcm/bcm2835-power.c
@@ -520,6 +520,7 @@ bcm2835_init_power_domain(struct bcm2835_power *power,
}
dom->base.name = name;
+ dom->base.flags = GENPD_FLAG_ACTIVE_WAKEUP;
dom->base.power_on = bcm2835_power_pd_power_on;
dom->base.power_off = bcm2835_power_pd_power_off;
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 6c94137865c9..9b2f28b34bb5 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -697,6 +697,37 @@ bool dev_pm_genpd_get_hwmode(struct device *dev)
}
EXPORT_SYMBOL_GPL(dev_pm_genpd_get_hwmode);
+/**
+ * dev_pm_genpd_rpm_always_on() - Control if the PM domain can be powered off.
+ *
+ * @dev: Device for which the PM domain may need to stay on for.
+ * @on: Value to set or unset for the condition.
+ *
+ * For some usecases a consumer driver requires its device to remain power-on
+ * from the PM domain perspective during runtime. This function allows the
+ * behaviour to be dynamically controlled for a device attached to a genpd.
+ *
+ * It is assumed that the users guarantee that the genpd wouldn't be detached
+ * while this routine is getting called.
+ *
+ * Return: Returns 0 on success and negative error values on failures.
+ */
+int dev_pm_genpd_rpm_always_on(struct device *dev, bool on)
+{
+ struct generic_pm_domain *genpd;
+
+ genpd = dev_to_genpd_safe(dev);
+ if (!genpd)
+ return -ENODEV;
+
+ genpd_lock(genpd);
+ dev_gpd_data(dev)->rpm_always_on = on;
+ genpd_unlock(genpd);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dev_pm_genpd_rpm_always_on);
+
static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed)
{
unsigned int state_idx = genpd->state_idx;
@@ -868,6 +899,10 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
if (!pm_runtime_suspended(pdd->dev) ||
irq_safe_dev_in_sleep_domain(pdd->dev, genpd))
not_suspended++;
+
+ /* The device may need its PM domain to stay powered on. */
+ if (to_gpd_data(pdd)->rpm_always_on)
+ return -EBUSY;
}
if (not_suspended > 1 || (not_suspended == 1 && !one_dev_on))
diff --git a/drivers/pmdomain/imx/gpcv2.c b/drivers/pmdomain/imx/gpcv2.c
index 958d34d4821b..105fcaf13a34 100644
--- a/drivers/pmdomain/imx/gpcv2.c
+++ b/drivers/pmdomain/imx/gpcv2.c
@@ -1361,7 +1361,7 @@ static int imx_pgc_domain_probe(struct platform_device *pdev)
}
if (IS_ENABLED(CONFIG_LOCKDEP) &&
- of_property_read_bool(domain->dev->of_node, "power-domains"))
+ of_property_present(domain->dev->of_node, "power-domains"))
lockdep_set_subclass(&domain->genpd.mlock, 1);
ret = of_genpd_add_provider_simple(domain->dev->of_node,
diff --git a/drivers/pmdomain/renesas/rcar-sysc.c b/drivers/pmdomain/renesas/rcar-sysc.c
index b99326917330..dce1a6d37e80 100644
--- a/drivers/pmdomain/renesas/rcar-sysc.c
+++ b/drivers/pmdomain/renesas/rcar-sysc.c
@@ -434,8 +434,6 @@ static int __init rcar_sysc_pd_init(void)
}
error = of_genpd_add_provider_onecell(np, &domains->onecell_data);
- if (!error)
- fwnode_dev_initialized(of_fwnode_handle(np), true);
out_put:
of_node_put(np);
diff --git a/drivers/pmdomain/rockchip/Kconfig b/drivers/pmdomain/rockchip/Kconfig
index b0d70f1a8439..218d43186e5b 100644
--- a/drivers/pmdomain/rockchip/Kconfig
+++ b/drivers/pmdomain/rockchip/Kconfig
@@ -4,6 +4,8 @@ if ARCH_ROCKCHIP || COMPILE_TEST
config ROCKCHIP_PM_DOMAINS
bool "Rockchip generic power domain"
depends on PM
+ depends on HAVE_ARM_SMCCC_DISCOVERY
+ depends on REGULATOR
select PM_GENERIC_DOMAINS
help
Say y here to enable power domain support.
diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index cb0f93800138..03bcf79a461f 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -5,6 +5,7 @@
* Copyright (c) 2015 ROCKCHIP, Co. Ltd.
*/
+#include <linux/arm-smccc.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/err.h>
@@ -18,8 +19,10 @@
#include <linux/of_clk.h>
#include <linux/clk.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/mfd/syscon.h>
#include <soc/rockchip/pm_domains.h>
+#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/rk3036-power.h>
@@ -44,6 +47,7 @@ struct rockchip_domain_info {
int idle_mask;
int ack_mask;
bool active_wakeup;
+ bool need_regulator;
int pwr_w_mask;
int req_w_mask;
int clk_ungate_mask;
@@ -92,6 +96,8 @@ struct rockchip_pm_domain {
u32 *qos_save_regs[MAX_QOS_REGS_NUM];
int num_clks;
struct clk_bulk_data *clks;
+ struct device_node *node;
+ struct regulator *supply;
};
struct rockchip_pmu {
@@ -129,7 +135,7 @@ struct rockchip_pmu {
.active_wakeup = wakeup, \
}
-#define DOMAIN_M_O_R(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, wakeup) \
+#define DOMAIN_M_O_R(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, wakeup, regulator) \
{ \
.name = _name, \
.pwr_offset = p_offset, \
@@ -145,6 +151,7 @@ struct rockchip_pmu {
.idle_mask = (idle), \
.ack_mask = (ack), \
.active_wakeup = wakeup, \
+ .need_regulator = regulator, \
}
#define DOMAIN_M_O_R_G(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, g_mask, wakeup) \
@@ -303,8 +310,8 @@ void rockchip_pmu_unblock(void)
}
EXPORT_SYMBOL_GPL(rockchip_pmu_unblock);
-#define DOMAIN_RK3588(name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, wakeup) \
- DOMAIN_M_O_R(name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, idle, wakeup)
+#define DOMAIN_RK3588(name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, wakeup, regulator) \
+ DOMAIN_M_O_R(name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, idle, wakeup, regulator)
static bool rockchip_pmu_domain_is_idle(struct rockchip_pm_domain *pd)
{
@@ -533,16 +540,18 @@ error:
return ret;
}
-static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
- bool on)
+static int rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
+ bool on)
{
struct rockchip_pmu *pmu = pd->pmu;
struct generic_pm_domain *genpd = &pd->genpd;
u32 pd_pwr_offset = pd->info->pwr_offset;
bool is_on, is_mem_on = false;
+ struct arm_smccc_res res;
+ int ret;
if (pd->info->pwr_mask == 0)
- return;
+ return 0;
if (on && pd->info->mem_status_mask)
is_mem_on = rockchip_pmu_domain_is_mem_on(pd);
@@ -557,16 +566,28 @@ static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
wmb();
- if (is_mem_on && rockchip_pmu_domain_mem_reset(pd))
- return;
+ if (is_mem_on) {
+ ret = rockchip_pmu_domain_mem_reset(pd);
+ if (ret)
+ return ret;
+ }
- if (readx_poll_timeout_atomic(rockchip_pmu_domain_is_on, pd, is_on,
- is_on == on, 0, 10000)) {
- dev_err(pmu->dev,
- "failed to set domain '%s', val=%d\n",
- genpd->name, is_on);
- return;
+
+ ret = readx_poll_timeout_atomic(rockchip_pmu_domain_is_on, pd, is_on,
+ is_on == on, 0, 10000);
+ if (ret) {
+ dev_err(pmu->dev, "failed to set domain '%s' %s, val=%d\n",
+ genpd->name, on ? "on" : "off", is_on);
+ return ret;
}
+
+ /* Inform firmware to keep this pd on or off */
+ if (arm_smccc_1_1_get_conduit() != SMCCC_CONDUIT_NONE)
+ arm_smccc_smc(ROCKCHIP_SIP_SUSPEND_MODE, ROCKCHIP_SLEEP_PD_CONFIG,
+ pmu->info->pwr_offset + pd_pwr_offset,
+ pd->info->pwr_mask, on, 0, 0, 0, &res);
+
+ return 0;
}
static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
@@ -574,54 +595,99 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
struct rockchip_pmu *pmu = pd->pmu;
int ret;
- mutex_lock(&pmu->mutex);
+ guard(mutex)(&pmu->mutex);
- if (rockchip_pmu_domain_is_on(pd) != power_on) {
- ret = clk_bulk_enable(pd->num_clks, pd->clks);
- if (ret < 0) {
- dev_err(pmu->dev, "failed to enable clocks\n");
- mutex_unlock(&pmu->mutex);
- return ret;
- }
+ if (rockchip_pmu_domain_is_on(pd) == power_on)
+ return 0;
- rockchip_pmu_ungate_clk(pd, true);
+ ret = clk_bulk_enable(pd->num_clks, pd->clks);
+ if (ret < 0) {
+ dev_err(pmu->dev, "failed to enable clocks\n");
+ return ret;
+ }
- if (!power_on) {
- rockchip_pmu_save_qos(pd);
+ rockchip_pmu_ungate_clk(pd, true);
- /* if powering down, idle request to NIU first */
- rockchip_pmu_set_idle_request(pd, true);
- }
+ if (!power_on) {
+ rockchip_pmu_save_qos(pd);
- rockchip_do_pmu_set_power_domain(pd, power_on);
+ /* if powering down, idle request to NIU first */
+ ret = rockchip_pmu_set_idle_request(pd, true);
+ if (ret < 0)
+ goto out;
+ }
- if (power_on) {
- /* if powering up, leave idle mode */
- rockchip_pmu_set_idle_request(pd, false);
+ ret = rockchip_do_pmu_set_power_domain(pd, power_on);
+ if (ret < 0)
+ goto out;
- rockchip_pmu_restore_qos(pd);
- }
+ if (power_on) {
+ /* if powering up, leave idle mode */
+ ret = rockchip_pmu_set_idle_request(pd, false);
+ if (ret < 0)
+ goto out;
- rockchip_pmu_ungate_clk(pd, false);
- clk_bulk_disable(pd->num_clks, pd->clks);
+ rockchip_pmu_restore_qos(pd);
}
- mutex_unlock(&pmu->mutex);
- return 0;
+out:
+ rockchip_pmu_ungate_clk(pd, false);
+ clk_bulk_disable(pd->num_clks, pd->clks);
+
+ return ret;
+}
+
+static int rockchip_pd_regulator_disable(struct rockchip_pm_domain *pd)
+{
+ return IS_ERR_OR_NULL(pd->supply) ? 0 : regulator_disable(pd->supply);
+}
+
+static int rockchip_pd_regulator_enable(struct rockchip_pm_domain *pd)
+{
+ struct rockchip_pmu *pmu = pd->pmu;
+
+ if (!pd->info->need_regulator)
+ return 0;
+
+ if (IS_ERR_OR_NULL(pd->supply)) {
+ pd->supply = devm_of_regulator_get(pmu->dev, pd->node, "domain");
+
+ if (IS_ERR(pd->supply))
+ return PTR_ERR(pd->supply);
+ }
+
+ return regulator_enable(pd->supply);
}
static int rockchip_pd_power_on(struct generic_pm_domain *domain)
{
struct rockchip_pm_domain *pd = to_rockchip_pd(domain);
+ int ret;
+
+ ret = rockchip_pd_regulator_enable(pd);
+ if (ret) {
+ dev_err(pd->pmu->dev, "Failed to enable supply: %d\n", ret);
+ return ret;
+ }
- return rockchip_pd_power(pd, true);
+ ret = rockchip_pd_power(pd, true);
+ if (ret)
+ rockchip_pd_regulator_disable(pd);
+
+ return ret;
}
static int rockchip_pd_power_off(struct generic_pm_domain *domain)
{
struct rockchip_pm_domain *pd = to_rockchip_pd(domain);
+ int ret;
- return rockchip_pd_power(pd, false);
+ ret = rockchip_pd_power(pd, false);
+ if (ret)
+ return ret;
+
+ rockchip_pd_regulator_disable(pd);
+ return ret;
}
static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd,
@@ -702,6 +768,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
pd->info = pd_info;
pd->pmu = pmu;
+ pd->node = node;
pd->num_clks = of_clk_get_parent_count(node);
if (pd->num_clks > 0) {
@@ -1165,35 +1232,35 @@ static const struct rockchip_domain_info rk3576_pm_domains[] = {
};
static const struct rockchip_domain_info rk3588_pm_domains[] = {
- [RK3588_PD_GPU] = DOMAIN_RK3588("gpu", 0x0, BIT(0), 0, 0x0, 0, BIT(1), 0x0, BIT(0), BIT(0), false),
- [RK3588_PD_NPU] = DOMAIN_RK3588("npu", 0x0, BIT(1), BIT(1), 0x0, 0, 0, 0x0, 0, 0, false),
- [RK3588_PD_VCODEC] = DOMAIN_RK3588("vcodec", 0x0, BIT(2), BIT(2), 0x0, 0, 0, 0x0, 0, 0, false),
- [RK3588_PD_NPUTOP] = DOMAIN_RK3588("nputop", 0x0, BIT(3), 0, 0x0, BIT(11), BIT(2), 0x0, BIT(1), BIT(1), false),
- [RK3588_PD_NPU1] = DOMAIN_RK3588("npu1", 0x0, BIT(4), 0, 0x0, BIT(12), BIT(3), 0x0, BIT(2), BIT(2), false),
- [RK3588_PD_NPU2] = DOMAIN_RK3588("npu2", 0x0, BIT(5), 0, 0x0, BIT(13), BIT(4), 0x0, BIT(3), BIT(3), false),
- [RK3588_PD_VENC0] = DOMAIN_RK3588("venc0", 0x0, BIT(6), 0, 0x0, BIT(14), BIT(5), 0x0, BIT(4), BIT(4), false),
- [RK3588_PD_VENC1] = DOMAIN_RK3588("venc1", 0x0, BIT(7), 0, 0x0, BIT(15), BIT(6), 0x0, BIT(5), BIT(5), false),
- [RK3588_PD_RKVDEC0] = DOMAIN_RK3588("rkvdec0", 0x0, BIT(8), 0, 0x0, BIT(16), BIT(7), 0x0, BIT(6), BIT(6), false),
- [RK3588_PD_RKVDEC1] = DOMAIN_RK3588("rkvdec1", 0x0, BIT(9), 0, 0x0, BIT(17), BIT(8), 0x0, BIT(7), BIT(7), false),
- [RK3588_PD_VDPU] = DOMAIN_RK3588("vdpu", 0x0, BIT(10), 0, 0x0, BIT(18), BIT(9), 0x0, BIT(8), BIT(8), false),
- [RK3588_PD_RGA30] = DOMAIN_RK3588("rga30", 0x0, BIT(11), 0, 0x0, BIT(19), BIT(10), 0x0, 0, 0, false),
- [RK3588_PD_AV1] = DOMAIN_RK3588("av1", 0x0, BIT(12), 0, 0x0, BIT(20), BIT(11), 0x0, BIT(9), BIT(9), false),
- [RK3588_PD_VI] = DOMAIN_RK3588("vi", 0x0, BIT(13), 0, 0x0, BIT(21), BIT(12), 0x0, BIT(10), BIT(10), false),
- [RK3588_PD_FEC] = DOMAIN_RK3588("fec", 0x0, BIT(14), 0, 0x0, BIT(22), BIT(13), 0x0, 0, 0, false),
- [RK3588_PD_ISP1] = DOMAIN_RK3588("isp1", 0x0, BIT(15), 0, 0x0, BIT(23), BIT(14), 0x0, BIT(11), BIT(11), false),
- [RK3588_PD_RGA31] = DOMAIN_RK3588("rga31", 0x4, BIT(0), 0, 0x0, BIT(24), BIT(15), 0x0, BIT(12), BIT(12), false),
- [RK3588_PD_VOP] = DOMAIN_RK3588("vop", 0x4, BIT(1), 0, 0x0, BIT(25), BIT(16), 0x0, BIT(13) | BIT(14), BIT(13) | BIT(14), false),
- [RK3588_PD_VO0] = DOMAIN_RK3588("vo0", 0x4, BIT(2), 0, 0x0, BIT(26), BIT(17), 0x0, BIT(15), BIT(15), false),
- [RK3588_PD_VO1] = DOMAIN_RK3588("vo1", 0x4, BIT(3), 0, 0x0, BIT(27), BIT(18), 0x4, BIT(0), BIT(16), false),
- [RK3588_PD_AUDIO] = DOMAIN_RK3588("audio", 0x4, BIT(4), 0, 0x0, BIT(28), BIT(19), 0x4, BIT(1), BIT(17), false),
- [RK3588_PD_PHP] = DOMAIN_RK3588("php", 0x4, BIT(5), 0, 0x0, BIT(29), BIT(20), 0x4, BIT(5), BIT(21), false),
- [RK3588_PD_GMAC] = DOMAIN_RK3588("gmac", 0x4, BIT(6), 0, 0x0, BIT(30), BIT(21), 0x0, 0, 0, false),
- [RK3588_PD_PCIE] = DOMAIN_RK3588("pcie", 0x4, BIT(7), 0, 0x0, BIT(31), BIT(22), 0x0, 0, 0, true),
- [RK3588_PD_NVM] = DOMAIN_RK3588("nvm", 0x4, BIT(8), BIT(24), 0x4, 0, 0, 0x4, BIT(2), BIT(18), false),
- [RK3588_PD_NVM0] = DOMAIN_RK3588("nvm0", 0x4, BIT(9), 0, 0x4, BIT(1), BIT(23), 0x0, 0, 0, false),
- [RK3588_PD_SDIO] = DOMAIN_RK3588("sdio", 0x4, BIT(10), 0, 0x4, BIT(2), BIT(24), 0x4, BIT(3), BIT(19), false),
- [RK3588_PD_USB] = DOMAIN_RK3588("usb", 0x4, BIT(11), 0, 0x4, BIT(3), BIT(25), 0x4, BIT(4), BIT(20), true),
- [RK3588_PD_SDMMC] = DOMAIN_RK3588("sdmmc", 0x4, BIT(13), 0, 0x4, BIT(5), BIT(26), 0x0, 0, 0, false),
+ [RK3588_PD_GPU] = DOMAIN_RK3588("gpu", 0x0, BIT(0), 0, 0x0, 0, BIT(1), 0x0, BIT(0), BIT(0), false, true),
+ [RK3588_PD_NPU] = DOMAIN_RK3588("npu", 0x0, BIT(1), BIT(1), 0x0, 0, 0, 0x0, 0, 0, false, true),
+ [RK3588_PD_VCODEC] = DOMAIN_RK3588("vcodec", 0x0, BIT(2), BIT(2), 0x0, 0, 0, 0x0, 0, 0, false, false),
+ [RK3588_PD_NPUTOP] = DOMAIN_RK3588("nputop", 0x0, BIT(3), 0, 0x0, BIT(11), BIT(2), 0x0, BIT(1), BIT(1), false, false),
+ [RK3588_PD_NPU1] = DOMAIN_RK3588("npu1", 0x0, BIT(4), 0, 0x0, BIT(12), BIT(3), 0x0, BIT(2), BIT(2), false, false),
+ [RK3588_PD_NPU2] = DOMAIN_RK3588("npu2", 0x0, BIT(5), 0, 0x0, BIT(13), BIT(4), 0x0, BIT(3), BIT(3), false, false),
+ [RK3588_PD_VENC0] = DOMAIN_RK3588("venc0", 0x0, BIT(6), 0, 0x0, BIT(14), BIT(5), 0x0, BIT(4), BIT(4), false, false),
+ [RK3588_PD_VENC1] = DOMAIN_RK3588("venc1", 0x0, BIT(7), 0, 0x0, BIT(15), BIT(6), 0x0, BIT(5), BIT(5), false, false),
+ [RK3588_PD_RKVDEC0] = DOMAIN_RK3588("rkvdec0", 0x0, BIT(8), 0, 0x0, BIT(16), BIT(7), 0x0, BIT(6), BIT(6), false, false),
+ [RK3588_PD_RKVDEC1] = DOMAIN_RK3588("rkvdec1", 0x0, BIT(9), 0, 0x0, BIT(17), BIT(8), 0x0, BIT(7), BIT(7), false, false),
+ [RK3588_PD_VDPU] = DOMAIN_RK3588("vdpu", 0x0, BIT(10), 0, 0x0, BIT(18), BIT(9), 0x0, BIT(8), BIT(8), false, false),
+ [RK3588_PD_RGA30] = DOMAIN_RK3588("rga30", 0x0, BIT(11), 0, 0x0, BIT(19), BIT(10), 0x0, 0, 0, false, false),
+ [RK3588_PD_AV1] = DOMAIN_RK3588("av1", 0x0, BIT(12), 0, 0x0, BIT(20), BIT(11), 0x0, BIT(9), BIT(9), false, false),
+ [RK3588_PD_VI] = DOMAIN_RK3588("vi", 0x0, BIT(13), 0, 0x0, BIT(21), BIT(12), 0x0, BIT(10), BIT(10), false, false),
+ [RK3588_PD_FEC] = DOMAIN_RK3588("fec", 0x0, BIT(14), 0, 0x0, BIT(22), BIT(13), 0x0, 0, 0, false, false),
+ [RK3588_PD_ISP1] = DOMAIN_RK3588("isp1", 0x0, BIT(15), 0, 0x0, BIT(23), BIT(14), 0x0, BIT(11), BIT(11), false, false),
+ [RK3588_PD_RGA31] = DOMAIN_RK3588("rga31", 0x4, BIT(0), 0, 0x0, BIT(24), BIT(15), 0x0, BIT(12), BIT(12), false, false),
+ [RK3588_PD_VOP] = DOMAIN_RK3588("vop", 0x4, BIT(1), 0, 0x0, BIT(25), BIT(16), 0x0, BIT(13) | BIT(14), BIT(13) | BIT(14), false, false),
+ [RK3588_PD_VO0] = DOMAIN_RK3588("vo0", 0x4, BIT(2), 0, 0x0, BIT(26), BIT(17), 0x0, BIT(15), BIT(15), false, false),
+ [RK3588_PD_VO1] = DOMAIN_RK3588("vo1", 0x4, BIT(3), 0, 0x0, BIT(27), BIT(18), 0x4, BIT(0), BIT(16), false, false),
+ [RK3588_PD_AUDIO] = DOMAIN_RK3588("audio", 0x4, BIT(4), 0, 0x0, BIT(28), BIT(19), 0x4, BIT(1), BIT(17), false, false),
+ [RK3588_PD_PHP] = DOMAIN_RK3588("php", 0x4, BIT(5), 0, 0x0, BIT(29), BIT(20), 0x4, BIT(5), BIT(21), false, false),
+ [RK3588_PD_GMAC] = DOMAIN_RK3588("gmac", 0x4, BIT(6), 0, 0x0, BIT(30), BIT(21), 0x0, 0, 0, false, false),
+ [RK3588_PD_PCIE] = DOMAIN_RK3588("pcie", 0x4, BIT(7), 0, 0x0, BIT(31), BIT(22), 0x0, 0, 0, true, false),
+ [RK3588_PD_NVM] = DOMAIN_RK3588("nvm", 0x4, BIT(8), BIT(24), 0x4, 0, 0, 0x4, BIT(2), BIT(18), false, false),
+ [RK3588_PD_NVM0] = DOMAIN_RK3588("nvm0", 0x4, BIT(9), 0, 0x4, BIT(1), BIT(23), 0x0, 0, 0, false, false),
+ [RK3588_PD_SDIO] = DOMAIN_RK3588("sdio", 0x4, BIT(10), 0, 0x4, BIT(2), BIT(24), 0x4, BIT(3), BIT(19), false, false),
+ [RK3588_PD_USB] = DOMAIN_RK3588("usb", 0x4, BIT(11), 0, 0x4, BIT(3), BIT(25), 0x4, BIT(4), BIT(20), true, false),
+ [RK3588_PD_SDMMC] = DOMAIN_RK3588("sdmmc", 0x4, BIT(13), 0, 0x4, BIT(5), BIT(26), 0x0, 0, 0, false, false),
};
static const struct rockchip_pmu_info px30_pmu = {
diff --git a/drivers/pmdomain/sunxi/sun20i-ppu.c b/drivers/pmdomain/sunxi/sun20i-ppu.c
index 8700f9dd5f75..9f002748d224 100644
--- a/drivers/pmdomain/sunxi/sun20i-ppu.c
+++ b/drivers/pmdomain/sunxi/sun20i-ppu.c
@@ -182,11 +182,26 @@ static const struct sun20i_ppu_desc sun20i_d1_ppu_desc = {
.num_domains = ARRAY_SIZE(sun20i_d1_ppu_pd_names),
};
+static const char *const sun8i_v853_ppu_pd_names[] = {
+ "RISCV",
+ "NPU",
+ "VE",
+};
+
+static const struct sun20i_ppu_desc sun8i_v853_ppu_desc = {
+ .names = sun8i_v853_ppu_pd_names,
+ .num_domains = ARRAY_SIZE(sun8i_v853_ppu_pd_names),
+};
+
static const struct of_device_id sun20i_ppu_of_match[] = {
{
.compatible = "allwinner,sun20i-d1-ppu",
.data = &sun20i_d1_ppu_desc,
},
+ {
+ .compatible = "allwinner,sun8i-v853-ppu",
+ .data = &sun8i_v853_ppu_desc,
+ },
{ }
};
MODULE_DEVICE_TABLE(of, sun20i_ppu_of_match);
diff --git a/drivers/pmdomain/thead/Kconfig b/drivers/pmdomain/thead/Kconfig
new file mode 100644
index 000000000000..7d52f8374b07
--- /dev/null
+++ b/drivers/pmdomain/thead/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config TH1520_PM_DOMAINS
+ tristate "Support TH1520 Power Domains"
+ depends on TH1520_AON_PROTOCOL
+ select REGMAP_MMIO
+ help
+ This driver enables power domain management for the T-HEAD
+ TH-1520 SoC. On this SoC there are number of power domains,
+ which can be managed independently. For example GPU, NPU,
+ and DPU reside in their own power domains which can be
+ turned on/off.
diff --git a/drivers/pmdomain/thead/Makefile b/drivers/pmdomain/thead/Makefile
new file mode 100644
index 000000000000..adfdf5479c68
--- /dev/null
+++ b/drivers/pmdomain/thead/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_TH1520_PM_DOMAINS) += th1520-pm-domains.o
diff --git a/drivers/pmdomain/thead/th1520-pm-domains.c b/drivers/pmdomain/thead/th1520-pm-domains.c
new file mode 100644
index 000000000000..f702e20306f4
--- /dev/null
+++ b/drivers/pmdomain/thead/th1520-pm-domains.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * Author: Michal Wilczynski <m.wilczynski@samsung.com>
+ */
+
+#include <linux/firmware/thead/thead,th1520-aon.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+
+#include <dt-bindings/power/thead,th1520-power.h>
+
+struct th1520_power_domain {
+ struct th1520_aon_chan *aon_chan;
+ struct generic_pm_domain genpd;
+ u32 rsrc;
+};
+
+struct th1520_power_info {
+ const char *name;
+ u32 rsrc;
+ bool disabled;
+};
+
+/*
+ * The AUDIO power domain is marked as disabled to prevent the driver from
+ * managing its power state. Direct AON firmware calls to control this power
+ * island trigger a firmware bug causing system instability. Until this
+ * firmware issue is resolved, the AUDIO power domain must remain disabled
+ * to avoid crashes.
+ */
+static const struct th1520_power_info th1520_pd_ranges[] = {
+ [TH1520_AUDIO_PD] = {"audio", TH1520_AON_AUDIO_PD, true },
+ [TH1520_VDEC_PD] = { "vdec", TH1520_AON_VDEC_PD, false },
+ [TH1520_NPU_PD] = { "npu", TH1520_AON_NPU_PD, false },
+ [TH1520_VENC_PD] = { "venc", TH1520_AON_VENC_PD, false },
+ [TH1520_GPU_PD] = { "gpu", TH1520_AON_GPU_PD, false },
+ [TH1520_DSP0_PD] = { "dsp0", TH1520_AON_DSP0_PD, false },
+ [TH1520_DSP1_PD] = { "dsp1", TH1520_AON_DSP1_PD, false }
+};
+
+static inline struct th1520_power_domain *
+to_th1520_power_domain(struct generic_pm_domain *genpd)
+{
+ return container_of(genpd, struct th1520_power_domain, genpd);
+}
+
+static int th1520_pd_power_on(struct generic_pm_domain *domain)
+{
+ struct th1520_power_domain *pd = to_th1520_power_domain(domain);
+
+ return th1520_aon_power_update(pd->aon_chan, pd->rsrc, true);
+}
+
+static int th1520_pd_power_off(struct generic_pm_domain *domain)
+{
+ struct th1520_power_domain *pd = to_th1520_power_domain(domain);
+
+ return th1520_aon_power_update(pd->aon_chan, pd->rsrc, false);
+}
+
+static struct generic_pm_domain *th1520_pd_xlate(const struct of_phandle_args *spec,
+ void *data)
+{
+ struct generic_pm_domain *domain = ERR_PTR(-ENOENT);
+ struct genpd_onecell_data *pd_data = data;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) {
+ struct th1520_power_domain *pd;
+
+ if (th1520_pd_ranges[i].disabled)
+ continue;
+
+ pd = to_th1520_power_domain(pd_data->domains[i]);
+ if (pd->rsrc == spec->args[0]) {
+ domain = &pd->genpd;
+ break;
+ }
+ }
+
+ return domain;
+}
+
+static struct th1520_power_domain *
+th1520_add_pm_domain(struct device *dev, const struct th1520_power_info *pi)
+{
+ struct th1520_power_domain *pd;
+ int ret;
+
+ pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return ERR_PTR(-ENOMEM);
+
+ pd->rsrc = pi->rsrc;
+ pd->genpd.power_on = th1520_pd_power_on;
+ pd->genpd.power_off = th1520_pd_power_off;
+ pd->genpd.name = pi->name;
+
+ ret = pm_genpd_init(&pd->genpd, NULL, true);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return pd;
+}
+
+static void th1520_pd_init_all_off(struct generic_pm_domain **domains,
+ struct device *dev)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) {
+ struct th1520_power_domain *pd;
+
+ if (th1520_pd_ranges[i].disabled)
+ continue;
+
+ pd = to_th1520_power_domain(domains[i]);
+
+ ret = th1520_aon_power_update(pd->aon_chan, pd->rsrc, false);
+ if (ret)
+ dev_err(dev,
+ "Failed to initially power down power domain %s\n",
+ pd->genpd.name);
+ }
+}
+
+static int th1520_pd_probe(struct platform_device *pdev)
+{
+ struct generic_pm_domain **domains;
+ struct genpd_onecell_data *pd_data;
+ struct th1520_aon_chan *aon_chan;
+ struct device *dev = &pdev->dev;
+ int i, ret;
+
+ aon_chan = th1520_aon_init(dev);
+ if (IS_ERR(aon_chan))
+ return dev_err_probe(dev, PTR_ERR(aon_chan),
+ "Failed to get AON channel\n");
+
+ domains = devm_kcalloc(dev, ARRAY_SIZE(th1520_pd_ranges),
+ sizeof(*domains), GFP_KERNEL);
+ if (!domains) {
+ ret = -ENOMEM;
+ goto err_clean_aon;
+ }
+
+ pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL);
+ if (!pd_data) {
+ ret = -ENOMEM;
+ goto err_clean_aon;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(th1520_pd_ranges); i++) {
+ struct th1520_power_domain *pd;
+
+ if (th1520_pd_ranges[i].disabled)
+ continue;
+
+ pd = th1520_add_pm_domain(dev, &th1520_pd_ranges[i]);
+ if (IS_ERR(pd)) {
+ ret = PTR_ERR(pd);
+ goto err_clean_genpd;
+ }
+
+ pd->aon_chan = aon_chan;
+ domains[i] = &pd->genpd;
+ dev_dbg(dev, "added power domain %s\n", pd->genpd.name);
+ }
+
+ pd_data->domains = domains;
+ pd_data->num_domains = ARRAY_SIZE(th1520_pd_ranges);
+ pd_data->xlate = th1520_pd_xlate;
+
+ /*
+ * Initialize all power domains to off to ensure they start in a
+ * low-power state. This allows device drivers to manage power
+ * domains by turning them on or off as needed.
+ */
+ th1520_pd_init_all_off(domains, dev);
+
+ ret = of_genpd_add_provider_onecell(dev->of_node, pd_data);
+ if (ret)
+ goto err_clean_genpd;
+
+ return 0;
+
+err_clean_genpd:
+ for (i--; i >= 0; i--)
+ pm_genpd_remove(domains[i]);
+err_clean_aon:
+ th1520_aon_deinit(aon_chan);
+
+ return ret;
+}
+
+static const struct of_device_id th1520_pd_match[] = {
+ { .compatible = "thead,th1520-aon" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, th1520_pd_match);
+
+static struct platform_driver th1520_pd_driver = {
+ .driver = {
+ .name = "th1520-pd",
+ .of_match_table = th1520_pd_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = th1520_pd_probe,
+};
+module_platform_driver(th1520_pd_driver);
+
+MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
+MODULE_DESCRIPTION("T-HEAD TH1520 SoC power domain controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pmdomain/ti/omap_prm.c b/drivers/pmdomain/ti/omap_prm.c
index b8ceb3c2b81c..79d165331d8c 100644
--- a/drivers/pmdomain/ti/omap_prm.c
+++ b/drivers/pmdomain/ti/omap_prm.c
@@ -613,7 +613,7 @@ static int omap_prm_domain_attach_clock(struct device *dev,
if (!of_device_is_compatible(np, "simple-pm-bus"))
return 0;
- if (!of_property_read_bool(np, "clocks"))
+ if (!of_property_present(np, "clocks"))
return 0;
error = pm_clk_create(dev);