[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, &reg);
> -
> -       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, &reg);
> -       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, &reg);
> -       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, &reg);
> +
> +       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, &reg);
> +       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, &reg);
> +       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