Linux Power Management development
 help / color / mirror / Atom feed
* Re: [PATCH RESEND v1] thermal: core: fix blocking in unregistering zone
From: Guenter Roeck @ 2026-04-08 15:32 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Jiajia Liu, Daniel Lezcano, Zhang Rui, Lukasz Luba, linux-pm,
	linux-kernel, Armin Wolf, linux-hwmon
In-Reply-To: <CAJZ5v0jfi_gXPVq9E2eJe_0MG4vVojyDo6=ABv4fNFK=Q_qpug@mail.gmail.com>

On 4/8/26 08:05, Rafael J. Wysocki wrote:
> On Sun, Apr 5, 2026 at 5:34 AM Guenter Roeck <linux@roeck-us.net> wrote:
>>
>> On 4/4/26 10:38, Rafael J. Wysocki wrote:
>>> On Sat, Apr 4, 2026 at 4:02 PM Guenter Roeck <linux@roeck-us.net> wrote:
>>>>
>>>> On 4/4/26 05:58, Rafael J. Wysocki wrote:
>>>>> On Fri, Apr 3, 2026 at 4:20 PM Guenter Roeck <linux@roeck-us.net> wrote:
>>>>>>
>>>>>> On 4/3/26 05:52, Rafael J. Wysocki wrote:
>>>>>> .[ ... ]
>>>>>>> It appears to work for me, but I'm not sure if having multiple hwmon class
>>>>>>> devices with the same value in the name attribute is fine.
>>>>>>
>>>>>> Like this ?
>>>>>>
>>>>>> $ cd /sys/class/hwmon
>>>>>> $ grep . */name
>>>>>> hwmon0/name:r8169_0_c00:00
>>>>>> hwmon1/name:nvme
>>>>>> hwmon2/name:nvme
>>>>>> hwmon3/name:nct6687
>>>>>> hwmon4/name:k10temp
>>>>>> hwmon5/name:spd5118
>>>>>> hwmon6/name:spd5118
>>>>>> hwmon7/name:spd5118
>>>>>> hwmon8/name:spd5118
>>>>>> hwmon9/name:mt7921_phy0
>>>>>
>>>>> Yes.
>>>>>
>>>>>> Names such as "r8169_0_c00:00" and "mt7921_phy0" are actually overkill
>>>>>> since the "sensors" command makes it
>>>>>>
>>>>>> r8169_0_c00:00-mdio-0
>>>>>> Adapter: MDIO adapter
>>>>>> temp1:        +36.0°C  (high = +120.0°C)
>>>>>>
>>>>>> mt7921_phy0-pci-0d00
>>>>>> Adapter: PCI adapter
>>>>>> temp1:        +30.0°C
>>>>>>
>>>>>> essentially duplicating the device index.
>>>>>
>>>>> Well, with the patch posted by me, the output of sensors from a test
>>>>> system looks like this:
>>>>>
>>>>> acpitz-acpi-0
>>>>> Adapter: ACPI interface
>>>>> temp1:        +16.8°C
>>>>>
>>>>> pch_cannonlake-virtual-0
>>>>> Adapter: Virtual device
>>>>> temp1:        +33.0°C
>>>>>
>>>>> acpitz-acpi-0
>>>>> Adapter: ACPI interface
>>>>> temp1:        +27.8°C
>>>>>
>>>>> (some further data excluded), which is kind of confusing (note the
>>>>> duplicate acpitz-acpi-0 entries with different values of temp1).
>>>>>
>>>>
>>>> Yes, agreed, that is confusing. I would have expected the second one
>>>> to be identified as "acpitz-acpi-1". Do they both have the same parent ?
>>>
>>> No, they don't.
>>>
>>> The parent of each of them is a thermal zone device and both parents
>>> have the same "type" value.
>>>
>>>>> That could be disambiguated by concatenating the thermal zone ID
>>>>> (possibly after a '_') to the name.  Or the "temp*" things for thermal
>>>>> zones of the same type could carry different numbers.
>>>>>
>>>>> A less attractive alternative would be to register a special virtual
>>>>> device serving as a parent for all hwmon interfaces registered
>>>>> automatically for thermal zones.
>>>>
>>>> If they all have the same parent, technically it should be a single
>>>> hwmon device with multiple sensors, as in:
>>>>
>>>> acpitz-acpi-0
>>>> Adapter: ACPI interface
>>>> temp1:        +16.8°C
>>>> temp2:        +27.8°C
>>>
>>> So somebody tried to make it look like that by registering hwmon
>>> interfaces for all of the thermal zones of the same type under one of
>>> them, but that (quite obviously) doesn't work.
>>
>> Not sure I understand why that doesn't work or why that is obvious,
>> but I'll take you by your word (I would agree that the current
>> _implementation_ looks problematic).
> 
> For example, say that there are two ACPI thermal zones on a system
> 
> /sys/devices/virtual/thermal/thermal_zone0/
> /sys/devices/virtual/thermal/thermal_zone1/
> 
> The current mainline code registers a hwmon class device for thermal_zone0 only:
> 
> /sys/devices/virtual/thermal/thermal_zone0/hwmon0/
> 
> because the type is "acpitz" for both of them, but it adds a sysfs
> attribute that belongs to thermal_zone1 under it:
> 
> /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp2_input
> 
> There is also
> 
> /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp1_input
> 
> but it belongs to thermal_zone0.
> 
> Interesting things happen when thermal_zone0 is removed, for example
> because the ACPI thermal driver is unbound from the underlying
> platform device.  Namely, the removal code skips the removal of hwmon0
> because of the temp2_input attribute belonging to thermal_zone1 which
> effectively prevents thermal_zone0 removal from making progress.
> 
> AFAICS, nothing particularly smart can be done to address this issue
> while retaining the current design of the code.  Reparenting hwmon0 to
> thermal_zone1 may confuse user space as well as removing hwmon0 along
> with temp2_input.  That's why I think that this is a design issue.
> 

The ACPI power meter driver has pretty much the same problem. A clear
solution would require making hwmon sysfs attributes dynamic in nature
(i.e., by adding the ability to change the visibility of attributes in
runtime). I have started working on that, but did not have time to
complete the work. The ACPI power meter driver uses a kludge around that:
It unregisters the hwmon device whenever it gets a METER_NOTIFY_CONFIG
event and re-registers it.

Anyway, registering separate hwmon devices, one per thermal zone,
is perfectly fine with me.

Guenter


^ permalink raw reply

* Re: [PATCH RESEND v1] thermal: core: fix blocking in unregistering zone
From: Rafael J. Wysocki @ 2026-04-08 15:57 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Rafael J. Wysocki, Jiajia Liu, Daniel Lezcano, Zhang Rui,
	Lukasz Luba, linux-pm, linux-kernel, Armin Wolf, linux-hwmon
In-Reply-To: <e2e9c203-608b-4c96-b034-ba95eec61ac5@roeck-us.net>

On Wed, Apr 8, 2026 at 5:32 PM Guenter Roeck <linux@roeck-us.net> wrote:
>
> On 4/8/26 08:05, Rafael J. Wysocki wrote:
> > On Sun, Apr 5, 2026 at 5:34 AM Guenter Roeck <linux@roeck-us.net> wrote:
> >>
> >> On 4/4/26 10:38, Rafael J. Wysocki wrote:
> >>> On Sat, Apr 4, 2026 at 4:02 PM Guenter Roeck <linux@roeck-us.net> wrote:
> >>>>
> >>>> On 4/4/26 05:58, Rafael J. Wysocki wrote:
> >>>>> On Fri, Apr 3, 2026 at 4:20 PM Guenter Roeck <linux@roeck-us.net> wrote:
> >>>>>>
> >>>>>> On 4/3/26 05:52, Rafael J. Wysocki wrote:
> >>>>>> .[ ... ]
> >>>>>>> It appears to work for me, but I'm not sure if having multiple hwmon class
> >>>>>>> devices with the same value in the name attribute is fine.
> >>>>>>
> >>>>>> Like this ?
> >>>>>>
> >>>>>> $ cd /sys/class/hwmon
> >>>>>> $ grep . */name
> >>>>>> hwmon0/name:r8169_0_c00:00
> >>>>>> hwmon1/name:nvme
> >>>>>> hwmon2/name:nvme
> >>>>>> hwmon3/name:nct6687
> >>>>>> hwmon4/name:k10temp
> >>>>>> hwmon5/name:spd5118
> >>>>>> hwmon6/name:spd5118
> >>>>>> hwmon7/name:spd5118
> >>>>>> hwmon8/name:spd5118
> >>>>>> hwmon9/name:mt7921_phy0
> >>>>>
> >>>>> Yes.
> >>>>>
> >>>>>> Names such as "r8169_0_c00:00" and "mt7921_phy0" are actually overkill
> >>>>>> since the "sensors" command makes it
> >>>>>>
> >>>>>> r8169_0_c00:00-mdio-0
> >>>>>> Adapter: MDIO adapter
> >>>>>> temp1:        +36.0°C  (high = +120.0°C)
> >>>>>>
> >>>>>> mt7921_phy0-pci-0d00
> >>>>>> Adapter: PCI adapter
> >>>>>> temp1:        +30.0°C
> >>>>>>
> >>>>>> essentially duplicating the device index.
> >>>>>
> >>>>> Well, with the patch posted by me, the output of sensors from a test
> >>>>> system looks like this:
> >>>>>
> >>>>> acpitz-acpi-0
> >>>>> Adapter: ACPI interface
> >>>>> temp1:        +16.8°C
> >>>>>
> >>>>> pch_cannonlake-virtual-0
> >>>>> Adapter: Virtual device
> >>>>> temp1:        +33.0°C
> >>>>>
> >>>>> acpitz-acpi-0
> >>>>> Adapter: ACPI interface
> >>>>> temp1:        +27.8°C
> >>>>>
> >>>>> (some further data excluded), which is kind of confusing (note the
> >>>>> duplicate acpitz-acpi-0 entries with different values of temp1).
> >>>>>
> >>>>
> >>>> Yes, agreed, that is confusing. I would have expected the second one
> >>>> to be identified as "acpitz-acpi-1". Do they both have the same parent ?
> >>>
> >>> No, they don't.
> >>>
> >>> The parent of each of them is a thermal zone device and both parents
> >>> have the same "type" value.
> >>>
> >>>>> That could be disambiguated by concatenating the thermal zone ID
> >>>>> (possibly after a '_') to the name.  Or the "temp*" things for thermal
> >>>>> zones of the same type could carry different numbers.
> >>>>>
> >>>>> A less attractive alternative would be to register a special virtual
> >>>>> device serving as a parent for all hwmon interfaces registered
> >>>>> automatically for thermal zones.
> >>>>
> >>>> If they all have the same parent, technically it should be a single
> >>>> hwmon device with multiple sensors, as in:
> >>>>
> >>>> acpitz-acpi-0
> >>>> Adapter: ACPI interface
> >>>> temp1:        +16.8°C
> >>>> temp2:        +27.8°C
> >>>
> >>> So somebody tried to make it look like that by registering hwmon
> >>> interfaces for all of the thermal zones of the same type under one of
> >>> them, but that (quite obviously) doesn't work.
> >>
> >> Not sure I understand why that doesn't work or why that is obvious,
> >> but I'll take you by your word (I would agree that the current
> >> _implementation_ looks problematic).
> >
> > For example, say that there are two ACPI thermal zones on a system
> >
> > /sys/devices/virtual/thermal/thermal_zone0/
> > /sys/devices/virtual/thermal/thermal_zone1/
> >
> > The current mainline code registers a hwmon class device for thermal_zone0 only:
> >
> > /sys/devices/virtual/thermal/thermal_zone0/hwmon0/
> >
> > because the type is "acpitz" for both of them, but it adds a sysfs
> > attribute that belongs to thermal_zone1 under it:
> >
> > /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp2_input
> >
> > There is also
> >
> > /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp1_input
> >
> > but it belongs to thermal_zone0.
> >
> > Interesting things happen when thermal_zone0 is removed, for example
> > because the ACPI thermal driver is unbound from the underlying
> > platform device.  Namely, the removal code skips the removal of hwmon0
> > because of the temp2_input attribute belonging to thermal_zone1 which
> > effectively prevents thermal_zone0 removal from making progress.
> >
> > AFAICS, nothing particularly smart can be done to address this issue
> > while retaining the current design of the code.  Reparenting hwmon0 to
> > thermal_zone1 may confuse user space as well as removing hwmon0 along
> > with temp2_input.  That's why I think that this is a design issue.
> >
>
> The ACPI power meter driver has pretty much the same problem. A clear
> solution would require making hwmon sysfs attributes dynamic in nature
> (i.e., by adding the ability to change the visibility of attributes in
> runtime). I have started working on that, but did not have time to
> complete the work. The ACPI power meter driver uses a kludge around that:
> It unregisters the hwmon device whenever it gets a METER_NOTIFY_CONFIG
> event and re-registers it.
>
> Anyway, registering separate hwmon devices, one per thermal zone,
> is perfectly fine with me.

OK, I'll do that and see how it goes.

Thanks for the feedback!

^ permalink raw reply

* Re: [patch V2 01/11] hrtimer: Provide hrtimer_start_range_ns_user()
From: Frederic Weisbecker @ 2026-04-08 16:53 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: LKML, Calvin Owens, Anna-Maria Behnsen, Peter Zijlstra (Intel),
	John Stultz, Stephen Boyd, Alexander Viro, Christian Brauner,
	Jan Kara, linux-fsdevel, Sebastian Reichel, linux-pm,
	Pablo Neira Ayuso, Florian Westphal, Phil Sutter, netfilter-devel,
	coreteam
In-Reply-To: <20260408114951.995031895@kernel.org>

Le Wed, Apr 08, 2026 at 01:53:46PM +0200, Thomas Gleixner a écrit :
> Calvin reported an odd NMI watchdog lockup which claims that the CPU locked
> up in user space. He provided a reproducer, which set's up a timerfd based
> timer and then rearms it in a loop with an absolute expiry time of 1ns.
> 
> As the expiry time is in the past, the timer ends up as the first expiring
> timer in the per CPU hrtimer base and the clockevent device is programmed
> with the minimum delta value. If the machine is fast enough, this ends up
> in a endless loop of programming the delta value to the minimum value
> defined by the clock event device, before the timer interrupt can fire,
> which starves the interrupt and consequently triggers the lockup detector
> because the hrtimer callback of the lockup mechanism is never invoked.
> 
> The clockevents code already has a last resort mechanism to prevent that,
> but it's sensible to catch such issues before trying to reprogram the clock
> event device.
> 
> Provide a variant of hrtimer_start_range_ns(), which sanity checks the
> timer after queueing it. It does not so before because the timer might be
> armed and therefore needs to be dequeued. also we optimize for the latest
> possible point to check, so that the clock event prevention is avoided as
> much as possible.
> 
> If the timer is already expired _before_ the clock event is reprogrammed,
> remove the timer from the queue and signal to the caller that the operation
> failed by returning false.
> 
> That allows the caller to take immediate action without going through the
> loops and hoops of the hrtimer interrupt.
> 
> The queueing code can't invoke the timer callback as the caller might hold
> a lock which is taken in the callback.
> 
> Add a tracepoint which allows to analyze the expired at start situation.
> 
> Reported-by: Calvin Owens <calvin@wbinvd.org>
> Signed-off-by: Thomas Gleixner <tglx@kernel.org>
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Frederic Weisbecker <frederic@kernel.org>

Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply

* Re: [patch V2 02/11] hrtimer: Use hrtimer_start_expires_user() for hrtimer sleepers
From: Frederic Weisbecker @ 2026-04-08 20:41 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: LKML, Peter Zijlstra (Intel), Anna-Maria Behnsen, Calvin Owens,
	John Stultz, Stephen Boyd, Alexander Viro, Christian Brauner,
	Jan Kara, linux-fsdevel, Sebastian Reichel, linux-pm,
	Pablo Neira Ayuso, Florian Westphal, Phil Sutter, netfilter-devel,
	coreteam
In-Reply-To: <20260408114952.062400833@kernel.org>

Le Wed, Apr 08, 2026 at 01:53:52PM +0200, Thomas Gleixner a écrit :
> Most hrtimer sleepers are user controlled and user space can hand arbitrary
> expiry values in as long as they are valid timespecs. If the expiry value
> is in the past then this requires a full loop through reprogramming the
> clock event device, taking the hrtimer interrupt, waking the task and
> reprogram again.
> 
> Use hrtimer_start_expires_user() which avoids the full round trip by
> checking the timer for expiry on enqueue.
> 
> Signed-off-by: Thomas Gleixner <tglx@kernel.org>
> Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Frederic Weisbecker <frederic@kernel.org>

Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply

* [PATCH v2 0/7] Enable NVL support in intel_pmc_core
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm

This patch series introduces two new features, enhances existing
functionalities, and adds NVL support to the intel_pmc_core driver.

The first three patches add new attributes to improve Package C-state
debugging. The fourth and fifth patches refine current functionality
for better support. The sixth patch enables the intel_pmc_core driver
retrieves PMC information only for available PMCs. Finally, the last
patch adds support for Nova Lake platforms.

v2->v1:
- Add a patch to use __free(pci_dev_put) in pmc_core_punit_pmt_init().
- When using scoped base cleanup method, move variable declaration and
  assignment in one place.
- Simplifies logic and remove unneeded offset variables.
- Create common helper function to used by pmc_core_pkgc_ltr_blocker_show()
  and pmc_core_pkgc_blocker_residency_show()
- Add num_pmcs field in pmc_dev_info struct to store the number of PMCs
  available in the platform.
- Use lowercase letter for variable in nvl.c().
- Fix typo.

Xi Pardee (7):
  platform/x86/intel/pmc: Use __free() in pmc_core_punit_pmt_init()
  platform/x86/intel/pmc: Enable PkgC LTR blocking counter
  platform/x86/intel/pmc: Enable Pkgc blocking residency counter
  platform/x86/intel/pmc: Use PCI DID for PMC SSRAM device discovery
  platform/x86/intel/pmc: Add support for variable DMU offsets
  platform/x86/intel/pmc: Retrieve PMC info only for available PMCs
  platform/x86/intel/pmc: Add Nova Lake support to intel_pmc_core driver

 drivers/platform/x86/intel/pmc/Makefile |    3 +-
 drivers/platform/x86/intel/pmc/arl.c    |   13 +-
 drivers/platform/x86/intel/pmc/core.c   |  137 +-
 drivers/platform/x86/intel/pmc/core.h   |   66 +-
 drivers/platform/x86/intel/pmc/lnl.c    |    6 +-
 drivers/platform/x86/intel/pmc/mtl.c    |    7 +-
 drivers/platform/x86/intel/pmc/nvl.c    | 1539 +++++++++++++++++++++++
 drivers/platform/x86/intel/pmc/ptl.c    |    8 +-
 drivers/platform/x86/intel/pmc/wcl.c    |    6 +-
 9 files changed, 1747 insertions(+), 38 deletions(-)
 create mode 100644 drivers/platform/x86/intel/pmc/nvl.c

-- 
2.43.0


^ permalink raw reply

* [PATCH v2 1/7] platform/x86/intel/pmc: Use __free() in pmc_core_punit_pmt_init()
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Use scope-based cleanup in pmc_core_punit_pmt_init() instead of manually
freeing. This simplifies the code flow by removing the explicit put call,
making it less error-prone.

Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/core.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index d91e1ab842d65..c8a92d6235203 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1325,16 +1325,15 @@ static struct telem_endpoint *pmc_core_register_endpoint(struct pci_dev *pcidev,
 void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids)
 {
 	struct telem_endpoint *ep;
-	struct pci_dev *pcidev;
 
-	pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(10, 0));
+	struct pci_dev *pcidev __free(pci_dev_put) = pci_get_domain_bus_and_slot(0, 0,
+										 PCI_DEVFN(10, 0));
 	if (!pcidev) {
 		dev_err(&pmcdev->pdev->dev, "PUNIT PMT device not found.");
 		return;
 	}
 
 	ep = pmc_core_register_endpoint(pcidev, guids);
-	pci_dev_put(pcidev);
 	if (IS_ERR(ep)) {
 		dev_err(&pmcdev->pdev->dev,
 			"pmc_core: couldn't get DMU telem endpoint %ld",
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 2/7] platform/x86/intel/pmc: Enable PkgC LTR blocking counter
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Enable the Package C-state LTR blocking counter in the PMT telemetry
region. This counter records how many times any Package C-state entry
is blocked for the specified reasons.

Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/core.c | 74 +++++++++++++++++++++++----
 drivers/platform/x86/intel/pmc/core.h | 15 +++++-
 2 files changed, 77 insertions(+), 12 deletions(-)

diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index c8a92d6235203..5c519942ec58c 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1071,6 +1071,29 @@ static int pmc_core_die_c6_us_show(struct seq_file *s, void *unused)
 }
 DEFINE_SHOW_ATTRIBUTE(pmc_core_die_c6_us);
 
+static int pmc_core_pkgc_ltr_blocker_show(struct seq_file *s, void *unused)
+{
+	struct pmc_dev *pmcdev = s->private;
+	const char **pkgc_ltr_blocker_counters;
+	unsigned int i;
+	u32 counter;
+	int ret;
+
+	pkgc_ltr_blocker_counters = pmcdev->pkgc_ltr_blocker_counters;
+	for (i = 0; pkgc_ltr_blocker_counters[i]; i++) {
+		ret = pmt_telem_read32(pmcdev->pc_ep,
+				       pmcdev->pkgc_ltr_blocker_offset + i,
+				       &counter, 1);
+
+		if (ret)
+			return ret;
+		seq_printf(s, "%-30s %-30u\n", pkgc_ltr_blocker_counters[i], counter);
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_ltr_blocker);
+
 static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
 {
 	struct pmc_dev *pmcdev = s->private;
@@ -1322,7 +1345,7 @@ static struct telem_endpoint *pmc_core_register_endpoint(struct pci_dev *pcidev,
 	return ERR_PTR(-ENODEV);
 }
 
-void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids)
+void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
 {
 	struct telem_endpoint *ep;
 
@@ -1333,16 +1356,32 @@ void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids)
 		return;
 	}
 
-	ep = pmc_core_register_endpoint(pcidev, guids);
-	if (IS_ERR(ep)) {
-		dev_err(&pmcdev->pdev->dev,
-			"pmc_core: couldn't get DMU telem endpoint %ld",
-			PTR_ERR(ep));
-		return;
+	if (pmc_dev_info->dmu_guids) {
+		ep = pmc_core_register_endpoint(pcidev, pmc_dev_info->dmu_guids);
+		if (IS_ERR(ep)) {
+			dev_err(&pmcdev->pdev->dev,
+				"pmc_core: couldn't get DMU telem endpoint %ld",
+				PTR_ERR(ep));
+			return;
+		}
+
+		pmcdev->punit_ep = ep;
+		pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET;
 	}
 
-	pmcdev->punit_ep = ep;
-	pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET;
+	if (pmc_dev_info->pc_guid) {
+		ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc_dev_info->pc_guid, 0);
+		if (IS_ERR(ep)) {
+			dev_err(&pmcdev->pdev->dev,
+				"pmc_core: couldn't get Package C-state telem endpoint %ld",
+				PTR_ERR(ep));
+			return;
+		}
+
+		pmcdev->pc_ep = ep;
+		pmcdev->pkgc_ltr_blocker_counters = pmc_dev_info->pkgc_ltr_blocker_counters;
+		pmcdev->pkgc_ltr_blocker_offset = pmc_dev_info->pkgc_ltr_blocker_offset;
+	}
 }
 
 void pmc_core_set_device_d3(unsigned int device)
@@ -1466,6 +1505,13 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info
 				    pmcdev->dbgfs_dir, pmcdev,
 				    &pmc_core_die_c6_us_fops);
 	}
+
+	if (pmcdev->pc_ep) {
+		debugfs_create_file("pkgc_ltr_blocker_show", 0444,
+				    pmcdev->dbgfs_dir, pmcdev,
+				    &pmc_core_pkgc_ltr_blocker_fops);
+	}
+
 }
 
 /*
@@ -1716,8 +1762,8 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
 	}
 
 	pmc_core_get_low_power_modes(pmcdev);
-	if (pmc_dev_info->dmu_guids)
-		pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guids);
+	if (pmc_dev_info->dmu_guids || pmc_dev_info->pc_guid)
+		pmc_core_punit_pmt_init(pmcdev, pmc_dev_info);
 
 	if (ssram) {
 		ret = pmc_core_get_telem_info(pmcdev, pmc_dev_info);
@@ -1738,6 +1784,9 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
 	if (pmcdev->punit_ep)
 		pmt_telem_unregister_endpoint(pmcdev->punit_ep);
 
+	if (pmcdev->pc_ep)
+		pmt_telem_unregister_endpoint(pmcdev->pc_ep);
+
 	return ret;
 }
 
@@ -1834,6 +1883,9 @@ static void pmc_core_clean_structure(struct platform_device *pdev)
 	if (pmcdev->punit_ep)
 		pmt_telem_unregister_endpoint(pmcdev->punit_ep);
 
+	if (pmcdev->pc_ep)
+		pmt_telem_unregister_endpoint(pmcdev->pc_ep);
+
 	platform_set_drvdata(pdev, NULL);
 }
 
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index 118c8740ad3aa..a20aab73c1409 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -453,6 +453,9 @@ struct pmc {
  * @suspend:		Function to perform platform specific suspend
  * @resume:		Function to perform platform specific resume
  *
+ * @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters
+ * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
+ *
  * pmc_dev contains info about power management controller device.
  */
 struct pmc_dev {
@@ -471,8 +474,12 @@ struct pmc_dev {
 	u8 num_of_pkgc;
 
 	u32 die_c6_offset;
+	struct telem_endpoint *pc_ep;
 	struct telem_endpoint *punit_ep;
 	struct pmc_info *regmap_list;
+
+	const char **pkgc_ltr_blocker_counters;
+	u32 pkgc_ltr_blocker_offset;
 };
 
 enum pmc_index {
@@ -486,12 +493,15 @@ enum pmc_index {
  * struct pmc_dev_info - Structure to keep PMC device info
  * @pci_func:		Function number of the primary PMC
  * @dmu_guids:		List of Die Management Unit GUID
+ * @pc_guid:		GUID for telemetry region to read PKGC blocker info
+ * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
  * @regmap_list:	Pointer to a list of pmc_info structure that could be
  *			available for the platform. When set, this field implies
  *			SSRAM support.
  * @map:		Pointer to a pmc_reg_map struct that contains platform
  *			specific attributes of the primary PMC
  * @sub_req_show:	File operations to show substate requirements
+ * @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters
  * @suspend:		Function to perform platform specific suspend
  * @resume:		Function to perform platform specific resume
  * @init:		Function to perform platform specific init action
@@ -500,9 +510,12 @@ enum pmc_index {
 struct pmc_dev_info {
 	u8 pci_func;
 	u32 *dmu_guids;
+	u32 pc_guid;
+	u32 pkgc_ltr_blocker_offset;
 	struct pmc_info *regmap_list;
 	const struct pmc_reg_map *map;
 	const struct file_operations *sub_req_show;
+	const char **pkgc_ltr_blocker_counters;
 	void (*suspend)(struct pmc_dev *pmcdev);
 	int (*resume)(struct pmc_dev *pmcdev);
 	int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
@@ -535,7 +548,7 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
 
 int pmc_core_resume_common(struct pmc_dev *pmcdev);
 int get_primary_reg_base(struct pmc *pmc);
-void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
+void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
 void pmc_core_set_device_d3(unsigned int device);
 
 int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 3/7] platform/x86/intel/pmc: Enable Pkgc blocking residency counter
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Enable the Package C-state blocking counter in the PMT telemetry
region. This counter reports the number of 10 µs intervals during
which a Package C-state 10.2/3 entry was blocked for the specified
reasons.

Create a common helper for pmc_core_pkgc_ltr_blocker_show() and
pmc_core_pkgc_blocker_residency_show() as these two functions
share similar logic.

Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/core.c | 40 ++++++++++++++++++++-------
 drivers/platform/x86/intel/pmc/core.h |  8 ++++++
 2 files changed, 38 insertions(+), 10 deletions(-)

diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 5c519942ec58c..94ae098a155a6 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1071,29 +1071,44 @@ static int pmc_core_die_c6_us_show(struct seq_file *s, void *unused)
 }
 DEFINE_SHOW_ATTRIBUTE(pmc_core_die_c6_us);
 
-static int pmc_core_pkgc_ltr_blocker_show(struct seq_file *s, void *unused)
+static int pmc_core_pkgc_counters_show(struct seq_file *s,
+				       struct telem_endpoint *ep,
+				       u32 offset, const char **counters)
 {
-	struct pmc_dev *pmcdev = s->private;
-	const char **pkgc_ltr_blocker_counters;
 	unsigned int i;
 	u32 counter;
 	int ret;
 
-	pkgc_ltr_blocker_counters = pmcdev->pkgc_ltr_blocker_counters;
-	for (i = 0; pkgc_ltr_blocker_counters[i]; i++) {
-		ret = pmt_telem_read32(pmcdev->pc_ep,
-				       pmcdev->pkgc_ltr_blocker_offset + i,
-				       &counter, 1);
-
+	for (i = 0; counters[i]; i++) {
+		ret = pmt_telem_read32(ep, offset + i, &counter, 1);
 		if (ret)
 			return ret;
-		seq_printf(s, "%-30s %-30u\n", pkgc_ltr_blocker_counters[i], counter);
+		seq_printf(s, "%-30s %-30u\n", counters[i], counter);
 	}
 
 	return 0;
 }
+
+static int pmc_core_pkgc_ltr_blocker_show(struct seq_file *s, void *unused)
+{
+	struct pmc_dev *pmcdev = s->private;
+
+	return pmc_core_pkgc_counters_show(s, pmcdev->pc_ep,
+					   pmcdev->pkgc_ltr_blocker_offset,
+					   pmcdev->pkgc_ltr_blocker_counters);
+}
 DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_ltr_blocker);
 
+static int pmc_core_pkgc_blocker_residency_show(struct seq_file *s, void *unused)
+{
+	struct pmc_dev *pmcdev = s->private;
+
+	return pmc_core_pkgc_counters_show(s, pmcdev->pc_ep,
+					   pmcdev->pkgc_blocker_offset,
+					   pmcdev->pkgc_blocker_counters);
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_blocker_residency);
+
 static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
 {
 	struct pmc_dev *pmcdev = s->private;
@@ -1381,6 +1396,8 @@ void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_de
 		pmcdev->pc_ep = ep;
 		pmcdev->pkgc_ltr_blocker_counters = pmc_dev_info->pkgc_ltr_blocker_counters;
 		pmcdev->pkgc_ltr_blocker_offset = pmc_dev_info->pkgc_ltr_blocker_offset;
+		pmcdev->pkgc_blocker_counters = pmc_dev_info->pkgc_blocker_counters;
+		pmcdev->pkgc_blocker_offset = pmc_dev_info->pkgc_blocker_offset;
 	}
 }
 
@@ -1510,6 +1527,9 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info
 		debugfs_create_file("pkgc_ltr_blocker_show", 0444,
 				    pmcdev->dbgfs_dir, pmcdev,
 				    &pmc_core_pkgc_ltr_blocker_fops);
+		debugfs_create_file("pkgc_blocker_residency_show", 0444,
+				    pmcdev->dbgfs_dir, pmcdev,
+				    &pmc_core_pkgc_blocker_residency_fops);
 	}
 
 }
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index a20aab73c1409..829b1dee3f636 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -455,6 +455,8 @@ struct pmc {
  *
  * @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters
  * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
+ * @pkgc_blocker_counters: Array of PKGC blocker counters
+ * @pkgc_blocker_offset: Offset to PKGC blocker in telemetry region
  *
  * pmc_dev contains info about power management controller device.
  */
@@ -480,6 +482,8 @@ struct pmc_dev {
 
 	const char **pkgc_ltr_blocker_counters;
 	u32 pkgc_ltr_blocker_offset;
+	const char **pkgc_blocker_counters;
+	u32 pkgc_blocker_offset;
 };
 
 enum pmc_index {
@@ -495,6 +499,7 @@ enum pmc_index {
  * @dmu_guids:		List of Die Management Unit GUID
  * @pc_guid:		GUID for telemetry region to read PKGC blocker info
  * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
+ * @pkgc_blocker_offset:Offset to PKGC blocker in telemetry region
  * @regmap_list:	Pointer to a list of pmc_info structure that could be
  *			available for the platform. When set, this field implies
  *			SSRAM support.
@@ -502,6 +507,7 @@ enum pmc_index {
  *			specific attributes of the primary PMC
  * @sub_req_show:	File operations to show substate requirements
  * @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters
+ * @pkgc_blocker_counters: Array of PKGC blocker counters
  * @suspend:		Function to perform platform specific suspend
  * @resume:		Function to perform platform specific resume
  * @init:		Function to perform platform specific init action
@@ -512,10 +518,12 @@ struct pmc_dev_info {
 	u32 *dmu_guids;
 	u32 pc_guid;
 	u32 pkgc_ltr_blocker_offset;
+	u32 pkgc_blocker_offset;
 	struct pmc_info *regmap_list;
 	const struct pmc_reg_map *map;
 	const struct file_operations *sub_req_show;
 	const char **pkgc_ltr_blocker_counters;
+	const char **pkgc_blocker_counters;
 	void (*suspend)(struct pmc_dev *pmcdev);
 	int (*resume)(struct pmc_dev *pmcdev);
 	int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 4/7] platform/x86/intel/pmc: Use PCI DID for PMC SSRAM device discovery
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Update the PMC SSRAM discovery process to identify the device using its
PCI Device ID rather than relying on a fixed PCI bus location. The
enumeration of integrated devices on the PCI bus is no longer guaranteed
to be consistent across CPUs.

On earlier platforms, the IOE and PCH SSRAM devices were hidden from the
BIOS, and the SOC SSRAM device is associated to telemetry regions from all
available SSRAM devices. Starting with Nova Lake, the IOE and PCH SSRAM
devices register their telemetry regions independently, meaning each
telemetry region is now linked to its corresponding SSRAM device. A new
ssram_hidden attribute has been added to the pmc_dev_info structure to
reflect this distinction.

Signed-off-by: David E. Box <david.e.box@linux.intel.com>
Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/arl.c  |  4 ++--
 drivers/platform/x86/intel/pmc/core.c | 17 ++++++++++++-----
 drivers/platform/x86/intel/pmc/core.h |  6 ++++--
 drivers/platform/x86/intel/pmc/lnl.c  |  2 +-
 drivers/platform/x86/intel/pmc/mtl.c  |  2 +-
 drivers/platform/x86/intel/pmc/ptl.c  |  2 +-
 drivers/platform/x86/intel/pmc/wcl.c  |  2 +-
 7 files changed, 22 insertions(+), 13 deletions(-)

diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c
index eb23bc68340ab..95372a0807acf 100644
--- a/drivers/platform/x86/intel/pmc/arl.c
+++ b/drivers/platform/x86/intel/pmc/arl.c
@@ -720,7 +720,6 @@ static int arl_h_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_
 
 static u32 ARL_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, 0x0};
 struct pmc_dev_info arl_pmc_dev = {
-	.pci_func = 0,
 	.dmu_guids = ARL_PMT_DMU_GUIDS,
 	.regmap_list = arl_pmc_info_list,
 	.map = &arl_socs_reg_map,
@@ -729,11 +728,11 @@ struct pmc_dev_info arl_pmc_dev = {
 	.resume = arl_resume,
 	.init = arl_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
+	.ssram_hidden = true,
 };
 
 static u32 ARL_H_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, ARL_H_PMT_DMU_GUID, 0x0};
 struct pmc_dev_info arl_h_pmc_dev = {
-	.pci_func = 2,
 	.dmu_guids = ARL_H_PMT_DMU_GUIDS,
 	.regmap_list = arl_pmc_info_list,
 	.map = &mtl_socm_reg_map,
@@ -742,4 +741,5 @@ struct pmc_dev_info arl_h_pmc_dev = {
 	.resume = arl_h_resume,
 	.init = arl_h_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
+	.ssram_hidden = true,
 };
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 94ae098a155a6..e0ac329e6723a 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1646,17 +1646,13 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
 
 static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
 {
-	struct pci_dev *pcidev __free(pci_dev_put) = NULL;
 	struct telem_endpoint *ep;
 	unsigned int pmc_idx;
 	int ret;
 
-	pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, pmc_dev_info->pci_func));
-	if (!pcidev)
-		return -ENODEV;
-
 	for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
 		struct pmc *pmc;
+		u16 devid;
 
 		pmc = pmcdev->pmcs[pmc_idx];
 		if (!pmc)
@@ -1665,6 +1661,16 @@ static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *
 		if (!pmc->map->lpm_req_guid)
 			return -ENXIO;
 
+		if (pmc_dev_info->ssram_hidden)
+			devid = pmcdev->pmcs[PMC_IDX_MAIN]->devid;
+		else
+			devid = pmc->devid;
+
+		struct pci_dev *pcidev __free(pci_dev_put) =
+			pci_get_device(PCI_VENDOR_ID_INTEL, devid, NULL);
+		if (!pcidev)
+			return -ENODEV;
+
 		ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc->map->lpm_req_guid, 0);
 		if (IS_ERR(ep)) {
 			dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep);
@@ -1715,6 +1721,7 @@ static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx)
 
 	pmc->map = map;
 	pmc->base_addr = pmc_ssram_telemetry.base_addr;
+	pmc->devid = pmc_ssram_telemetry.devid;
 	pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length);
 
 	if (!pmc->regbase) {
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index 829b1dee3f636..f385a0eccd2c2 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -425,6 +425,7 @@ struct pmc_info {
  * @ltr_ign:		Holds LTR ignore data while suspended
  * @num_lpm_modes:	Count of enabled modes
  * @lpm_en_modes:	Array of enabled modes from lowest to highest priority
+ * @devid:		Device ID of the SSRAM device
  *
  * pmc contains info about one power management controller device.
  */
@@ -436,6 +437,7 @@ struct pmc {
 	u32 ltr_ign;
 	u8 num_lpm_modes;
 	u8 lpm_en_modes[LPM_MAX_NUM_MODES];
+	u16 devid;
 };
 
 /**
@@ -495,7 +497,6 @@ enum pmc_index {
 
 /**
  * struct pmc_dev_info - Structure to keep PMC device info
- * @pci_func:		Function number of the primary PMC
  * @dmu_guids:		List of Die Management Unit GUID
  * @pc_guid:		GUID for telemetry region to read PKGC blocker info
  * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
@@ -512,9 +513,9 @@ enum pmc_index {
  * @resume:		Function to perform platform specific resume
  * @init:		Function to perform platform specific init action
  * @sub_req:		Function to achieve low power mode substate requirements
+ * @ssram_hidden:	Some SSRAM devices are hidden on this platform
  */
 struct pmc_dev_info {
-	u8 pci_func;
 	u32 *dmu_guids;
 	u32 pc_guid;
 	u32 pkgc_ltr_blocker_offset;
@@ -528,6 +529,7 @@ struct pmc_dev_info {
 	int (*resume)(struct pmc_dev *pmcdev);
 	int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
 	int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
+	bool ssram_hidden;
 };
 
 extern const struct pmc_bit_map msr_map[];
diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c
index 1cd81ee54dcf8..18f303af328e3 100644
--- a/drivers/platform/x86/intel/pmc/lnl.c
+++ b/drivers/platform/x86/intel/pmc/lnl.c
@@ -571,7 +571,6 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 }
 
 struct pmc_dev_info lnl_pmc_dev = {
-	.pci_func = 2,
 	.regmap_list = lnl_pmc_info_list,
 	.map = &lnl_socm_reg_map,
 	.sub_req_show = &pmc_core_substate_req_regs_fops,
@@ -579,4 +578,5 @@ struct pmc_dev_info lnl_pmc_dev = {
 	.resume = lnl_resume,
 	.init = lnl_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
+	.ssram_hidden = true,
 };
diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c
index 57508cbf9cd42..193ebbe584023 100644
--- a/drivers/platform/x86/intel/pmc/mtl.c
+++ b/drivers/platform/x86/intel/pmc/mtl.c
@@ -994,7 +994,6 @@ static int mtl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 
 static u32 MTL_PMT_DMU_GUIDS[] = {MTL_PMT_DMU_GUID, 0x0};
 struct pmc_dev_info mtl_pmc_dev = {
-	.pci_func = 2,
 	.dmu_guids = MTL_PMT_DMU_GUIDS,
 	.regmap_list = mtl_pmc_info_list,
 	.map = &mtl_socm_reg_map,
@@ -1003,4 +1002,5 @@ struct pmc_dev_info mtl_pmc_dev = {
 	.resume = mtl_resume,
 	.init = mtl_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
+	.ssram_hidden = true,
 };
diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c
index 1f48e2bbc699f..6c68772e738c8 100644
--- a/drivers/platform/x86/intel/pmc/ptl.c
+++ b/drivers/platform/x86/intel/pmc/ptl.c
@@ -569,7 +569,6 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 }
 
 struct pmc_dev_info ptl_pmc_dev = {
-	.pci_func = 2,
 	.regmap_list = ptl_pmc_info_list,
 	.map = &ptl_pcdp_reg_map,
 	.sub_req_show = &pmc_core_substate_blk_req_fops,
@@ -577,4 +576,5 @@ struct pmc_dev_info ptl_pmc_dev = {
 	.resume = ptl_resume,
 	.init = ptl_core_init,
 	.sub_req = pmc_core_pmt_get_blk_sub_req,
+	.ssram_hidden = true,
 };
diff --git a/drivers/platform/x86/intel/pmc/wcl.c b/drivers/platform/x86/intel/pmc/wcl.c
index a45707e6364f2..b55069945e9e7 100644
--- a/drivers/platform/x86/intel/pmc/wcl.c
+++ b/drivers/platform/x86/intel/pmc/wcl.c
@@ -493,7 +493,6 @@ static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 }
 
 struct pmc_dev_info wcl_pmc_dev = {
-	.pci_func = 2,
 	.regmap_list = wcl_pmc_info_list,
 	.map = &wcl_pcdn_reg_map,
 	.sub_req_show = &pmc_core_substate_blk_req_fops,
@@ -501,4 +500,5 @@ struct pmc_dev_info wcl_pmc_dev = {
 	.resume = wcl_resume,
 	.init = wcl_core_init,
 	.sub_req = pmc_core_pmt_get_blk_sub_req,
+	.ssram_hidden = true,
 };
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 5/7] platform/x86/intel/pmc: Add support for variable DMU offsets
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Add support for handling different DMU Die C6 offsets across platforms.
The previous implementation assumed a uniform DMU Die C6 offset for all
platforms, which is no longer valid.

Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/arl.c  | 2 ++
 drivers/platform/x86/intel/pmc/core.c | 2 +-
 drivers/platform/x86/intel/pmc/core.h | 2 ++
 drivers/platform/x86/intel/pmc/mtl.c  | 1 +
 4 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c
index 95372a0807acf..4d91ee010f6d0 100644
--- a/drivers/platform/x86/intel/pmc/arl.c
+++ b/drivers/platform/x86/intel/pmc/arl.c
@@ -729,6 +729,7 @@ struct pmc_dev_info arl_pmc_dev = {
 	.init = arl_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
 	.ssram_hidden = true,
+	.die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET,
 };
 
 static u32 ARL_H_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, ARL_H_PMT_DMU_GUID, 0x0};
@@ -742,4 +743,5 @@ struct pmc_dev_info arl_h_pmc_dev = {
 	.init = arl_h_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
 	.ssram_hidden = true,
+	.die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET,
 };
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index e0ac329e6723a..5d2e2681b0eba 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1381,7 +1381,7 @@ void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_de
 		}
 
 		pmcdev->punit_ep = ep;
-		pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET;
+		pmcdev->die_c6_offset = pmc_dev_info->die_c6_offset;
 	}
 
 	if (pmc_dev_info->pc_guid) {
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index f385a0eccd2c2..ef69de160ffbc 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -514,6 +514,7 @@ enum pmc_index {
  * @init:		Function to perform platform specific init action
  * @sub_req:		Function to achieve low power mode substate requirements
  * @ssram_hidden:	Some SSRAM devices are hidden on this platform
+ * @die_c6_offset:	Telemetry offset to read Die C6 residency
  */
 struct pmc_dev_info {
 	u32 *dmu_guids;
@@ -530,6 +531,7 @@ struct pmc_dev_info {
 	int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
 	int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
 	bool ssram_hidden;
+	u32 die_c6_offset;
 };
 
 extern const struct pmc_bit_map msr_map[];
diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c
index 193ebbe584023..b724dd8c34dba 100644
--- a/drivers/platform/x86/intel/pmc/mtl.c
+++ b/drivers/platform/x86/intel/pmc/mtl.c
@@ -1003,4 +1003,5 @@ struct pmc_dev_info mtl_pmc_dev = {
 	.init = mtl_core_init,
 	.sub_req = pmc_core_pmt_get_lpm_req,
 	.ssram_hidden = true,
+	.die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET,
 };
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 6/7] platform/x86/intel/pmc: Retrieve PMC info only for available PMCs
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Update the Intel PMC Core driver to fetch PMC information only for
available PMCs. Previously, the driver attempted to retrieve PMC info
even when the corresponding PMC was not present.

This change aligns with recent updates to the Intel SSRAM Telemetry
driver. Starting with NVL, the SSRAM Telemetry driver is probed for
each individual SSRAM device. The prior implementation could not
differentiate between an unavailable PMC and one that had not yet
completed information retrieval. To resolve this, the PMC Core driver
now skips obtaining PMC info for unavailable PMCs.

Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/arl.c  |  7 +++++++
 drivers/platform/x86/intel/pmc/core.c | 19 +++++++++++--------
 drivers/platform/x86/intel/pmc/core.h |  4 ++++
 drivers/platform/x86/intel/pmc/lnl.c  |  4 ++++
 drivers/platform/x86/intel/pmc/mtl.c  |  4 ++++
 drivers/platform/x86/intel/pmc/ptl.c  |  4 ++++
 drivers/platform/x86/intel/pmc/wcl.c  |  4 ++++
 7 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c
index 4d91ee010f6d0..cd15559864317 100644
--- a/drivers/platform/x86/intel/pmc/arl.c
+++ b/drivers/platform/x86/intel/pmc/arl.c
@@ -672,6 +672,9 @@ static struct pmc_info arl_pmc_info_list[] = {
 	{}
 };
 
+static u8 arl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE, PMC_IDX_PCH};
+static u8 arl_h_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE};
+
 #define ARL_NPU_PCI_DEV			0xad1d
 #define ARL_GNA_PCI_DEV			0xae4c
 #define ARL_H_NPU_PCI_DEV		0x7d1d
@@ -721,6 +724,8 @@ static int arl_h_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_
 static u32 ARL_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, 0x0};
 struct pmc_dev_info arl_pmc_dev = {
 	.dmu_guids = ARL_PMT_DMU_GUIDS,
+	.num_pmcs = ARRAY_SIZE(arl_pmc_list),
+	.pmc_list = arl_pmc_list,
 	.regmap_list = arl_pmc_info_list,
 	.map = &arl_socs_reg_map,
 	.sub_req_show = &pmc_core_substate_req_regs_fops,
@@ -735,6 +740,8 @@ struct pmc_dev_info arl_pmc_dev = {
 static u32 ARL_H_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, ARL_H_PMT_DMU_GUID, 0x0};
 struct pmc_dev_info arl_h_pmc_dev = {
 	.dmu_guids = ARL_H_PMT_DMU_GUIDS,
+	.num_pmcs = ARRAY_SIZE(arl_h_pmc_list),
+	.pmc_list = arl_h_pmc_list,
 	.regmap_list = arl_pmc_info_list,
 	.map = &mtl_socm_reg_map,
 	.sub_req_show = &pmc_core_substate_req_regs_fops,
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 5d2e2681b0eba..c84e75b19aac3 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1734,16 +1734,17 @@ static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx)
 	return 0;
 }
 
-static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev)
+static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev, u8 num_pmcs, u8 *pmc_list)
 {
+	unsigned int i;
 	int ret;
 
-	ret = pmc_core_pmc_add(pmcdev, PMC_IDX_MAIN);
-	if (ret)
-		return ret;
-
-	pmc_core_pmc_add(pmcdev, PMC_IDX_IOE);
-	pmc_core_pmc_add(pmcdev, PMC_IDX_PCH);
+	for (i = 0; i < num_pmcs; ++i) {
+		/* Non-MAIN PMCs are allowed to fail */
+		ret = pmc_core_pmc_add(pmcdev, pmc_list[i]);
+		if (ret && (pmc_list[i] == PMC_IDX_MAIN))
+			return ret;
+	}
 
 	return 0;
 }
@@ -1765,7 +1766,9 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
 	ssram = pmc_dev_info->regmap_list != NULL;
 	if (ssram) {
 		pmcdev->regmap_list = pmc_dev_info->regmap_list;
-		ret = pmc_core_ssram_get_reg_base(pmcdev);
+		ret = pmc_core_ssram_get_reg_base(pmcdev,
+						  pmc_dev_info->num_pmcs,
+						  pmc_dev_info->pmc_list);
 		/*
 		 * EAGAIN error code indicates Intel PMC SSRAM Telemetry driver
 		 * has not finished probe and PMC info is not available yet. Try
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index ef69de160ffbc..a741e4698f195 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -501,6 +501,8 @@ enum pmc_index {
  * @pc_guid:		GUID for telemetry region to read PKGC blocker info
  * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region
  * @pkgc_blocker_offset:Offset to PKGC blocker in telemetry region
+ * @num_pmcs:		Number of entries in @pmc_list
+ * @pmc_list:		Index list of available PMC
  * @regmap_list:	Pointer to a list of pmc_info structure that could be
  *			available for the platform. When set, this field implies
  *			SSRAM support.
@@ -521,6 +523,8 @@ struct pmc_dev_info {
 	u32 pc_guid;
 	u32 pkgc_ltr_blocker_offset;
 	u32 pkgc_blocker_offset;
+	u8 num_pmcs;
+	u8 *pmc_list;
 	struct pmc_info *regmap_list;
 	const struct pmc_reg_map *map;
 	const struct file_operations *sub_req_show;
diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c
index 18f303af328e3..02cbcfbb8e2a6 100644
--- a/drivers/platform/x86/intel/pmc/lnl.c
+++ b/drivers/platform/x86/intel/pmc/lnl.c
@@ -544,6 +544,8 @@ static struct pmc_info lnl_pmc_info_list[] = {
 	{}
 };
 
+static u8 lnl_pmc_list[] = {PMC_IDX_MAIN};
+
 #define LNL_NPU_PCI_DEV		0x643e
 #define LNL_IPU_PCI_DEV		0x645d
 
@@ -571,6 +573,8 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 }
 
 struct pmc_dev_info lnl_pmc_dev = {
+	.num_pmcs = ARRAY_SIZE(lnl_pmc_list),
+	.pmc_list = lnl_pmc_list,
 	.regmap_list = lnl_pmc_info_list,
 	.map = &lnl_socm_reg_map,
 	.sub_req_show = &pmc_core_substate_req_regs_fops,
diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c
index b724dd8c34dba..e4ac09ce07a71 100644
--- a/drivers/platform/x86/intel/pmc/mtl.c
+++ b/drivers/platform/x86/intel/pmc/mtl.c
@@ -965,6 +965,8 @@ static struct pmc_info mtl_pmc_info_list[] = {
 	{}
 };
 
+static u8 mtl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE};
+
 #define MTL_GNA_PCI_DEV	0x7e4c
 #define MTL_IPU_PCI_DEV	0x7d19
 #define MTL_VPU_PCI_DEV	0x7d1d
@@ -995,6 +997,8 @@ static int mtl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 static u32 MTL_PMT_DMU_GUIDS[] = {MTL_PMT_DMU_GUID, 0x0};
 struct pmc_dev_info mtl_pmc_dev = {
 	.dmu_guids = MTL_PMT_DMU_GUIDS,
+	.num_pmcs = ARRAY_SIZE(mtl_pmc_list),
+	.pmc_list = mtl_pmc_list,
 	.regmap_list = mtl_pmc_info_list,
 	.map = &mtl_socm_reg_map,
 	.sub_req_show = &pmc_core_substate_req_regs_fops,
diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c
index 6c68772e738c8..7aa39db256770 100644
--- a/drivers/platform/x86/intel/pmc/ptl.c
+++ b/drivers/platform/x86/intel/pmc/ptl.c
@@ -543,6 +543,8 @@ static struct pmc_info ptl_pmc_info_list[] = {
 	{}
 };
 
+static u8 ptl_pmc_list[] = {PMC_IDX_MAIN};
+
 #define PTL_NPU_PCI_DEV                0xb03e
 #define PTL_IPU_PCI_DEV                0xb05d
 
@@ -569,6 +571,8 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 }
 
 struct pmc_dev_info ptl_pmc_dev = {
+	.num_pmcs = ARRAY_SIZE(ptl_pmc_list),
+	.pmc_list = ptl_pmc_list,
 	.regmap_list = ptl_pmc_info_list,
 	.map = &ptl_pcdp_reg_map,
 	.sub_req_show = &pmc_core_substate_blk_req_fops,
diff --git a/drivers/platform/x86/intel/pmc/wcl.c b/drivers/platform/x86/intel/pmc/wcl.c
index b55069945e9e7..4cae8501872aa 100644
--- a/drivers/platform/x86/intel/pmc/wcl.c
+++ b/drivers/platform/x86/intel/pmc/wcl.c
@@ -469,6 +469,8 @@ static struct pmc_info wcl_pmc_info_list[] = {
 	{}
 };
 
+static u8 wcl_pmc_list[] = {PMC_IDX_MAIN};
+
 #define WCL_NPU_PCI_DEV                0xfd3e
 
 /*
@@ -494,6 +496,8 @@ static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
 
 struct pmc_dev_info wcl_pmc_dev = {
 	.regmap_list = wcl_pmc_info_list,
+	.num_pmcs = ARRAY_SIZE(wcl_pmc_list),
+	.pmc_list = wcl_pmc_list,
 	.map = &wcl_pcdn_reg_map,
 	.sub_req_show = &pmc_core_substate_blk_req_fops,
 	.suspend = cnl_suspend,
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 7/7] platform/x86/intel/pmc: Add Nova Lake support to intel_pmc_core driver
From: Xi Pardee @ 2026-04-08 22:21 UTC (permalink / raw)
  To: xi.pardee, irenic.rajneesh, david.e.box, ilpo.jarvinen,
	platform-driver-x86, linux-kernel, linux-pm
In-Reply-To: <20260408222144.3288928-1-xi.pardee@linux.intel.com>

Add Nova Lake support in intel_pmc_core driver

Signed-off-by: Xi Pardee <xi.pardee@linux.intel.com>
---
 drivers/platform/x86/intel/pmc/Makefile |    3 +-
 drivers/platform/x86/intel/pmc/core.c   |    2 +
 drivers/platform/x86/intel/pmc/core.h   |   31 +
 drivers/platform/x86/intel/pmc/nvl.c    | 1539 +++++++++++++++++++++++
 drivers/platform/x86/intel/pmc/ptl.c    |    2 +-
 5 files changed, 1575 insertions(+), 2 deletions(-)
 create mode 100644 drivers/platform/x86/intel/pmc/nvl.c

diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile
index bb960c8721d77..23853e867c912 100644
--- a/drivers/platform/x86/intel/pmc/Makefile
+++ b/drivers/platform/x86/intel/pmc/Makefile
@@ -4,7 +4,8 @@
 #
 
 intel_pmc_core-y			:= core.o spt.o cnp.o icl.o \
-					   tgl.o adl.o mtl.o arl.o lnl.o ptl.o wcl.o
+					   tgl.o adl.o mtl.o arl.o \
+					   lnl.o ptl.o wcl.o nvl.o
 obj-$(CONFIG_INTEL_PMC_CORE)		+= intel_pmc_core.o
 intel_pmc_core_pltdrv-y			:= pltdrv.o
 obj-$(CONFIG_INTEL_PMC_CORE)		+= intel_pmc_core_pltdrv.o
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index c84e75b19aac3..207708f4ceb94 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -1849,6 +1849,8 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
 	X86_MATCH_VFM(INTEL_LUNARLAKE_M,	&lnl_pmc_dev),
 	X86_MATCH_VFM(INTEL_PANTHERLAKE_L,	&ptl_pmc_dev),
 	X86_MATCH_VFM(INTEL_WILDCATLAKE_L,	&wcl_pmc_dev),
+	X86_MATCH_VFM(INTEL_NOVALAKE,		&nvl_s_pmc_dev),
+	X86_MATCH_VFM(INTEL_NOVALAKE_L,		&nvl_h_pmc_dev),
 	{}
 };
 
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index a741e4698f195..f2b4a20d2ff44 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -307,6 +307,29 @@ enum ppfear_regs {
 #define WCL_NUM_S0IX_BLOCKER			94
 #define WCL_BLK_REQ_OFFSET			50
 
+/* Nova Lake */
+#define NVL_PCDH_PPFEAR_NUM_ENTRIES		13
+#define NVL_PCDH_PMC_MMIO_REG_LEN		0x363c
+#define NVL_PCDS_PMC_MMIO_REG_LEN		0x3118
+#define NVL_PCHS_PMC_MMIO_REG_LEN		0x30d8
+#define NVL_LPM_PRI_OFFSET			0x17a4
+#define NVL_LPM_EN_OFFSET			0x17a0
+#define NVL_LPM_RESIDENCY_OFFSET		0x17a8
+#define NVL_LPM_LIVE_STATUS_OFFSET		0x1760
+#define NVL_LPM_NUM_MAPS			15
+#define NVL_PCDH_NUM_S0IX_BLOCKER		107
+#define NVL_PCDS_NUM_S0IX_BLOCKER		71
+#define NVL_PCHS_NUM_S0IX_BLOCKER		54
+#define NVL_PCDS_PMC_LTR_RESERVED		0x1bac
+#define NVL_PCDH_BLK_REQ_OFFSET			53
+#define NVL_PCDS_BLK_REQ_OFFSET			18
+#define NVL_PCHS_BLK_REQ_OFFSET			46
+#define NVL_PMT_PC_GUID				0x13000101
+#define NVL_PMT_DMU_GUID			0x1a000101
+#define NVL_LTR_BLK_OFFSET			64
+#define NVL_PKGC_BLK_OFFSET			4
+#define NVL_PMT_DMU_DIE_C6_OFFSET		25
+
 /* SSRAM PMC Device ID */
 /* LNL */
 #define PMC_DEVID_LNL_SOCM	0xa87f
@@ -329,6 +352,11 @@ enum ppfear_regs {
 #define PMC_DEVID_MTL_IOEP	0x7ecf
 #define PMC_DEVID_MTL_IOEM	0x7ebf
 
+/* NVL */
+#define PMC_DEVID_NVL_PCDH	0xd37e
+#define PMC_DEVID_NVL_PCDS	0xd47e
+#define PMC_DEVID_NVL_PCHS	0x6e27
+
 extern const char *pmc_lpm_modes[];
 
 struct pmc_bit_map {
@@ -558,6 +586,7 @@ extern const struct pmc_reg_map mtl_ioep_reg_map;
 extern const struct pmc_bit_map ptl_pcdp_clocksource_status_map[];
 extern const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[];
 extern const struct pmc_bit_map ptl_pcdp_signal_status_map[];
+extern const struct pmc_bit_map ptl_pcdp_ltr_show_map[];
 
 void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev);
 int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
@@ -581,6 +610,8 @@ extern struct pmc_dev_info arl_h_pmc_dev;
 extern struct pmc_dev_info lnl_pmc_dev;
 extern struct pmc_dev_info ptl_pmc_dev;
 extern struct pmc_dev_info wcl_pmc_dev;
+extern struct pmc_dev_info nvl_s_pmc_dev;
+extern struct pmc_dev_info nvl_h_pmc_dev;
 
 void cnl_suspend(struct pmc_dev *pmcdev);
 int cnl_resume(struct pmc_dev *pmcdev);
diff --git a/drivers/platform/x86/intel/pmc/nvl.c b/drivers/platform/x86/intel/pmc/nvl.c
new file mode 100644
index 0000000000000..96f4244d602be
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/nvl.c
@@ -0,0 +1,1539 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains platform specific structure definitions
+ * and init function used by Nova Lake PCH.
+ *
+ * Copyright (c) 2026, Intel Corporation.
+ */
+
+#include <linux/pci.h>
+
+#include "core.h"
+
+/* PMC SSRAM PMT Telemetry GUIDS */
+#define PCDH_LPM_REQ_GUID 0x01093101
+#define PCHS_LPM_REQ_GUID 0x01092101
+#define PCDS_LPM_REQ_GUID 0x01091102
+
+/*
+ * Die Mapping to Product.
+ * Product PCDDie PCHDie
+ * NVL-H   PCD-H  None
+ * NVL-S   PCD-S  PCH-S
+ */
+
+static const struct pmc_bit_map nvl_pcdh_pfear_map[] = {
+	{"PMC_PGD0",                 BIT(0)},
+	{"FUSE_OSSE_PGD0",           BIT(1)},
+	{"SPI_PGD0",                 BIT(2)},
+	{"XHCI_PGD0",                BIT(3)},
+	{"SPA_PGD0",                 BIT(4)},
+	{"SPB_PGD0",                 BIT(5)},
+	{"MPFPW2_PGD0",              BIT(6)},
+	{"GBE_PGD0",                 BIT(7)},
+
+	{"SBR16B20_PGD0",            BIT(0)},
+	{"DBG_SBR_PGD0",             BIT(1)},
+	{"SBR16B7_PGD0",             BIT(2)},
+	{"STRC_PGD0",                BIT(3)},
+	{"SBR16B8_PGD0",             BIT(4)},
+	{"D2D_DISP_PGD1",            BIT(5)},
+	{"LPSS_PGD0",                BIT(6)},
+	{"LPC_PGD0",                 BIT(7)},
+
+	{"SMB_PGD0",                 BIT(0)},
+	{"ISH_PGD0",                 BIT(1)},
+	{"SBR16B2_PGD0",             BIT(2)},
+	{"NPK_PGD0",                 BIT(3)},
+	{"D2D_NOC_PGD1",             BIT(4)},
+	{"DBG_SBR16B_PGD0",          BIT(5)},
+	{"FUSE_PGD0",                BIT(6)},
+	{"SBR16B0_PGD0",             BIT(7)},
+
+	{"P2SB0_PGD0",               BIT(0)},
+	{"OTG_PGD0",                 BIT(1)},
+	{"EXI_PGD0",                 BIT(2)},
+	{"CSE_PGD0",                 BIT(3)},
+	{"CSME_KVM_PGD0",            BIT(4)},
+	{"CSME_PMT_PGD0",            BIT(5)},
+	{"CSME_CLINK_PGD0",          BIT(6)},
+	{"SBR16B21_PGD0",            BIT(7)},
+
+	{"CSME_USBR_PGD0",           BIT(0)},
+	{"SBR16B22_PGD0",            BIT(1)},
+	{"CSME_SMT1_PGD0",           BIT(2)},
+	{"MPFPW1_PGD0",              BIT(3)},
+	{"CSME_SMS2_PGD0",           BIT(4)},
+	{"CSME_SMS_PGD0",            BIT(5)},
+	{"CSME_RTC_PGD0",            BIT(6)},
+	{"CSMEPSF_PGD0",             BIT(7)},
+
+	{"D2D_NOC_PGD0",             BIT(0)},
+	{"ESE_PGD0",                 BIT(1)},
+	{"SBR16B6_PGD0",             BIT(2)},
+	{"P2SB1_PGD0",               BIT(3)},
+	{"SBR16B3_PGD0",             BIT(4)},
+	{"OSSE_SMT1_PGD0",           BIT(5)},
+	{"D2D_DISP_PGD0",            BIT(6)},
+	{"SNPS_USB2_A_PGD0",         BIT(7)},
+
+	{"U3FPW1_PGD0",              BIT(0)},
+	{"FIA_X_PGD0",               BIT(1)},
+	{"PSF4_PGD0",                BIT(2)},
+	{"CNVI_PGD0",                BIT(3)},
+	{"UFSX2_PGD0",               BIT(4)},
+	{"ENDBG_PGD0",               BIT(5)},
+	{"DBC_PGD0",                 BIT(6)},
+	{"FIA_PG_PGD0",              BIT(7)},
+
+	{"D2D_IPU_PGD0",             BIT(0)},
+	{"NPK_PGD1",                 BIT(1)},
+	{"FIACPCB_X_PGD0",           BIT(2)},
+	{"SBR8B4_PGD0",              BIT(3)},
+	{"DBG_PSF_PGD0",             BIT(4)},
+	{"PSF6_PGD0",                BIT(5)},
+	{"UFSPW1_PGD0",              BIT(6)},
+	{"FIA_U_PGD0",               BIT(7)},
+
+	{"PSF8_PGD0",                BIT(0)},
+	{"SBR16B9_PGD0",             BIT(1)},
+	{"PSF0_PGD0",                BIT(2)},
+	{"FIACPCB_U_PGD0",           BIT(3)},
+	{"TAM_PGD0",                 BIT(4)},
+	{"D2D_NOC_PGD2",             BIT(5)},
+	{"SBR8B2_PGD0",              BIT(6)},
+	{"THC0_PGD0",                BIT(7)},
+
+	{"THC1_PGD0",                BIT(0)},
+	{"PMC_PGD1",                 BIT(1)},
+	{"DISP_PGA1_PGD0",           BIT(2)},
+	{"TCSS_PGD0",                BIT(3)},
+	{"DISP_PGA_PGD0",            BIT(4)},
+	{"SBR16B1_PGD0",             BIT(5)},
+	{"SBRG_PGD0",                BIT(6)},
+	{"PSF5_PGD0",                BIT(7)},
+
+	{"SBR8B3_PGD0",              BIT(0)},
+	{"ACE_PGD0",                 BIT(1)},
+	{"ACE_PGD1",                 BIT(2)},
+	{"ACE_PGD2",                 BIT(3)},
+	{"ACE_PGD3",                 BIT(4)},
+	{"ACE_PGD4",                 BIT(5)},
+	{"ACE_PGD5",                 BIT(6)},
+	{"ACE_PGD6",                 BIT(7)},
+
+	{"ACE_PGD7",                 BIT(0)},
+	{"ACE_PGD8",                 BIT(1)},
+	{"ACE_PGD9",                 BIT(2)},
+	{"ACE_PGD10",                BIT(3)},
+	{"FIACPCB_PG_PGD0",          BIT(4)},
+	{"SNPS_USB2_B_PGD0",         BIT(5)},
+	{"OSSE_PGD0",                BIT(6)},
+	{"SBR8B0_PGD0",              BIT(7)},
+
+	{"SBR16B4_PGD0",             BIT(0)},
+	{"CSME_PTIO_PGD0",           BIT(1)},
+	{}
+};
+
+static const struct pmc_bit_map *ext_nvl_pcdh_pfear_map[] = {
+	nvl_pcdh_pfear_map,
+	NULL
+};
+
+const struct pmc_bit_map nvl_pcdh_clocksource_status_map[] = {
+	{"AON2_OFF_STS",                 BIT(0),	1},
+	{"AON3_OFF_STS",                 BIT(1),	0},
+	{"AON4_OFF_STS",                 BIT(2),	1},
+	{"AON5_OFF_STS",                 BIT(3),	1},
+	{"AON1_OFF_STS",                 BIT(4),	0},
+	{"XTAL_LVM_OFF_STS",             BIT(5),	0},
+	{"MPFPW1_0_PLL_OFF_STS",         BIT(6),	1},
+	{"D2D_PLL_OFF_STS",              BIT(7),	1},
+	{"USB3_PLL_OFF_STS",             BIT(8),	1},
+	{"AON3_SPL_OFF_STS",             BIT(9),	1},
+	{"MPFPW2_0_PLL_OFF_STS",         BIT(12),	1},
+	{"XTAL_AGGR_OFF_STS",            BIT(17),	1},
+	{"USB2_PLL_OFF_STS",             BIT(18),	0},
+	{"DDI2_PLL_OFF_STS",             BIT(19),	1},
+	{"SE_TCSS_PLL_OFF_STS",          BIT(20),	1},
+	{"DDI_PLL_OFF_STS",              BIT(21),	1},
+	{"FILTER_PLL_OFF_STS",           BIT(22),	1},
+	{"ACE_PLL_OFF_STS",              BIT(24),	0},
+	{"FABRIC_PLL_OFF_STS",           BIT(25),	1},
+	{"SOC_PLL_OFF_STS",              BIT(26),	1},
+	{"REF_PLL_OFF_STS",              BIT(28),	1},
+	{"IMG_PLL_OFF_STS",              BIT(29),	1},
+	{"GENLOCK_FILTER_PLL_OFF_STS",   BIT(30),	1},
+	{"RTC_PLL_OFF_STS",              BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_power_gating_status_0_map[] = {
+	{"PMC_PGD0_PG_STS",              BIT(0),	0},
+	{"FUSE_OSSE_PGD0_PG_STS",	 BIT(1),	0},
+	{"ESPISPI_PGD0_PG_STS",          BIT(2),	0},
+	{"XHCI_PGD0_PG_STS",             BIT(3),	1},
+	{"SPA_PGD0_PG_STS",              BIT(4),	1},
+	{"SPB_PGD0_PG_STS",              BIT(5),	1},
+	{"MPFPW2_PGD0_PG_STS",           BIT(6),	0},
+	{"GBE_PGD0_PG_STS",              BIT(7),	1},
+	{"SBR16B20_PGD0_PG_STS",         BIT(8),	0},
+	{"DBG_PGD0_PG_STS",              BIT(9),	0},
+	{"SBR16B7_PGD0_PG_STS",          BIT(10),	0},
+	{"STRC_PGD0_PG_STS",             BIT(11),	0},
+	{"SBR16B8_PGD0_PG_STS",          BIT(12),	0},
+	{"D2D_DISP_PGD1_PG_STS",         BIT(13),	1},
+	{"LPSS_PGD0_PG_STS",             BIT(14),	1},
+	{"LPC_PGD0_PG_STS",              BIT(15),	0},
+	{"SMB_PGD0_PG_STS",              BIT(16),	0},
+	{"ISH_PGD0_PG_STS",              BIT(17),	0},
+	{"SBR16B2_PGD0_PG_STS",          BIT(18),	0},
+	{"NPK_PGD0_PG_STS",              BIT(19),	0},
+	{"D2D_NOC_PGD1_PG_STS",          BIT(20),	1},
+	{"DBG_SBR16B_PGD0_PG_STS",       BIT(21),	0},
+	{"FUSE_PGD0_PG_STS",             BIT(22),	0},
+	{"SBR16B0_PGD0_PG_STS",          BIT(23),	0},
+	{"P2SB0_PGD0_PG_STS",            BIT(24),	1},
+	{"XDCI_PGD0_PG_STS",             BIT(25),	1},
+	{"EXI_PGD0_PG_STS",              BIT(26),	0},
+	{"CSE_PGD0_PG_STS",              BIT(27),	1},
+	{"KVMCC_PGD0_PG_STS",            BIT(28),	1},
+	{"PMT_PGD0_PG_STS",              BIT(29),	1},
+	{"CLINK_PGD0_PG_STS",            BIT(30),	1},
+	{"SBR16B21_PGD0_PG_STS",         BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_power_gating_status_1_map[] = {
+	{"USBR0_PGD0_PG_STS",            BIT(0),	1},
+	{"SBR16B22_PGD0_PG_STS",         BIT(1),	0},
+	{"SMT1_PGD0_PG_STS",             BIT(2),	1},
+	{"MPFPW1_PGD0_PG_STS",           BIT(3),	0},
+	{"SMS2_PGD0_PG_STS",             BIT(4),	1},
+	{"SMS1_PGD0_PG_STS",             BIT(5),	1},
+	{"CSMERTC_PGD0_PG_STS",          BIT(6),	0},
+	{"CSMEPSF_PGD0_PG_STS",          BIT(7),	0},
+	{"D2D_NOC_PGD0_PG_STS",          BIT(8),	0},
+	{"ESE_PGD0_PG_STS",              BIT(9),	1},
+	{"SBR16B6_PGD0_PG_STS",          BIT(10),	0},
+	{"P2SB1_PGD0_PG_STS",            BIT(11),	1},
+	{"SBR16B3_PGD0_PG_STS",          BIT(12),	0},
+	{"OSSE_SMT1_PGD0_PG_STS",        BIT(13),	1},
+	{"D2D_DISP_PGD0_PG_STS",         BIT(14),	1},
+	{"SNPA_USB2_A_PGD0_PG_STS",      BIT(15),	0},
+	{"U3FPW1_PGD0_PG_STS",           BIT(16),	0},
+	{"FIA_X_PGD0_PG_STS",            BIT(17),	0},
+	{"PSF4_PGD0_PG_STS",             BIT(18),	0},
+	{"CNVI_PGD0_PG_STS",             BIT(19),	0},
+	{"UFSX2_PGD0_PG_STS",            BIT(20),	1},
+	{"ENDBG_PGD0_PG_STS",            BIT(21),	0},
+	{"DBC_PGD0_PG_STS",              BIT(22),	0},
+	{"FIA_PG_PGD0_PG_STS",           BIT(23),	0},
+	{"D2D_IPU_PGD0_PG_STS",          BIT(24),	1},
+	{"NPK_PGD1_PG_STS",              BIT(25),	0},
+	{"FIACPCB_X_PGD0_PG_STS",        BIT(26),	0},
+	{"SBR8B4_PGD0_PG_STS",           BIT(27),	0},
+	{"DBG_PSF_PGD0_PG_STS",          BIT(28),	0},
+	{"PSF6_PGD0_PG_STS",             BIT(29),	0},
+	{"UFSPW1_PGD0_PG_STS",           BIT(30),	0},
+	{"FIA_U_PGD0_PG_STS",            BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_power_gating_status_2_map[] = {
+	{"PSF8_PGD0_PG_STS",             BIT(0),	0},
+	{"SBR16B9_PGD0_PG_STS",          BIT(1),	0},
+	{"PSF0_PGD0_PG_STS",             BIT(2),	0},
+	{"FIACPCB_U_PGD0_PG_STS",        BIT(3),	0},
+	{"TAM_PGD0_PG_STS",              BIT(4),	1},
+	{"D2D_NOC_PGD2_PG_STS",          BIT(5),	1},
+	{"SBR8B2_PGD0_PG_STS",           BIT(6),	0},
+	{"THC0_PGD0_PG_STS",             BIT(7),	1},
+	{"THC1_PGD0_PG_STS",             BIT(8),	1},
+	{"PMC_PGD1_PG_STS",              BIT(9),	0},
+	{"DISP_PGA1_PGD0_PG_STS",        BIT(10),	0},
+	{"TCSS_PGD0_PG_STS",             BIT(11),	0},
+	{"DISP_PGA_PGD0_PG_STS",         BIT(12),	0},
+	{"SBR16B1_PGD0_PG_STS",          BIT(13),	0},
+	{"SBRG_PGD0_PG_STS",             BIT(14),	0},
+	{"PSF5_PGD0_PG_STS",             BIT(15),	0},
+	{"SBR8B3_PGD0_PG_STS",           BIT(16),	0},
+	{"ACE_PGD0_PG_STS",              BIT(17),	0},
+	{"ACE_PGD1_PG_STS",              BIT(18),	0},
+	{"ACE_PGD2_PG_STS",              BIT(19),	0},
+	{"ACE_PGD3_PG_STS",              BIT(20),	0},
+	{"ACE_PGD4_PG_STS",              BIT(21),	0},
+	{"ACE_PGD5_PG_STS",              BIT(22),	0},
+	{"ACE_PGD6_PG_STS",              BIT(23),	0},
+	{"ACE_PGD7_PG_STS",              BIT(24),	0},
+	{"ACE_PGD8_PG_STS",              BIT(25),	0},
+	{"ACE_PGD9_PG_STS",              BIT(26),	0},
+	{"ACE_PGD10_PG_STS",             BIT(27),	0},
+	{"FIACPCB_PG_PGD0_PG_STS",       BIT(28),	0},
+	{"SNPS_USB2_B_PGD0_PG_STS",      BIT(29),	0},
+	{"OSSE_PGD0_PG_STS",             BIT(30),	1},
+	{"SBR8B0_PGD0_PG_STS",           BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_power_gating_status_3_map[] = {
+	{"SBR16B4_PGD0_PG_STS",          BIT(0),	0},
+	{"PTIO_PGD0_PG_STS",             BIT(1),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_d3_status_0_map[] = {
+	{"LPSS_D3_STS",                  BIT(3),	1},
+	{"XDCI_D3_STS",                  BIT(4),	1},
+	{"XHCI_D3_STS",                  BIT(5),	1},
+	{"OSSE_D3_STS",                  BIT(6),	0},
+	{"SPA_D3_STS",                   BIT(12),	0},
+	{"SPB_D3_STS",                   BIT(13),	0},
+	{"ESPISPI_D3_STS",               BIT(18),	0},
+	{"PSTH_D3_STS",                  BIT(21),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_d3_status_1_map[] = {
+	{"OSSE_SMT1_D3_STS",             BIT(0),	0},
+	{"GBE_D3_STS",                   BIT(19),	0},
+	{"ITSS_D3_STS",                  BIT(23),	0},
+	{"CNVI_D3_STS",                  BIT(27),	0},
+	{"UFSX2_D3_STS",                 BIT(28),	0},
+	{"ESE_D3_STS",                   BIT(29),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_d3_status_2_map[] = {
+	{"CSMERTC_D3_STS",               BIT(1),	0},
+	{"CSE_D3_STS",                   BIT(4),	0},
+	{"KVMCC_D3_STS",                 BIT(5),	0},
+	{"USBR0_D3_STS",                 BIT(6),	0},
+	{"ISH_D3_STS",                   BIT(7),	0},
+	{"SMT1_D3_STS",                  BIT(8),	0},
+	{"SMT2_D3_STS",                  BIT(9),	0},
+	{"SMT3_D3_STS",                  BIT(10),	0},
+	{"OSSE_SMT2_D3_STS",             BIT(11),	0},
+	{"CLINK_D3_STS",                 BIT(14),	0},
+	{"PTIO_D3_STS",                  BIT(16),	0},
+	{"PMT_D3_STS",                   BIT(17),	0},
+	{"SMS1_D3_STS",                  BIT(18),	0},
+	{"SMS2_D3_STS",                  BIT(19),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_d3_status_3_map[] = {
+	{"THC0_D3_STS",                  BIT(14),	1},
+	{"THC1_D3_STS",                  BIT(15),	1},
+	{"OSSE_SMT3_D3_STS",             BIT(16),	0},
+	{"ACE_D3_STS",                   BIT(23),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_vnn_req_status_0_map[] = {
+	{"LPSS_VNN_REQ_STS",             BIT(3),	1},
+	{"OSSE_VNN_REQ_STS",             BIT(6),	1},
+	{"ESPISPI_VNN_REQ_STS",          BIT(18),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_vnn_req_status_1_map[] = {
+	{"OSSE_SMT1_VNN_REQ_STS",        BIT(0),	1},
+	{"NPK_VNN_REQ_STS",              BIT(4),	1},
+	{"DFXAGG_VNN_REQ_STS",           BIT(8),	0},
+	{"EXI_VNN_REQ_STS",              BIT(9),	1},
+	{"P2D_VNN_REQ_STS",              BIT(18),	1},
+	{"GBE_VNN_REQ_STS",              BIT(19),	1},
+	{"SMB_VNN_REQ_STS",              BIT(25),	1},
+	{"LPC_VNN_REQ_STS",              BIT(26),	0},
+	{"ESE_VNN_REQ_STS",              BIT(29),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_vnn_req_status_2_map[] = {
+	{"CSMERTC_VNN_REQ_STS",          BIT(1),	1},
+	{"CSE_VNN_REQ_STS",              BIT(4),	1},
+	{"ISH_VNN_REQ_STS",              BIT(7),	1},
+	{"SMT1_VNN_REQ_STS",             BIT(8),	1},
+	{"CLINK_VNN_REQ_STS",            BIT(14),	1},
+	{"SMS1_VNN_REQ_STS",             BIT(18),	1},
+	{"SMS2_VNN_REQ_STS",             BIT(19),	1},
+	{"GPIOCOM4_VNN_REQ_STS",         BIT(20),	1},
+	{"GPIOCOM3_VNN_REQ_STS",         BIT(21),	1},
+	{"DISP_SHIM_VNN_REQ_STS",        BIT(22),	1},
+	{"GPIOCOM1_VNN_REQ_STS",         BIT(23),	1},
+	{"GPIOCOM0_VNN_REQ_STS",         BIT(24),	1},
+	{}
+};
+
+const struct pmc_bit_map nvl_pcdh_vnn_req_status_3_map[] = {
+	{"DTS0_VNN_REQ_STS",             BIT(7),	0},
+	{"GPIOCOM5_VNN_REQ_STS",         BIT(11),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_vnn_misc_status_map[] = {
+	{"CPU_C10_REQ_STS",              BIT(0),	0},
+	{"TS_OFF_REQ_STS",               BIT(1),	0},
+	{"PNDE_MET_REQ_STS",             BIT(2),	1},
+	{"PG5_PMA0_REQ_STS",             BIT(3),	1},
+	{"FW_THROTTLE_ALLOWED_REQ_STS",  BIT(4),	0},
+	{"VNN_SOC_REQ_STS",              BIT(6),	1},
+	{"ISH_VNNAON_REQ_STS",           BIT(7),	0},
+	{"D2D_NOC_CFI_QACTIVE_REQ_STS",	 BIT(8),	1},
+	{"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9),	1},
+	{"D2D_IPU_QACTIVE_REQ_STS",      BIT(10),	1},
+	{"PLT_GREATER_REQ_STS",          BIT(11),	1},
+	{"ALL_SBR_IDLE_REQ_STS",         BIT(12),	0},
+	{"PMC_IDLE_FB_OCP_REQ_STS",      BIT(13),	0},
+	{"PM_SYNC_STATES_REQ_STS",       BIT(14),	0},
+	{"EA_REQ_STS",                   BIT(15),	0},
+	{"MPHY_CORE_OFF_REQ_STS",        BIT(16),	0},
+	{"BRK_EV_EN_REQ_STS",            BIT(17),	0},
+	{"AUTO_DEMO_EN_REQ_STS",         BIT(18),	0},
+	{"ITSS_CLK_SRC_REQ_STS",         BIT(19),	1},
+	{"ARC_IDLE_REQ_STS",             BIT(21),	0},
+	{"PG5_PMA1_REQ_STS",             BIT(22),	1},
+	{"FIA_DEEP_PM_REQ_STS",          BIT(23),	0},
+	{"XDCI_ATTACHED_REQ_STS",        BIT(24),	1},
+	{"ARC_INTERRUPT_WAKE_REQ_STS",   BIT(25),	0},
+	{"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26),	1},
+	{"PRE_WAKE0_REQ_STS",            BIT(27),	1},
+	{"PRE_WAKE1_REQ_STS",            BIT(28),	1},
+	{"PRE_WAKE2_REQ_STS",            BIT(29),	1},
+	{"PG5_PMA2_GVNN",                BIT(30),	1},
+	{"D2D_DISP_EDP_QACTIVE_REQ_STS", BIT(31),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcdh_rsc_status_map[] = {
+	{"CORE",		0,		1},
+	{"Memory",		0,		1},
+	{"PRIM_D2D",		0,		1},
+	{"PSF0",		0,		1},
+	{"PSF4",		0,		1},
+	{"PSF6",		0,		1},
+	{"PSF8",		0,		1},
+	{"SB",			0,		1},
+	{}
+};
+
+static const struct pmc_bit_map *nvl_pcdh_lpm_maps[] = {
+	nvl_pcdh_clocksource_status_map,
+	nvl_pcdh_power_gating_status_0_map,
+	nvl_pcdh_power_gating_status_1_map,
+	nvl_pcdh_power_gating_status_2_map,
+	nvl_pcdh_power_gating_status_3_map,
+	nvl_pcdh_d3_status_0_map,
+	nvl_pcdh_d3_status_1_map,
+	nvl_pcdh_d3_status_2_map,
+	nvl_pcdh_d3_status_3_map,
+	nvl_pcdh_vnn_req_status_0_map,
+	nvl_pcdh_vnn_req_status_1_map,
+	nvl_pcdh_vnn_req_status_2_map,
+	nvl_pcdh_vnn_req_status_3_map,
+	nvl_pcdh_vnn_misc_status_map,
+	ptl_pcdp_signal_status_map,
+	NULL
+};
+
+static const struct pmc_bit_map *nvl_pcdh_blk_maps[] = {
+	nvl_pcdh_power_gating_status_0_map,
+	nvl_pcdh_power_gating_status_1_map,
+	nvl_pcdh_power_gating_status_2_map,
+	nvl_pcdh_power_gating_status_3_map,
+	nvl_pcdh_rsc_status_map,
+	nvl_pcdh_vnn_req_status_0_map,
+	nvl_pcdh_vnn_req_status_1_map,
+	nvl_pcdh_vnn_req_status_2_map,
+	nvl_pcdh_vnn_req_status_3_map,
+	nvl_pcdh_d3_status_0_map,
+	nvl_pcdh_d3_status_1_map,
+	nvl_pcdh_d3_status_2_map,
+	nvl_pcdh_d3_status_3_map,
+	nvl_pcdh_clocksource_status_map,
+	nvl_pcdh_vnn_misc_status_map,
+	ptl_pcdp_signal_status_map,
+	NULL
+};
+
+static const struct pmc_bit_map nvl_pcds_pfear_map[] = {
+	{"PMC_PGD0",                 BIT(0)},
+	{"FUSE_OSSE_PGD0",           BIT(1)},
+	{"SPI_PGD0",                 BIT(2)},
+	{"XHCI_PGD0",                BIT(3)},
+	{"SPA_PGD0",                 BIT(4)},
+	{"SPB_PGD0",                 BIT(5)},
+	{"RSVD6",                    BIT(6)},
+	{"GBE_PGD0",                 BIT(7)},
+
+	{"RSVD8",                    BIT(0)},
+	{"RSVD9",                    BIT(1)},
+	{"SBR16B7_PGD0",             BIT(2)},
+	{"SBR16B21_PGD0",            BIT(3)},
+	{"RSVD12",                   BIT(4)},
+	{"D2D_DISP_PGD1",            BIT(5)},
+	{"LPSS_PGD0",                BIT(6)},
+	{"LPC_PGD0",                 BIT(7)},
+
+	{"SMB_PGD0",                 BIT(0)},
+	{"ISH_PGD0",                 BIT(1)},
+	{"SBR16B1_PGD0",             BIT(2)},
+	{"NPK_PGD0",                 BIT(3)},
+	{"D2D_NOC_PGD1",             BIT(4)},
+	{"DBG_SBR16B_PGD0",          BIT(5)},
+	{"FUSE_PGD0",                BIT(6)},
+	{"RSVD23",                   BIT(7)},
+
+	{"P2SB0_PGD0",               BIT(0)},
+	{"OTG_PGD0",                 BIT(1)},
+	{"EXI_PGD0",                 BIT(2)},
+	{"CSE_PGD0",                 BIT(3)},
+	{"CSME_KVM_PGD0",            BIT(4)},
+	{"CSME_PMT_PGD0",            BIT(5)},
+	{"CSME_CLINK_PGD0",          BIT(6)},
+	{"CSME_PTIO_PGD0",           BIT(7)},
+
+	{"CSME_USBR_PGD0",           BIT(0)},
+	{"SBR16B22_PGD0",            BIT(1)},
+	{"CSME_SMT1_PGD0",           BIT(2)},
+	{"P2SB1_PGD0",               BIT(3)},
+	{"CSME_SMS2_PGD0",           BIT(4)},
+	{"CSME_SMS_PGD0",            BIT(5)},
+	{"CSME_RTC_PGD0",            BIT(6)},
+	{"CSMEPSF_PGD0",             BIT(7)},
+
+	{"D2D_NOC_PGD0",             BIT(0)},
+	{"RSVD41",                   BIT(1)},
+	{"RSVD42",                   BIT(2)},
+	{"RSVD43",                   BIT(3)},
+	{"SBR16B2_PGD0",             BIT(4)},
+	{"OSSE_SMT1_PGD0",           BIT(5)},
+	{"D2D_DISP_PGD0",            BIT(6)},
+	{"RSVD47_PGD0",              BIT(7)},
+
+	{"RSVD48",                   BIT(0)},
+	{"DBG_PSF_PGD0",             BIT(1)},
+	{"RSVD50",                   BIT(2)},
+	{"CNVI_PGD0",                BIT(3)},
+	{"UFSX2_PGD0",               BIT(4)},
+	{"ENDBG_PGD0",               BIT(5)},
+	{"DBC_PGD0",                 BIT(6)},
+	{"SBR16B4_PGD0",             BIT(7)},
+
+	{"RSVD56",                   BIT(0)},
+	{"NPK_PGD1",                 BIT(1)},
+	{"RSVD58",                   BIT(2)},
+	{"SBR16B20_PGD0",            BIT(3)},
+	{"RSVD60",                   BIT(4)},
+	{"SBR8B20_PGD0",             BIT(5)},
+	{"RSVD62",                   BIT(6)},
+	{"FIA_U_PGD0",               BIT(7)},
+
+	{"PSF8_PGD0",                BIT(0)},
+	{"RSVD65",                   BIT(1)},
+	{"RSVD66",                   BIT(2)},
+	{"FIACPCB_U_PGD0",           BIT(3)},
+	{"TAM_PGD0",                 BIT(4)},
+	{"D2D_NOC_PGD2",             BIT(5)},
+	{"SBR8B2_PGD0",              BIT(6)},
+	{"THC0_PGD0",                BIT(7)},
+
+	{"THC1_PGD0",                BIT(0)},
+	{"PMC_PGD1",                 BIT(1)},
+	{"SBR16B3_PGD0",             BIT(2)},
+	{"TCSS_PGD0",                BIT(3)},
+	{"DISP_PGA_PGD0",            BIT(4)},
+	{"RSVD77",                   BIT(5)},
+	{"RSVD78",                   BIT(6)},
+	{"RSVD79",                   BIT(7)},
+
+	{"SBRG_PGD0",                BIT(0)},
+	{"RSVD81",                   BIT(1)},
+	{"SBR16B0_PGD0",             BIT(2)},
+	{"SBR8B0_PGD0",              BIT(3)},
+	{"PSF7_PGD0",                BIT(4)},
+	{"RSVD85",                   BIT(5)},
+	{"RSVD86",                   BIT(6)},
+	{"RSVD87",                   BIT(7)},
+
+	{"SBR16B6_PGD0",             BIT(0)},
+	{"PSD0_PGD0",                BIT(1)},
+	{"STRC_PGD0",                BIT(2)},
+	{"RSVD91",                   BIT(3)},
+	{"DBG_SBR_PGD0",             BIT(4)},
+	{"RSVD93",                   BIT(5)},
+	{"OSSE_PGD0",                BIT(6)},
+	{"DISP_PGA1_PGD0",           BIT(7)},
+	{}
+};
+
+static const struct pmc_bit_map *ext_nvl_pcds_pfear_map[] = {
+	nvl_pcds_pfear_map,
+	NULL
+};
+
+static const struct pmc_bit_map nvl_pcds_ltr_show_map[] = {
+	{"SOUTHPORT_A",		CNP_PMC_LTR_SPA},
+	{"SOUTHPORT_B",		CNP_PMC_LTR_SPB},
+	{"SATA",		CNP_PMC_LTR_SATA},
+	{"GIGABIT_ETHERNET",	CNP_PMC_LTR_GBE},
+	{"XHCI",		CNP_PMC_LTR_XHCI},
+	{"SOUTHPORT_F",		ADL_PMC_LTR_SPF},
+	{"ME",			CNP_PMC_LTR_ME},
+	{"SATA1",		CNP_PMC_LTR_EVA},
+	{"SOUTHPORT_C",		CNP_PMC_LTR_SPC},
+	{"HD_AUDIO",		CNP_PMC_LTR_AZ},
+	{"CNV",			CNP_PMC_LTR_CNV},
+	{"LPSS",		CNP_PMC_LTR_LPSS},
+	{"SOUTHPORT_D",		CNP_PMC_LTR_SPD},
+	{"SOUTHPORT_E",		CNP_PMC_LTR_SPE},
+	{"SATA2",		PTL_PMC_LTR_SATA2},
+	{"ESPI",		CNP_PMC_LTR_ESPI},
+	{"SCC",			CNP_PMC_LTR_SCC},
+	{"ISH",			CNP_PMC_LTR_ISH},
+	{"UFSX2",		CNP_PMC_LTR_UFSX2},
+	{"EMMC",		CNP_PMC_LTR_EMMC},
+	{"WIGIG",		ICL_PMC_LTR_WIGIG},
+	{"THC0",		TGL_PMC_LTR_THC0},
+	{"THC1",		TGL_PMC_LTR_THC1},
+	{"SOUTHPORT_G",		MTL_PMC_LTR_SPG},
+	{"RSVD",		NVL_PCDS_PMC_LTR_RESERVED},
+	{"IOE_PMC",		MTL_PMC_LTR_IOE_PMC},
+	{"DMI3",		ARL_PMC_LTR_DMI3},
+	{"OSSE",		LNL_PMC_LTR_OSSE},
+
+	/* Below two cannot be used for LTR_IGNORE */
+	{"CURRENT_PLATFORM",	PTL_PMC_LTR_CUR_PLT},
+	{"AGGREGATED_SYSTEM",	PTL_PMC_LTR_CUR_ASLT},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_clocksource_status_map[] = {
+	{"AON2_OFF_STS",                 BIT(0),	1},
+	{"AON3_OFF_STS",                 BIT(1),	0},
+	{"AON4_OFF_STS",                 BIT(2),	1},
+	{"AON5_OFF_STS",                 BIT(3),	1},
+	{"AON1_OFF_STS",                 BIT(4),	0},
+	{"XTAL_LVM_OFF_STS",             BIT(5),	0},
+	{"D2D_OFF_STS",                  BIT(8),	1},
+	{"AON3_SPL_OFF_STS",             BIT(9),	1},
+	{"XTAL_AGGR_OFF_STS",            BIT(17),	1},
+	{"BCLK_EXT_INJ_OFF_STS",         BIT(18),	1},
+	{"DDI2_PLL_OFF_STS",             BIT(19),	1},
+	{"SE_TCSS_PLL_OFF_STS",          BIT(20),	1},
+	{"DDI_PLL_OFF_STS",              BIT(21),	1},
+	{"FILTER_PLL_OFF_STS",           BIT(22),	1},
+	{"PHY_OC_EXT_INJ_OFF_STS",       BIT(23),	1},
+	{"ACE_PLL_OFF_STS",              BIT(24),	0},
+	{"FABRIC_PLL_OFF_STS",           BIT(25),	1},
+	{"SOC_PLL_OFF_STS",              BIT(26),	1},
+	{"REF_PLL_OFF_STS",              BIT(28),	1},
+	{"GENLOCK_FILTER_PLL_OFF_STS",   BIT(30),	1},
+	{"RTC_PLL_OFF_STS",              BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_power_gating_status_0_map[] = {
+	{"PMC_PGD0_PG_STS",              BIT(0),	0},
+	{"FUSE_OSSE_PGD0_PG_STS",	 BIT(1),	0},
+	{"ESPISPI_PGD0_PG_STS",          BIT(2),	0},
+	{"XHCI_PGD0_PG_STS",             BIT(3),	0},
+	{"SPA_PGD0_PG_STS",              BIT(4),	0},
+	{"SPB_PGD0_PG_STS",              BIT(5),	0},
+	{"RSVD_6",                       BIT(6),	0},
+	{"GBE_PGD0_PG_STS",              BIT(7),	0},
+	{"RSVD_8",                       BIT(8),	0},
+	{"RSVD_9",                       BIT(9),	0},
+	{"SBR16B7_PGD0_PG_STS",          BIT(10),	0},
+	{"SBR16B21_PGD0_PG_STS",         BIT(11),	0},
+	{"RSVD_12",                      BIT(12),	0},
+	{"D2D_DISP_PGD1_PG_STS",         BIT(13),	1},
+	{"LPSS_PGD0_PG_STS",             BIT(14),	0},
+	{"LPC_PGD0_PG_STS",              BIT(15),	0},
+	{"SMB_PGD0_PG_STS",              BIT(16),	0},
+	{"ISH_PGD0_PG_STS",              BIT(17),	0},
+	{"SBR16B1_PGD0_PG_STS",          BIT(18),	0},
+	{"NPK_PGD0_PG_STS",              BIT(19),	0},
+	{"D2D_NOC_PGD1_PG_STS",          BIT(20),	1},
+	{"DBG_SBR16B_PGD0_PG_STS",       BIT(21),	0},
+	{"FUSE_PGD0_PG_STS",             BIT(22),	0},
+	{"RSVD_23",                      BIT(23),	0},
+	{"P2SB0_PGD0_PG_STS",            BIT(24),	1},
+	{"XDCI_PGD0_PG_STS",             BIT(25),	0},
+	{"EXI_PGD0_PG_STS",              BIT(26),	0},
+	{"CSE_PGD0_PG_STS",              BIT(27),	1},
+	{"KVMCC_PGD0_PG_STS",            BIT(28),	0},
+	{"PMT_PGD0_PG_STS",              BIT(29),	0},
+	{"CLINK_PGD0_PG_STS",            BIT(30),	0},
+	{"PTIO_PGD0_PG_STS",             BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_power_gating_status_1_map[] = {
+	{"USBR0_PGD0_PG_STS",            BIT(0),	0},
+	{"SBR16B22_PGD0_PG_STS",         BIT(1),	0},
+	{"SMT1_PGD0_PG_STS",             BIT(2),	0},
+	{"P2SB1_PGD0_PG_STS",            BIT(3),	1},
+	{"SMS2_PGD0_PG_STS",             BIT(4),	0},
+	{"SMS1_PGD0_PG_STS",             BIT(5),	0},
+	{"CSMERTC_PGD0_PG_STS",          BIT(6),	0},
+	{"CSMEPSF_PGD0_PG_STS",          BIT(7),	0},
+	{"D2D_NOC_PGD0_PG_STS",          BIT(8),	0},
+	{"RSVD_9",                       BIT(9),	0},
+	{"RSVD_10",                      BIT(10),	0},
+	{"RSVD_11",                      BIT(11),	0},
+	{"SBR16B2_PGD0_PG_STS",          BIT(12),	0},
+	{"OSSE_SMT1_PGD0_PG_STS",        BIT(13),	1},
+	{"D2D_DISP_PGD0_PG_STS",         BIT(14),	1},
+	{"RSVD_15",                      BIT(15),	0},
+	{"RSVD_16",                      BIT(16),	0},
+	{"DBG_PSF_PGD0_PG_STS",          BIT(17),	0},
+	{"RSVD_18",                      BIT(18),	0},
+	{"CNVI_PGD0_PG_STS",             BIT(19),	0},
+	{"UFSX2_PGD0_PG_STS",            BIT(20),	0},
+	{"ENDBG_PGD0_PG_STS",            BIT(21),	0},
+	{"DBC_PGD0_PG_STS",              BIT(22),	0},
+	{"SBR16B4_PGD0_PG_STS",          BIT(23),	0},
+	{"RSVD_24",                      BIT(24),	0},
+	{"NPK_PGD1_PG_STS",              BIT(25),	0},
+	{"RSVD_26",                      BIT(26),	0},
+	{"SBR16B20_PGD0_PG_STS",         BIT(27),	0},
+	{"RSVD_28",                      BIT(28),	0},
+	{"SBR8B20_PGD0_PG_STS",          BIT(29),	0},
+	{"RSVD_30",                      BIT(30),	0},
+	{"FIA_U_PGD0_PG_STS",            BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_power_gating_status_2_map[] = {
+	{"PSF8_PGD0_PG_STS",             BIT(0),	0},
+	{"RSVD_1",                       BIT(1),	0},
+	{"RSVD_2",                       BIT(2),	0},
+	{"FIACPCB_U_PGD0_PG_STS",        BIT(3),	0},
+	{"TAM_PGD0_PG_STS",              BIT(4),	1},
+	{"D2D_NOC_PGD2_PG_STS",          BIT(5),	1},
+	{"SBR8B2_PGD0_PG_STS",           BIT(6),	0},
+	{"THC0_PGD0_PG_STS",             BIT(7),	0},
+	{"THC1_PGD0_PG_STS",             BIT(8),	0},
+	{"PMC_PGD1_PG_STS",              BIT(9),	0},
+	{"SBR16B3_PGD0_PG_STS",          BIT(10),	0},
+	{"TCSS_PGD0_PG_STS",             BIT(11),	0},
+	{"DISP_PGA_PGD0_PG_STS",         BIT(12),	0},
+	{"RSVD_13",                      BIT(13),	0},
+	{"RSVD_14",                      BIT(14),	0},
+	{"RSVD_15",                      BIT(15),	0},
+	{"SBRG_PGD0_PG_STS",             BIT(16),	0},
+	{"RSVD_17",                      BIT(17),	0},
+	{"SBR16B0_PGD0_PG_STS",          BIT(18),	0},
+	{"SBR8B0_PGD0_PG_STS",           BIT(19),	0},
+	{"PSF7_PGD0_PG_STS",             BIT(20),	0},
+	{"RSVD_21",                      BIT(21),	0},
+	{"RSVD_22",                      BIT(22),	0},
+	{"RSVD_23",                      BIT(23),	0},
+	{"SBR16B6_PGD0_PG_STS",          BIT(24),	0},
+	{"PSF0_PGD0_PG_STS",             BIT(25),	0},
+	{"STRC_PGD0_PG_STS",             BIT(26),	0},
+	{"RSVD_27",                      BIT(27),	0},
+	{"DBG_SBR_PGD0_PG_STS",          BIT(28),	0},
+	{"RSVD_29",                      BIT(29),	0},
+	{"OSSE_PGD0_PG_STS",             BIT(30),	1},
+	{"DISP_PGA1_PGD0_PG_STS",        BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_d3_status_0_map[] = {
+	{"LPSS_D3_STS",                  BIT(3),	1},
+	{"XDCI_D3_STS",                  BIT(4),	1},
+	{"XHCI_D3_STS",                  BIT(5),	1},
+	{"SPA_D3_STS",                   BIT(12),	0},
+	{"SPB_D3_STS",                   BIT(13),	0},
+	{"ESPISPI_D3_STS",               BIT(18),	0},
+	{"PSTH_D3_STS",                  BIT(21),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_d3_status_1_map[] = {
+	{"OSSE_D3_STS",                  BIT(14),	0},
+	{"GBE_D3_STS",                   BIT(19),	0},
+	{"ITSS_D3_STS",                  BIT(23),	0},
+	{"CNVI_D3_STS",                  BIT(27),	0},
+	{"UFSX2_D3_STS",                 BIT(28),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_d3_status_2_map[] = {
+	{"CSMERTC_D3_STS",               BIT(1),	0},
+	{"CSE_D3_STS",                   BIT(4),	0},
+	{"KVMCC_D3_STS",                 BIT(5),	0},
+	{"USBR0_D3_STS",                 BIT(6),	0},
+	{"ISH_D3_STS",                   BIT(7),	0},
+	{"SMT1_D3_STS",                  BIT(8),	0},
+	{"SMT2_D3_STS",                  BIT(9),	0},
+	{"SMT3_D3_STS",                  BIT(10),	0},
+	{"OSSE_SMT1_D3_STS",             BIT(12),	0},
+	{"CLINK_D3_STS",                 BIT(14),	0},
+	{"PTIO_D3_STS",                  BIT(16),	0},
+	{"PMT_D3_STS",                   BIT(17),	0},
+	{"SMS1_D3_STS",                  BIT(18),	0},
+	{"SMS2_D3_STS",                  BIT(19),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_d3_status_3_map[] = {
+	{"OSSE_SMT2_D3_STS",             BIT(0),	0},
+	{"THC0_D3_STS",                  BIT(14),	1},
+	{"THC1_D3_STS",                  BIT(15),	1},
+	{"OSSE_SMT3_D3_STS",             BIT(19),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_vnn_req_status_0_map[] = {
+	{"LPSS_VNN_REQ_STS",             BIT(3),	0},
+	{"ESPISPI_VNN_REQ_STS",          BIT(18),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_vnn_req_status_1_map[] = {
+	{"NPK_VNN_REQ_STS",              BIT(4),	1},
+	{"DFXAGG_VNN_REQ_STS",           BIT(8),	0},
+	{"EXI_VNN_REQ_STS",              BIT(9),	1},
+	{"OSSE_VNN_REQ_STS",             BIT(14),	1},
+	{"P2D_VNN_REQ_STS",              BIT(18),	1},
+	{"GBE_VNN_REQ_STS",              BIT(19),	0},
+	{"SMB_VNN_REQ_STS",              BIT(25),	1},
+	{"LPC_VNN_REQ_STS",              BIT(26),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_vnn_req_status_2_map[] = {
+	{"CSMERTC_VNN_REQ_STS",          BIT(1),	0},
+	{"CSE_VNN_REQ_STS",              BIT(4),	1},
+	{"ISH_VNN_REQ_STS",              BIT(7),	0},
+	{"SMT1_VNN_REQ_STS",             BIT(8),	0},
+	{"OSSE_SMT1_VNN_REQ_STS",        BIT(12),	1},
+	{"CLINK_VNN_REQ_STS",            BIT(14),	0},
+	{"SMS1_VNN_REQ_STS",             BIT(18),	0},
+	{"SMS2_VNN_REQ_STS",             BIT(19),	0},
+	{"GPIOCOM4_VNN_REQ_STS",         BIT(20),	0},
+	{"GPIOCOM3_VNN_REQ_STS",         BIT(21),	1},
+	{"GPIOCOM1_VNN_REQ_STS",         BIT(23),	1},
+	{"GPIOCOM0_VNN_REQ_STS",         BIT(24),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_vnn_req_status_3_map[] = {
+	{"DISP_SHIM_VNN_REQ_STS",        BIT(4),	1},
+	{"DTS0_VNN_REQ_STS",             BIT(7),	0},
+	{"GPIOCOM5_VNN_REQ_STS",         BIT(11),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_vnn_misc_status_map[] = {
+	{"CPU_C10_REQ_STS",              BIT(0),	0},
+	{"TS_OFF_REQ_STS",               BIT(1),	0},
+	{"PNDE_MET_REQ_STS",             BIT(2),	1},
+	{"PG5_PMA0_REQ_STS",             BIT(3),	1},
+	{"FW_THROTTLE_ALLOWED_REQ_STS",  BIT(4),	0},
+	{"VNN_SOC_REQ_STS",              BIT(6),	1},
+	{"ISH_VNNAON_REQ_STS",           BIT(7),	0},
+	{"D2D_NOC_CFI_QACTIVE_REQ_STS",	 BIT(8),	1},
+	{"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9),	1},
+	{"PLT_GREATER_REQ_STS",          BIT(11),	1},
+	{"ALL_SBR_IDLE_REQ_STS",         BIT(12),	0},
+	{"PMC_IDLE_FB_OCP_REQ_STS",      BIT(13),	0},
+	{"PM_SYNC_STATES_REQ_STS",       BIT(14),	0},
+	{"EA_REQ_STS",                   BIT(15),	0},
+	{"MPHY_CORE_OFF_REQ_STS",        BIT(16),	0},
+	{"BRK_EV_EN_REQ_STS",            BIT(17),	0},
+	{"AUTO_DEMO_EN_REQ_STS",         BIT(18),	0},
+	{"ITSS_CLK_SRC_REQ_STS",         BIT(19),	1},
+	{"ARC_IDLE_REQ_STS",             BIT(21),	0},
+	{"PG5_PMA1_REQ_STS",             BIT(22),	1},
+	{"DG5_PMA0_REQ_STS",             BIT(23),	1},
+	{"ARC_INTERRUPT_WAKE_REQ_STS",   BIT(25),	0},
+	{"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26),	1},
+	{"PRE_WAKE0_REQ_STS",            BIT(27),	1},
+	{"PRE_WAKE1_REQ_STS",            BIT(28),	1},
+	{"PRE_WAKE2_REQ_STS",            BIT(29),	1},
+	{"D2D_DISP_EDP_QACTIVE_REQ_STS", BIT(31),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_rsc_status_map[] = {
+	{"CORE",		0,		1},
+	{"Memory",		0,		1},
+	{"PRIM_D2D",		0,		1},
+	{"PSF0",		0,		1},
+	{"SB",			0,		1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pcds_signal_status_map[] = {
+	{"LSX_Wake0_STS",		 BIT(0),	0},
+	{"LSX_Wake1_STS",		 BIT(1),	0},
+	{"LSX_Wake2_STS",		 BIT(2),	0},
+	{"LSX_Wake3_STS",		 BIT(3),	0},
+	{"LSX_Wake4_STS",		 BIT(4),	0},
+	{"LSX_Wake5_STS",		 BIT(5),	0},
+	{"LSX_Wake6_STS",		 BIT(6),	0},
+	{"LSX_Wake7_STS",		 BIT(7),	0},
+	{"LPSS_Wake0_STS",		 BIT(8),	1},
+	{"LPSS_Wake1_STS",		 BIT(9),	1},
+	{"Int_Timer_SS_Wake0_STS",	 BIT(10),	1},
+	{"Int_Timer_SS_Wake1_STS",	 BIT(11),	1},
+	{"Int_Timer_SS_Wake2_STS",	 BIT(12),	1},
+	{"Int_Timer_SS_Wake3_STS",	 BIT(13),	1},
+	{"Int_Timer_SS_Wake4_STS",	 BIT(14),	1},
+	{"Int_Timer_SS_Wake5_STS",	 BIT(15),	1},
+	{}
+};
+
+static const struct pmc_bit_map *nvl_pcds_lpm_maps[] = {
+	nvl_pcds_clocksource_status_map,
+	nvl_pcds_power_gating_status_0_map,
+	nvl_pcds_power_gating_status_1_map,
+	nvl_pcds_power_gating_status_2_map,
+	nvl_pcds_d3_status_0_map,
+	nvl_pcds_d3_status_1_map,
+	nvl_pcds_d3_status_2_map,
+	nvl_pcds_d3_status_3_map,
+	nvl_pcds_vnn_req_status_0_map,
+	nvl_pcds_vnn_req_status_1_map,
+	nvl_pcds_vnn_req_status_2_map,
+	nvl_pcds_vnn_req_status_3_map,
+	nvl_pcds_vnn_misc_status_map,
+	nvl_pcds_signal_status_map,
+	NULL
+};
+
+static const struct pmc_bit_map *nvl_pcds_blk_maps[] = {
+	nvl_pcds_power_gating_status_0_map,
+	nvl_pcds_power_gating_status_1_map,
+	nvl_pcds_power_gating_status_2_map,
+	nvl_pcds_rsc_status_map,
+	nvl_pcds_vnn_req_status_0_map,
+	nvl_pcds_vnn_req_status_1_map,
+	nvl_pcds_vnn_req_status_2_map,
+	nvl_pcds_vnn_req_status_3_map,
+	nvl_pcds_d3_status_0_map,
+	nvl_pcds_d3_status_1_map,
+	nvl_pcds_d3_status_2_map,
+	nvl_pcds_d3_status_3_map,
+	nvl_pcds_clocksource_status_map,
+	nvl_pcds_vnn_misc_status_map,
+	nvl_pcds_signal_status_map,
+	NULL
+};
+
+static const struct pmc_bit_map nvl_pchs_pfear_map[] = {
+	{"PMC_PGD0",                 BIT(0)},
+	{"FIA_D_PGD0",               BIT(1)},
+	{"SPI_PGD0",                 BIT(2)},
+	{"XHCI_PGD0",                BIT(3)},
+	{"SPA_PGD0",                 BIT(4)},
+	{"SPB_PGD0",                 BIT(5)},
+	{"MPFPW2_PGD0",              BIT(6)},
+	{"GBE_PGD0",                 BIT(7)},
+
+	{"RSVD8",                    BIT(0)},
+	{"PSF3_PGD0",                BIT(1)},
+	{"SBR5_PGD0",                BIT(2)},
+	{"SBR0_PGD0",                BIT(3)},
+	{"RSVD12",                   BIT(4)},
+	{"D2D_DISP_PGD1",            BIT(5)},
+	{"LPSS_PGD0",                BIT(6)},
+	{"LPC_PGD0",                 BIT(7)},
+
+	{"SMB_PGD0",                 BIT(0)},
+	{"ISH_PGD0",                 BIT(1)},
+	{"P2SB_PGD0",                BIT(2)},
+	{"NPK_PGD0",                 BIT(3)},
+	{"D2D_NOC_PGD1",             BIT(4)},
+	{"EAH_PGD0",                 BIT(5)},
+	{"FUSE_PGD0",                BIT(6)},
+	{"SBR8_PGD0",                BIT(7)},
+
+	{"PSF7_PGD0",                BIT(0)},
+	{"OTG_PGD0",                 BIT(1)},
+	{"EXI_PGD0",                 BIT(2)},
+	{"CSE_PGD0",                 BIT(3)},
+	{"CSME_KVM_PGD0",            BIT(4)},
+	{"CSME_PMT_PGD0",            BIT(5)},
+	{"CSME_CLINK_PGD0",          BIT(6)},
+	{"CSME_PTIO_PGD0",           BIT(7)},
+
+	{"CSME_USBR_PGD0",           BIT(0)},
+	{"SBR1_PGD0",                BIT(1)},
+	{"CSME_SMT1_PGD0",           BIT(2)},
+	{"MPFPW1_PGD0",              BIT(3)},
+	{"CSME_SMS2_PGD0",           BIT(4)},
+	{"CSME_SMS_PGD0",            BIT(5)},
+	{"CSME_RTC_PGD0",            BIT(6)},
+	{"CSMEPSF_PGD0",             BIT(7)},
+
+	{"D2D_NOC_PGD0",             BIT(0)},
+	{"ESE_PGD0",                 BIT(1)},
+	{"SBR2_PGD0",                BIT(2)},
+	{"SBR3_PGD0",                BIT(3)},
+	{"SBR4_PGD0",                BIT(4)},
+	{"RSVD45",                   BIT(5)},
+	{"D2D_DISP_PGD0",            BIT(6)},
+	{"PSF1_PGD0",                BIT(7)},
+
+	{"U3FPW1_PGD0",              BIT(0)},
+	{"DMI3FPW_PGD0",             BIT(1)},
+	{"PSF4_PGD0",                BIT(2)},
+	{"CNVI_PGD0",                BIT(3)},
+	{"RSVD52",                   BIT(4)},
+	{"ENDBG_PGD0",               BIT(5)},
+	{"DBC_PGD0",                 BIT(6)},
+	{"SMT4_PGD0",                BIT(7)},
+
+	{"RSVD56",                   BIT(0)},
+	{"NPK_PGD1",                 BIT(1)},
+	{"RSVD58",                   BIT(2)},
+	{"DMI3_PGD0",                BIT(3)},
+	{"RSVD60",                   BIT(4)},
+	{"FIACPCB_D_PGD0",           BIT(5)},
+	{"RSVD62",                   BIT(6)},
+	{"FIA_U_PGD0",               BIT(7)},
+
+	{"FIACPCB_PGS_PGD0",         BIT(0)},
+	{"FIA_PGS_PGD0",             BIT(1)},
+	{"RSVD66",                   BIT(2)},
+	{"FIACPCB_U_PGD0",           BIT(3)},
+	{"TAM_PGD0",                 BIT(4)},
+	{"D2D_NOC_PGD2",             BIT(5)},
+	{"PSF2_PGD0",                BIT(6)},
+	{"THC0_PGD0",                BIT(7)},
+
+	{"THC1_PGD0",                BIT(0)},
+	{"PMC_PGD1",                 BIT(1)},
+	{"SBR9_PGD0",                BIT(2)},
+	{"U3FPW2_PGD0",              BIT(3)},
+	{"RSVD76",                   BIT(4)},
+	{"DBG_PSF_PGD0",             BIT(5)},
+	{"DBG_SBR_PGD0",             BIT(6)},
+	{"SBR6_PGD0",                BIT(7)},
+
+	{"SPC_PGD0",                 BIT(0)},
+	{"ACE_PGD0",                 BIT(1)},
+	{"ACE_PGD1",                 BIT(2)},
+	{"ACE_PGD2",                 BIT(3)},
+	{"ACE_PGD3",                 BIT(4)},
+	{"ACE_PGD4",                 BIT(5)},
+	{"ACE_PGD5",                 BIT(6)},
+	{"ACE_PGD6",                 BIT(7)},
+
+	{"ACE_PGD7",                 BIT(0)},
+	{"ACE_PGD8",                 BIT(1)},
+	{"ACE_PGD9",                 BIT(2)},
+	{"ACE_PGD10",                BIT(3)},
+	{"U3FPW3_PGD0",              BIT(4)},
+	{"SBR7_PGD0",                BIT(5)},
+	{"OSSE_PGD0",                BIT(6)},
+	{"ST_PGD0",                  BIT(7)},
+	{}
+};
+
+static const struct pmc_bit_map *ext_nvl_pchs_pfear_map[] = {
+	nvl_pchs_pfear_map,
+	NULL
+};
+
+static const struct pmc_bit_map nvl_pchs_clocksource_status_map[] = {
+	{"AON2_OFF_STS",                 BIT(0),	1},
+	{"AON3_OFF_STS",                 BIT(1),	0},
+	{"AON4_OFF_STS",                 BIT(2),	0},
+	{"AON2_SPL_OFF_STS",             BIT(3),	0},
+	{"AONL_OFF_STS",                 BIT(4),	0},
+	{"XTAL_LVM_OFF_STS",             BIT(5),	0},
+	{"AON5_OFF_STS",                 BIT(6),	0},
+	{"USB3_PLL_OFF_STS",             BIT(8),	1},
+	{"MAIN_CRO_OFF_STS",             BIT(11),	0},
+	{"MAIN_DIVIDER_OFF_STS",         BIT(12),	1},
+	{"REF_PLL_NON_OC_OFF_STS",       BIT(13),	1},
+	{"DMI_PLL_OFF_STS",              BIT(14),	1},
+	{"PHY_EXT_INJ_OFF_STS",          BIT(15),	1},
+	{"AON6_MCRO_OFF_STS",            BIT(16),	0},
+	{"XTAL_AGGR_OFF_STS",            BIT(17),	0},
+	{"USB2_PLL_OFF_STS",             BIT(18),	1},
+	{"GBE_PLL_OFF_STS",              BIT(21),	1},
+	{"SATA_PLL_OFF_STS",             BIT(22),	1},
+	{"PCIE0_PLL_OFF_STS",            BIT(23),	1},
+	{"PCIE1_PLL_OFF_STS",            BIT(24),	1},
+	{"FABRIC_PLL_OFF_STS",           BIT(25),	1},
+	{"PCIE2_PLL_OFF_STS",            BIT(26),	1},
+	{"REF_PLL_OFF_STS",              BIT(28),	1},
+	{"REF38P4_PLL_OFF_STS",          BIT(31),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_power_gating_status_0_map[] = {
+	{"PMC_PGD0_PG_STS",              BIT(0),	0},
+	{"FIA_D_PGD0_PG_STS",            BIT(1),	0},
+	{"ESPISPI_PGD0_PG_STS",          BIT(2),	0},
+	{"XHCI_PGD0_PG_STS",             BIT(3),	0},
+	{"SPA_PGD0_PG_STS",              BIT(4),	1},
+	{"SPB_PGD0_PG_STS",              BIT(5),	1},
+	{"MPFPW2_PGD0_PG_STS",           BIT(6),	0},
+	{"GBE_PGD0_PG_STS",              BIT(7),	1},
+	{"RSVD_8",                       BIT(8),	0},
+	{"PSF3_PGD0_PG_STS",             BIT(9),	0},
+	{"SBR5_PGD0_PG_STS",             BIT(10),	0},
+	{"SBR0_PGD0_PG_STS",             BIT(11),	0},
+	{"RSVD_12",                      BIT(12),	0},
+	{"D2D_DISP_PGD1_PG_STS",         BIT(13),	0},
+	{"LPSS_PGD0_PG_STS",             BIT(14),	1},
+	{"LPC_PGD0_PG_STS",              BIT(15),	0},
+	{"SMB_PGD0_PG_STS",              BIT(16),	0},
+	{"ISH_PGD0_PG_STS",              BIT(17),	0},
+	{"P2S_PGD0_PG_STS",              BIT(18),	0},
+	{"NPK_PGD0_PG_STS",              BIT(19),	0},
+	{"D2D_NOC_PGD1_PG_STS",          BIT(20),	0},
+	{"EAH_PGD0_PG_STS",              BIT(21),	0},
+	{"FUSE_PGD0_PG_STS",             BIT(22),	0},
+	{"SBR8_PGD0_PG_STS",             BIT(23),	0},
+	{"PSF7_PGD0_PG_STS",             BIT(24),	0},
+	{"XDCI_PGD0_PG_STS",             BIT(25),	1},
+	{"EXI_PGD0_PG_STS",              BIT(26),	0},
+	{"CSE_PGD0_PG_STS",              BIT(27),	1},
+	{"KVMCC_PGD0_PG_STS",            BIT(28),	1},
+	{"PMT_PGD0_PG_STS",              BIT(29),	1},
+	{"CLINK_PGD0_PG_STS",            BIT(30),	1},
+	{"PTIO_PGD0_PG_STS",             BIT(31),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_power_gating_status_1_map[] = {
+	{"USBR0_PGD0_PG_STS",            BIT(0),	1},
+	{"SBR1_PGD0_PG_STS",             BIT(1),	0},
+	{"SMT1_PGD0_PG_STS",             BIT(2),	1},
+	{"MPFPW1_PGD0_PG_STS",           BIT(3),	0},
+	{"SMS2_PGD0_PG_STS",             BIT(4),	1},
+	{"SMS1_PGD0_PG_STS",             BIT(5),	1},
+	{"CSMERTC_PGD0_PG_STS",          BIT(6),	0},
+	{"CSMEPSF_PGD0_PG_STS",          BIT(7),	0},
+	{"D2D_NOC_PGD0_PG_STS",          BIT(8),	0},
+	{"ESE_PGD0_PG_STS",              BIT(9),	1},
+	{"SBR2_PGD0_PG_STS",             BIT(10),	0},
+	{"SBR3_PGD0_PG_STS",             BIT(11),	0},
+	{"SBR4_PGD0_PG_STS",             BIT(12),	0},
+	{"RSVD_13",                      BIT(13),	0},
+	{"D2D_DISP_PGD0_PG_STS",         BIT(14),	0},
+	{"PSF1_PGD0_PG_STS",             BIT(15),	0},
+	{"U3FPW1_PGD0_PG_STS",           BIT(16),	0},
+	{"DMI3FPW_PGD0_PG_STS",          BIT(17),	0},
+	{"PSF4_PGD0_PG_STS",             BIT(18),	0},
+	{"CNVI_PGD0_PG_STS",             BIT(19),	0},
+	{"RSVD_20",                      BIT(20),	0},
+	{"ENDBG_PGD0_PG_STS",            BIT(21),	0},
+	{"DBC_PGD0_PG_STS",              BIT(22),	0},
+	{"SMT4_PGD0_PG_STS",             BIT(23),	1},
+	{"RSVD_24",                      BIT(24),	0},
+	{"NPK_PGD1_PG_STS",              BIT(25),	0},
+	{"RSVD_26",                      BIT(26),	0},
+	{"DMI3_PGD0_PG_STS",             BIT(27),	1},
+	{"RSVD_28",                      BIT(28),	0},
+	{"FIACPCB_D_PGD0_PG_STS",        BIT(29),	0},
+	{"RSVD_30",                      BIT(30),	0},
+	{"FIA_U_PGD0_PG_STS",            BIT(31),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_power_gating_status_2_map[] = {
+	{"FIACPCB_PGS_PGD0_PG_STS",      BIT(0),	0},
+	{"FIA_PGS_PGD0_PG_STS",          BIT(1),	0},
+	{"RSVD_2",                       BIT(2),	0},
+	{"FIACPCB_U_PGD0_PG_STS",        BIT(3),	0},
+	{"TAM_PGD0_PG_STS",              BIT(4),	0},
+	{"D2D_NOC_PGD2_PG_STS",          BIT(5),	0},
+	{"PSF2_PGD0_PG_STS",             BIT(6),	0},
+	{"THC0_PGD0_PG_STS",             BIT(7),	1},
+	{"THC1_PGD0_PG_STS",             BIT(8),	1},
+	{"PMC_PGD1_PG_STS",              BIT(9),	0},
+	{"SBR9_PGA0_PGD0_PG_STS",        BIT(10),	0},
+	{"U3FPW2_PGD0_PG_STS",           BIT(11),	0},
+	{"RSVD_12",                      BIT(12),	0},
+	{"DBG_PSF_PGD0_PG_STS",          BIT(13),	0},
+	{"DBG_SBR_PGD0_PG_STS",          BIT(14),	0},
+	{"SBR6_PGD0_PG_STS",             BIT(15),	0},
+	{"SPC_PGD0_PG_STS",              BIT(16),	1},
+	{"ACE_PGD0_PG_STS",              BIT(17),	0},
+	{"ACE_PGD1_PG_STS",              BIT(18),	0},
+	{"ACE_PGD2_PG_STS",              BIT(19),	0},
+	{"ACE_PGD3_PG_STS",              BIT(20),	0},
+	{"ACE_PGD4_PG_STS",              BIT(21),	0},
+	{"ACE_PGD5_PG_STS",              BIT(22),	0},
+	{"ACE_PGD6_PG_STS",              BIT(23),	0},
+	{"ACE_PGD7_PG_STS",              BIT(24),	0},
+	{"ACE_PGD8_PG_STS",              BIT(25),	0},
+	{"ACE_PGD9_PG_STS",              BIT(26),	0},
+	{"ACE_PGD10_PG_STS",             BIT(27),	0},
+	{"U3FPW3_PGD0_PG_STS",           BIT(28),	0},
+	{"SBR7_PGD0_PG_STS",             BIT(29),	0},
+	{"OSSE_PGD0_PG_STS",             BIT(30),	0},
+	{"SATA_PGD0_PG_STS",             BIT(31),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_d3_status_0_map[] = {
+	{"LPSS_D3_STS",                  BIT(3),	1},
+	{"XDCI_D3_STS",                  BIT(4),	1},
+	{"XHCI_D3_STS",                  BIT(5),	0},
+	{"SPA_D3_STS",                   BIT(12),	0},
+	{"SPB_D3_STS",                   BIT(13),	0},
+	{"SPC_D3_STS",                   BIT(14),	0},
+	{"ESPISPI_D3_STS",               BIT(18),	0},
+	{"SATA_D3_STS",                  BIT(20),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_d3_status_1_map[] = {
+	{"OSSE_D3_STS",                  BIT(6),	0},
+	{"GBE_D3_STS",                   BIT(19),	0},
+	{"ITSS_D3_STS",                  BIT(23),	0},
+	{"P2S_D3_STS",                   BIT(24),	0},
+	{"CNVI_D3_STS",                  BIT(27),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_d3_status_2_map[] = {
+	{"CSMERTC_D3_STS",               BIT(1),	0},
+	{"CSE_D3_STS",                   BIT(4),	0},
+	{"KVMCC_D3_STS",                 BIT(5),	0},
+	{"USBR0_D3_STS",                 BIT(6),	0},
+	{"ISH_D3_STS",                   BIT(7),	0},
+	{"SMT1_D3_STS",                  BIT(8),	0},
+	{"SMT2_D3_STS",                  BIT(9),	0},
+	{"SMT3_D3_STS",                  BIT(10),	0},
+	{"SMT4_D3_STS",                  BIT(11),	0},
+	{"SMT5_D3_STS",                  BIT(12),	0},
+	{"SMT6_D3_STS",                  BIT(13),	0},
+	{"CLINK_D3_STS",                 BIT(14),	0},
+	{"PTIO_D3_STS",                  BIT(16),	0},
+	{"PMT_D3_STS",                   BIT(17),	0},
+	{"SMS1_D3_STS",                  BIT(18),	0},
+	{"SMS2_D3_STS",                  BIT(19),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_d3_status_3_map[] = {
+	{"THC0_D3_STS",                  BIT(14),	0},
+	{"THC1_D3_STS",                  BIT(15),	0},
+	{"ACE_D3_STS",                   BIT(23),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_vnn_req_status_1_map[] = {
+	{"NPK_VNN_REQ_STS",              BIT(4),	0},
+	{"OSSE_VNN_REQ_STS",             BIT(6),	0},
+	{"DFXAGG_VNN_REQ_STS",           BIT(8),	0},
+	{"EXI_VNN_REQ_STS",              BIT(9),	0},
+	{"GBE_VNN_REQ_STS",              BIT(19),	0},
+	{"SMB_VNN_REQ_STS",              BIT(25),	0},
+	{"LPC_VNN_REQ_STS",              BIT(26),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_vnn_req_status_2_map[] = {
+	{"CSMERTC_VNN_REQ_STS",          BIT(1),	0},
+	{"CSE_VNN_REQ_STS",              BIT(4),	0},
+	{"ISH_VNN_REQ_STS",              BIT(7),	0},
+	{"SMT1_VNN_REQ_STS",             BIT(8),	0},
+	{"SMT4_VNN_REQ_STS",             BIT(11),	0},
+	{"CLINK_VNN_REQ_STS",            BIT(14),	0},
+	{"SMS1_VNN_REQ_STS",             BIT(18),	0},
+	{"SMS2_VNN_REQ_STS",             BIT(19),	0},
+	{"GPIOCOM4_VNN_REQ_STS",         BIT(20),	0},
+	{"GPIOCOM3_VNN_REQ_STS",         BIT(21),	0},
+	{"GPIOCOM2_VNN_REQ_STS",         BIT(22),	0},
+	{"GPIOCOM1_VNN_REQ_STS",         BIT(23),	0},
+	{"GPIOCOM0_VNN_REQ_STS",         BIT(24),	0},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_vnn_misc_status_map[] = {
+	{"CPU_C10_REQ_STS",              BIT(0),	0},
+	{"TS_OFF_REQ_STS",               BIT(1),	0},
+	{"PNDE_MET_REQ_STS",             BIT(2),	1},
+	{"PG5_PMA0_GVNN_REQ_STS",        BIT(3),	1},
+	{"FW_THROTTLE_ALLOWED_REQ_STS",  BIT(4),	0},
+	{"DMI_IN_L1_REQ_STS",            BIT(6),	0},
+	{"ISH_VNNAON_REQ_STS",           BIT(7),	0},
+	{"PLT_GREATER_REQ_STS",          BIT(11),	1},
+	{"ALL_SBR_IDLE_REQ_STS",         BIT(12),	0},
+	{"PMC_IDLE_FB_OCP_REQ_STS",      BIT(13),	0},
+	{"PM_SYNC_STATES_REQ_STS",       BIT(14),	0},
+	{"EA_REQ_STS",                   BIT(15),	0},
+	{"DMI_CLKREQ_B_REQ_STS",         BIT(16),	0},
+	{"BRK_EV_EN_REQ_STS",            BIT(17),	0},
+	{"AUTO_DEMO_EN_REQ_STS",         BIT(18),	0},
+	{"ITSS_CLK_SRC_REQ_STS",         BIT(19),	1},
+	{"ARC_IDLE_REQ_STS",             BIT(21),	0},
+	{"PG5_PMA1_GVNN_REQ_STS",        BIT(22),	1},
+	{"FIA_DEEP_PM_REQ_STS",          BIT(23),	0},
+	{"XDCI_ATTACHED_REQ_STS",        BIT(24),	0},
+	{"ARC_INTERRUPT_WAKE_REQ_STS",   BIT(25),	0},
+	{"PRE_WAKE0_REQ_STS",            BIT(27),	1},
+	{"PRE_WAKE1_REQ_STS",            BIT(28),	1},
+	{"PRE_WAKE2_EN_REQ_STS",         BIT(29),	0},
+	{"PG5_PMA2_GVNN_REQ_STS",        BIT(30),	1},
+	{}
+};
+
+static const struct pmc_bit_map nvl_pchs_rsc_status_map[] = {
+	{"Memory",		0,		1},
+	{"Memory_NS",		0,		1},
+	{"PSF1",		0,		1},
+	{"PSF2",		0,		1},
+	{"PSF3",		0,		1},
+	{"REF_PLL",		0,		1},
+	{"SB",			0,		1},
+	{}
+};
+
+static const struct pmc_bit_map *nvl_pchs_lpm_maps[] = {
+	nvl_pchs_clocksource_status_map,
+	nvl_pchs_power_gating_status_0_map,
+	nvl_pchs_power_gating_status_1_map,
+	nvl_pchs_power_gating_status_2_map,
+	nvl_pchs_d3_status_0_map,
+	nvl_pchs_d3_status_1_map,
+	nvl_pchs_d3_status_2_map,
+	nvl_pchs_d3_status_3_map,
+	nvl_pcds_vnn_req_status_0_map,
+	nvl_pchs_vnn_req_status_1_map,
+	nvl_pchs_vnn_req_status_2_map,
+	nvl_pcdh_vnn_req_status_3_map,
+	nvl_pchs_vnn_misc_status_map,
+	ptl_pcdp_signal_status_map,
+	NULL
+};
+
+static const struct pmc_bit_map *nvl_pchs_blk_maps[] = {
+	nvl_pchs_power_gating_status_0_map,
+	nvl_pchs_power_gating_status_1_map,
+	nvl_pchs_power_gating_status_2_map,
+	nvl_pchs_rsc_status_map,
+	nvl_pchs_d3_status_0_map,
+	nvl_pchs_clocksource_status_map,
+	nvl_pchs_vnn_misc_status_map,
+	NULL
+};
+
+static const struct pmc_reg_map nvl_pcdh_reg_map = {
+	.pfear_sts = ext_nvl_pcdh_pfear_map,
+	.slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+	.slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+	.ltr_show_sts = ptl_pcdp_ltr_show_map,
+	.msr_sts = msr_map,
+	.ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+	.regmap_length = NVL_PCDH_PMC_MMIO_REG_LEN,
+	.ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+	.ppfear_buckets = NVL_PCDH_PPFEAR_NUM_ENTRIES,
+	.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+	.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+	.lpm_num_maps = NVL_LPM_NUM_MAPS,
+	.ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED,
+	.lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+	.etr3_offset = ETR3_OFFSET,
+	.lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET,
+	.lpm_priority_offset = NVL_LPM_PRI_OFFSET,
+	.lpm_en_offset = NVL_LPM_EN_OFFSET,
+	.lpm_residency_offset = NVL_LPM_RESIDENCY_OFFSET,
+	.lpm_sts = nvl_pcdh_lpm_maps,
+	.lpm_status_offset = MTL_LPM_STATUS_OFFSET,
+	.lpm_live_status_offset = NVL_LPM_LIVE_STATUS_OFFSET,
+	.s0ix_blocker_maps = nvl_pcdh_blk_maps,
+	.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+	.num_s0ix_blocker = NVL_PCDH_NUM_S0IX_BLOCKER,
+	.blocker_req_offset = NVL_PCDH_BLK_REQ_OFFSET,
+	.lpm_req_guid = PCDH_LPM_REQ_GUID,
+};
+
+static const struct pmc_reg_map nvl_pcds_reg_map = {
+	.pfear_sts = ext_nvl_pcds_pfear_map,
+	.slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+	.slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+	.ltr_show_sts = nvl_pcds_ltr_show_map,
+	.msr_sts = msr_map,
+	.ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+	.regmap_length = NVL_PCDS_PMC_MMIO_REG_LEN,
+	.ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+	.ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES,
+	.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+	.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+	.lpm_num_maps = PTL_LPM_NUM_MAPS,
+	.ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED,
+	.lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+	.etr3_offset = ETR3_OFFSET,
+	.lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET,
+	.lpm_priority_offset = MTL_LPM_PRI_OFFSET,
+	.lpm_en_offset = MTL_LPM_EN_OFFSET,
+	.lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET,
+	.lpm_sts = nvl_pcds_lpm_maps,
+	.lpm_status_offset = MTL_LPM_STATUS_OFFSET,
+	.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
+	.s0ix_blocker_maps = nvl_pcds_blk_maps,
+	.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+	.num_s0ix_blocker = NVL_PCDS_NUM_S0IX_BLOCKER,
+	.lpm_req_guid = PCDS_LPM_REQ_GUID,
+	.blocker_req_offset = NVL_PCDS_BLK_REQ_OFFSET,
+};
+
+static const struct pmc_reg_map nvl_pchs_reg_map = {
+	.pfear_sts = ext_nvl_pchs_pfear_map,
+	.slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+	.slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+	.ltr_show_sts = ptl_pcdp_ltr_show_map,
+	.msr_sts = msr_map,
+	.ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+	.regmap_length = NVL_PCHS_PMC_MMIO_REG_LEN,
+	.ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+	.ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES,
+	.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+	.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+	.lpm_num_maps = PTL_LPM_NUM_MAPS,
+	.ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED,
+	.lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+	.etr3_offset = ETR3_OFFSET,
+	.lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET,
+	.lpm_priority_offset = MTL_LPM_PRI_OFFSET,
+	.lpm_en_offset = MTL_LPM_EN_OFFSET,
+	.lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET,
+	.lpm_sts = nvl_pchs_lpm_maps,
+	.lpm_status_offset = MTL_LPM_STATUS_OFFSET,
+	.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
+	.s0ix_blocker_maps = nvl_pchs_blk_maps,
+	.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+	.num_s0ix_blocker = NVL_PCHS_NUM_S0IX_BLOCKER,
+	.blocker_req_offset = NVL_PCHS_BLK_REQ_OFFSET,
+	.lpm_req_guid = PCHS_LPM_REQ_GUID,
+};
+
+static struct pmc_info nvl_pmc_info_list[] = {
+	{
+		.devid	= PMC_DEVID_NVL_PCDH,
+		.map	= &nvl_pcdh_reg_map,
+	},
+	{
+		.devid  = PMC_DEVID_NVL_PCDS,
+		.map    = &nvl_pcds_reg_map,
+	},
+	{
+		.devid  = PMC_DEVID_NVL_PCHS,
+		.map    = &nvl_pchs_reg_map,
+	},
+	{}
+};
+
+const char *nvl_ltr_block_counter_arr[] = {
+	"PKGC_PREVENT_LTR_IADOMAIN",
+	"PKGC_PREVENT_LTR_GDIE",
+	"PKGC_PREVENT_LTR_PCH",
+	"PKGC_PREVENT_LTR_DISPLAY",
+	"PKGC_PREVENT_LTR_IPU",
+	NULL
+};
+
+const char *nvl_pkgc_blocker_residency[] = {
+	"PKGC_BLOCK_RESIDENCY_INVALID",
+	"PKGC_BLOCK_RESIDENCY_MISC",
+	"PKGC_BLOCK_RESIDENCY_CDIE_MISC",
+	"PKGC_BLOCK_RESIDENCY_MEDIA_MISC",
+	"PKGC_BLOCK_RESIDENCY_GT_MISC",
+	"PKGC_BLOCK_RESIDENCY_HUBATOM_MISC",
+	"PKGC_BLOCK_RESIDENCY_IPU_BUSY",
+	"PKGC_BLOCK_RESIDENCY_IPU_LTR",
+	"PKGC_BLOCK_RESIDENCY_IPU_TIMER",
+	"PKGC_BLOCK_RESIDENCY_DISP_BUSY",
+	"PKGC_BLOCK_RESIDENCY_DISP_LTR",
+	"PKGC_BLOCK_RESIDENCY_DISP_TIMER",
+	"PKGC_BLOCK_RESIDENCY_VPU_BUSY",
+	"PKGC_BLOCK_RESIDENCY_VPU_TIMER",
+	"PKGC_BLOCK_RESIDENCY_PMC_BUSY",
+	"PKGC_BLOCK_RESIDENCY_PMC_LTR",
+	"PKGC_BLOCK_RESIDENCY_PMC_TIMER",
+	"PKGC_BLOCK_RESIDENCY_HUBATOM_ARAT",
+	"PKGC_BLOCK_RESIDENCY_CDIE0_ARAT",
+	"PKGC_BLOCK_RESIDENCY_CDIE1_ARAT",
+	"PKGC_BLOCK_RESIDENCY_GT_ARAT",
+	"PKGC_BLOCK_RESIDENCY_MEDIA_ARAT",
+	"PKGC_BLOCK_RESIDENCY_DEMOTION",
+	"PKGC_BLOCK_RESIDENCY_THERMALS",
+	"PKGC_BLOCK_RESIDENCY_SNCU",
+	"PKGC_BLOCK_RESIDENCY_SVTU",
+	"PKGC_BLOCK_RESIDENCY_IAA",
+	"PKGC_BLOCK_RESIDENCY_IOC",
+	NULL,
+};
+
+static u8 nvl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_PCH};
+static u8 nvl_h_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_PCH};
+
+#define NVL_NPU_PCI_DEV                0xd71d
+
+/*
+ * Set power state of select devices that do not have drivers to D3
+ * so that they do not block Package C entry.
+ */
+static void nvl_d3_fixup(void)
+{
+	pmc_core_set_device_d3(NVL_NPU_PCI_DEV);
+}
+
+static int nvl_resume(struct pmc_dev *pmcdev)
+{
+	nvl_d3_fixup();
+	return cnl_resume(pmcdev);
+}
+
+static int nvl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
+{
+	nvl_d3_fixup();
+	return generic_core_init(pmcdev, pmc_dev_info);
+}
+
+static u32 nvl_pmt_dmu_guids[] = {NVL_PMT_DMU_GUID, 0x0};
+struct pmc_dev_info nvl_s_pmc_dev = {
+	.num_pmcs = ARRAY_SIZE(nvl_pmc_list),
+	.pmc_list = nvl_pmc_list,
+	.regmap_list = nvl_pmc_info_list,
+	.map = &nvl_pcds_reg_map,
+	.sub_req_show = &pmc_core_substate_blk_req_fops,
+	.suspend = cnl_suspend,
+	.resume = nvl_resume,
+	.init = nvl_core_init,
+	.sub_req = pmc_core_pmt_get_blk_sub_req,
+	.dmu_guids = nvl_pmt_dmu_guids,
+	.pc_guid = NVL_PMT_PC_GUID,
+	.pkgc_ltr_blocker_offset = NVL_LTR_BLK_OFFSET,
+	.pkgc_ltr_blocker_counters = nvl_ltr_block_counter_arr,
+	.pkgc_blocker_offset = NVL_PKGC_BLK_OFFSET,
+	.pkgc_blocker_counters = nvl_pkgc_blocker_residency,
+	.ssram_hidden = false,
+	.die_c6_offset = NVL_PMT_DMU_DIE_C6_OFFSET,
+};
+
+struct pmc_dev_info nvl_h_pmc_dev = {
+	.num_pmcs = ARRAY_SIZE(nvl_h_pmc_list),
+	.pmc_list = nvl_h_pmc_list,
+	.regmap_list = nvl_pmc_info_list,
+	.map = &nvl_pcdh_reg_map,
+	.sub_req_show = &pmc_core_substate_blk_req_fops,
+	.suspend = cnl_suspend,
+	.resume = nvl_resume,
+	.init = nvl_core_init,
+	.sub_req = pmc_core_pmt_get_blk_sub_req,
+	.dmu_guids = nvl_pmt_dmu_guids,
+	.pc_guid = NVL_PMT_PC_GUID,
+	.pkgc_ltr_blocker_offset = NVL_LTR_BLK_OFFSET,
+	.pkgc_ltr_blocker_counters = nvl_ltr_block_counter_arr,
+	.pkgc_blocker_offset = NVL_PKGC_BLK_OFFSET,
+	.pkgc_blocker_counters = nvl_pkgc_blocker_residency,
+	.ssram_hidden = false,
+	.die_c6_offset = NVL_PMT_DMU_DIE_C6_OFFSET,
+};
diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c
index 7aa39db256770..3e1cf6905e111 100644
--- a/drivers/platform/x86/intel/pmc/ptl.c
+++ b/drivers/platform/x86/intel/pmc/ptl.c
@@ -137,7 +137,7 @@ static const struct pmc_bit_map *ext_ptl_pcdp_pfear_map[] = {
 	NULL
 };
 
-static const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = {
+const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = {
 	{"SOUTHPORT_A",		CNP_PMC_LTR_SPA},
 	{"SOUTHPORT_B",		CNP_PMC_LTR_SPB},
 	{"SATA",		CNP_PMC_LTR_SATA},
-- 
2.43.0


^ permalink raw reply related

* RE: Status of thermal support for i.MX93
From: Jacky Bai @ 2026-04-09  1:59 UTC (permalink / raw)
  To: Stefan Wahren, Alice Guo, Frank Li
  Cc: Fabio Estevam, imx@lists.linux.dev, Linux ARM,
	open list:GENERIC PM DOMAINS, Daniel Lezcano, Sascha Hauer
In-Reply-To: <1ad05dc4-9eaa-4fc2-a665-e17521fd333c@gmx.net>

Hi Stefan,

> Subject: Status of thermal support for i.MX93
> 
> Hi,
> 
> AFAIK the thermal support for i.MX93 hasn't been mainlined yet. The last
> version I can find is here [1].
> 
> Are there any plans to finish this work?
> 

I thought Frank answered the comments and no further action to do from my side, So it slipped
From my memory.

I just rechecked the comments history, it seems still have some comments need to be resolved.
I will handle them and send out a new version.

Thx for point this out.

BR
> Thanks
> 

^ permalink raw reply

* Re: [PATCH v2] interconnect: imx: fix use-after-free in imx_icc_node_init_qos()
From: Frank Li @ 2026-04-09  3:34 UTC (permalink / raw)
  To: Wentao Liang
  Cc: Georgi Djakov, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, linux-pm, imx, linux-arm-kernel, linux-kernel,
	stable
In-Reply-To: <20260408153022.401123-1-vulab@iscas.ac.cn>

On Wed, Apr 08, 2026 at 03:30:22PM +0000, Wentao Liang wrote:
> The function imx_icc_node_init_qos() manually manages the reference count
> of struct device_node *dn using of_node_put(). However, some error paths
> use dn after the put, leading to use-after-free. Convert to automatic
> cleanup using __free(device_node) to ensure the reference is always
> released when dn goes out of scope.
>
> Fixes: f0d8048525d7 ("interconnect: Add imx core driver")
> Cc: stable@vger.kernel.org
> Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
> ---
> Changes in v2:
> - Use auto cheanup to fix the problem.
> ---

Reviewed-by: Frank Li <Frank.Li@nxp.com>

>  drivers/interconnect/imx/imx.c | 6 ++----
>  1 file changed, 2 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/interconnect/imx/imx.c b/drivers/interconnect/imx/imx.c
> index 9511f80cf041..e5fcdcb88cfb 100644
> --- a/drivers/interconnect/imx/imx.c
> +++ b/drivers/interconnect/imx/imx.c
> @@ -120,7 +120,8 @@ static int imx_icc_node_init_qos(struct icc_provider *provider,
>  	struct imx_icc_node *node_data = node->data;
>  	const struct imx_icc_node_adj_desc *adj = node_data->desc->adj;
>  	struct device *dev = provider->dev;
> -	struct device_node *dn = NULL;
> +	struct device_node *__free(device_nod) dn = of_parse_phandle(dev->of_node,
> +			adj->phandle_name, 0);
>  	struct platform_device *pdev;
>
>  	if (adj->main_noc) {
> @@ -128,7 +129,6 @@ static int imx_icc_node_init_qos(struct icc_provider *provider,
>  		dev_dbg(dev, "icc node %s[%d] is main noc itself\n",
>  			node->name, node->id);
>  	} else {
> -		dn = of_parse_phandle(dev->of_node, adj->phandle_name, 0);
>  		if (!dn) {
>  			dev_warn(dev, "Failed to parse %s\n",
>  				 adj->phandle_name);
> @@ -138,12 +138,10 @@ static int imx_icc_node_init_qos(struct icc_provider *provider,
>  		if (!of_device_is_available(dn)) {
>  			dev_warn(dev, "Missing property %s, skip scaling %s\n",
>  				 adj->phandle_name, node->name);
> -			of_node_put(dn);
>  			return 0;
>  		}
>
>  		pdev = of_find_device_by_node(dn);
> -		of_node_put(dn);
>  		if (!pdev) {
>  			dev_warn(dev, "node %s[%d] missing device for %pOF\n",
>  				 node->name, node->id, dn);
> --
> 2.34.1
>

^ permalink raw reply

* Re: [PATCH V10 4/4] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
From: Daniel Lezcano @ 2026-04-09  6:12 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: jic23, robh, krzk+dt, conor+dt, agross, andersson, lumag,
	dmitry.baryshkov, konradybcio, daniel.lezcano, sboyd, amitk,
	thara.gopinath, lee, rafael, subbaraman.narayanamurthy,
	david.collins, anjelique.melendez, kamal.wadhwa, rui.zhang,
	lukasz.luba, devicetree, linux-arm-msm, linux-iio, linux-kernel,
	linux-pm, cros-qcom-dts-watchers, quic_kotarake, neil.armstrong,
	stephan.gerhold
In-Reply-To: <20260130115421.2197892-5-jishnu.prakash@oss.qualcomm.com>

On Fri, Jan 30, 2026 at 05:24:21PM +0530, Jishnu Prakash wrote:
> Add support for ADC_TM part of PMIC5 Gen3.
> 
> This is an auxiliary driver under the Gen3 ADC driver, which implements the
> threshold setting and interrupt generating functionalities of QCOM ADC_TM
> drivers, used to support thermal trip points.
> 
> Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
> ---
> Changes since v9:
> - Replaced the break statement within scoped_guard() in tm_handler_work() with
>   return statement to fix the error reported by kernel test robot.
> 
> Changes since v8:
> - Made following changes to address Dmitry's comment to use module_auxiliary_driver():
>   - Dropped the wrapper struct containing the auxiliary driver (struct adc_tm5_auxiliary_drv)
>     which was originally meant to expose the TM interrupt callback to be called by
>     main driver and replaced it with standalone definition of the auxiliary_driver struct.
>   - Added call to adc5_gen3_register_tm_event_notifier() in probe to initialize the
>     TM callback for main driver.
>   - Replaced the module_init() and module_exit() calls with module_auxiliary_driver().
> - Made following changes to address Jonathan's comments:
>   - Updated header files included in drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
>     to follow IWYU (include-what-you-use) principles.
>   - Added a DEFINE_GUARD() definition for mutex lock/unlock functions and replaced
>     their existing calls with guard() and scoped_guard() statements using this definition.
>   - Moved some variable declarations in tm_handler_work() to inside the for() loop.
>   - Fixed if() check condition for low_temp in adc_tm5_gen3_set_trip_temp().
> - Dropped the wrapper function adc_tm5_gen3_disable_channel() around
>   _adc_tm5_gen3_disable_channel() as it only calls the inner function with no other actions.
> - Replaced a pr_debug() call with dev_dbg() in tm_handler_work().
> 
> Changes since v7:
> - Addressed following comments from Jonathan:
>   - Replaced {0} with { } in tm_handler_work()
>   - Simplified logic for setting upper_set and lower_set into
>     a single line each, in tm_handler_work()
>   - Cleaned up local variable declarations and high/low threshold
>     check in adc_tm5_gen3_configure()
>   - Moved cleanup action to disable all ADC_TM channels to probe
>     end and added comment to describe it.
>   - Fixed { } formatting in adctm5_auxiliary_id_table[].
> 
> Changes since v6:
> - Addressed following comments from Jonathan:
>   - Added error check for devm_thermal_add_hwmon_sysfs() call.
>   - Used local variable `dev` in multiple places in adc_tm5_probe().
>     in place of `&aux_dev->dev` and `adc_tm5->dev`.
>   - Added a comment to explain cleanup action calling adc5_gen3_clear_work()
>     near probe end.
>   - Fixed return statement at probe end to return last called API's
>     return value directly.
> 
> Changes since v5:
> - Addressed following comments from Jonathan:
>   - Corrected all files to follow kernel-doc formatting fully.
>   - Cleaned up formatting in struct definitions.
>   - Used sizeof() to specify length in register read/write calls
>     instead of using integers directly.
>   - Added comments in adc_tm5_probe() for skipping first SDAM for
>     IRQ request and for usage of auxiliary_set_drvdata().
>   - Corrected line wrap length driver file.
>   - Moved INIT_WORK() and auxiliary_set_drvdata() to earlier
>     locations to ensure they are ready when needed.
> 
> Changes since v4:
> - Fixed a compilation error and updated dependencies in config as suggested
>   by Krzysztof.
> 
>  drivers/thermal/qcom/Kconfig                  |   9 +
>  drivers/thermal/qcom/Makefile                 |   1 +
>  drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 512 ++++++++++++++++++
>  3 files changed, 522 insertions(+)
>  create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
> 
> diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
> index a6bb01082ec6..1acb11e4ac80 100644
> --- a/drivers/thermal/qcom/Kconfig
> +++ b/drivers/thermal/qcom/Kconfig
> @@ -21,6 +21,15 @@ config QCOM_SPMI_ADC_TM5
>  	  Thermal client sets threshold temperature for both warm and cool and
>  	  gets updated when a threshold is reached.
>  
> +config QCOM_SPMI_ADC_TM5_GEN3
> +	tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3"
> +	depends on QCOM_SPMI_ADC5_GEN3
> +	help
> +	  This enables the auxiliary thermal driver for the ADC5 Gen3 thermal
> +	  monitoring device. It shows up as a thermal zone with multiple trip points.
> +	  Thermal client sets threshold temperature for both warm and cool and
> +	  gets updated when a threshold is reached.
> +
>  config QCOM_SPMI_TEMP_ALARM
>  	tristate "Qualcomm SPMI PMIC Temperature Alarm"
>  	depends on OF && SPMI && IIO
> diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
> index 0fa2512042e7..828d9e7bc797 100644
> --- a/drivers/thermal/qcom/Makefile
> +++ b/drivers/thermal/qcom/Makefile
> @@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS)	+= qcom_tsens.o
>  qcom_tsens-y			+= tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
>  				   tsens-8960.o
>  obj-$(CONFIG_QCOM_SPMI_ADC_TM5)	+= qcom-spmi-adc-tm5.o
> +obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3)	+= qcom-spmi-adc-tm5-gen3.o
>  obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
>  obj-$(CONFIG_QCOM_LMH)		+= lmh.o
> diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
> new file mode 100644
> index 000000000000..882355d6606d
> --- /dev/null
> +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
> @@ -0,0 +1,512 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/cleanup.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/device/devres.h>
> +#include <linux/dev_printk.h>
> +#include <linux/err.h>
> +#include <linux/iio/adc/qcom-adc5-gen3-common.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +#include <linux/unaligned.h>
> +
> +#include "../thermal_hwmon.h"
> +
> +struct adc_tm5_gen3_chip;
> +
> +/**
> + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure
> + * @timer: time period of recurring TM measurement.
> + * @tm_chan_index: TM channel number used (ranging from 1-7).
> + * @sdam_index: SDAM on which this TM channel lies.
> + * @common_props: structure with common  ADC channel properties.
> + * @high_thr_en: TM high threshold crossing detection enabled.
> + * @low_thr_en: TM low threshold crossing detection enabled.
> + * @chip: ADC TM device.
> + * @tzd: pointer to thermal device corresponding to TM channel.
> + * @last_temp: last temperature that caused threshold violation,
> + *	or a thermal TM channel.
> + * @last_temp_set: indicates if last_temp is stored.
> + */
> +struct adc_tm5_gen3_channel_props {
> +	unsigned int timer;
> +	unsigned int tm_chan_index;
> +	unsigned int sdam_index;
> +	struct adc5_channel_common_prop common_props;
> +	bool high_thr_en;
> +	bool low_thr_en;
> +	struct adc_tm5_gen3_chip *chip;
> +	struct thermal_zone_device *tzd;
> +	int last_temp;
> +	bool last_temp_set;
> +};
> +
> +/**
> + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure
> + * @dev_data: Top-level ADC device data.
> + * @chan_props: Array of ADC_TM channel structures.
> + * @nchannels: number of TM channels allocated
> + * @dev: SPMI ADC5 Gen3 device.
> + * @tm_handler_work: handler for TM interrupt for threshold violation.
> + */
> +struct adc_tm5_gen3_chip {
> +	struct adc5_device_data *dev_data;
> +	struct adc_tm5_gen3_channel_props *chan_props;
> +	unsigned int nchannels;
> +	struct device *dev;
> +	struct work_struct tm_handler_work;
> +};
> +
> +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, adc5_gen3_mutex_lock(_T->dev),
> +	     adc5_gen3_mutex_unlock(_T->dev))
> +
> +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq)
> +{
> +	int i;
> +
> +	for (i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
> +		if (adc_tm5->dev_data->base[i].irq == irq)
> +			return i;
> +	}
> +	return -ENOENT;
> +}
> +
> +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
> +	int ret, sdam_num;
> +	u8 tm_status[2];
> +	u8 status, val;
> +
> +	sdam_num = get_sdam_from_irq(adc_tm5, irq);
> +	if (sdam_num < 0) {
> +		dev_err(adc_tm5->dev, "adc irq %d not associated with an sdam\n",
> +			irq);
> +		return IRQ_HANDLED;
> +	}
> +
> +	ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1,
> +			     &status, sizeof(status));
> +	if (ret) {
> +		dev_err(adc_tm5->dev, "adc read status1 failed with %d\n", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
> +		dev_err_ratelimited(adc_tm5->dev,
> +				    "Unexpected conversion fault, status:%#x\n",
> +				    status);
> +		val = ADC5_GEN3_CONV_ERR_CLR_REQ;
> +		adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num,
> +				       ADC5_GEN3_CONV_ERR_CLR, &val, 1);
> +		return IRQ_HANDLED;
> +	}
> +
> +	ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS,
> +			     tm_status, sizeof(tm_status));
> +	if (ret) {
> +		dev_err(adc_tm5->dev, "adc read TM status failed with %d\n", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (tm_status[0] || tm_status[1])
> +		schedule_work(&adc_tm5->tm_handler_work);
> +
> +	dev_dbg(adc_tm5->dev, "Interrupt status:%#x, high:%#x, low:%#x\n",
> +		status, tm_status[0], tm_status[1]);
> +
> +	return IRQ_HANDLED;

This ISR routine should be revisited:

 - no error message inside

 - use a shared interrupt to split what is handled by the ADC and the
    TM drivers

 - do not return IRQ_HANDLED in case of error (cf. irqreturn.h doc)

 - do not use a dedicated workqueue but the threaded mechanism of the irq

> +}
> +
> +static int adc5_gen3_tm_status_check(struct adc_tm5_gen3_chip *adc_tm5,
> +				     int sdam_index, u8 *tm_status, u8 *buf)
> +{
> +	int ret;
> +
> +	ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS,
> +			     tm_status, 2);
> +	if (ret) {
> +		dev_err(adc_tm5->dev, "adc read TM status failed with %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS_CLR,
> +				     tm_status, 2);
> +	if (ret) {
> +		dev_err(adc_tm5->dev, "adc status clear conv_req failed with %d\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_CH_DATA0(0),
> +			     buf, 16);
> +	if (ret)
> +		dev_err(adc_tm5->dev, "adc read data failed with %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static void tm_handler_work(struct work_struct *work)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = container_of(work, struct adc_tm5_gen3_chip,
> +							 tm_handler_work);
> +	int sdam_index = -1;
> +	u8 tm_status[2] = { };
> +	u8 buf[16] = { };
> +
> +	for (int i = 0; i < adc_tm5->nchannels; i++) {
> +		struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i];
> +		int offset = chan_prop->tm_chan_index;
> +		bool upper_set, lower_set;
> +		int ret, temp;
> +		u16 code;
> +
> +		scoped_guard(adc5_gen3, adc_tm5) {
> +			if (chan_prop->sdam_index != sdam_index) {
> +				sdam_index = chan_prop->sdam_index;
> +				ret = adc5_gen3_tm_status_check(adc_tm5, sdam_index,
> +								tm_status, buf);
> +				if (ret)
> +					return;
> +			}
> +
> +			upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en);
> +			lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en);
> +		}
> +
> +		if (!(upper_set || lower_set))
> +			continue;
> +
> +		code = get_unaligned_le16(&buf[2 * offset]);
> +		dev_dbg(adc_tm5->dev, "ADC_TM threshold code:%#x\n", code);

Please avoid debug traces when possible

> +		ret = adc5_gen3_therm_code_to_temp(adc_tm5->dev,
> +						   &chan_prop->common_props,
> +						   code, &temp);
> +		if (ret) {
> +			dev_err(adc_tm5->dev,
> +				"Invalid temperature reading, ret = %d, code=%#x\n",
> +				ret, code);

And avoid error traces in the runtime path

> +			continue;
> +		}
> +
> +		chan_prop->last_temp = temp;
> +		chan_prop->last_temp_set = true;
> +		thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED);
> +	}
> +}
> +
> +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp)
> +{
> +	struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
> +	struct adc_tm5_gen3_chip *adc_tm5;
> +
> +	if (!prop || !prop->chip)
> +		return -EINVAL;
> +
> +	adc_tm5 = prop->chip;
> +
> +	if (prop->last_temp_set) {
> +		pr_debug("last_temp: %d\n", prop->last_temp);
> +		prop->last_temp_set = false;
> +		*temp = prop->last_temp;
> +		return 0;
> +	}

Why do you need to do that?

The temperature should reflect the current situation even if the
reading was triggered by a thermal trip violation.

> +
> +	return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props,
> +					    temp);
> +}
> +
> +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
> +	int ret;
> +	u8 val;
> +
> +	prop->high_thr_en = false;
> +	prop->low_thr_en = false;
> +
> +	ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
> +	if (ret)
> +		return ret;
> +
> +	val = BIT(prop->tm_chan_index);
> +	ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> +			      ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val));
> +	if (ret)
> +		return ret;
> +
> +	val = MEAS_INT_DISABLE;
> +	ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> +			      ADC5_GEN3_TIMER_SEL, &val, sizeof(val));
> +	if (ret)
> +		return ret;
> +
> +	/* To indicate there is an actual conversion request */
> +	val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
> +	ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> +			      ADC5_GEN3_PERPH_CH, &val, sizeof(val));
> +	if (ret)
> +		return ret;
> +
> +	val = ADC5_GEN3_CONV_REQ_REQ;
> +	return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> +			       ADC5_GEN3_CONV_REQ, &val, sizeof(val));
> +}
> +
> +#define ADC_TM5_GEN3_CONFIG_REGS 12

Please define at the top of the file

> +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop,
> +				  int low_temp, int high_temp)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
> +	u8 buf[ADC_TM5_GEN3_CONFIG_REGS];
> +	u8 conv_req;
> +	u16 adc_code;
> +	int ret;
> +
> +	ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index,
> +			     ADC5_GEN3_SID, buf, sizeof(buf));
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Write SID */
> +	buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid);
> +
> +	/* Select TM channel and indicate there is an actual conversion request */
> +	buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
> +
> +	buf[2] = prop->timer;
> +
> +	/* Digital param selection */
> +	adc5_gen3_update_dig_param(&prop->common_props, &buf[3]);
> +
> +	/* Update fast average sample value */
> +	buf[4] &= ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK;
> +	buf[4] |= prop->common_props.avg_samples | ADC5_GEN3_FAST_AVG_CTL_EN;
> +
> +	/* Select ADC channel */
> +	buf[5] = prop->common_props.channel;
> +
> +	/* Select HW settle delay for channel */
> +	buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
> +			    prop->common_props.hw_settle_time_us);
> +
> +	/* High temperature corresponds to low voltage threshold */
> +	prop->low_thr_en = (high_temp != INT_MAX);
> +	if (prop->low_thr_en) {
> +		adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp);
> +		put_unaligned_le16(adc_code, &buf[8]);
> +	}
> +
> +	/* Low temperature corresponds to high voltage threshold */
> +	prop->high_thr_en = (low_temp != -INT_MAX);
> +	if (prop->high_thr_en) {
> +		adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp);
> +		put_unaligned_le16(adc_code, &buf[10]);
> +	}
> +
> +	buf[7] = 0;
> +	if (prop->high_thr_en)
> +		buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN;
> +	if (prop->low_thr_en)
> +		buf[7] |= ADC5_GEN3_LOW_THR_INT_EN;
> +
> +	ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID,
> +			      buf, sizeof(buf));
> +	if (ret < 0)
> +		return ret;
> +
> +	conv_req = ADC5_GEN3_CONV_REQ_REQ;
> +	return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> +			       ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
> +}
> +
> +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz,
> +				      int low_temp, int high_temp)
> +{
> +	struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
> +	struct adc_tm5_gen3_chip *adc_tm5;
> +
> +	if (!prop || !prop->chip)
> +		return -EINVAL;
> +
> +	adc_tm5 = prop->chip;
> +
> +	dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n",
> +		prop->common_props.label, low_temp, high_temp);
> +
> +	guard(adc5_gen3)(adc_tm5);
> +	if (high_temp == INT_MAX && low_temp == -INT_MAX)
> +		return adc_tm5_gen3_disable_channel(prop);

Why disable the channel instead of returning an errno ?

> +	return adc_tm5_gen3_configure(prop, low_temp, high_temp);
> +}
> +
> +static const struct thermal_zone_device_ops adc_tm_ops = {
> +	.get_temp = adc_tm5_gen3_get_temp,
> +	.set_trips = adc_tm5_gen3_set_trip_temp,
> +};
> +
> +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5)
> +{
> +	unsigned int i, channel;
> +	struct thermal_zone_device *tzd;
> +	int ret;
> +
> +	for (i = 0; i < adc_tm5->nchannels; i++) {
> +		channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props);
> +		tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel,
> +						    &adc_tm5->chan_props[i],
> +						    &adc_tm_ops);
> +
> +		if (IS_ERR(tzd)) {
> +			if (PTR_ERR(tzd) == -ENODEV) {
> +				dev_warn(adc_tm5->dev,
> +					 "thermal sensor on channel %d is not used\n",
> +					 channel);
> +				continue;
> +			}
> +			return dev_err_probe(adc_tm5->dev, PTR_ERR(tzd),
> +					     "Error registering TZ zone:%ld for channel:%d\n",
> +					     PTR_ERR(tzd), channel);
> +		}
> +		adc_tm5->chan_props[i].tzd = tzd;
> +		ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +static void adc5_gen3_clear_work(void *data)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = data;
> +
> +	cancel_work_sync(&adc_tm5->tm_handler_work);
> +}
> +
> +static void adc5_gen3_disable(void *data)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = data;
> +	int i;
> +
> +	guard(adc5_gen3)(adc_tm5);
> +	/* Disable all available TM channels */
> +	for (i = 0; i < adc_tm5->nchannels; i++)
> +		adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]);
> +}
> +
> +static void adctm_event_handler(struct auxiliary_device *adev)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5 = auxiliary_get_drvdata(adev);
> +
> +	schedule_work(&adc_tm5->tm_handler_work);
> +}
> +
> +static int adc_tm5_probe(struct auxiliary_device *aux_dev,
> +			 const struct auxiliary_device_id *id)
> +{
> +	struct adc_tm5_gen3_chip *adc_tm5;
> +	struct tm5_aux_dev_wrapper *aux_dev_wrapper;
> +	struct device *dev = &aux_dev->dev;
> +	int i, ret;
> +
> +	adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL);
> +	if (!adc_tm5)
> +		return -ENOMEM;
> +
> +	aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper,
> +				       aux_dev);
> +
> +	adc_tm5->dev = dev;
> +	adc_tm5->dev_data = aux_dev_wrapper->dev_data;
> +	adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels;
> +	adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels,
> +					   sizeof(*adc_tm5->chan_props), GFP_KERNEL);
> +	if (!adc_tm5->chan_props)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < adc_tm5->nchannels; i++) {
> +		adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i];
> +		adc_tm5->chan_props[i].timer = MEAS_INT_1S;
> +		adc_tm5->chan_props[i].sdam_index = (i + 1) / 8;
> +		adc_tm5->chan_props[i].tm_chan_index = (i + 1) % 8;
> +		adc_tm5->chan_props[i].chip = adc_tm5;
> +	}
> +
> +	INIT_WORK(&adc_tm5->tm_handler_work, tm_handler_work);

Why is it needed

> +	/*
> +	 * Skipping first SDAM IRQ as it is requested in parent driver.
> +	 * If there is a TM violation on that IRQ, the parent driver calls
> +	 * the notifier (adctm_event_handler) exposed from this driver to handle it.
> +	 */
> +	for (i = 1; i < adc_tm5->dev_data->num_sdams; i++) {
> +		ret = devm_request_threaded_irq(dev,
> +						adc_tm5->dev_data->base[i].irq,
> +						NULL, adctm5_gen3_isr, IRQF_ONESHOT,
> +						adc_tm5->dev_data->base[i].irq_name,
> +						adc_tm5);

The threaded interrupts set the isr in a thread and from the thread
handling the event, there is a work queue scheduled. Why not use the
top and bottom halves of the threaded interrupt ? Hopefully you should
be able to remove the lock.

> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	/*
> +	 * This drvdata is only used in the function (adctm_event_handler)
> +	 * called by parent ADC driver in case of TM violation on the first SDAM.
> +	 */
> +	auxiliary_set_drvdata(aux_dev, adc_tm5);
> +
> +	adc5_gen3_register_tm_event_notifier(dev, adctm_event_handler);
> +
> +	/*
> +	 * This is to cancel any instances of tm_handler_work scheduled by
> +	 * TM interrupt, at the time of module removal.
> +	 */
> +

Remove the extra line

> +	ret = devm_add_action(dev, adc5_gen3_clear_work, adc_tm5);
> +	if (ret)
> +		return ret;
> +
> +	ret = adc_tm5_register_tzd(adc_tm5);
> +	if (ret)
> +		return ret;
> +
> +	/* This is to disable all ADC_TM channels in case of probe failure. */
> +

Remove the extra line

> +	return devm_add_action(dev, adc5_gen3_disable, adc_tm5);
> +}
> +
> +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = {
> +	{ .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3", },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table);
> +
> +static struct auxiliary_driver adctm5gen3_auxiliary_driver = {
> +	.id_table = adctm5_auxiliary_id_table,
> +	.probe = adc_tm5_probe,
> +};
> +
> +module_auxiliary_driver(adctm5gen3_auxiliary_driver);
> +
> +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
> -- 
> 2.25.1
> 

-- 

^ permalink raw reply

* [PATCH v3 0/2] pmdomain: imx: Fix i.MX8MP VC8000E power up sequence
From: Peng Fan (OSS) @ 2026-04-09  8:07 UTC (permalink / raw)
  To: Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Daniel Baluta
  Cc: linux-pm, imx, linux-arm-kernel, linux-kernel, Peng Fan, stable

There is an errata for i.MX8MP VC8000E:
    ERR050531: VPU_NOC power down handshake may hang during VC8000E/VPUMIX
    power up/down cycling.
    Description: VC8000E reset de-assertion edge and AXI clock may have a
    timing issue.
    Workaround: Set bit2 (vc8000e_clk_en) of BLK_CLK_EN_CSR to 0 to gate off
    both AXI clock and VC8000E clock sent to VC8000E and AXI clock sent to
    VPU_NOC m_v_2 interface during VC8000E power up(VC8000E reset is
    de-asserted by HW)

This patchset is to fix the errata. More info could be found in each
patch commit.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
Changes in v3:
- Separate power up notifier fix into patch 1
- Link to v2: https://lore.kernel.org/r/20260228-imx8mp-vc8000e-pm-v2-1-fd255a0d5958@nxp.com

Changes in v2:
- Add errata link in commit message
- Add comment for is_errata_err050531
- Link to v1: https://lore.kernel.org/r/20260128-imx8mp-vc8000e-pm-v1-1-6c171451c732@nxp.com

---
Peng Fan (2):
      pmdomain: imx: Fix i.MX8MP power notifier
      pmdomain: imx: Fix i.MX8MP VC8000E power up sequence

 drivers/pmdomain/imx/imx8m-blk-ctrl.c | 45 +++++++++++++++++++++++++++++++++--
 1 file changed, 43 insertions(+), 2 deletions(-)
---
base-commit: b3ab9a7b9b32806b1b68c4fe7d5298702195eb3a
change-id: 20260128-imx8mp-vc8000e-pm-4278e6d48b54

Best regards,
-- 
Peng Fan <peng.fan@nxp.com>


^ permalink raw reply

* [PATCH v3 1/2] pmdomain: imx: Fix i.MX8MP power notifier
From: Peng Fan (OSS) @ 2026-04-09  8:07 UTC (permalink / raw)
  To: Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Daniel Baluta
  Cc: linux-pm, imx, linux-arm-kernel, linux-kernel, Peng Fan, stable
In-Reply-To: <20260409-imx8mp-vc8000e-pm-v3-0-3e023eaa245b@nxp.com>

From: Peng Fan <peng.fan@nxp.com>

Using imx8mm_vpu_power_notifier() for i.MX8MP is wrong, as it ungates
the VPU clocks to provide the ADB clock, which is necessary on i.MX8MM,
but on i.MX8MP there is a separate gate (bit 3) for the NoC. So add
imx8mp_vpu_power_notifier() for i.MX8MP.

Fixes: a1a5f15f7f6cb ("soc: imx: imx8m-blk-ctrl: add i.MX8MP VPU blk ctrl")
Cc: stable@vger.kernel.org
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/pmdomain/imx/imx8m-blk-ctrl.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c
index 19e992d2ee3b845bc9382bcd494a5d96f9c6ac44..e13a47eeed75d7189aa15370a7bee4cceb05a1d6 100644
--- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c
+++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c
@@ -514,9 +514,34 @@ static const struct imx8m_blk_ctrl_domain_data imx8mp_vpu_blk_ctl_domain_data[]
 	},
 };
 
+static int imx8mp_vpu_power_notifier(struct notifier_block *nb,
+				     unsigned long action, void *data)
+{
+	struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl,
+						 power_nb);
+
+	if (action == GENPD_NOTIFY_ON) {
+		/*
+		 * On power up we have no software backchannel to the GPC to
+		 * wait for the ADB handshake to happen, so we just delay for a
+		 * bit. On power down the GPC driver waits for the handshake.
+		 */
+
+		udelay(5);
+
+		/* set "fuse" bits to enable the VPUs */
+		regmap_set_bits(bc->regmap, 0x8, 0xffffffff);
+		regmap_set_bits(bc->regmap, 0xc, 0xffffffff);
+		regmap_set_bits(bc->regmap, 0x10, 0xffffffff);
+		regmap_set_bits(bc->regmap, 0x14, 0xffffffff);
+	}
+
+	return NOTIFY_OK;
+}
+
 static const struct imx8m_blk_ctrl_data imx8mp_vpu_blk_ctl_dev_data = {
 	.max_reg = 0x18,
-	.power_notifier_fn = imx8mm_vpu_power_notifier,
+	.power_notifier_fn = imx8mp_vpu_power_notifier,
 	.domains = imx8mp_vpu_blk_ctl_domain_data,
 	.num_domains = ARRAY_SIZE(imx8mp_vpu_blk_ctl_domain_data),
 };

-- 
2.37.1


^ permalink raw reply related

* [PATCH v3 2/2] pmdomain: imx: Fix i.MX8MP VC8000E power up sequence
From: Peng Fan (OSS) @ 2026-04-09  8:07 UTC (permalink / raw)
  To: Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
	Fabio Estevam, Daniel Baluta
  Cc: linux-pm, imx, linux-arm-kernel, linux-kernel, Peng Fan, stable
In-Reply-To: <20260409-imx8mp-vc8000e-pm-v3-0-3e023eaa245b@nxp.com>

From: Peng Fan <peng.fan@nxp.com>

Per errata[1]:
ERR050531: VPU_NOC power down handshake may hang during VC8000E/VPUMIX
power up/down cycling.
Description: VC8000E reset de-assertion edge and AXI clock may have a
timing issue.
Workaround: Set bit2 (vc8000e_clk_en) of BLK_CLK_EN_CSR to 0 to gate off
both AXI clock and VC8000E clock sent to VC8000E and AXI clock sent to
VPU_NOC m_v_2 interface during VC8000E power up(VC8000E reset is
de-asserted by HW)

Add a bool variable is_errata_err050531 in
'struct imx8m_blk_ctrl_domain_data' to represent whether the workaround
is needed. If is_errata_err050531 is true, first clear the clk before
powering up gpc, then enable the clk after powering up gpc.

[1] https://www.nxp.com/webapp/Download?colCode=IMX8MP_1P33A

Fixes: a1a5f15f7f6cb ("soc: imx: imx8m-blk-ctrl: add i.MX8MP VPU blk ctrl")
Cc: stable@vger.kernel.org
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/pmdomain/imx/imx8m-blk-ctrl.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c
index e13a47eeed75d7189aa15370a7bee4cceb05a1d6..1cd0a22ce3e533358dd7449da9989162b36c5fe6 100644
--- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c
+++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c
@@ -54,6 +54,15 @@ struct imx8m_blk_ctrl_domain_data {
 	 * register.
 	 */
 	u32 mipi_phy_rst_mask;
+
+	/*
+	 * VC8000E reset de-assertion edge and AXI clock may have a timing issue.
+	 * Workaround: Set bit2 (vc8000e_clk_en) of BLK_CLK_EN_CSR to 0 to gate off
+	 * both AXI clock and VC8000E clock sent to VC8000E and AXI clock sent to
+	 * VPU_NOC m_v_2 interface during VC8000E power up(VC8000E reset is
+	 * de-asserted by HW)
+	 */
+	bool is_errata_err050531;
 };
 
 #define DOMAIN_MAX_CLKS 4
@@ -108,7 +117,11 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd)
 		dev_err(bc->dev, "failed to enable clocks\n");
 		goto bus_put;
 	}
-	regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
+
+	if (data->is_errata_err050531)
+		regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
+	else
+		regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
 
 	/* power up upstream GPC domain */
 	ret = pm_runtime_get_sync(domain->power_dev);
@@ -117,6 +130,9 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd)
 		goto clk_disable;
 	}
 
+	if (data->is_errata_err050531)
+		regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
+
 	/* wait for reset to propagate */
 	udelay(5);
 

-- 
2.37.1


^ permalink raw reply related

* Re: [patch V2 03/11] posix-timers: Expand timer_[re]arm() callbacks with a boolean return value
From: Frederic Weisbecker @ 2026-04-09  9:28 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: LKML, Peter Zijlstra (Intel), John Stultz, Stephen Boyd,
	Anna-Maria Behnsen, Calvin Owens, Alexander Viro,
	Christian Brauner, Jan Kara, linux-fsdevel, Sebastian Reichel,
	linux-pm, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	netfilter-devel, coreteam
In-Reply-To: <20260408114952.130222296@kernel.org>

Le Wed, Apr 08, 2026 at 01:53:56PM +0200, Thomas Gleixner a écrit :
> In order to catch expiry times which are already in the past the
> timer_arm() and timer_rearm() callbacks need to be able to report back to
> the caller whether the timer has been queued or not.
> 
> Change the function signature and let all implementations return true for
> now. While at it simplify posix_cpu_timer_rearm().
> 
> No functional change intended.
> 
> Signed-off-by: Thomas Gleixner <tglx@kernel.org>
> Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
> Acked-by: John Stultz <jstultz@google.com>
> Cc: Stephen Boyd <sboyd@kernel.org>
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Frederic Weisbecker <frederic@kernel.org>

Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply

* [PATCH v2 0/2] cpufreq: governor: Fix races and stale baseline on prev_cpu_nice
From: Zhongqiu Han @ 2026-04-09 11:14 UTC (permalink / raw)
  To: rafael, viresh.kumar
  Cc: venkatesh.pallipadi, davej, trenn, linux-pm, linux-kernel,
	zhongqiu.han

Patch 1 fixes a data race between sysfs store callbacks and the DBS
work handler.  gov_update_cpu_data() writes prev_cpu_idle and
prev_cpu_nice while holding only attr_set->update_lock, whereas
dbs_update() reads and writes the same fields while holding only
policy_dbs->update_mutex.  Because these are independent locks, the
two paths are not mutually exclusive.  The fix acquires
policy_dbs->update_mutex inside gov_update_cpu_data() for each
policy, and also holds it around the initialization loop in
cpufreq_dbs_governor_start() to close a similar window against
concurrent sysfs writes.

Patch 2 fixes a stale-baseline spike on prev_cpu_nice that occurs
when ignore_nice_load is enabled via sysfs.  Because prev_cpu_nice
is only advanced in dbs_update() when ignore_nice is true, it
accumulates an unbounded debt of nice CPU time while ignore_nice is
false.  The moment ignore_nice_load is flipped to 1, the next
dbs_update() computes a massive idle_time delta against the stale
baseline, producing an incorrect CPU load value.  The fix
unconditionally samples and advances prev_cpu_nice on every
dbs_update() call, regardless of ignore_nice, so the baseline is
always current.  As a consequence, the prev_cpu_nice reset in
gov_update_cpu_data() and the gov_update_cpu_data() call in
ignore_nice_load_store() are no longer needed and are removed.

Changelog:
- Update linux-next base
- Based on v1 review, patch 1 is updated to add the missing
  protection around cpufreq_dbs_governor_start(), and patch 2/2 is added.
- Link to v1: https://lore.kernel.org/all/20260406110113.3475920-1-zhongqiu.han@oss.qualcomm.com/


Zhongqiu Han (2):
  cpufreq: governor: Fix race between sysfs store and dbs work handler
  cpufreq: governor: Fix stale prev_cpu_nice spike when enabling
    ignore_nice_load

 drivers/cpufreq/cpufreq_conservative.c |  3 ---
 drivers/cpufreq/cpufreq_governor.c     | 35 ++++++++++++++++++--------
 drivers/cpufreq/cpufreq_ondemand.c     |  3 ---
 3 files changed, 24 insertions(+), 17 deletions(-)


base-commit: f3e6330d7fe42b204af05a2dbc68b379e0ad179e
-- 
2.43.0


^ permalink raw reply

* [PATCH v2 1/2] cpufreq: governor: Fix race between sysfs store and dbs work handler
From: Zhongqiu Han @ 2026-04-09 11:14 UTC (permalink / raw)
  To: rafael, viresh.kumar
  Cc: venkatesh.pallipadi, davej, trenn, linux-pm, linux-kernel,
	zhongqiu.han
In-Reply-To: <20260409111407.9775-1-zhongqiu.han@oss.qualcomm.com>

gov_update_cpu_data() resets per-CPU prev_cpu_idle and prev_cpu_nice
for every CPU in the governed domain. It is called from sysfs store
callbacks (e.g. ignore_nice_load_store) which run under
attr_set->update_lock, held by the surrounding governor_store().

Concurrently, dbs_work_handler() calls gov->gov_dbs_update() (which
calls dbs_update()) under policy_dbs->update_mutex. dbs_update() both
reads and writes the same prev_cpu_idle / prev_cpu_nice fields. The
potential race path is:

Path A (sysfs write, holds attr_set->update_lock only):

  governor_store()
    mutex_lock(&attr_set->update_lock)
    ignore_nice_load_store()
      dbs_data->ignore_nice_load = input
      gov_update_cpu_data(dbs_data)
        list_for_each_entry(policy_dbs, ...)
          for_each_cpu(j, ...)
            j_cdbs->prev_cpu_idle = get_cpu_idle_time(...)  /* write */
            j_cdbs->prev_cpu_nice = kcpustat_field(...)     /* write */
    mutex_unlock(&attr_set->update_lock)

Path B (work queue, holds policy_dbs->update_mutex only):

  dbs_work_handler()
    mutex_lock(&policy_dbs->update_mutex)
    gov->gov_dbs_update(policy)
      dbs_update()
        for_each_cpu(j, policy->cpus)
          idle_time = cur - j_cdbs->prev_cpu_idle           /* read  */
          j_cdbs->prev_cpu_idle = cur_idle_time             /* write */
          idle_time += cur_nice - j_cdbs->prev_cpu_nice     /* read  */
          j_cdbs->prev_cpu_nice = cur_nice                  /* write */
    mutex_unlock(&policy_dbs->update_mutex)

Because attr_set->update_lock and policy_dbs->update_mutex are two
completely independent locks, the two paths are not mutually exclusive.
This results in a data race on cpu_dbs_info.prev_cpu_idle and
cpu_dbs_info.prev_cpu_nice.

Fix this by also acquiring policy_dbs->update_mutex in
gov_update_cpu_data() for each policy, so that path A participates in
the mutual exclusion already established by dbs_work_handler(). Also
update the function comment to accurately reflect the two-level locking
contract.

Additionally, cpufreq_dbs_governor_start() initializes prev_cpu_idle
and prev_cpu_nice without holding policy_dbs->update_mutex. After
cpufreq_dbs_governor_init() returns, the new policy is already visible
in attr_set->policy_list and sysfs attributes are accessible. A
concurrent sysfs write can therefore call gov_update_cpu_data() and
race with the initialization loop on the same u64 fields. Fix this by
holding policy_dbs->update_mutex around the initialization loop in
cpufreq_dbs_governor_start() as well.

The root of this race dates back to the original ondemand/conservative
governors. Before commit ee88415caf73 ("[CPUFREQ] Cleanup locking in
conservative governor") and commit 5a75c82828e7 ("[CPUFREQ] Cleanup
locking in ondemand governor"), all accesses to prev_cpu_idle and
prev_cpu_nice in cpufreq_governor_dbs() (path X), store_ignore_nice_load()
(path Y), and do_dbs_timer() (path Z) were serialised by the same
dbs_mutex, so no race existed. Those two commits switched do_dbs_timer()
from dbs_mutex to a per-policy/per-cpu timer_mutex to reduce lock
contention, but left store_ignore_nice_load() still holding dbs_mutex.
As a result, path Y (store) and path Z (do_dbs_timer) no longer shared a
common lock, introducing a potential race on prev_cpu_idle/prev_cpu_nice
between store_ignore_nice_load() and dbs_check_cpu().

Commit 326c86deaed54a ("[CPUFREQ] Remove unneeded locks") then removed
dbs_mutex from store_ignore_nice_load() entirely, introducing an
additional potential race between store_ignore_nice_load() (path Y, now
lockless) and cpufreq_governor_dbs() (path X, still holding dbs_mutex),
while the race between path Y and path Z remained.

Fixes: ee88415caf736b ("[CPUFREQ] Cleanup locking in conservative governor")
Fixes: 5a75c82828e7c0 ("[CPUFREQ] Cleanup locking in ondemand governor")
Fixes: 326c86deaed54a ("[CPUFREQ] Remove unneeded locks")
Signed-off-by: Zhongqiu Han <zhongqiu.han@oss.qualcomm.com>
---
 drivers/cpufreq/cpufreq_governor.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/cpufreq/cpufreq_governor.c b/drivers/cpufreq/cpufreq_governor.c
index 86f35e451914..c0d419c95609 100644
--- a/drivers/cpufreq/cpufreq_governor.c
+++ b/drivers/cpufreq/cpufreq_governor.c
@@ -90,7 +90,8 @@ EXPORT_SYMBOL_GPL(sampling_rate_store);
  * (that may be a single policy or a bunch of them if governor tunables are
  * system-wide).
  *
- * Call under the @dbs_data mutex.
+ * Call under the @dbs_data->attr_set.update_lock. The per-policy
+ * update_mutex is acquired and released internally for each policy.
  */
 void gov_update_cpu_data(struct dbs_data *dbs_data)
 {
@@ -99,6 +100,7 @@ void gov_update_cpu_data(struct dbs_data *dbs_data)
 	list_for_each_entry(policy_dbs, &dbs_data->attr_set.policy_list, list) {
 		unsigned int j;
 
+		mutex_lock(&policy_dbs->update_mutex);
 		for_each_cpu(j, policy_dbs->policy->cpus) {
 			struct cpu_dbs_info *j_cdbs = &per_cpu(cpu_dbs, j);
 
@@ -107,6 +109,7 @@ void gov_update_cpu_data(struct dbs_data *dbs_data)
 			if (dbs_data->ignore_nice_load)
 				j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
 		}
+		mutex_unlock(&policy_dbs->update_mutex);
 	}
 }
 EXPORT_SYMBOL_GPL(gov_update_cpu_data);
@@ -529,6 +532,7 @@ int cpufreq_dbs_governor_start(struct cpufreq_policy *policy)
 	ignore_nice = dbs_data->ignore_nice_load;
 	io_busy = dbs_data->io_is_busy;
 
+	mutex_lock(&policy_dbs->update_mutex);
 	for_each_cpu(j, policy->cpus) {
 		struct cpu_dbs_info *j_cdbs = &per_cpu(cpu_dbs, j);
 
@@ -541,6 +545,7 @@ int cpufreq_dbs_governor_start(struct cpufreq_policy *policy)
 		if (ignore_nice)
 			j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
 	}
+	mutex_unlock(&policy_dbs->update_mutex);
 
 	gov->start(policy);
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 2/2] cpufreq: governor: Fix stale prev_cpu_nice spike when enabling ignore_nice_load
From: Zhongqiu Han @ 2026-04-09 11:14 UTC (permalink / raw)
  To: rafael, viresh.kumar
  Cc: venkatesh.pallipadi, davej, trenn, linux-pm, linux-kernel,
	zhongqiu.han
In-Reply-To: <20260409111407.9775-1-zhongqiu.han@oss.qualcomm.com>

When ignore_nice_load is toggled from 0 to 1 via sysfs, dbs_update()
may run concurrently and observe the new tunable value while
prev_cpu_nice still holds a stale baseline, producing a spurious
massive idle_time that results in an incorrect CPU load value.

The root cause is that prev_cpu_nice is only updated inside dbs_update()
when ignore_nice is true.  While ignore_nice is false, prev_cpu_nice is
never advanced, so it accumulates an unbounded debt of nice CPU time.
The moment ignore_nice is flipped to 1, the very next dbs_update() call
computes:

  idle_time += cur_nice - j_cdbs->prev_cpu_nice

where prev_cpu_nice is stale (possibly 0 if never updated since boot),
making idle_time artificially large.

The race can be illustrated with two concurrent paths:

Path A (sysfs write, holds attr_set->update_lock):

  governor_store()
    mutex_lock(&attr_set->update_lock)
    ignore_nice_load_store()
      dbs_data->ignore_nice_load = 1              /* (A1) */
      gov_update_cpu_data(dbs_data)
        mutex_lock(&policy_dbs->update_mutex)     /* (A2) */
          j_cdbs->prev_cpu_nice = kcpustat_field(...)
        mutex_unlock(&policy_dbs->update_mutex)
    mutex_unlock(&attr_set->update_lock)

Path B (work queue, wins the race between A1 and A2):

  dbs_work_handler()
    mutex_lock(&policy_dbs->update_mutex)         /* acquired before A2 */
    dbs_update()
      ignore_nice = dbs_data->ignore_nice_load    /* sees new value: 1 */
      cur_nice = kcpustat_field(...)
      idle_time += cur_nice - j_cdbs->prev_cpu_nice /* stale */
      j_cdbs->prev_cpu_nice = cur_nice
    mutex_unlock(&policy_dbs->update_mutex)

Note that even without the race, the anomaly occurs deterministically
on the very first dbs_update() call after ignore_nice_load is enabled,
because prev_cpu_nice has never been updated while ignore_nice was 0.
The race only widens the window in which the stale read can happen.

Fix this by unconditionally sampling cur_nice and advancing prev_cpu_nice
in dbs_update() on every call, regardless of ignore_nice.  With
prev_cpu_nice always reflecting the most recent sample, enabling
ignore_nice_load can never produce a stale-baseline spike: the delta
will always be the nice time accumulated in the last sampling interval,
not since boot.

As a consequence of always tracking prev_cpu_nice:

  - gov_update_cpu_data() no longer needs to reset prev_cpu_nice when
    ignore_nice_load changes; remove that conditional.
  - cpufreq_dbs_governor_start() must unconditionally initialize
    prev_cpu_nice so the very first dbs_update() has a valid baseline;
    remove the ignore_nice guard and the now-unused ignore_nice variable.
  - ignore_nice_load_store() no longer needs to call gov_update_cpu_data()
    at all (prev_cpu_nice is always current); remove that call.

Fixes: ee88415caf736b ("[CPUFREQ] Cleanup locking in conservative governor")
Fixes: 5a75c82828e7c0 ("[CPUFREQ] Cleanup locking in ondemand governor")
Signed-off-by: Zhongqiu Han <zhongqiu.han@oss.qualcomm.com>
---
 drivers/cpufreq/cpufreq_conservative.c |  3 ---
 drivers/cpufreq/cpufreq_governor.c     | 28 +++++++++++++++++---------
 drivers/cpufreq/cpufreq_ondemand.c     |  3 ---
 3 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/drivers/cpufreq/cpufreq_conservative.c b/drivers/cpufreq/cpufreq_conservative.c
index df01d33993d8..5c316d2d3ddd 100644
--- a/drivers/cpufreq/cpufreq_conservative.c
+++ b/drivers/cpufreq/cpufreq_conservative.c
@@ -213,9 +213,6 @@ static ssize_t ignore_nice_load_store(struct gov_attr_set *attr_set,
 
 	dbs_data->ignore_nice_load = input;
 
-	/* we need to re-evaluate prev_cpu_idle */
-	gov_update_cpu_data(dbs_data);
-
 	return count;
 }
 
diff --git a/drivers/cpufreq/cpufreq_governor.c b/drivers/cpufreq/cpufreq_governor.c
index c0d419c95609..cfbfa5d8bb36 100644
--- a/drivers/cpufreq/cpufreq_governor.c
+++ b/drivers/cpufreq/cpufreq_governor.c
@@ -92,6 +92,12 @@ EXPORT_SYMBOL_GPL(sampling_rate_store);
  *
  * Call under the @dbs_data->attr_set.update_lock. The per-policy
  * update_mutex is acquired and released internally for each policy.
+ *
+ * Note: prev_cpu_nice is intentionally not reset here. dbs_update() tracks
+ * prev_cpu_nice unconditionally on every sample, so it is always current.
+ * Resetting it here is therefore unnecessary and would only introduce a
+ * one-sample spike if a concurrent dbs_update() ran between the reset and
+ * the next sample.
  */
 void gov_update_cpu_data(struct dbs_data *dbs_data)
 {
@@ -106,8 +112,6 @@ void gov_update_cpu_data(struct dbs_data *dbs_data)
 
 			j_cdbs->prev_cpu_idle = get_cpu_idle_time(j, &j_cdbs->prev_update_time,
 								  dbs_data->io_is_busy);
-			if (dbs_data->ignore_nice_load)
-				j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
 		}
 		mutex_unlock(&policy_dbs->update_mutex);
 	}
@@ -167,12 +171,18 @@ unsigned int dbs_update(struct cpufreq_policy *policy)
 
 		j_cdbs->prev_cpu_idle = cur_idle_time;
 
-		if (ignore_nice) {
-			u64 cur_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
+		/*
+		 * Always sample cur_nice and advance prev_cpu_nice, regardless
+		 * of ignore_nice.  This keeps prev_cpu_nice current so that
+		 * enabling ignore_nice_load via sysfs never produces a
+		 * stale-baseline spike (the delta will be at most one sampling
+		 * interval of accumulated nice time, not since boot).
+		 */
+		u64 cur_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
 
+		if (ignore_nice)
 			idle_time += div_u64(cur_nice - j_cdbs->prev_cpu_nice, NSEC_PER_USEC);
-			j_cdbs->prev_cpu_nice = cur_nice;
-		}
+		j_cdbs->prev_cpu_nice = cur_nice;
 
 		if (unlikely(!time_elapsed)) {
 			/*
@@ -519,7 +529,7 @@ int cpufreq_dbs_governor_start(struct cpufreq_policy *policy)
 	struct dbs_governor *gov = dbs_governor_of(policy);
 	struct policy_dbs_info *policy_dbs = policy->governor_data;
 	struct dbs_data *dbs_data = policy_dbs->dbs_data;
-	unsigned int sampling_rate, ignore_nice, j;
+	unsigned int sampling_rate, j;
 	unsigned int io_busy;
 
 	if (!policy->cur)
@@ -529,7 +539,6 @@ int cpufreq_dbs_governor_start(struct cpufreq_policy *policy)
 	policy_dbs->rate_mult = 1;
 
 	sampling_rate = dbs_data->sampling_rate;
-	ignore_nice = dbs_data->ignore_nice_load;
 	io_busy = dbs_data->io_is_busy;
 
 	mutex_lock(&policy_dbs->update_mutex);
@@ -542,8 +551,7 @@ int cpufreq_dbs_governor_start(struct cpufreq_policy *policy)
 		 */
 		j_cdbs->prev_load = 0;
 
-		if (ignore_nice)
-			j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
+		j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j);
 	}
 	mutex_unlock(&policy_dbs->update_mutex);
 
diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c
index 9942dbb38dae..d8d843183c21 100644
--- a/drivers/cpufreq/cpufreq_ondemand.c
+++ b/drivers/cpufreq/cpufreq_ondemand.c
@@ -261,9 +261,6 @@ static ssize_t ignore_nice_load_store(struct gov_attr_set *attr_set,
 	}
 	dbs_data->ignore_nice_load = input;
 
-	/* we need to re-evaluate prev_cpu_idle */
-	gov_update_cpu_data(dbs_data);
-
 	return count;
 }
 
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 0/7] thermal: samsung: Add support for Google GS101 TMU
From: Tudor Ambarus @ 2026-04-09 12:22 UTC (permalink / raw)
  To: Alexey Klimov, daniel.lezcano
  Cc: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
	Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
	Gustavo A. R. Silva, Peter Griffin, André Draszik,
	willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
	linux-pm, devicetree, linux-arm-kernel, linux-hardening
In-Reply-To: <DHNUUPQPD5DR.18P18VV0LNTI8@linaro.org>



On 4/8/26 5:49 PM, Alexey Klimov wrote:
> On Mon Jan 19, 2026 at 12:08 PM GMT, Tudor Ambarus wrote:
>> Add support for the Thermal Management Unit (TMU) on the Google GS101
>> SoC.
>>
>> The GS101 TMU implementation utilizes a hybrid architecture where
>> management is shared between the kernel and the Alive Clock and
>> Power Manager (ACPM) firmware.
> 
> Do you plan to update or work on this series? If, by some reason,

I'd like to resubmit, but I got derailed by other tasks.

> this series is postphoned I can rebase it and re-send, for example.
> IIRC it needs a clean rebase as a minimial change.
> 

No, it's more than that. When I talked with Daniel about this driver, he
suggested I shall really focus on using the .set_trips callback instead of
.set_trip_temp. I'm not sure if it's possible given the static nature of
the ACPM interface. So it needs a bit of investigation, which I couldn't
do lately.

If we can go initially with .set_trip_temp and then come up with an iterative
patch about .set_trips, I can of course respin, it takes me just a few
minutes to rebase and test. But it's Daniel to decide.

Oh, and the device tree needs a little update on the trip points, but other
than that, we're good to go.

Cheers,
ta

^ permalink raw reply

* [PATCH] thermal: renesas: rzg3e: Remove stale @trim_offset kernel-doc entry
From: John Madieu @ 2026-04-09 12:59 UTC (permalink / raw)
  To: rafael, daniel.lezcano
  Cc: rui.zhang, lukasz.luba, linux-pm, linux-kernel, linux-renesas-soc,
	Biju Das, Geert Uytterhoeven, John Madieu

The trim_offset field was removed from struct rzg3e_thermal_priv but
its kernel-doc entry was left behind. Remove it to fix the mismatch.

Signed-off-by: John Madieu <john.madieu.xa@bp.renesas.com>
---
 drivers/thermal/renesas/rzg3e_thermal.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c
index dde021e283b7..f0e29fe633db 100644
--- a/drivers/thermal/renesas/rzg3e_thermal.c
+++ b/drivers/thermal/renesas/rzg3e_thermal.c
@@ -93,7 +93,6 @@ struct rzg3e_thermal_info {
  * @info: chip type specific information
  * @trmval0: calibration value 0 (b)
  * @trmval1: calibration value 1 (c)
- * @trim_offset: offset for trim registers in syscon
  * @lock: protects hardware access during conversions
  */
 struct rzg3e_thermal_priv {
-- 
2.25.1


^ permalink raw reply related

* [BUG] Lenovo 83JL / WD SN7100S: intermittent loss of secondary NVMe after s2idle resume, root port 00:02.1 retraining fails
From: Jacopo Labardi @ 2026-04-09 14:52 UTC (permalink / raw)
  To: linux-nvme; +Cc: linux-pci, linux-pm

Hello,

I am reporting an intermittent suspend/resume failure on a Lenovo IdeaPad Pro 5
14AKP10 (machine type 83JL) where the secondary NVMe drive can become unusable
after s2idle resume on Linux.

I am CCing linux-nvme, linux-pci and linux-pm because the visible failure path
starts with PCIe root-port link retraining on 0000:00:02.1 during resume and
ends with nvme reset failure on the downstream device at 0000:bf:00.0.

Summary
- The problem is intermittent. It does not always happen on a fixed cycle.
- On this machine it may happen on the first resume or only after several
  suspend/resume cycles.
- The clean reproducer below failed on the third suspend/resume cycle, but that
  cycle count is not stable and should not be interpreted as deterministic.
- When it fails, the secondary/data NVMe is effectively lost until a full
  reboot.
- The system NVMe on 0000:c2:00.0 survives.

Hardware
- Laptop: Lenovo IdeaPad Pro 5 14AKP10 (83JL)
- BIOS: LENOVO QKCN29WW, release date 2025-12-23
- Platform: AMD Krackan / Ryzen AI 350
- Root port for failing device: 0000:00:02.1, AMD [1022:1126]
- System NVMe: Lexar NM790 2TB at 0000:c2:00.0
- Secondary/data NVMe: Sandisk/WD PC SN7100S M.2 2242 NVMe SSD (DRAM-less)
  [15b7:5044] at 0000:bf:00.0
- PCIe topology:
  0000:00:02.1 -> 0000:bf:00.0

Software
- Kernel for the clean reproducer: 6.19.11-arch1-1
- Kernel taint during reproducer: 0
- Sleep mode exposed by the platform on Linux: [s2idle]
- acpi_call-dkms is installed on disk, but the acpi_call module was not loaded
  during the reproducer and the kernel was not tainted
- Boot command line used for the clean reproducer:
  quiet nowatchdog rw rootflags=subvol=/@ rootfstype=btrfs
  root=UUID=<redacted> amd_pstate=active iommu=pt i8042.nopnp loglevel=3
  8250.nr_uarts=0 tpm_tis.interrupts=0 random.trust_cpu=on
  snd_hda_intel.power_save=10 snd_hda_intel.power_save_controller=Y

At boot on this kernel, both NVMe controllers log:
- nvme 0000:c2:00.0: platform quirk: setting simple suspend
- nvme 0000:bf:00.0: platform quirk: setting simple suspend

Clean reproduction used for this report
- No NVMe-specific udev overrides were active
- No custom NVMe-related systemd sleep hooks or suspend services were active
- No NVMe-specific kernel parameters were active

Reproducer
1. Boot the machine into 6.19.11-arch1-1.
2. Confirm tainted=0 and that both NVMe devices are present.
3. Suspend to s2idle and resume.
4. Repeat suspend/resume until the failure occurs.

Observed behavior
- In one clean run used for this report:
  - first cycle resumed successfully
  - second cycle resumed successfully
  - third cycle resumed with the WD drive lost
- In prior testing on the same machine, the failure sometimes happened on the
  first cycle and sometimes only after several cycles.

Relevant kernel log excerpt from the failing run
  Apr 09 16:24:34 kernel: PM: suspend entry (s2idle)
  Apr 09 16:24:54 kernel: pcieport 0000:00:02.1: broken device,
retraining non-functional downstream link at 2.5GT/s
  Apr 09 16:24:54 kernel: pcieport 0000:00:02.1: retraining failed
  Apr 09 16:24:54 kernel: pcieport 0000:00:02.1: Data Link Layer Link
Active not set in 100 msec
  Apr 09 16:24:54 kernel: nvme nvme1: Disabling device after reset failure: -19
  Apr 09 16:24:54 kernel: PM: suspend exit
  Apr 09 16:25:04 kernel: ntfs3(nvme1n1p5): failed to read volume at
offset 0x10c000
  Apr 09 16:25:26 kernel: ntfs3: 107 callbacks suppressed
  Apr 09 16:25:40 kernel: nvme nvme1: Identify namespace failed (-5)

State after the failure
- nvme list shows only the system Lexar drive; the WD is no longer listed
- lspci -nnvv -s bf:00.0 still shows the device, but with:
  - !!! Unknown header type 7f
  - Kernel driver in use: nvme
- sysfs state at collection time:
  - /sys/bus/pci/devices/0000:bf:00.0/power_state = D3cold
  - /sys/bus/pci/devices/0000:bf:00.0/power/runtime_status = active
  - /sys/bus/pci/devices/0000:bf:00.0/d3cold_allowed = 1
  - /sys/bus/pci/devices/0000:bf:00.0/power/control = on
  - /sys/bus/pci/devices/0000:00:02.1/power_state = D0
  - /sys/bus/pci/devices/0000:00:02.1/power/runtime_status = active
  - /sys/bus/pci/devices/0000:00:02.1/d3cold_allowed = 1
  - /sys/bus/pci/devices/0000:00:02.1/power/control = auto
- smartctl -x /dev/nvme1 failed with "Resource temporarily unavailable"

Expected behavior
- The secondary WD NVMe should resume normally and remain usable after s2idle.

Previous mitigation/debug attempts on the same machine
These are not part of the clean reproducer above; they are prior experiments
done to narrow the failure mode.

- The issue also reproduced while the machine was configured with
  pcie_aspm.policy=performance; removing that option did not eliminate it
- nvme_core.default_ps_max_latency_us=2000
  - no fix; failure still reproduced
- Force power/control=on and d3cold_allowed=0 on both 0000:00:02.1 and
  0000:bf:00.0
  - no fix; in failing runs the WD could still end up inaccessible / in D3cold
- pm_async=off
  - same failure mode
- SuspendState=freeze via systemd sleep configuration
  - ineffective on this platform; kernel still reported s2idle behavior and the
    issue remained
- nvme.noacpi=1
  - removed the "platform quirk: setting simple suspend" message, but resume
    often degraded into a black screen / forced reboot instead of fixing the WD
- pcie_port_pm=off
  - no usable fix; often resulted in black-screen resume / forced reboot
- pcie_ports=compat
  - no usable fix; often resulted in black-screen resume / forced reboot
- User-space suspend/resume hooks that tried unbind/bind/remove/rescan around
  the WD path
  - no reliable recovery; they only lengthened resume and still ended in reset
    failure / missing device

Additional context
- Linux on this machine exposes only s2idle.
- The issue was reproduced on multiple Linux distributions, not only one
  userspace/kernel packaging combination.
- Windows on the same hardware resumes correctly.
- I do not currently have a known-good Linux kernel version on this machine, so
  I am not claiming this is a regression in a specific upstream release.

I am not sure whether the underlying bug belongs primarily in nvme/pci, PCIe
power-management, or a platform/firmware interaction. The first failing
messages in the clean repro are from the root-port retraining path, which is
why I am CCing PCI/PM in addition to NVMe.

I collected a local bundle containing:
- full kernel log from the reproducer boot
- systemd suspend log
- lspci -nnvv for 0000:00:02.1 and 0000:bf:00.0
- sysfs power-state snapshots
- dmidecode output

The raw bundle contains machine serial/UUID, so I am not attaching it publicly
as-is. I can provide redacted logs or specific files immediately if requested.

If useful, I can also test additional debug options or a current mainline/-rc
kernel.

Thanks.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox