[PATCH v3 3/6] soc: samsung: exynos-pmu: generalise gs101-specific cpu{idle,hotplug} for Exynos SoCs
Peter Griffin
peter.griffin at linaro.org
Fri May 8 13:37:04 PDT 2026
Hi Alexey,
On Thu, 30 Apr 2026 at 02:56, Alexey Klimov <alexey.klimov at linaro.org> wrote:
>
> The cpuhotplug and cpuidle support for GS101-based SoCs which
> utilizes GS101 PMU interrupts generation block can be generalised
> to be (re)used for other Exynos-based SoCs. Also, the GS101 PMU
> interrupts generation block is not exclusive to Google GS101 SoCs
> and should be made more Exynos-generic.
>
> Specifically, apply the following changes:
> - rename gs101-specific calls, structs, names to be exynos-prefixed;
> - move exynos_pmu_context and CPU_INFORM_* defines into exynos-pmu.h;
> - introduce cpu_pmu_{offline,online} callbacks in driver-specific
> exynos_pmu_data which can be used to hold PMU and PMU intr gen
> update routines for different platforms and update cpuidle and cpuhotplug
> support to use them;
> - add checks for the presense of cpu_pmu_{offline,online} callbacks;
> - move and rename gs101-specific cpu{offline,online} PMU updates
> routines into gs101-pmu.c file, also removing underscore prefix;
> - update gs101_pmu_data to use newly introduced callbacks;
> - rename PMU interrupts generation GS101_INTR_* regs to EXYNOS_INTR_*.
>
> This allows other platforms to add cpuhotplug and cpuidle support in
> a similar manner, using their own platform-specific PMU and
> PMU intr gen update routines.
>
> Signed-off-by: Alexey Klimov <alexey.klimov at linaro.org>
> ---
It's nice to see this being extended for other Exynos SoCs :)
Reviewed-by: Peter Griffin <peter.griffin at linaro.org>
Tested-by: Peter Griffin <peter.griffin at linaro.org>
Tested on Pixel 6: -
* CPU hotplug still works fine
* CPUIdle C2 idle state (which requires the ACPM hints and code paths
renamed/moved in this patch) are still functional
regards,
Peter
> drivers/soc/samsung/exynos-pmu.c | 118 ++++++----------------------
> drivers/soc/samsung/exynos-pmu.h | 31 ++++++++
> drivers/soc/samsung/gs101-pmu.c | 57 ++++++++++++++
> include/linux/soc/samsung/exynos-regs-pmu.h | 10 +--
> 4 files changed, 115 insertions(+), 101 deletions(-)
>
> diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c
> index d58376c38179..660416c0db43 100644
> --- a/drivers/soc/samsung/exynos-pmu.c
> +++ b/drivers/soc/samsung/exynos-pmu.c
> @@ -24,22 +24,6 @@
>
> #include "exynos-pmu.h"
>
> -struct exynos_pmu_context {
> - struct device *dev;
> - const struct exynos_pmu_data *pmu_data;
> - struct regmap *pmureg;
> - struct regmap *pmuintrgen;
> - /*
> - * Serialization lock for CPU hot plug and cpuidle ACPM hint
> - * programming. Also protects in_cpuhp, sys_insuspend & sys_inreboot
> - * flags.
> - */
> - raw_spinlock_t cpupm_lock;
> - unsigned long *in_cpuhp;
> - bool sys_insuspend;
> - bool sys_inreboot;
> -};
> -
> void __iomem *pmu_base_addr;
> static struct exynos_pmu_context *pmu_context;
> /* forward declaration */
> @@ -219,44 +203,8 @@ struct regmap *exynos_get_pmu_regmap_by_phandle(struct device_node *np,
> }
> EXPORT_SYMBOL_GPL(exynos_get_pmu_regmap_by_phandle);
>
> -/*
> - * CPU_INFORM register "hint" values are required to be programmed in addition to
> - * the standard PSCI calls to have functional CPU hotplug and CPU idle states.
> - * This is required to workaround limitations in the el3mon/ACPM firmware.
> - */
> -#define CPU_INFORM_CLEAR 0
> -#define CPU_INFORM_C2 1
> -
> -/*
> - * __gs101_cpu_pmu_ prefix functions are common code shared by CPU PM notifiers
> - * (CPUIdle) and CPU hotplug callbacks. Functions should be called with IRQs
> - * disabled and cpupm_lock held.
> - */
> -static int __gs101_cpu_pmu_online(unsigned int cpu)
> - __must_hold(&pmu_context->cpupm_lock)
> -{
> - unsigned int cpuhint = smp_processor_id();
> - u32 reg, mask;
> -
> - /* clear cpu inform hint */
> - regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint),
> - CPU_INFORM_CLEAR);
> -
> - mask = BIT(cpu);
> -
> - regmap_update_bits(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_ENABLE,
> - mask, (0 << cpu));
> -
> - regmap_read(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_UPEND, ®);
> -
> - regmap_write(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_CLEAR,
> - reg & mask);
> -
> - return 0;
> -}
> -
> /* Called from CPU PM notifier (CPUIdle code path) with IRQs disabled */
> -static int gs101_cpu_pmu_online(void)
> +static int exynos_cpu_pmu_online(void)
> {
> int cpu;
>
> @@ -268,20 +216,20 @@ static int gs101_cpu_pmu_online(void)
> }
>
> cpu = smp_processor_id();
> - __gs101_cpu_pmu_online(cpu);
> + pmu_context->pmu_data->cpu_pmu_online(pmu_context, cpu);
> raw_spin_unlock(&pmu_context->cpupm_lock);
>
> return NOTIFY_OK;
> }
>
> /* Called from CPU hot plug callback with IRQs enabled */
> -static int gs101_cpuhp_pmu_online(unsigned int cpu)
> +static int exynos_cpuhp_pmu_online(unsigned int cpu)
> {
> unsigned long flags;
>
> raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags);
>
> - __gs101_cpu_pmu_online(cpu);
> + pmu_context->pmu_data->cpu_pmu_online(pmu_context, cpu);
> /*
> * Mark this CPU as having finished the hotplug.
> * This means this CPU can now enter C2 idle state.
> @@ -292,35 +240,8 @@ static int gs101_cpuhp_pmu_online(unsigned int cpu)
> return 0;
> }
>
> -/* Common function shared by both CPU hot plug and CPUIdle */
> -static int __gs101_cpu_pmu_offline(unsigned int cpu)
> - __must_hold(&pmu_context->cpupm_lock)
> -{
> - unsigned int cpuhint = smp_processor_id();
> - u32 reg, mask;
> -
> - /* set cpu inform hint */
> - regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint),
> - CPU_INFORM_C2);
> -
> - mask = BIT(cpu);
> - regmap_update_bits(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_ENABLE,
> - mask, BIT(cpu));
> -
> - regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, ®);
> - regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR,
> - reg & mask);
> -
> - mask = (BIT(cpu + 8));
> - regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, ®);
> - regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR,
> - reg & mask);
> -
> - return 0;
> -}
> -
> /* Called from CPU PM notifier (CPUIdle code path) with IRQs disabled */
> -static int gs101_cpu_pmu_offline(void)
> +static int exynos_cpu_pmu_offline(void)
> {
> int cpu;
>
> @@ -338,14 +259,14 @@ static int gs101_cpu_pmu_offline(void)
> return NOTIFY_OK;
> }
>
> - __gs101_cpu_pmu_offline(cpu);
> + pmu_context->pmu_data->cpu_pmu_offline(pmu_context, cpu);
> raw_spin_unlock(&pmu_context->cpupm_lock);
>
> return NOTIFY_OK;
> }
>
> /* Called from CPU hot plug callback with IRQs enabled */
> -static int gs101_cpuhp_pmu_offline(unsigned int cpu)
> +static int exynos_cpuhp_pmu_offline(unsigned int cpu)
> {
> unsigned long flags;
>
> @@ -355,29 +276,29 @@ static int gs101_cpuhp_pmu_offline(unsigned int cpu)
> * ACPM the CPU entering hotplug should not enter C2 idle state.
> */
> set_bit(cpu, pmu_context->in_cpuhp);
> - __gs101_cpu_pmu_offline(cpu);
> + pmu_context->pmu_data->cpu_pmu_offline(pmu_context, cpu);
>
> raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags);
>
> return 0;
> }
>
> -static int gs101_cpu_pm_notify_callback(struct notifier_block *self,
> +static int exynos_cpu_pm_notify_callback(struct notifier_block *self,
> unsigned long action, void *v)
> {
> switch (action) {
> case CPU_PM_ENTER:
> - return gs101_cpu_pmu_offline();
> + return exynos_cpu_pmu_offline();
>
> case CPU_PM_EXIT:
> - return gs101_cpu_pmu_online();
> + return exynos_cpu_pmu_online();
> }
>
> return NOTIFY_OK;
> }
>
> -static struct notifier_block gs101_cpu_pm_notifier = {
> - .notifier_call = gs101_cpu_pm_notify_callback,
> +static struct notifier_block exynos_cpu_pm_notifier = {
> + .notifier_call = exynos_cpu_pm_notify_callback,
> /*
> * We want to be called first, as the ACPM hint and handshake is what
> * puts the CPU into C2.
> @@ -425,6 +346,11 @@ static int setup_cpuhp_and_cpuidle(struct device *dev)
> return 0;
> }
>
> + if (!pmu_context->pmu_data->cpu_pmu_offline || !pmu_context->pmu_data->cpu_pmu_online) {
> + dev_err(dev, "PMU write/read sequence is not present for cpuhotplug and cpuidle\n");
> + return -ENODEV;
> + }
> +
> /*
> * To avoid lockdep issues (CPU PM notifiers use raw spinlocks) create
> * a mmio regmap for pmu-intr-gen that uses raw spinlocks instead of
> @@ -458,17 +384,17 @@ static int setup_cpuhp_and_cpuidle(struct device *dev)
>
> /* set PMU to power on */
> for_each_online_cpu(cpu)
> - gs101_cpuhp_pmu_online(cpu);
> + exynos_cpuhp_pmu_online(cpu);
>
> /* register CPU hotplug callbacks */
> cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "soc/exynos-pmu:prepare",
> - gs101_cpuhp_pmu_online, NULL);
> + exynos_cpuhp_pmu_online, NULL);
>
> cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/exynos-pmu:online",
> - NULL, gs101_cpuhp_pmu_offline);
> + NULL, exynos_cpuhp_pmu_offline);
>
> /* register CPU PM notifiers for cpuidle */
> - cpu_pm_register_notifier(&gs101_cpu_pm_notifier);
> + cpu_pm_register_notifier(&exynos_cpu_pm_notifier);
> register_reboot_notifier(&exynos_cpupm_reboot_nb);
> return 0;
> }
> diff --git a/drivers/soc/samsung/exynos-pmu.h b/drivers/soc/samsung/exynos-pmu.h
> index fbe381e2a2e1..186299a049a8 100644
> --- a/drivers/soc/samsung/exynos-pmu.h
> +++ b/drivers/soc/samsung/exynos-pmu.h
> @@ -13,6 +13,14 @@
>
> #define PMU_TABLE_END (-1U)
>
> +/*
> + * CPU_INFORM register "hint" values are required to be programmed in addition to
> + * the standard PSCI calls to have functional CPU hotplug and CPU idle states.
> + * This is required to workaround limitations in the el3mon/ACPM firmware.
> + */
> +#define CPU_INFORM_CLEAR 0
> +#define CPU_INFORM_C2 1
> +
> struct regmap_access_table;
>
> struct exynos_pmu_conf {
> @@ -20,6 +28,22 @@ struct exynos_pmu_conf {
> u8 val[NUM_SYS_POWERDOWN];
> };
>
> +struct exynos_pmu_context {
> + struct device *dev;
> + const struct exynos_pmu_data *pmu_data;
> + struct regmap *pmureg;
> + struct regmap *pmuintrgen;
> + /*
> + * Serialization lock for CPU hot plug and cpuidle ACPM hint
> + * programming. Also protects in_cpuhp, sys_insuspend & sys_inreboot
> + * flags.
> + */
> + raw_spinlock_t cpupm_lock;
> + unsigned long *in_cpuhp;
> + bool sys_insuspend;
> + bool sys_inreboot;
> +};
> +
> /**
> * struct exynos_pmu_data - of_device_id (match) data
> *
> @@ -44,6 +68,10 @@ struct exynos_pmu_conf {
> * used (i.e. when @pmu_secure is @true).
> * @wr_table: A table of writable register ranges in case a custom regmap is
> * used (i.e. when @pmu_secure is @true).
> + * @cpu_pmu_offline: Optional callback to be called before entering CPU offline
> + * or idle state. Only valid when pmu_cpuhp set to true.
> + * @cpu_pmu_online: Optional callback to be called after CPU onlined or after
> + * exiting idle state. Only valid when pmu_cpuhp set to true.
> */
> struct exynos_pmu_data {
> const struct exynos_pmu_conf *pmu_config;
> @@ -57,6 +85,9 @@ struct exynos_pmu_data {
>
> const struct regmap_access_table *rd_table;
> const struct regmap_access_table *wr_table;
> +
> + int (*cpu_pmu_offline)(struct exynos_pmu_context *pmu_context, unsigned int cpu);
> + int (*cpu_pmu_online)(struct exynos_pmu_context *pmu_context, unsigned int cpu);
> };
>
> extern void __iomem *pmu_base_addr;
> diff --git a/drivers/soc/samsung/gs101-pmu.c b/drivers/soc/samsung/gs101-pmu.c
> index 17dadc1b9c6e..5f2a59924144 100644
> --- a/drivers/soc/samsung/gs101-pmu.c
> +++ b/drivers/soc/samsung/gs101-pmu.c
> @@ -322,11 +322,68 @@ static const struct regmap_access_table gs101_pmu_wr_table = {
> .n_no_ranges = ARRAY_SIZE(gs101_pmu_ro_registers),
> };
>
> +/*
> + * gs101_cpu_pmu_ prefix functions are common code shared by CPU PM notifiers
> + * (CPUIdle) and CPU hotplug callbacks. Functions should be called with IRQs
> + * disabled and cpupm_lock held.
> + */
> +static int gs101_cpu_pmu_online(struct exynos_pmu_context *pmu_context, unsigned int cpu)
> + __must_hold(&pmu_context->cpupm_lock)
> +{
> + unsigned int cpuhint = smp_processor_id();
> + u32 reg, mask;
> +
> + /* clear cpu inform hint */
> + regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint),
> + CPU_INFORM_CLEAR);
> +
> + mask = BIT(cpu);
> +
> + regmap_update_bits(pmu_context->pmuintrgen, EXYNOS_GRP2_INTR_BID_ENABLE,
> + mask, (0 << cpu));
> +
> + regmap_read(pmu_context->pmuintrgen, EXYNOS_GRP2_INTR_BID_UPEND, ®);
> +
> + regmap_write(pmu_context->pmuintrgen, EXYNOS_GRP2_INTR_BID_CLEAR,
> + reg & mask);
> +
> + return 0;
> +}
> +
> +/* Common function shared by both CPU hot plug and CPUIdle */
> +static int gs101_cpu_pmu_offline(struct exynos_pmu_context *pmu_context, unsigned int cpu)
> + __must_hold(&pmu_context->cpupm_lock)
> +{
> + unsigned int cpuhint = smp_processor_id();
> + u32 reg, mask;
> +
> + /* set cpu inform hint */
> + regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint),
> + CPU_INFORM_C2);
> +
> + mask = BIT(cpu);
> + regmap_update_bits(pmu_context->pmuintrgen, EXYNOS_GRP2_INTR_BID_ENABLE,
> + mask, BIT(cpu));
> +
> + regmap_read(pmu_context->pmuintrgen, EXYNOS_GRP1_INTR_BID_UPEND, ®);
> + regmap_write(pmu_context->pmuintrgen, EXYNOS_GRP1_INTR_BID_CLEAR,
> + reg & mask);
> +
> + mask = (BIT(cpu + 8));
> + regmap_read(pmu_context->pmuintrgen, EXYNOS_GRP1_INTR_BID_UPEND, ®);
> + regmap_write(pmu_context->pmuintrgen, EXYNOS_GRP1_INTR_BID_CLEAR,
> + reg & mask);
> +
> + return 0;
> +}
> +
> const struct exynos_pmu_data gs101_pmu_data = {
> .pmu_secure = true,
> .pmu_cpuhp = true,
> .rd_table = &gs101_pmu_rd_table,
> .wr_table = &gs101_pmu_wr_table,
> + .cpu_pmu_offline = gs101_cpu_pmu_offline,
> + .cpu_pmu_online = gs101_cpu_pmu_online,
> };
>
> /*
> diff --git a/include/linux/soc/samsung/exynos-regs-pmu.h b/include/linux/soc/samsung/exynos-regs-pmu.h
> index db8a7ca81080..9c4d3da41dbf 100644
> --- a/include/linux/soc/samsung/exynos-regs-pmu.h
> +++ b/include/linux/soc/samsung/exynos-regs-pmu.h
> @@ -1009,11 +1009,11 @@
> #define GS101_PHY_CTRL_UFS 0x3ec8
>
> /* PMU INTR GEN */
> -#define GS101_GRP1_INTR_BID_UPEND (0x0108)
> -#define GS101_GRP1_INTR_BID_CLEAR (0x010c)
> -#define GS101_GRP2_INTR_BID_ENABLE (0x0200)
> -#define GS101_GRP2_INTR_BID_UPEND (0x0208)
> -#define GS101_GRP2_INTR_BID_CLEAR (0x020c)
> +#define EXYNOS_GRP1_INTR_BID_UPEND (0x0108)
> +#define EXYNOS_GRP1_INTR_BID_CLEAR (0x010c)
> +#define EXYNOS_GRP2_INTR_BID_ENABLE (0x0200)
> +#define EXYNOS_GRP2_INTR_BID_UPEND (0x0208)
> +#define EXYNOS_GRP2_INTR_BID_CLEAR (0x020c)
>
> /* exynosautov920 */
> #define EXYNOSAUTOV920_PHY_CTRL_USB20 (0x0710)
>
> --
> 2.51.0
>
More information about the linux-arm-kernel
mailing list