1
0
mirror of https://github.com/torvalds/linux.git synced 2025-04-09 14:45:27 +00:00

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
 -----BEGIN PGP SIGNATURE-----
 
 iQJLBAABCgA1FiEEugLDXPmKSktSkQsV/iaEJXNYjCkFAmfimQ8XHHVsZi5oYW5z
 c29uQGxpbmFyby5vcmcACgkQ/iaEJXNYjCmp5A//QuqG0PiwrDyR/qOgOaYXHLe3
 lYohfHtLyKVO0qAxhhiRbUZQrK4yitkRUJoXHcJuIqqXXjiM3tKu5Vp5loqVpqZi
 Q8nj6gEIUA1FQjY0h8VTS+NWXA5xbsqgayzw2U6BAfKHQwsvcMXn/hT5v8d0Q2WG
 UVNb+Xz25q6qzZPbhR/wfJ8kvFkGjV1GtIG3PPwA+C31jFjdcZhU+Rlwtgu+WDZE
 yofA/pkw5jdDkODTyysYhHKpZlnX+V1yUqs2xym27M2xmbCDpsn9IM45omuFCdnh
 7dyKtG55XLd9wpAtO2DVvUWW0bhtr/zfDpWvDQdevQLjwrIdw5wdg53SE3NpNR7/
 cCWLM7OFaTJDuuK/upuT75ZKaFqEu5QV9+Na5skQhL0Tl4V9A0nNRPLQXJItGZWv
 XNfV9OxljYK8c+5fEEEB+pBymZ2LeRvw2+P3DIMSgYNwdZMudmNRWsQe2SjbC4jI
 G9XzpXw6YaIUNmI8fGGZ4U4CqMg0bOjY7zlQL2VMTe3+JJGdpCRmONT8EV/LH3PQ
 2V4dSjwoWH0lmQLo2trNDuIWj6AdGNObSL3LXSKPo6ORXg24dWdI9Dbc7PpPvOb0
 CZ9AV3SezfmkSyODI5G5ULUeH1hy4h6jn9py2SoVRS3SQyznh0HZj9kBlyuVgfmL
 mArHaUCmVHPKhAvLc1g=
 =Wihe
 -----END PGP SIGNATURE-----

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
  ...
This commit is contained in:
Linus Torvalds 2025-03-25 20:40:51 -07:00
commit 2a2274e90a
29 changed files with 1039 additions and 90 deletions

@ -0,0 +1,53 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/firmware/thead,th1520-aon.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: T-HEAD TH1520 AON (Always-On) Firmware
description: |
The Always-On (AON) subsystem in the TH1520 SoC is responsible for managing
low-power states, system wakeup events, and power management tasks. It is
designed to operate independently in a dedicated power domain, allowing it to
remain functional even during the SoC's deep sleep states.
At the heart of the AON subsystem is the E902, a low-power core that executes
firmware responsible for coordinating tasks such as power domain control,
clock management, and system wakeup signaling. Communication between the main
SoC and the AON subsystem is handled through a mailbox interface, which
enables message-based interactions with the AON firmware.
maintainers:
- Michal Wilczynski <m.wilczynski@samsung.com>
properties:
compatible:
const: thead,th1520-aon
mboxes:
maxItems: 1
mbox-names:
items:
- const: aon
"#power-domain-cells":
const: 1
required:
- compatible
- mboxes
- mbox-names
- "#power-domain-cells"
additionalProperties: false
examples:
- |
aon: aon {
compatible = "thead,th1520-aon";
mboxes = <&mbox_910t 1>;
mbox-names = "aon";
#power-domain-cells = <1>;
};

@ -17,6 +17,7 @@ properties:
compatible:
enum:
- allwinner,sun20i-d1-ppu
- allwinner,sun8i-v853-ppu
reg:
maxItems: 1

@ -132,6 +132,9 @@ $defs:
A number of phandles to clocks that need to be enabled
while power domain switches state.
domain-supply:
description: domain regulator supply.
pm_qos:
$ref: /schemas/types.yaml#/definitions/phandle-array
items:

@ -6103,9 +6103,11 @@ F: include/linux/platform_data/cpuidle-exynos.h
CPUIDLE DRIVER - ARM PSCI
M: Lorenzo Pieralisi <lpieralisi@kernel.org>
M: Sudeep Holla <sudeep.holla@arm.com>
M: Ulf Hansson <ulf.hansson@linaro.org>
L: linux-pm@vger.kernel.org
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git
F: drivers/cpuidle/cpuidle-psci.c
CPUIDLE DRIVER - ARM PSCI PM DOMAIN
@ -20510,15 +20512,20 @@ L: linux-riscv@lists.infradead.org
S: Maintained
T: git https://github.com/pdp7/linux.git
F: Documentation/devicetree/bindings/clock/thead,th1520-clk-ap.yaml
F: Documentation/devicetree/bindings/firmware/thead,th1520-aon.yaml
F: Documentation/devicetree/bindings/mailbox/thead,th1520-mbox.yaml
F: Documentation/devicetree/bindings/net/thead,th1520-gmac.yaml
F: Documentation/devicetree/bindings/pinctrl/thead,th1520-pinctrl.yaml
F: arch/riscv/boot/dts/thead/
F: drivers/clk/thead/clk-th1520-ap.c
F: drivers/firmware/thead,th1520-aon.c
F: drivers/mailbox/mailbox-th1520.c
F: drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
F: drivers/pinctrl/pinctrl-th1520.c
F: drivers/pmdomain/thead/
F: include/dt-bindings/clock/thead,th1520-clk-ap.h
F: include/dt-bindings/power/thead,th1520-power.h
F: include/linux/firmware/thead/thead,th1520-aon.h
RNBD BLOCK DRIVERS
M: Md. Haris Iqbal <haris.iqbal@ionos.com>

@ -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);

@ -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

@ -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

@ -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");

@ -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"

@ -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

@ -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)

@ -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;

@ -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))

@ -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,

@ -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);

@ -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.

@ -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 (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;
if (is_mem_on) {
ret = rockchip_pmu_domain_mem_reset(pd);
if (ret)
return ret;
}
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);
if (!power_on) {
rockchip_pmu_save_qos(pd);
/* if powering down, idle request to NIU first */
rockchip_pmu_set_idle_request(pd, true);
}
rockchip_do_pmu_set_power_domain(pd, power_on);
if (power_on) {
/* if powering up, leave idle mode */
rockchip_pmu_set_idle_request(pd, false);
rockchip_pmu_restore_qos(pd);
}
rockchip_pmu_ungate_clk(pd, false);
clk_bulk_disable(pd->num_clks, pd->clks);
ret = clk_bulk_enable(pd->num_clks, pd->clks);
if (ret < 0) {
dev_err(pmu->dev, "failed to enable clocks\n");
return ret;
}
mutex_unlock(&pmu->mutex);
return 0;
rockchip_pmu_ungate_clk(pd, true);
if (!power_on) {
rockchip_pmu_save_qos(pd);
/* if powering down, idle request to NIU first */
ret = rockchip_pmu_set_idle_request(pd, true);
if (ret < 0)
goto out;
}
ret = rockchip_do_pmu_set_power_domain(pd, power_on);
if (ret < 0)
goto out;
if (power_on) {
/* if powering up, leave idle mode */
ret = rockchip_pmu_set_idle_request(pd, false);
if (ret < 0)
goto out;
rockchip_pmu_restore_qos(pd);
}
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;
return rockchip_pd_power(pd, true);
ret = rockchip_pd_regulator_enable(pd);
if (ret) {
dev_err(pd->pmu->dev, "Failed to enable supply: %d\n", ret);
return ret;
}
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 = {

@ -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);

@ -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.

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_TH1520_PM_DOMAINS) += th1520-pm-domains.o

@ -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");

@ -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);

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
#ifndef _DT_BINDINGS_POWER_SUN8I_V853_PPU_H_
#define _DT_BINDINGS_POWER_SUN8I_V853_PPU_H_
#define PD_RISCV 0
#define PD_NPU 1
#define PD_VE 2
#endif

@ -65,7 +65,7 @@
#define SM6350_MSS 4
#define SM6350_MX 5
/* SM6350 Power Domain Indexes */
/* SM6375 Power Domain Indexes */
#define SM6375_VDDCX 0
#define SM6375_VDDCX_AO 1
#define SM6375_VDDCX_VFL 2

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* Copyright (C) 2022 Alibaba Group Holding Limited.
* Copyright (c) 2024 Samsung Electronics Co., Ltd.
* Author: Michal Wilczynski <m.wilczynski@samsung.com>
*/
#ifndef __DT_BINDINGS_POWER_TH1520_H
#define __DT_BINDINGS_POWER_TH1520_H
#define TH1520_AUDIO_PD 0
#define TH1520_VDEC_PD 1
#define TH1520_NPU_PD 2
#define TH1520_VENC_PD 3
#define TH1520_GPU_PD 4
#define TH1520_DSP0_PD 5
#define TH1520_DSP1_PD 6
#endif

@ -0,0 +1,200 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2021 Alibaba Group Holding Limited.
*/
#ifndef _THEAD_AON_H
#define _THEAD_AON_H
#include <linux/device.h>
#include <linux/types.h>
#define AON_RPC_MSG_MAGIC (0xef)
#define TH1520_AON_RPC_VERSION 2
#define TH1520_AON_RPC_MSG_NUM 7
struct th1520_aon_chan;
enum th1520_aon_rpc_svc {
TH1520_AON_RPC_SVC_UNKNOWN = 0,
TH1520_AON_RPC_SVC_PM = 1,
TH1520_AON_RPC_SVC_MISC = 2,
TH1520_AON_RPC_SVC_AVFS = 3,
TH1520_AON_RPC_SVC_SYS = 4,
TH1520_AON_RPC_SVC_WDG = 5,
TH1520_AON_RPC_SVC_LPM = 6,
TH1520_AON_RPC_SVC_MAX = 0x3F,
};
enum th1520_aon_misc_func {
TH1520_AON_MISC_FUNC_UNKNOWN = 0,
TH1520_AON_MISC_FUNC_SET_CONTROL = 1,
TH1520_AON_MISC_FUNC_GET_CONTROL = 2,
TH1520_AON_MISC_FUNC_REGDUMP_CFG = 3,
};
enum th1520_aon_wdg_func {
TH1520_AON_WDG_FUNC_UNKNOWN = 0,
TH1520_AON_WDG_FUNC_START = 1,
TH1520_AON_WDG_FUNC_STOP = 2,
TH1520_AON_WDG_FUNC_PING = 3,
TH1520_AON_WDG_FUNC_TIMEOUTSET = 4,
TH1520_AON_WDG_FUNC_RESTART = 5,
TH1520_AON_WDG_FUNC_GET_STATE = 6,
TH1520_AON_WDG_FUNC_POWER_OFF = 7,
TH1520_AON_WDG_FUNC_AON_WDT_ON = 8,
TH1520_AON_WDG_FUNC_AON_WDT_OFF = 9,
};
enum th1520_aon_sys_func {
TH1520_AON_SYS_FUNC_UNKNOWN = 0,
TH1520_AON_SYS_FUNC_AON_RESERVE_MEM = 1,
};
enum th1520_aon_lpm_func {
TH1520_AON_LPM_FUNC_UNKNOWN = 0,
TH1520_AON_LPM_FUNC_REQUIRE_STR = 1,
TH1520_AON_LPM_FUNC_RESUME_STR = 2,
TH1520_AON_LPM_FUNC_REQUIRE_STD = 3,
TH1520_AON_LPM_FUNC_CPUHP = 4,
TH1520_AON_LPM_FUNC_REGDUMP_CFG = 5,
};
enum th1520_aon_pm_func {
TH1520_AON_PM_FUNC_UNKNOWN = 0,
TH1520_AON_PM_FUNC_SET_RESOURCE_REGULATOR = 1,
TH1520_AON_PM_FUNC_GET_RESOURCE_REGULATOR = 2,
TH1520_AON_PM_FUNC_SET_RESOURCE_POWER_MODE = 3,
TH1520_AON_PM_FUNC_PWR_SET = 4,
TH1520_AON_PM_FUNC_PWR_GET = 5,
TH1520_AON_PM_FUNC_CHECK_FAULT = 6,
TH1520_AON_PM_FUNC_GET_TEMPERATURE = 7,
};
struct th1520_aon_rpc_msg_hdr {
u8 ver; /* version of msg hdr */
u8 size; /* msg size ,uinit in bytes,the size includes rpc msg header self */
u8 svc; /* rpc main service id */
u8 func; /* rpc sub func id of specific service, sent by caller */
} __packed __aligned(1);
struct th1520_aon_rpc_ack_common {
struct th1520_aon_rpc_msg_hdr hdr;
u8 err_code;
} __packed __aligned(1);
#define RPC_SVC_MSG_TYPE_DATA 0
#define RPC_SVC_MSG_TYPE_ACK 1
#define RPC_SVC_MSG_NEED_ACK 0
#define RPC_SVC_MSG_NO_NEED_ACK 1
#define RPC_GET_VER(MESG) ((MESG)->ver)
#define RPC_SET_VER(MESG, VER) ((MESG)->ver = (VER))
#define RPC_GET_SVC_ID(MESG) ((MESG)->svc & 0x3F)
#define RPC_SET_SVC_ID(MESG, ID) ((MESG)->svc |= 0x3F & (ID))
#define RPC_GET_SVC_FLAG_MSG_TYPE(MESG) (((MESG)->svc & 0x80) >> 7)
#define RPC_SET_SVC_FLAG_MSG_TYPE(MESG, TYPE) ((MESG)->svc |= (TYPE) << 7)
#define RPC_GET_SVC_FLAG_ACK_TYPE(MESG) (((MESG)->svc & 0x40) >> 6)
#define RPC_SET_SVC_FLAG_ACK_TYPE(MESG, ACK) ((MESG)->svc |= (ACK) << 6)
#define RPC_SET_BE64(MESG, OFFSET, SET_DATA) \
do { \
u8 *data = (u8 *)(MESG); \
u64 _offset = (OFFSET); \
u64 _set_data = (SET_DATA); \
data[_offset + 7] = _set_data & 0xFF; \
data[_offset + 6] = (_set_data & 0xFF00) >> 8; \
data[_offset + 5] = (_set_data & 0xFF0000) >> 16; \
data[_offset + 4] = (_set_data & 0xFF000000) >> 24; \
data[_offset + 3] = (_set_data & 0xFF00000000) >> 32; \
data[_offset + 2] = (_set_data & 0xFF0000000000) >> 40; \
data[_offset + 1] = (_set_data & 0xFF000000000000) >> 48; \
data[_offset + 0] = (_set_data & 0xFF00000000000000) >> 56; \
} while (0)
#define RPC_SET_BE32(MESG, OFFSET, SET_DATA) \
do { \
u8 *data = (u8 *)(MESG); \
u64 _offset = (OFFSET); \
u64 _set_data = (SET_DATA); \
data[_offset + 3] = (_set_data) & 0xFF; \
data[_offset + 2] = (_set_data & 0xFF00) >> 8; \
data[_offset + 1] = (_set_data & 0xFF0000) >> 16; \
data[_offset + 0] = (_set_data & 0xFF000000) >> 24; \
} while (0)
#define RPC_SET_BE16(MESG, OFFSET, SET_DATA) \
do { \
u8 *data = (u8 *)(MESG); \
u64 _offset = (OFFSET); \
u64 _set_data = (SET_DATA); \
data[_offset + 1] = (_set_data) & 0xFF; \
data[_offset + 0] = (_set_data & 0xFF00) >> 8; \
} while (0)
#define RPC_SET_U8(MESG, OFFSET, SET_DATA) \
do { \
u8 *data = (u8 *)(MESG); \
data[OFFSET] = (SET_DATA) & 0xFF; \
} while (0)
#define RPC_GET_BE64(MESG, OFFSET, PTR) \
do { \
u8 *data = (u8 *)(MESG); \
u64 _offset = (OFFSET); \
*(u32 *)(PTR) = \
(data[_offset + 7] | data[_offset + 6] << 8 | \
data[_offset + 5] << 16 | data[_offset + 4] << 24 | \
data[_offset + 3] << 32 | data[_offset + 2] << 40 | \
data[_offset + 1] << 48 | data[_offset + 0] << 56); \
} while (0)
#define RPC_GET_BE32(MESG, OFFSET, PTR) \
do { \
u8 *data = (u8 *)(MESG); \
u64 _offset = (OFFSET); \
*(u32 *)(PTR) = \
(data[_offset + 3] | data[_offset + 2] << 8 | \
data[_offset + 1] << 16 | data[_offset + 0] << 24); \
} while (0)
#define RPC_GET_BE16(MESG, OFFSET, PTR) \
do { \
u8 *data = (u8 *)(MESG); \
u64 _offset = (OFFSET); \
*(u16 *)(PTR) = (data[_offset + 1] | data[_offset + 0] << 8); \
} while (0)
#define RPC_GET_U8(MESG, OFFSET, PTR) \
do { \
u8 *data = (u8 *)(MESG); \
*(u8 *)(PTR) = (data[OFFSET]); \
} while (0)
/*
* Defines for SC PM Power Mode
*/
#define TH1520_AON_PM_PW_MODE_OFF 0 /* Power off */
#define TH1520_AON_PM_PW_MODE_STBY 1 /* Power in standby */
#define TH1520_AON_PM_PW_MODE_LP 2 /* Power in low-power */
#define TH1520_AON_PM_PW_MODE_ON 3 /* Power on */
/*
* Defines for AON power islands
*/
#define TH1520_AON_AUDIO_PD 0
#define TH1520_AON_VDEC_PD 1
#define TH1520_AON_NPU_PD 2
#define TH1520_AON_VENC_PD 3
#define TH1520_AON_GPU_PD 4
#define TH1520_AON_DSP0_PD 5
#define TH1520_AON_DSP1_PD 6
struct th1520_aon_chan *th1520_aon_init(struct device *dev);
void th1520_aon_deinit(struct th1520_aon_chan *aon_chan);
int th1520_aon_call_rpc(struct th1520_aon_chan *aon_chan, void *msg);
int th1520_aon_power_update(struct th1520_aon_chan *aon_chan, u16 rsrc,
bool power_on);
#endif /* _THEAD_AON_H */

@ -261,6 +261,7 @@ struct generic_pm_domain_data {
unsigned int rpm_pstate;
unsigned int opp_token;
bool hw_mode;
bool rpm_always_on;
void *data;
};
@ -293,6 +294,7 @@ ktime_t dev_pm_genpd_get_next_hrtimer(struct device *dev);
void dev_pm_genpd_synced_poweroff(struct device *dev);
int dev_pm_genpd_set_hwmode(struct device *dev, bool enable);
bool dev_pm_genpd_get_hwmode(struct device *dev);
int dev_pm_genpd_rpm_always_on(struct device *dev, bool on);
extern struct dev_power_governor simple_qos_governor;
extern struct dev_power_governor pm_domain_always_on_gov;
@ -376,6 +378,11 @@ static inline bool dev_pm_genpd_get_hwmode(struct device *dev)
return false;
}
static inline int dev_pm_genpd_rpm_always_on(struct device *dev, bool on)
{
return -EOPNOTSUPP;
}
#define simple_qos_governor (*(struct dev_power_governor *)(NULL))
#define pm_domain_always_on_gov (*(struct dev_power_governor *)(NULL))
#endif

@ -6,6 +6,9 @@
#ifndef __SOC_ROCKCHIP_SIP_H
#define __SOC_ROCKCHIP_SIP_H
#define ROCKCHIP_SIP_SUSPEND_MODE 0x82000003
#define ROCKCHIP_SLEEP_PD_CONFIG 0xff
#define ROCKCHIP_SIP_DRAM_FREQ 0x82000008
#define ROCKCHIP_SIP_CONFIG_DRAM_INIT 0x00
#define ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE 0x01

@ -62,6 +62,43 @@ TRACE_EVENT(cpu_idle_miss,
(unsigned long)__entry->state, (__entry->below)?"below":"above")
);
DECLARE_EVENT_CLASS(psci_domain_idle,
TP_PROTO(unsigned int cpu_id, unsigned int state, bool s2idle),
TP_ARGS(cpu_id, state, s2idle),
TP_STRUCT__entry(
__field(u32, cpu_id)
__field(u32, state)
__field(bool, s2idle)
),
TP_fast_assign(
__entry->cpu_id = cpu_id;
__entry->state = state;
__entry->s2idle = s2idle;
),
TP_printk("cpu_id=%lu state=0x%lx is_s2idle=%s",
(unsigned long)__entry->cpu_id, (unsigned long)__entry->state,
(__entry->s2idle)?"yes":"no")
);
DEFINE_EVENT(psci_domain_idle, psci_domain_idle_enter,
TP_PROTO(unsigned int cpu_id, unsigned int state, bool s2idle),
TP_ARGS(cpu_id, state, s2idle)
);
DEFINE_EVENT(psci_domain_idle, psci_domain_idle_exit,
TP_PROTO(unsigned int cpu_id, unsigned int state, bool s2idle),
TP_ARGS(cpu_id, state, s2idle)
);
TRACE_EVENT(powernv_throttle,
TP_PROTO(int chip_id, const char *reason, int pmax),