Skip to contentSkip to conversation

usb: xhci: add AMD PROM21 xHCI hwmon support for temperature monitoring

PATCH Superseded Jihong Min 8 participants 21 messages May 6 – May 7

Subsystems (5)

v1 of 7 Superseded Latest is v7 →

lore
Share & tools
lore

Jihong Min

author
#0 lore raw

PATCH 1/1 usb: xhci: add AMD PROM21 xHCI hwmon support for temperature monitoring

AMD PROM21 xHCI controllers expose a temperature byte through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add hwmon support for this, limited to temperature monitoring, and initialize it from the xHCI PCI driver probe path.

The xhci-pci probe and remove paths call the PROM21 hwmon helpers only for AMD 1022:43fd controllers. The hwmon read path selects the temperature register through the vendor index register, reads the raw temperature byte from the data register, and restores the previous index before returning.

No public AMD register reference is available for this value. The conversion formula is derived from observed temperature readings:

temp[C] = raw * 0.9066 - 78.624

This is not implemented as a standalone driver because the temperature register belongs to the PROM21 xHCI PCI function, is accessed through the xHCI BAR after the host controller is initialized, and should share that device's lifetime and runtime PM. Keep the device-specific PROM21 code in a separate helper called only from xhci-pci, while linking it into xhci-hcd to match the existing xHCI object layout. The xhci-pci remove path invalidates the helper before HCD teardown.

Assisted-by Codex:gpt-5.5
---
 drivers/usb/host/Kconfig             |  10 ++
 drivers/usb/host/Makefile            |   4 +
 drivers/usb/host/xhci-pci.c          |   9 +
 drivers/usb/host/xhci-prom21-hwmon.c | 241 +++++++++++++++++++++++++++
 drivers/usb/host/xhci-prom21-hwmon.h |  26 +++
 5 files changed, 290 insertions(+)
 create mode 100644 drivers/usb/host/xhci-prom21-hwmon.c
 create mode 100644 drivers/usb/host/xhci-prom21-hwmon.h
drivers/usb/host/Kconfig +10 -0
index 0a277a07cf70..da41ebc272b0 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
 	depends on USB_PCI
 	default y
 
+config USB_XHCI_PCI_PROM21_HWMON
+	bool "AMD PROM21 xHCI temperature sensor support"
+	depends on USB_XHCI_PCI
+	depends on HWMON
+	help
+	  Say Y here to expose the AMD PROM21 xHCI temperature sensor
+	  through the hwmon subsystem. The sensor is accessed through a
+	  vendor-specific index/data register pair in the controller's PCI
+	  MMIO BAR and reports temperature only. If unsure, say N.
+
 config USB_XHCI_PCI_RENESAS
 	tristate "Support for additional Renesas xHCI controller with firmware"
 	depends on USB_XHCI_PCI
drivers/usb/host/Makefile +4 -0
index a07e7ba9cd53..22f141cb2af7 100644
@@ -36,6 +36,10 @@ ifneq ($(CONFIG_USB_XHCI_SIDEBAND),)
 	xhci-hcd-y		+= xhci-sideband.o
 endif
 
+ifneq ($(CONFIG_USB_XHCI_PCI_PROM21_HWMON),)
+	xhci-hcd-y += xhci-prom21-hwmon.o
+endif
+
 obj-$(CONFIG_USB_PCI)	+= pci-quirks.o
 
 obj-$(CONFIG_USB_EHCI_HCD)	+= ehci-hcd.o
drivers/usb/host/xhci-pci.c +9 -0
index 585b2f3117b0..54713efc931f 100644
@@ -18,6 +18,7 @@
 #include "xhci.h"
 #include "xhci-trace.h"
 #include "xhci-pci.h"
+#include "xhci-prom21-hwmon.h"
 
 #define SSIC_PORT_NUM		2
 #define SSIC_PORT_CFG2		0x880c
@@ -677,6 +678,10 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
 		pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
 
+	if (dev->vendor == PCI_VENDOR_ID_AMD &&
+	    dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+		xhci_prom21_hwmon_init(xhci, dev);
+
 	return 0;
 
 put_usb3_hcd:
@@ -713,6 +718,10 @@ void xhci_pci_remove(struct pci_dev *dev)
 	xhci = hcd_to_xhci(pci_get_drvdata(dev));
 	set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
 
+	if (dev->vendor == PCI_VENDOR_ID_AMD &&
+	    dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+		xhci_try_prom21_hwmon_invalidate(dev);
+
 	xhci->xhc_state |= XHCI_STATE_REMOVING;
 
 	if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
drivers/usb/host/xhci-prom21-hwmon.c new file +241 -0

241 lines not on this page · raw message

drivers/usb/host/xhci-prom21-hwmon.h new file +26 -0
new file mode 100644
index 000000000000..eca8db23c0ea
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Jihong Min <[email protected]> */
+
+#ifndef XHCI_PROM21_HWMON_H
+#define XHCI_PROM21_HWMON_H
+
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI 0x43fd
+
+struct pci_dev;
+struct xhci_hcd;
+
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21_HWMON)
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev *pdev);
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev);
+#else
+static inline void xhci_prom21_hwmon_init(struct xhci_hcd *xhci,
+					  struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev)
+{
+}
+#endif
+
+#endif
Signature · 1 line
2.53.0
#1 lore raw
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Potential linker error when USB_XHCI_PCI is built-in and HWMON is a module. --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Potential linker error when USB_XHCI_PCI is built-in and HWMON is a module. --

commit 66c564d56813f3e58d95a6491735b26bfb5eb91f
Author: Jihong Min <[email protected]>

usb: xhci: add AMD PROM21 xHCI hwmon support for temperature monitoring

This patch adds hwmon temperature monitoring for AMD PROM21 xHCI controllers. The hwmon code is built as an extension to the xHCI PCI driver and initialized during the device probe path.

drivers/usb/host/Kconfig config USB_XHCI_PCI · line 48

Quoted 12 lines
drivers/usb/host/Kconfig +4 -0
index 0a277a07cf706..da41ebc272b0f 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
 	depends on USB_PCI
 	default y
 
+config USB_XHCI_PCI_PROM21_HWMON
+	bool "AMD PROM21 xHCI temperature sensor support"
+	depends on USB_XHCI_PCI
+	depends on HWMON

Can this cause a linker error if CONFIG_USB_XHCI_PCI is built-in (y) and CONFIG_HWMON is built as a module (m)?

Since USB_XHCI_PCI_PROM21_HWMON is a bool, if its dependencies evaluate to a module, it can still be selected as built-in (y). This would compile xhci-prom21-hwmon.c directly into vmlinux, but the code references devm_hwmon_device_register_with_info(), which would be exported by the dynamically loaded hwmon module, leading to an undefined reference.

Would it be safer to restrict the dependency using something like depends on HWMON=y || HWMON=USB_XHCI_PCI?

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#2 lore raw
In reply to Jihong Min

Hi

Quoted 15 lines · Jihong Min

AMD PROM21 xHCI controllers expose a temperature byte through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add hwmon support for this, limited to temperature monitoring, and initialize it from the xHCI PCI driver probe path.

The xhci-pci probe and remove paths call the PROM21 hwmon helpers only for AMD 1022:43fd controllers. The hwmon read path selects the temperature register through the vendor index register, reads the raw temperature byte from the data register, and restores the previous index before returning.

No public AMD register reference is available for this value. The conversion formula is derived from observed temperature readings:

   temp[C] = raw * 0.9066 - 78.624
Quoted 8 lines

This is not implemented as a standalone driver because the temperature register belongs to the PROM21 xHCI PCI function, is accessed through the xHCI BAR after the host controller is initialized, and should share that device's lifetime and runtime PM. Keep the device-specific PROM21 code in a separate helper called only from xhci-pci, while linking it into xhci-hcd to match the existing xHCI object layout. The xhci-pci remove path invalidates the helper before HCD teardown.

To me it looks like a sepate device should be created for this, and a new driver in drivers/hwmon bind to it.

Not sure what is the best solution, do we create a parent mfd driver that binds to the PCI device, which then creates two child devices, xhci and hwmon. Or do we just create some kind of platform device as a child to this xhci PCI device.

Maybe someone on the PCI list would know (added to cc)? I guess it's not the first time we have a "multi function" PCI device that has just has one BAR.

The resources this device needs look simple, only access two registers at offset 0x3000 and 0x3008 from mmio base.

Does accessing those registers depend on xHC state? or is enough that the PCI device is enabled and in D0? i.e. does xHC need to be in "running" state?
I'd like to avoid resuming and restarting xHC every time temperature is read from a sysfs file.

Thanks
Mathias

(keeping reset of message as reference for pci list readers)

Quoted 361 lines
Assisted-by Codex:gpt-5.5
---
  drivers/usb/host/Kconfig             |  10 ++
  drivers/usb/host/Makefile            |   4 +
  drivers/usb/host/xhci-pci.c          |   9 +
  drivers/usb/host/xhci-prom21-hwmon.c | 241 +++++++++++++++++++++++++++
  drivers/usb/host/xhci-prom21-hwmon.h |  26 +++
  5 files changed, 290 insertions(+)

create mode 100644 drivers/usb/host/xhci-prom21-hwmon.c create mode 100644 drivers/usb/host/xhci-prom21-hwmon.h

drivers/usb/host/Kconfig +10 -0
index 0a277a07cf70..da41ebc272b0 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
  	depends on USB_PCI
  	default y
 
+config USB_XHCI_PCI_PROM21_HWMON
+	bool "AMD PROM21 xHCI temperature sensor support"
+	depends on USB_XHCI_PCI
+	depends on HWMON
+	help
+	  Say Y here to expose the AMD PROM21 xHCI temperature sensor
+	  through the hwmon subsystem. The sensor is accessed through a
+	  vendor-specific index/data register pair in the controller's PCI
+	  MMIO BAR and reports temperature only. If unsure, say N.
+
  config USB_XHCI_PCI_RENESAS
  	tristate "Support for additional Renesas xHCI controller with firmware"
  	depends on USB_XHCI_PCI
drivers/usb/host/Makefile +4 -0
index a07e7ba9cd53..22f141cb2af7 100644
@@ -36,6 +36,10 @@ ifneq ($(CONFIG_USB_XHCI_SIDEBAND),)
  	xhci-hcd-y		+= xhci-sideband.o
  endif
 
+ifneq ($(CONFIG_USB_XHCI_PCI_PROM21_HWMON),)
+	xhci-hcd-y += xhci-prom21-hwmon.o
+endif
+
  obj-$(CONFIG_USB_PCI)	+= pci-quirks.o
 
  obj-$(CONFIG_USB_EHCI_HCD)	+= ehci-hcd.o
drivers/usb/host/xhci-pci.c +9 -0
index 585b2f3117b0..54713efc931f 100644
@@ -18,6 +18,7 @@
  #include "xhci.h"
  #include "xhci-trace.h"
  #include "xhci-pci.h"
+#include "xhci-prom21-hwmon.h"
 
  #define SSIC_PORT_NUM		2
  #define SSIC_PORT_CFG2		0x880c
@@ -677,6 +678,10 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
  	if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
  		pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
 
+	if (dev->vendor == PCI_VENDOR_ID_AMD &&
+	    dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+		xhci_prom21_hwmon_init(xhci, dev);
+
  	return 0;
 
  put_usb3_hcd:
@@ -713,6 +718,10 @@ void xhci_pci_remove(struct pci_dev *dev)
  	xhci = hcd_to_xhci(pci_get_drvdata(dev));
  	set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
 
+	if (dev->vendor == PCI_VENDOR_ID_AMD &&
+	    dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+		xhci_try_prom21_hwmon_invalidate(dev);
+
  	xhci->xhc_state |= XHCI_STATE_REMOVING;
 
  	if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
drivers/usb/host/xhci-prom21-hwmon.c new file +241 -0
new file mode 100644
index 000000000000..5f71e72f4a90
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD PROM21 xHCI Hwmon Implementation
+ * (only temperature monitoring is supported)
+ *
+ * This can be effectively used as the alternative chipset temperature monitor.
+ *
+ * Copyright (C) 2026 Jihong Min <[email protected]>
+ */
+
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include "xhci.h"
+#include "xhci-prom21-hwmon.h"
+
+#define XHCI_PROM21_INDEX 0x3000
+#define XHCI_PROM21_DATA 0x3008
+#define XHCI_PROM21_TEMP_REG 0x0001e520
+
+#define XHCI_PROM21_HWMON_NAME "xhci_prom21"
+
+struct xhci_prom21_hwmon {
+	struct pci_dev *pdev;
+	void __iomem *regs;
+	bool removing;
+	struct mutex lock; /* protects removing and the index/data registers */
+};
+
+struct xhci_prom21_hwmon_devres {
+	struct xhci_prom21_hwmon *hwmon;
+};
+
+static void xhci_prom21_hwmon_invalidate(struct xhci_prom21_hwmon *hwmon)
+{
+	mutex_lock(&hwmon->lock);
+	hwmon->removing = true;
+	mutex_unlock(&hwmon->lock);
+}
+
+static void xhci_prom21_hwmon_devres_release(struct device *dev, void *res)
+{
+	struct xhci_prom21_hwmon_devres *devres = res;
+
+	/*
+	 * devm hwmon unregister runs after this lookup record is released.
+	 * Mark the data path closed first so any late sysfs read returns
+	 * without touching xHCI MMIO.
+	 */
+	xhci_prom21_hwmon_invalidate(devres->hwmon);
+}
+
+/*
+ * This is not a pure MMIO read. The PROM21 vendor data register is selected
+ * by temporarily writing XHCI_PROM21_TEMP_REG to the vendor index register.
+ * Keep the sequence short and restore the previous index before returning.
+ */
+static int
+xhci_prom21_read_temp_raw_restore_index(struct xhci_prom21_hwmon *hwmon,
+					u8 *raw)
+{
+	struct device *dev = &hwmon->pdev->dev;
+	u32 index;
+	u32 data;
+	int ret;
+
+	/*
+	 * xhci_try_prom21_hwmon_invalidate() uses the same lock before HCD
+	 * teardown. Keep runtime PM and MMIO inside the critical section so a
+	 * sysfs read cannot use the vendor register pair after remove starts.
+	 */
+	mutex_lock(&hwmon->lock);
+	if (hwmon->removing) {
+		mutex_unlock(&hwmon->lock);
+		return -ENODEV;
+	}
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret < 0) {
+		mutex_unlock(&hwmon->lock);
+		return ret;
+	}
+
+	index = readl(hwmon->regs + XHCI_PROM21_INDEX);
+	/* Select the PROM21 temperature register through the vendor index. */
+	writel(XHCI_PROM21_TEMP_REG, hwmon->regs + XHCI_PROM21_INDEX);
+	data = readl(hwmon->regs + XHCI_PROM21_DATA);
+	/* Restore the previous vendor index register value. */
+	writel(index, hwmon->regs + XHCI_PROM21_INDEX);
+	readl(hwmon->regs + XHCI_PROM21_INDEX);
+
+	/* Let xHCI PCI runtime PM coalesce repeated sysfs polling. */
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+	mutex_unlock(&hwmon->lock);
+
+	*raw = data & 0xff;
+	if (!*raw || *raw == 0xff)
+		return -ENODATA;
+
+	return 0;
+}
+
+static long xhci_prom21_raw_to_millicelsius(u8 raw)
+{
+	/*
+	 * No public AMD register reference is available for this value.
+	 * The scale was derived from observed PROM21 xHCI temperature readings:
+	 *  temp[C] = raw * 0.9066 - 78.624
+	 */
+	return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
+}
+
+static umode_t xhci_prom21_hwmon_is_visible(const void *drvdata,
+					    enum hwmon_sensor_types type,
+					    u32 attr, int channel)
+{
+	if (type != hwmon_temp || channel)
+		return 0;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		return 0444;
+	default:
+		return 0;
+	}
+}
+
+static int xhci_prom21_hwmon_read(struct device *dev,
+				  enum hwmon_sensor_types type, u32 attr,
+				  int channel, long *val)
+{
+	struct xhci_prom21_hwmon *hwmon = dev_get_drvdata(dev);
+	u8 raw;
+	int ret;
+
+	if (type != hwmon_temp || attr != hwmon_temp_input || channel)
+		return -EOPNOTSUPP;
+
+	ret = xhci_prom21_read_temp_raw_restore_index(hwmon, &raw);
+	if (ret)
+		return ret;
+
+	*val = xhci_prom21_raw_to_millicelsius(raw);
+	return 0;
+}
+
+static const struct hwmon_ops xhci_prom21_hwmon_ops = {
+	.is_visible = xhci_prom21_hwmon_is_visible,
+	.read = xhci_prom21_hwmon_read,
+};
+
+static const struct hwmon_channel_info *const xhci_prom21_hwmon_info[] = {
+	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+	NULL,
+};
+
+static const struct hwmon_chip_info xhci_prom21_chip_info = {
+	.ops = &xhci_prom21_hwmon_ops,
+	.info = xhci_prom21_hwmon_info,
+};
+
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev *pdev)
+{
+	struct xhci_prom21_hwmon_devres *devres;
+	struct xhci_prom21_hwmon *hwmon;
+	struct usb_hcd *hcd = xhci_to_hcd(xhci);
+	struct device *dev = &pdev->dev;
+	struct device *hwmon_dev;
+
+	if (!hcd->regs || hcd->rsrc_len < XHCI_PROM21_DATA + sizeof(u32)) {
+		dev_err(dev,
+			"AMD PROM21 hwmon unavailable: invalid MMIO resource\n");
+		return;
+	}
+
+	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+	if (!hwmon) {
+		/* The allocator reports OOM; add PROM21 device context. */
+		dev_err(dev, "AMD PROM21 hwmon state unavailable\n");
+		return;
+	}
+
+	devres = devres_alloc(xhci_prom21_hwmon_devres_release, sizeof(*devres),
+			      GFP_KERNEL);
+	if (!devres) {
+		dev_err(dev, "AMD PROM21 hwmon devres allocation failed\n");
+		return;
+	}
+
+	hwmon->pdev = pdev;
+	hwmon->regs = hcd->regs;
+	mutex_init(&hwmon->lock);
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev,
+							 XHCI_PROM21_HWMON_NAME,
+							 hwmon,
+							 &xhci_prom21_chip_info,
+							 NULL);
+	if (IS_ERR(hwmon_dev)) {
+		devres_free(devres);
+		dev_err(dev, "AMD PROM21 hwmon registration failed: %pe\n",
+			hwmon_dev);
+		return;
+	}
+
+	/*
+	 * Store a private devres record so the device remove path can find this
+	 * state without adding PROM21-specific part to xhci-pci.
+	 */
+	devres->hwmon = hwmon;
+	devres_add(dev, devres);
+}
+EXPORT_SYMBOL_GPL(xhci_prom21_hwmon_init);
+
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev)
+{
+	struct xhci_prom21_hwmon_devres *devres;
+
+	/*
+	 * This is called for every xHCI PCI device. Devices without PROM21
+	 * hwmon support simply have no matching helper devres entry.
+	 */
+	devres = devres_find(&pdev->dev, xhci_prom21_hwmon_devres_release, NULL,
+			     NULL);
+	if (!devres) {
+		dev_dbg(&pdev->dev, "AMD PROM21 hwmon state not found\n");
+		return;
+	}
+
+	xhci_prom21_hwmon_invalidate(devres->hwmon);
+}
+EXPORT_SYMBOL_GPL(xhci_try_prom21_hwmon_invalidate);
drivers/usb/host/xhci-prom21-hwmon.h new file +26 -0
new file mode 100644
index 000000000000..eca8db23c0ea
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Jihong Min <[email protected]> */
+
+#ifndef XHCI_PROM21_HWMON_H
+#define XHCI_PROM21_HWMON_H
+
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI 0x43fd
+
+struct pci_dev;
+struct xhci_hcd;
+
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21_HWMON)
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev *pdev);
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev);
+#else
+static inline void xhci_prom21_hwmon_init(struct xhci_hcd *xhci,
+					  struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev)
+{
+}
+#endif
+
+#endif
maintainer
#3 lore raw
Quoted 27 lines · Mathias Nyman

Hi

Quoted 15 lines · Jihong Min

AMD PROM21 xHCI controllers expose a temperature byte through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add hwmon support for this, limited to temperature monitoring, and initialize it from the xHCI PCI driver probe path.

The xhci-pci probe and remove paths call the PROM21 hwmon helpers only for AMD 1022:43fd controllers. The hwmon read path selects the temperature register through the vendor index register, reads the raw temperature byte from the data register, and restores the previous index before returning.

No public AMD register reference is available for this value. The conversion formula is derived from observed temperature readings:

   temp[C] = raw * 0.9066 - 78.624

Quoted 8 lines

This is not implemented as a standalone driver because the temperature register belongs to the PROM21 xHCI PCI function, is accessed through the xHCI BAR after the host controller is initialized, and should share that device's lifetime and runtime PM. Keep the device-specific PROM21 code in a separate helper called only from xhci-pci, while linking it into xhci-hcd to match the existing xHCI object layout. The xhci-pci remove path invalidates the helper before HCD teardown.

Quoted 8 lines

To me it looks like a sepate device should be created for this, and a new driver in drivers/hwmon bind to it.

Not sure what is the best solution, do we create a parent mfd driver that binds to the PCI device, which then creates two child devices, xhci and hwmon. Or do we just create some kind of platform device as a child to this xhci PCI device.

Auxiliary devices were created specifically for this purpose. The parent driver would create an auxiliary device in its probe function, which would then be instantiated from the client driver.

On a side note, the code below looks messy. I would personally prefer something like the Renesas solution.

Guenter

Quoted 371 lines

Maybe someone on the PCI list would know (added to cc)? I guess it's not the first time we have a "multi function" PCI device that has just has one BAR.

The resources this device needs look simple, only access two registers at offset 0x3000 and 0x3008 from mmio base.

Does accessing those registers depend on xHC state? or is enough that the PCI device is enabled and in D0?  i.e. does xHC need to be in "running" state?
I'd like to avoid resuming and restarting xHC every time temperature is read from a sysfs file.

Thanks
Mathias

(keeping reset of message as reference for pci list readers)

Quoted 353 lines
Assisted-by Codex:gpt-5.5

---
  drivers/usb/host/Kconfig             |  10 ++ drivers/usb/host/Makefile            |   4 + drivers/usb/host/xhci-pci.c          |   9 + drivers/usb/host/xhci-prom21-hwmon.c | 241 +++++++++++++++++++++++++++ drivers/usb/host/xhci-prom21-hwmon.h |  26 +++

  5 files changed, 290 insertions(+)

  create mode 100644 drivers/usb/host/xhci-prom21-hwmon.c create mode 100644 drivers/usb/host/xhci-prom21-hwmon.h

drivers/usb/host/Kconfig +0 -0
index 0a277a07cf70..da41ebc272b0 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI

      depends on USB_PCI
      default y

drivers/usb/host/Kconfig +10 -0
+config USB_XHCI_PCI_PROM21_HWMON
+    bool "AMD PROM21 xHCI temperature sensor support"
+    depends on USB_XHCI_PCI
+    depends on HWMON
+    help
+      Say Y here to expose the AMD PROM21 xHCI temperature sensor
+      through the hwmon subsystem. The sensor is accessed through a
+      vendor-specific index/data register pair in the controller's PCI
+      MMIO BAR and reports temperature only. If unsure, say N.
+

  config USB_XHCI_PCI_RENESAS
      tristate "Support for additional Renesas xHCI controller with firmware" depends on USB_XHCI_PCI

drivers/usb/host/Makefile +0 -0
index a07e7ba9cd53..22f141cb2af7 100644
@@ -36,6 +36,10 @@ ifneq ($(CONFIG_USB_XHCI_SIDEBAND),)

      xhci-hcd-y        += xhci-sideband.o
  endif

drivers/usb/host/Makefile +4 -0
+ifneq ($(CONFIG_USB_XHCI_PCI_PROM21_HWMON),)
+    xhci-hcd-y += xhci-prom21-hwmon.o
+endif
+

  obj-$(CONFIG_USB_PCI)    += pci-quirks.o
  obj-$(CONFIG_USB_EHCI_HCD)    += ehci-hcd.o

drivers/usb/host/xhci-pci.c +5 -0
index 585b2f3117b0..54713efc931f 100644
@@ -18,6 +18,7 @@
   #include "xhci.h"
   #include "xhci-trace.h"
   #include "xhci-pci.h"
+#include "xhci-prom21-hwmon.h"
   #define SSIC_PORT_NUM        2
   #define SSIC_PORT_CFG2        0x880c
@@ -677,6 +678,10 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
       if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
           pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
+    if (dev->vendor == PCI_VENDOR_ID_AMD &&
+        dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+        xhci_prom21_hwmon_init(xhci, dev);
+
       return 0;

  put_usb3_hcd:

drivers/usb/host/xhci-pci.c +4 -0
@@ -713,6 +718,10 @@ void xhci_pci_remove(struct pci_dev *dev)
       xhci = hcd_to_xhci(pci_get_drvdata(dev));
       set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
+    if (dev->vendor == PCI_VENDOR_ID_AMD &&
+        dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+        xhci_try_prom21_hwmon_invalidate(dev);
+
       xhci->xhc_state |= XHCI_STATE_REMOVING;
       if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
drivers/usb/host/xhci-prom21-hwmon.c new file +241 -0
new file mode 100644
index 000000000000..5f71e72f4a90
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD PROM21 xHCI Hwmon Implementation
+ * (only temperature monitoring is supported)
+ *
+ * This can be effectively used as the alternative chipset temperature monitor.
+ *
+ * Copyright (C) 2026 Jihong Min <[email protected]>
+ */
+
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include "xhci.h"
+#include "xhci-prom21-hwmon.h"
+
+#define XHCI_PROM21_INDEX 0x3000
+#define XHCI_PROM21_DATA 0x3008
+#define XHCI_PROM21_TEMP_REG 0x0001e520
+
+#define XHCI_PROM21_HWMON_NAME "xhci_prom21"
+
+struct xhci_prom21_hwmon {
+    struct pci_dev *pdev;
+    void __iomem *regs;
+    bool removing;
+    struct mutex lock; /* protects removing and the index/data registers */
+};
+
+struct xhci_prom21_hwmon_devres {
+    struct xhci_prom21_hwmon *hwmon;
+};
+
+static void xhci_prom21_hwmon_invalidate(struct xhci_prom21_hwmon *hwmon)
+{
+    mutex_lock(&hwmon->lock);
+    hwmon->removing = true;
+    mutex_unlock(&hwmon->lock);
+}
+
+static void xhci_prom21_hwmon_devres_release(struct device *dev, void *res)
+{
+    struct xhci_prom21_hwmon_devres *devres = res;
+
+    /*
+     * devm hwmon unregister runs after this lookup record is released.
+     * Mark the data path closed first so any late sysfs read returns
+     * without touching xHCI MMIO.
+     */
+    xhci_prom21_hwmon_invalidate(devres->hwmon);
+}
+
+/*
+ * This is not a pure MMIO read. The PROM21 vendor data register is selected
+ * by temporarily writing XHCI_PROM21_TEMP_REG to the vendor index register.
+ * Keep the sequence short and restore the previous index before returning.
+ */
+static int
+xhci_prom21_read_temp_raw_restore_index(struct xhci_prom21_hwmon *hwmon,
+                    u8 *raw)
+{
+    struct device *dev = &hwmon->pdev->dev;
+    u32 index;
+    u32 data;
+    int ret;
+
+    /*
+     * xhci_try_prom21_hwmon_invalidate() uses the same lock before HCD
+     * teardown. Keep runtime PM and MMIO inside the critical section so a
+     * sysfs read cannot use the vendor register pair after remove starts.
+     */
+    mutex_lock(&hwmon->lock);
+    if (hwmon->removing) {
+        mutex_unlock(&hwmon->lock);
+        return -ENODEV;
+    }
+
+    ret = pm_runtime_resume_and_get(dev);
+    if (ret < 0) {
+        mutex_unlock(&hwmon->lock);
+        return ret;
+    }
+
+    index = readl(hwmon->regs + XHCI_PROM21_INDEX);
+    /* Select the PROM21 temperature register through the vendor index. */
+    writel(XHCI_PROM21_TEMP_REG, hwmon->regs + XHCI_PROM21_INDEX);
+    data = readl(hwmon->regs + XHCI_PROM21_DATA);
+    /* Restore the previous vendor index register value. */
+    writel(index, hwmon->regs + XHCI_PROM21_INDEX);
+    readl(hwmon->regs + XHCI_PROM21_INDEX);
+
+    /* Let xHCI PCI runtime PM coalesce repeated sysfs polling. */
+    pm_runtime_mark_last_busy(dev);
+    pm_runtime_put_autosuspend(dev);
+    mutex_unlock(&hwmon->lock);
+
+    *raw = data & 0xff;
+    if (!*raw || *raw == 0xff)
+        return -ENODATA;
+
+    return 0;
+}
+
+static long xhci_prom21_raw_to_millicelsius(u8 raw)
+{
+    /*
+     * No public AMD register reference is available for this value.
+     * The scale was derived from observed PROM21 xHCI temperature readings:
+     *  temp[C] = raw * 0.9066 - 78.624
+     */
+    return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
+}
+
+static umode_t xhci_prom21_hwmon_is_visible(const void *drvdata,
+                        enum hwmon_sensor_types type,
+                        u32 attr, int channel)
+{
+    if (type != hwmon_temp || channel)
+        return 0;
+
+    switch (attr) {
+    case hwmon_temp_input:
+        return 0444;
+    default:
+        return 0;
+    }
+}
+
+static int xhci_prom21_hwmon_read(struct device *dev,
+                  enum hwmon_sensor_types type, u32 attr,
+                  int channel, long *val)
+{
+    struct xhci_prom21_hwmon *hwmon = dev_get_drvdata(dev);
+    u8 raw;
+    int ret;
+
+    if (type != hwmon_temp || attr != hwmon_temp_input || channel)
+        return -EOPNOTSUPP;
+
+    ret = xhci_prom21_read_temp_raw_restore_index(hwmon, &raw);
+    if (ret)
+        return ret;
+
+    *val = xhci_prom21_raw_to_millicelsius(raw);
+    return 0;
+}
+
+static const struct hwmon_ops xhci_prom21_hwmon_ops = {
+    .is_visible = xhci_prom21_hwmon_is_visible,
+    .read = xhci_prom21_hwmon_read,
+};
+
+static const struct hwmon_channel_info *const xhci_prom21_hwmon_info[] = {
+    HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+    NULL,
+};
+
+static const struct hwmon_chip_info xhci_prom21_chip_info = {
+    .ops = &xhci_prom21_hwmon_ops,
+    .info = xhci_prom21_hwmon_info,
+};
+
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev *pdev)
+{
+    struct xhci_prom21_hwmon_devres *devres;
+    struct xhci_prom21_hwmon *hwmon;
+    struct usb_hcd *hcd = xhci_to_hcd(xhci);
+    struct device *dev = &pdev->dev;
+    struct device *hwmon_dev;
+
+    if (!hcd->regs || hcd->rsrc_len < XHCI_PROM21_DATA + sizeof(u32)) {
+        dev_err(dev,
+            "AMD PROM21 hwmon unavailable: invalid MMIO resource\n");
+        return;
+    }
+
+    hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+    if (!hwmon) {
+        /* The allocator reports OOM; add PROM21 device context. */
+        dev_err(dev, "AMD PROM21 hwmon state unavailable\n");
+        return;
+    }
+
+    devres = devres_alloc(xhci_prom21_hwmon_devres_release, sizeof(*devres),
+                  GFP_KERNEL);
+    if (!devres) {
+        dev_err(dev, "AMD PROM21 hwmon devres allocation failed\n");
+        return;
+    }
+
+    hwmon->pdev = pdev;
+    hwmon->regs = hcd->regs;
+    mutex_init(&hwmon->lock);
+
+    hwmon_dev = devm_hwmon_device_register_with_info(dev,
+                             XHCI_PROM21_HWMON_NAME,
+                             hwmon,
+                             &xhci_prom21_chip_info,
+                             NULL);
+    if (IS_ERR(hwmon_dev)) {
+        devres_free(devres);
+        dev_err(dev, "AMD PROM21 hwmon registration failed: %pe\n",
+            hwmon_dev);
+        return;
+    }
+
+    /*
+     * Store a private devres record so the device remove path can find this
+     * state without adding PROM21-specific part to xhci-pci.
+     */
+    devres->hwmon = hwmon;
+    devres_add(dev, devres);
+}
+EXPORT_SYMBOL_GPL(xhci_prom21_hwmon_init);
+
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev)
+{
+    struct xhci_prom21_hwmon_devres *devres;
+
+    /*
+     * This is called for every xHCI PCI device. Devices without PROM21
+     * hwmon support simply have no matching helper devres entry.
+     */
+    devres = devres_find(&pdev->dev, xhci_prom21_hwmon_devres_release, NULL,
+                 NULL);
+    if (!devres) {
+        dev_dbg(&pdev->dev, "AMD PROM21 hwmon state not found\n");
+        return;
+    }
+
+    xhci_prom21_hwmon_invalidate(devres->hwmon);
+}
+EXPORT_SYMBOL_GPL(xhci_try_prom21_hwmon_invalidate);
drivers/usb/host/xhci-prom21-hwmon.h new file +26 -0
new file mode 100644
index 000000000000..eca8db23c0ea
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Jihong Min <[email protected]> */
+
+#ifndef XHCI_PROM21_HWMON_H
+#define XHCI_PROM21_HWMON_H
+
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI 0x43fd
+
+struct pci_dev;
+struct xhci_hcd;
+
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21_HWMON)
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev *pdev);
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev);
+#else
+static inline void xhci_prom21_hwmon_init(struct xhci_hcd *xhci,
+                      struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev)
+{
+}
+#endif
+
+#endif
#4 lore raw

Hi Mathias, Guenter,

Thank you for the review. I reworked the patch for v2 and addressed the main points as follows.

Quoted 2 lines

To me it looks like a separate device should be created for this, and a new driver in drivers/hwmon bind to it.

Addressed in v2. The PROM21-specific hwmon code is no longer linked into drivers/usb/host. The xhci-pci side now creates an auxiliary device for selected controllers, and the PROM21 temperature support is implemented as a separate driver under drivers/hwmon which binds to that auxiliary device.

The v2 series is split into two patches:

  1. usb: xhci-pci: add generic auxiliary device interface
  2. hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 3 lines

Auxiliary devices were created specifically for this purpose. The parent driver would create an auxiliary device in its probe function, which would then be instantiated from the client driver.

Addressed in v2. xhci-pci creates the auxiliary device only for controllers matched by an internal PCI ID table, currently only AMD 1022:43fd. It destroys that auxiliary device from the remove path before HCD teardown. The hwmon driver binds through the auxiliary bus and owns the hwmon device lifetime.

The hwmon device is registered under the parent PCI function so userspace still reports it as a PCI adapter, but the auxiliary driver explicitly unregisters it from the auxiliary remove path before xhci-pci tears down the HCD.

Quoted 2 lines

On a side note, the code below looks messy. I would personally prefer something like the Renesas solution.

I moved the PROM21-specific code out of xhci-pci and out of drivers/usb/host. xhci-pci now only has the generic auxiliary-device creation side; the register offsets, conversion formula, hwmon callbacks, and runtime PM policy are all in drivers/hwmon/prom21-hwmon.c.

I also tried to keep the generic auxiliary-device support in xhci-pci as small and generic as possible. Similar to the Renesas handling, xhci-pci uses PCI ID based filtering, and only matching devices get an auxiliary child device.

Quoted 5 lines

Does accessing those registers depend on xHC state? or is enough that the PCI device is enabled and in D0?  i.e. does xHC need to be in "running" state?
I'd like to avoid resuming and restarting xHC every time temperature is read from a sysfs file.

I tested this on my system. Reading the PROM21 temperature register without runtime-resuming the parent xHCI PCI function did not return a valid temperature value while the device was suspended. Reading after runtime resume returned valid values.

Based on that result, v2 keeps the default behavior of allowing a temperature read to wake the xHCI PCI device. I also added the allow_pm_switch module parameter. With allow_pm_switch=N, a hwmon read does not wake the xHCI PCI device; if the device is suspended, the read returns -EAGAIN instead. v2 also adds Documentation/hwmon/prom21-hwmon.rst, which documents this behavior.

Quoted 2 lines

Can this cause a linker error if CONFIG_USB_XHCI_PCI is built-in (y) and CONFIG_HWMON is built as a module (m)?

Addressed by the v2 structure. xhci-hcd no longer calls into hwmon APIs or links PROM21 hwmon code directly. The PROM21 temperature support is now a standalone hwmon driver using the auxiliary bus, so the xhci-pci side only creates the auxiliary device and does not reference hwmon symbols.

I also added Documentation/hwmon/prom21-hwmon.rst documenting the supported device, register offsets, conversion formula, sysfs attributes, lookup method, and allow_pm_switch behavior.

Sincerely,
Jihong Min

Quoted 443 lines · Guenter Roeck
Quoted 27 lines · Mathias Nyman

Hi

Quoted 15 lines · Jihong Min

AMD PROM21 xHCI controllers expose a temperature byte through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add hwmon support for this, limited to temperature monitoring, and initialize it from the xHCI PCI driver probe path.

The xhci-pci probe and remove paths call the PROM21 hwmon helpers only for AMD 1022:43fd controllers. The hwmon read path selects the temperature register through the vendor index register, reads the raw temperature byte from the data register, and restores the previous index before returning.

No public AMD register reference is available for this value. The conversion formula is derived from observed temperature readings:

   temp[C] = raw * 0.9066 - 78.624

Quoted 8 lines

This is not implemented as a standalone driver because the temperature register belongs to the PROM21 xHCI PCI function, is accessed through the xHCI BAR after the host controller is initialized, and should share that device's lifetime and runtime PM. Keep the device-specific PROM21 code in a separate helper called only from xhci-pci, while linking it into xhci-hcd to match the existing xHCI object layout. The xhci-pci remove path invalidates the helper before HCD teardown.

Quoted 8 lines

To me it looks like a sepate device should be created for this, and a new driver in drivers/hwmon bind to it.

Not sure what is the best solution, do we create a parent mfd driver that binds to the PCI device, which then creates two child devices, xhci and hwmon. Or do we just create some kind of platform device as a child to this xhci PCI device.

Auxiliary devices were created specifically for this purpose. The parent driver would create an auxiliary device in its probe function, which would then be instantiated from the client driver.

On a side note, the code below looks messy. I would personally prefer something like the Renesas solution.

Guenter

Quoted 396 lines

Maybe someone on the PCI list would know (added to cc)? I guess it's not the first time we have a "multi function" PCI device that has just has one BAR.

The resources this device needs look simple, only access two registers at offset 0x3000 and 0x3008 from mmio base.

Does accessing those registers depend on xHC state? or is enough that the PCI device is enabled and in D0?  i.e. does xHC need to be in "running" state?
I'd like to avoid resuming and restarting xHC every time temperature is read from a sysfs file.

Thanks
Mathias

(keeping reset of message as reference for pci list readers)

Quoted 378 lines
Assisted-by Codex:gpt-5.5

---
  drivers/usb/host/Kconfig             |  10 ++
  drivers/usb/host/Makefile            |   4 +
  drivers/usb/host/xhci-pci.c          |   9 +
  drivers/usb/host/xhci-prom21-hwmon.c | 241
+++++++++++++++++++++++++++
  drivers/usb/host/xhci-prom21-hwmon.h |  26 +++

  5 files changed, 290 insertions(+)

  create mode 100644 drivers/usb/host/xhci-prom21-hwmon.c create mode 100644 drivers/usb/host/xhci-prom21-hwmon.h

drivers/usb/host/Kconfig +0 -0
index 0a277a07cf70..da41ebc272b0 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI

      depends on USB_PCI
      default y

drivers/usb/host/Kconfig +10 -0
+config USB_XHCI_PCI_PROM21_HWMON
+    bool "AMD PROM21 xHCI temperature sensor support"
+    depends on USB_XHCI_PCI
+    depends on HWMON
+    help
+      Say Y here to expose the AMD PROM21 xHCI temperature sensor
+      through the hwmon subsystem. The sensor is accessed through a
+      vendor-specific index/data register pair in the controller's PCI
+      MMIO BAR and reports temperature only. If unsure, say N.
+

  config USB_XHCI_PCI_RENESAS
      tristate "Support for additional Renesas xHCI controller with firmware" depends on USB_XHCI_PCI

drivers/usb/host/Makefile +0 -0
index a07e7ba9cd53..22f141cb2af7 100644
@@ -36,6 +36,10 @@ ifneq ($(CONFIG_USB_XHCI_SIDEBAND),)

      xhci-hcd-y        += xhci-sideband.o
  endif

drivers/usb/host/Makefile +4 -0
+ifneq ($(CONFIG_USB_XHCI_PCI_PROM21_HWMON),)
+    xhci-hcd-y += xhci-prom21-hwmon.o
+endif
+

  obj-$(CONFIG_USB_PCI)    += pci-quirks.o
  obj-$(CONFIG_USB_EHCI_HCD)    += ehci-hcd.o

drivers/usb/host/xhci-pci.c +5 -0
index 585b2f3117b0..54713efc931f 100644
@@ -18,6 +18,7 @@
   #include "xhci.h"
   #include "xhci-trace.h"
   #include "xhci-pci.h"
+#include "xhci-prom21-hwmon.h"
   #define SSIC_PORT_NUM        2
   #define SSIC_PORT_CFG2        0x880c
@@ -677,6 +678,10 @@ int xhci_pci_common_probe(struct pci_dev *dev, 
 const struct pci_device_id *id)
       if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
           pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
+    if (dev->vendor == PCI_VENDOR_ID_AMD &&
+        dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+        xhci_prom21_hwmon_init(xhci, dev);
+
       return 0;

  put_usb3_hcd:

drivers/usb/host/xhci-pci.c +4 -0
@@ -713,6 +718,10 @@ void xhci_pci_remove(struct pci_dev *dev)
       xhci = hcd_to_xhci(pci_get_drvdata(dev));
       set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
+    if (dev->vendor == PCI_VENDOR_ID_AMD &&
+        dev->device == PCI_DEVICE_ID_AMD_PROM21_XHCI)
+        xhci_try_prom21_hwmon_invalidate(dev);
+
       xhci->xhc_state |= XHCI_STATE_REMOVING;
       if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
drivers/usb/host/xhci-prom21-hwmon.c new file +6 -0
new file mode 100644
index 000000000000..5f71e72f4a90
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD PROM21 xHCI Hwmon Implementation
+ * (only temperature monitoring is supported)
+ *
+ * This can be effectively used as the alternative chipset 

temperature monitor.
+ *
+ * Copyright (C) 2026 Jihong Min <[email protected]>
+ */

drivers/usb/host/xhci-prom21-hwmon.c new file +26 -0
+
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include "xhci.h"
+#include "xhci-prom21-hwmon.h"
+
+#define XHCI_PROM21_INDEX 0x3000
+#define XHCI_PROM21_DATA 0x3008
+#define XHCI_PROM21_TEMP_REG 0x0001e520
+
+#define XHCI_PROM21_HWMON_NAME "xhci_prom21"
+
+struct xhci_prom21_hwmon {
+    struct pci_dev *pdev;
+    void __iomem *regs;
+    bool removing;
+    struct mutex lock; /* protects removing and the index/data 

registers */

drivers/usb/host/xhci-prom21-hwmon.c new file +7 -0
+};
+
+struct xhci_prom21_hwmon_devres {
+    struct xhci_prom21_hwmon *hwmon;
+};
+
+static void xhci_prom21_hwmon_invalidate(struct xhci_prom21_hwmon 

*hwmon)

drivers/usb/host/xhci-prom21-hwmon.c new file +7 -0
+{
+    mutex_lock(&hwmon->lock);
+    hwmon->removing = true;
+    mutex_unlock(&hwmon->lock);
+}
+
+static void xhci_prom21_hwmon_devres_release(struct device *dev, 

void *res)

drivers/usb/host/xhci-prom21-hwmon.c new file +5 -0
+{
+    struct xhci_prom21_hwmon_devres *devres = res;
+
+    /*
+     * devm hwmon unregister runs after this lookup record is 

released.
+     * Mark the data path closed first so any late sysfs read returns +     * without touching xHCI MMIO.
+     */
+    xhci_prom21_hwmon_invalidate(devres->hwmon);

drivers/usb/host/xhci-prom21-hwmon.c new file +4 -0
+}
+
+/*
+ * This is not a pure MMIO read. The PROM21 vendor data register is 

selected
+ * by temporarily writing XHCI_PROM21_TEMP_REG to the vendor index register.
+ * Keep the sequence short and restore the previous index before returning.
+ */

drivers/usb/host/xhci-prom21-hwmon.c new file +2 -0
+static int
+xhci_prom21_read_temp_raw_restore_index(struct xhci_prom21_hwmon 

*hwmon,
+                    u8 *raw)

drivers/usb/host/xhci-prom21-hwmon.c new file +8 -0
+{
+    struct device *dev = &hwmon->pdev->dev;
+    u32 index;
+    u32 data;
+    int ret;
+
+    /*
+     * xhci_try_prom21_hwmon_invalidate() uses the same lock before 

HCD
+     * teardown. Keep runtime PM and MMIO inside the critical section so a
+     * sysfs read cannot use the vendor register pair after remove starts.
+     */
+    mutex_lock(&hwmon->lock);
+    if (hwmon->removing) {
+        mutex_unlock(&hwmon->lock);
+        return -ENODEV;
+    }

drivers/usb/host/xhci-prom21-hwmon.c new file +9 -0
+
+    ret = pm_runtime_resume_and_get(dev);
+    if (ret < 0) {
+        mutex_unlock(&hwmon->lock);
+        return ret;
+    }
+
+    index = readl(hwmon->regs + XHCI_PROM21_INDEX);
+    /* Select the PROM21 temperature register through the vendor 

index. */
+    writel(XHCI_PROM21_TEMP_REG, hwmon->regs + XHCI_PROM21_INDEX); +    data = readl(hwmon->regs + XHCI_PROM21_DATA);
+    /* Restore the previous vendor index register value. */ +    writel(index, hwmon->regs + XHCI_PROM21_INDEX);
+    readl(hwmon->regs + XHCI_PROM21_INDEX);

drivers/usb/host/xhci-prom21-hwmon.c new file +18 -0
+
+    /* Let xHCI PCI runtime PM coalesce repeated sysfs polling. */
+    pm_runtime_mark_last_busy(dev);
+    pm_runtime_put_autosuspend(dev);
+    mutex_unlock(&hwmon->lock);
+
+    *raw = data & 0xff;
+    if (!*raw || *raw == 0xff)
+        return -ENODATA;
+
+    return 0;
+}
+
+static long xhci_prom21_raw_to_millicelsius(u8 raw)
+{
+    /*
+     * No public AMD register reference is available for this value.
+     * The scale was derived from observed PROM21 xHCI temperature 

readings:
+     *  temp[C] = raw * 0.9066 - 78.624
+     */
+    return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;

drivers/usb/host/xhci-prom21-hwmon.c new file +42 -0
+}
+
+static umode_t xhci_prom21_hwmon_is_visible(const void *drvdata,
+                        enum hwmon_sensor_types type,
+                        u32 attr, int channel)
+{
+    if (type != hwmon_temp || channel)
+        return 0;
+
+    switch (attr) {
+    case hwmon_temp_input:
+        return 0444;
+    default:
+        return 0;
+    }
+}
+
+static int xhci_prom21_hwmon_read(struct device *dev,
+                  enum hwmon_sensor_types type, u32 attr,
+                  int channel, long *val)
+{
+    struct xhci_prom21_hwmon *hwmon = dev_get_drvdata(dev);
+    u8 raw;
+    int ret;
+
+    if (type != hwmon_temp || attr != hwmon_temp_input || channel)
+        return -EOPNOTSUPP;
+
+    ret = xhci_prom21_read_temp_raw_restore_index(hwmon, &raw);
+    if (ret)
+        return ret;
+
+    *val = xhci_prom21_raw_to_millicelsius(raw);
+    return 0;
+}
+
+static const struct hwmon_ops xhci_prom21_hwmon_ops = {
+    .is_visible = xhci_prom21_hwmon_is_visible,
+    .read = xhci_prom21_hwmon_read,
+};
+
+static const struct hwmon_channel_info *const 

xhci_prom21_hwmon_info[] = {
+    HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+    NULL,

drivers/usb/host/xhci-prom21-hwmon.c new file +8 -0
+};
+
+static const struct hwmon_chip_info xhci_prom21_chip_info = {
+    .ops = &xhci_prom21_hwmon_ops,
+    .info = xhci_prom21_hwmon_info,
+};
+
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev 

*pdev)

drivers/usb/host/xhci-prom21-hwmon.c new file +8 -0
+{
+    struct xhci_prom21_hwmon_devres *devres;
+    struct xhci_prom21_hwmon *hwmon;
+    struct usb_hcd *hcd = xhci_to_hcd(xhci);
+    struct device *dev = &pdev->dev;
+    struct device *hwmon_dev;
+
+    if (!hcd->regs || hcd->rsrc_len < XHCI_PROM21_DATA + 

sizeof(u32)) {
+        dev_err(dev,
+            "AMD PROM21 hwmon unavailable: invalid MMIO resource\n"); +        return;
+    }

drivers/usb/host/xhci-prom21-hwmon.c new file +9 -0
+
+    hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+    if (!hwmon) {
+        /* The allocator reports OOM; add PROM21 device context. */
+        dev_err(dev, "AMD PROM21 hwmon state unavailable\n");
+        return;
+    }
+
+    devres = devres_alloc(xhci_prom21_hwmon_devres_release, 

sizeof(*devres),
+                  GFP_KERNEL);
+    if (!devres) {
+        dev_err(dev, "AMD PROM21 hwmon devres allocation failed\n"); +        return;
+    }

drivers/usb/host/xhci-prom21-hwmon.c new file +19 -0
+
+    hwmon->pdev = pdev;
+    hwmon->regs = hcd->regs;
+    mutex_init(&hwmon->lock);
+
+    hwmon_dev = devm_hwmon_device_register_with_info(dev,
+                             XHCI_PROM21_HWMON_NAME,
+                             hwmon,
+                             &xhci_prom21_chip_info,
+                             NULL);
+    if (IS_ERR(hwmon_dev)) {
+        devres_free(devres);
+        dev_err(dev, "AMD PROM21 hwmon registration failed: %pe\n",
+            hwmon_dev);
+        return;
+    }
+
+    /*
+     * Store a private devres record so the device remove path can 

find this
+     * state without adding PROM21-specific part to xhci-pci. +     */
+    devres->hwmon = hwmon;
+    devres_add(dev, devres);

drivers/usb/host/xhci-prom21-hwmon.c new file +9 -0
+}
+EXPORT_SYMBOL_GPL(xhci_prom21_hwmon_init);
+
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev)
+{
+    struct xhci_prom21_hwmon_devres *devres;
+
+    /*
+     * This is called for every xHCI PCI device. Devices without 

PROM21
+     * hwmon support simply have no matching helper devres entry. +     */
+    devres = devres_find(&pdev->dev, xhci_prom21_hwmon_devres_release, NULL,
+                 NULL);
+    if (!devres) {
+        dev_dbg(&pdev->dev, "AMD PROM21 hwmon state not found\n"); +        return;
+    }

drivers/usb/host/xhci-prom21-hwmon.c new file +4 -0
+
+    xhci_prom21_hwmon_invalidate(devres->hwmon);
+}
+EXPORT_SYMBOL_GPL(xhci_try_prom21_hwmon_invalidate);
drivers/usb/host/xhci-prom21-hwmon.h new file +13 -0
new file mode 100644
index 000000000000..eca8db23c0ea
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Jihong Min <[email protected]> */
+
+#ifndef XHCI_PROM21_HWMON_H
+#define XHCI_PROM21_HWMON_H
+
+#define PCI_DEVICE_ID_AMD_PROM21_XHCI 0x43fd
+
+struct pci_dev;
+struct xhci_hcd;
+
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21_HWMON)
+void xhci_prom21_hwmon_init(struct xhci_hcd *xhci, struct pci_dev 

*pdev);

drivers/usb/host/xhci-prom21-hwmon.h new file +8 -0
+void xhci_try_prom21_hwmon_invalidate(struct pci_dev *pdev);
+#else
+static inline void xhci_prom21_hwmon_init(struct xhci_hcd *xhci,
+                      struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_try_prom21_hwmon_invalidate(struct pci_dev 

*pdev)

drivers/usb/host/xhci-prom21-hwmon.h new file +5 -0
+{
+}
+#endif
+
+#endif
author
#5 lore raw

v2 cover AMD PROM21 xHCI temperature hwmon support

In reply to Jihong Min

Hi,

This series adds hwmon support for the temperature sensor exposed by AMD PROM21 xHCI controllers. The temperature value is read through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR.

v1 implemented this directly under drivers/usb/host. Based on review feedback, v2 splits the support into a generic xhci-pci auxiliary-device interface and a standalone hwmon driver under drivers/hwmon. xhci-pci creates the auxiliary child only for PCI IDs matched by its internal table, currently only AMD 1022:43fd, and the hwmon driver binds to that child through the auxiliary bus.

The PROM21 hwmon driver registers the hwmon device under the parent PCI function so userspace reports it as a PCI adapter. The auxiliary driver still owns the lifetime and unregisters the hwmon device before xhci-pci tears down the HCD.

I tested register access while the parent xHCI PCI function was suspended. The temperature register did not return a valid value without runtime-resuming the device. v2 therefore keeps the default behavior of allowing a temperature read to wake the xHCI PCI device, and adds allow_pm_switch=N for users who prefer reads not to wake the device. In that mode a read returns -EAGAIN while the device is suspended.

Changes in v2:
- split the original single patch into a two-patch series
- add a generic xhci-pci auxiliary-device interface
- move PROM21 hwmon support to drivers/hwmon as an auxiliary driver
- avoid linking hwmon-specific code into xhci-hcd
- add Documentation/hwmon/prom21-hwmon.rst
- document register offsets, conversion formula, sysfs attributes, and allow_pm_switch behavior

Sincerely,
Jihong Min

Jihong Min (2):
usb: xhci-pci: add generic auxiliary device interface
hwmon: add initial support for AMD PROM21 xHCI temperature sensor

 Documentation/hwmon/index.rst        |   1 +
 Documentation/hwmon/prom21-hwmon.rst |  84 ++++++++
 drivers/hwmon/Kconfig                |  11 +
 drivers/hwmon/Makefile               |   1 +
 drivers/hwmon/prom21-hwmon.c         | 300 +++++++++++++++++++++++++++
 drivers/usb/host/Kconfig             |  10 +
 drivers/usb/host/xhci-pci.c          | 114 ++++++++++
 7 files changed, 521 insertions(+)
 create mode 100644 Documentation/hwmon/prom21-hwmon.rst
 create mode 100644 drivers/hwmon/prom21-hwmon.c
Signature · 1 line
2.53.0
author
#6 lore raw

PATCH v2 1/2 usb: xhci-pci: add generic auxiliary device interface

Some xHCI PCI controllers expose controller-specific functionality that is not part of generic xHCI operation and is better handled by optional child drivers in other subsystems. Add a small auxiliary device registration path for selected xHCI PCI controllers.

The initial table creates an xhci_pci.hwmon auxiliary device for AMD 1022:43fd controllers. Store the created auxiliary device in devres so the xhci-pci remove path destroys it before HCD teardown. Use a PCI-domain-qualified id so auxiliary device names remain unique across PCI domains.

This keeps xhci-pci responsible only for publishing selected controller functions while allowing subsystem-specific drivers to bind through the auxiliary bus.

Assisted-by Codex:gpt-5.5
---
 drivers/usb/host/Kconfig    |  10 ++++
 drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++
 2 files changed, 124 insertions(+)
drivers/usb/host/Kconfig +10 -0
index 0a277a07cf70..e0c2c7ac5c97 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
 	depends on USB_PCI
 	default y
 
+config USB_XHCI_PCI_AUXDEV
+	bool "xHCI PCI auxiliary device support"
+	depends on USB_XHCI_PCI
+	select AUXILIARY_BUS
+	help
+	  This enables xHCI PCI support for registering auxiliary devices
+	  for selected controllers. It is used by optional child drivers
+	  that bind to xHCI PCI controller-specific functionality through
+	  the auxiliary bus.
+
 config USB_XHCI_PCI_RENESAS
 	tristate "Support for additional Renesas xHCI controller with firmware"
 	depends on USB_XHCI_PCI
drivers/usb/host/xhci-pci.c +114 -0

138 lines not on this page · raw message

Signature · 1 line
2.53.0
#7 lore raw

Re: [PATCH v2 1/2] usb: xhci-pci: add generic auxiliary device interface

Quoted 83 lines · Jihong Min

[You don't often get email from [email protected]. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]

Some xHCI PCI controllers expose controller-specific functionality that is not part of generic xHCI operation and is better handled by optional child drivers in other subsystems. Add a small auxiliary device registration path for selected xHCI PCI controllers.

The initial table creates an xhci_pci.hwmon auxiliary device for AMD 1022:43fd controllers. Store the created auxiliary device in devres so the xhci-pci remove path destroys it before HCD teardown. Use a PCI-domain-qualified id so auxiliary device names remain unique across PCI domains.

This keeps xhci-pci responsible only for publishing selected controller functions while allowing subsystem-specific drivers to bind through the auxiliary bus.

Assisted-by Codex:gpt-5.5
---
  drivers/usb/host/Kconfig    |  10 ++++
  drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++
  2 files changed, 124 insertions(+)
drivers/usb/host/Kconfig +10 -0
index 0a277a07cf70..e0c2c7ac5c97 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
         depends on USB_PCI
         default y
 
+config USB_XHCI_PCI_AUXDEV
+       bool "xHCI PCI auxiliary device support"
+       depends on USB_XHCI_PCI
+       select AUXILIARY_BUS
+       help
+         This enables xHCI PCI support for registering auxiliary devices
+         for selected controllers. It is used by optional child drivers
+         that bind to xHCI PCI controller-specific functionality through
+         the auxiliary bus.
+
  config USB_XHCI_PCI_RENESAS
         tristate "Support for additional Renesas xHCI controller with firmware"
         depends on USB_XHCI_PCI
drivers/usb/host/xhci-pci.c +23 -0
index 585b2f3117b0..1ab27d2182eb 100644
@@ -8,6 +8,8 @@
   * Some code borrowed from the Linux EHCI driver.
   */
 
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
  #include <linux/pci.h>
  #include <linux/slab.h>
  #include <linux/module.h>
@@ -103,6 +105,114 @@ static int xhci_pci_run(struct usb_hcd *hcd);
  static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev,
                                       struct usb_tt *tt, gfp_t mem_flags);
 
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV)
+static const struct {
+       struct pci_device_id id;
+       const char *aux_dev_name;
+} pci_ids_have_aux[] = {
+       {
+               .id = { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x43fd) }, /* PROM21 xHCI */
+               .aux_dev_name = "hwmon",
+       },
+       { /* end: all zeroes */ }
+};
+
+struct xhci_pci_aux_devres {
+       struct auxiliary_device *auxdev;
+};
+
+static bool xhci_pci_aux_match_id(const struct pci_device_id *id,
+                                 const struct pci_dev *pdev)
+{
+       if (id->vendor != PCI_ANY_ID && id->vendor != pdev->vendor)
+               return false;

drivers/usb/host/xhci-pci.c static int xhci_pci_run(struct usb_hcd *hcd); · line 138

Quoted 10 lines
drivers/usb/host/xhci-pci.c +10 -0
+       if (id->device != PCI_ANY_ID && id->device != pdev->device)
+               return false;
+       if (id->subvendor != PCI_ANY_ID &&
+           id->subvendor != pdev->subsystem_vendor)
+               return false;
+       if (id->subdevice != PCI_ANY_ID &&
+           id->subdevice != pdev->subsystem_device)
+               return false;
+
+       return !((id->class ^ pdev->class) & id->class_mask);

What's wrong with pci_match_id()?

Quoted 101 lines
drivers/usb/host/xhci-pci.c +81 -1
+}
+
+static const char *xhci_pci_aux_dev_name(struct pci_dev *pdev)
+{
+       int i;
+
+       for (i = 0; pci_ids_have_aux[i].aux_dev_name; i++) {
+               if (xhci_pci_aux_match_id(&pci_ids_have_aux[i].id, pdev))
+                       return pci_ids_have_aux[i].aux_dev_name;
+       }
+
+       return NULL;
+}
+
+static void xhci_pci_aux_devres_release(struct device *dev, void *res)
+{
+       struct xhci_pci_aux_devres *devres = res;
+
+       if (devres->auxdev)
+               auxiliary_device_destroy(devres->auxdev);
+}
+
+static void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+       struct xhci_pci_aux_devres *devres;
+       struct auxiliary_device *auxdev;
+       const char *aux_dev_name;
+
+       aux_dev_name = xhci_pci_aux_dev_name(pdev);
+       if (!aux_dev_name)
+               return;
+
+       devres = devres_alloc(xhci_pci_aux_devres_release, sizeof(*devres),
+                             GFP_KERNEL);
+       if (!devres) {
+               dev_warn(&pdev->dev,
+                        "failed to allocate auxiliary device state\n");
+               return;
+       }
+
+       auxdev = auxiliary_device_create(&pdev->dev, KBUILD_MODNAME,
+                                        aux_dev_name, NULL,
+                                        (pci_domain_nr(pdev->bus) << 16) |
+                                                pci_dev_id(pdev));
+       if (!auxdev) {
+               devres_free(devres);
+               dev_warn(&pdev->dev, "failed to add %s auxiliary device\n",
+                        aux_dev_name);
+               return;
+       }
+
+       devres->auxdev = auxdev;
+       devres_add(&pdev->dev, devres);
+}
+
+static void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+       struct xhci_pci_aux_devres *devres;
+
+       devres = devres_find(&pdev->dev, xhci_pci_aux_devres_release, NULL,
+                            NULL);
+       if (!devres || !devres->auxdev)
+               return;
+
+       auxiliary_device_destroy(devres->auxdev);
+       devres->auxdev = NULL;
+}
+#else
+static inline void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+}
+#endif
+
  static const struct xhci_driver_overrides xhci_pci_overrides __initconst = {
         .reset = xhci_pci_setup,
         .start = xhci_pci_run,
@@ -677,6 +787,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
         if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
                 pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
 
+       xhci_pci_try_add_aux_device(dev);
+
         return 0;
 
  put_usb3_hcd:
@@ -713,6 +825,8 @@ void xhci_pci_remove(struct pci_dev *dev)
         xhci = hcd_to_xhci(pci_get_drvdata(dev));
         set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
 
+       xhci_pci_try_remove_aux_device(dev);
+
         xhci->xhc_state |= XHCI_STATE_REMOVING;
 
         if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
--

2.53.0

#8 lore raw

Re: [PATCH v2 1/2] usb: xhci-pci: add generic auxiliary device interface

I refactored the auxiliary device table to use PCI_DEVICE_DATA(), store the auxiliary device name in driver_data, and use pci_match_id() inside xhci_pci_aux_dev_name(). The relevant change is:

#define PCI_DEVICE_ID_AMD_PROM21_XHCI            0x43fd
...
static const struct pci_device_id pci_ids_have_aux[] = { { PCI_DEVICE_DATA(AMD, PROM21_XHCI, "hwmon") }, { /* end: all zeroes */ }
};
...
static const char *xhci_pci_aux_dev_name(struct pci_dev *pdev) {
    const struct pci_device_id *id;

    id = pci_match_id(pci_ids_have_aux, pdev);
    if (!id)
        return NULL;

    return (const char *)id->driver_data;
}

I will include this in v3.

Thank you,
Jihong Min

Quoted 209 lines · Mario Limonciello
Quoted 86 lines · Jihong Min

[You don't often get email from [email protected]. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]

Some xHCI PCI controllers expose controller-specific functionality that is not part of generic xHCI operation and is better handled by optional child drivers in other subsystems. Add a small auxiliary device registration path for selected xHCI PCI controllers.

The initial table creates an xhci_pci.hwmon auxiliary device for AMD 1022:43fd controllers. Store the created auxiliary device in devres so the xhci-pci remove path destroys it before HCD teardown. Use a PCI-domain-qualified id so auxiliary device names remain unique across PCI domains.

This keeps xhci-pci responsible only for publishing selected controller functions while allowing subsystem-specific drivers to bind through the auxiliary bus.

Assisted-by Codex:gpt-5.5

---
  drivers/usb/host/Kconfig    |  10 ++++ drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++

  2 files changed, 124 insertions(+)
drivers/usb/host/Kconfig +0 -0
index 0a277a07cf70..e0c2c7ac5c97 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI

         depends on USB_PCI
         default y

+config USB_XHCI_PCI_AUXDEV
+       bool "xHCI PCI auxiliary device support"
+       depends on USB_XHCI_PCI
+       select AUXILIARY_BUS
+       help
+         This enables xHCI PCI support for registering auxiliary devices
+         for selected controllers. It is used by optional child drivers +         that bind to xHCI PCI controller-specific functionality through
+         the auxiliary bus.
+
  config USB_XHCI_PCI_RENESAS
         tristate "Support for additional Renesas xHCI controller with firmware" depends on USB_XHCI_PCI

drivers/usb/host/xhci-pci.c +9 -0
index 585b2f3117b0..1ab27d2182eb 100644
@@ -8,6 +8,8 @@
    * Some code borrowed from the Linux EHCI driver.
    */
 
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
   #include <linux/pci.h>
   #include <linux/slab.h>
   #include <linux/module.h>
@@ -103,6 +105,114 @@ static int xhci_pci_run(struct usb_hcd *hcd);
   static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev,
                                        struct usb_tt *tt, gfp_t mem_flags);
 
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV)
+static const struct {
+       struct pci_device_id id;
+       const char *aux_dev_name;
+} pci_ids_have_aux[] = {
+       {
+               .id = { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x43fd) }, /* 

PROM21 xHCI */
+               .aux_dev_name = "hwmon",
+       },
+       { /* end: all zeroes */ }

drivers/usb/host/xhci-pci.c +11 -0
+};
+
+struct xhci_pci_aux_devres {
+       struct auxiliary_device *auxdev;
+};
+
+static bool xhci_pci_aux_match_id(const struct pci_device_id *id,
+                                 const struct pci_dev *pdev)
+{
+       if (id->vendor != PCI_ANY_ID && id->vendor != pdev->vendor)
+               return false;

drivers/usb/host/xhci-pci.c static int xhci_pci_run(struct usb_hcd *hcd); · line 138

Quoted 10 lines
drivers/usb/host/xhci-pci.c +10 -0
+       if (id->device != PCI_ANY_ID && id->device != pdev->device)
+               return false;
+       if (id->subvendor != PCI_ANY_ID &&
+           id->subvendor != pdev->subsystem_vendor)
+               return false;
+       if (id->subdevice != PCI_ANY_ID &&
+           id->subdevice != pdev->subsystem_device)
+               return false;
+
+       return !((id->class ^ pdev->class) & id->class_mask);

What's wrong with pci_match_id()?

Quoted 106 lines
drivers/usb/host/xhci-pci.c +8 -0
+}
+
+static const char *xhci_pci_aux_dev_name(struct pci_dev *pdev)
+{
+       int i;
+
+       for (i = 0; pci_ids_have_aux[i].aux_dev_name; i++) {
+               if (xhci_pci_aux_match_id(&pci_ids_have_aux[i].id, 

pdev))
+                       return pci_ids_have_aux[i].aux_dev_name; +       }

drivers/usb/host/xhci-pci.c +23 -0
+
+       return NULL;
+}
+
+static void xhci_pci_aux_devres_release(struct device *dev, void *res)
+{
+       struct xhci_pci_aux_devres *devres = res;
+
+       if (devres->auxdev)
+               auxiliary_device_destroy(devres->auxdev);
+}
+
+static void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+       struct xhci_pci_aux_devres *devres;
+       struct auxiliary_device *auxdev;
+       const char *aux_dev_name;
+
+       aux_dev_name = xhci_pci_aux_dev_name(pdev);
+       if (!aux_dev_name)
+               return;
+
+       devres = devres_alloc(xhci_pci_aux_devres_release, 

sizeof(*devres),
+                             GFP_KERNEL);
+       if (!devres) {
+               dev_warn(&pdev->dev,
+                        "failed to allocate auxiliary device state\n"); +               return;
+       }
+
+       auxdev = auxiliary_device_create(&pdev->dev, KBUILD_MODNAME, +                                        aux_dev_name, NULL,
+ (pci_domain_nr(pdev->bus) << 16) |
+ pci_dev_id(pdev));
+       if (!auxdev) {
+               devres_free(devres);
+               dev_warn(&pdev->dev, "failed to add %s auxiliary device\n",
+                        aux_dev_name);
+               return;
+       }

drivers/usb/host/xhci-pci.c +10 -0
+
+       devres->auxdev = auxdev;
+       devres_add(&pdev->dev, devres);
+}
+
+static void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+       struct xhci_pci_aux_devres *devres;
+
+       devres = devres_find(&pdev->dev, xhci_pci_aux_devres_release, 

NULL,
+                            NULL);
+       if (!devres || !devres->auxdev)
+               return;

drivers/usb/host/xhci-pci.c +14 -0
+
+       auxiliary_device_destroy(devres->auxdev);
+       devres->auxdev = NULL;
+}
+#else
+static inline void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+}
+#endif
+

  static const struct xhci_driver_overrides xhci_pci_overrides __initconst = { .reset = xhci_pci_setup,
         .start = xhci_pci_run,

drivers/usb/host/xhci-pci.c +2 -0
@@ -677,6 +787,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, 
 const struct pci_device_id *id)
          if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
                  pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
 
+       xhci_pci_try_add_aux_device(dev);
+
          return 0;
 

  put_usb3_hcd:

drivers/usb/host/xhci-pci.c +2 -0
@@ -713,6 +825,8 @@ void xhci_pci_remove(struct pci_dev *dev)
          xhci = hcd_to_xhci(pci_get_drvdata(dev));
          set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
 
+       xhci_pci_try_remove_aux_device(dev);
+
          xhci->xhc_state |= XHCI_STATE_REMOVING;
 
          if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
Signature · 1 line
2.53.0
#9 lore raw

Re: [PATCH v2 1/2] usb: xhci-pci: add generic auxiliary device interface

In reply to Jihong Min
Quoted 172 lines · Jihong Min

Some xHCI PCI controllers expose controller-specific functionality that is not part of generic xHCI operation and is better handled by optional child drivers in other subsystems. Add a small auxiliary device registration path for selected xHCI PCI controllers.

The initial table creates an xhci_pci.hwmon auxiliary device for AMD 1022:43fd controllers. Store the created auxiliary device in devres so the xhci-pci remove path destroys it before HCD teardown. Use a PCI-domain-qualified id so auxiliary device names remain unique across PCI domains.

This keeps xhci-pci responsible only for publishing selected controller functions while allowing subsystem-specific drivers to bind through the auxiliary bus.

Assisted-by Codex:gpt-5.5
---
 drivers/usb/host/Kconfig    |  10 ++++
 drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++
 2 files changed, 124 insertions(+)
drivers/usb/host/Kconfig +10 -0
index 0a277a07cf70..e0c2c7ac5c97 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
 	depends on USB_PCI
 	default y
 
+config USB_XHCI_PCI_AUXDEV
+	bool "xHCI PCI auxiliary device support"
+	depends on USB_XHCI_PCI
+	select AUXILIARY_BUS
+	help
+	  This enables xHCI PCI support for registering auxiliary devices
+	  for selected controllers. It is used by optional child drivers
+	  that bind to xHCI PCI controller-specific functionality through
+	  the auxiliary bus.
+
 config USB_XHCI_PCI_RENESAS
 	tristate "Support for additional Renesas xHCI controller with firmware"
 	depends on USB_XHCI_PCI
drivers/usb/host/xhci-pci.c +110 -0
index 585b2f3117b0..1ab27d2182eb 100644
@@ -8,6 +8,8 @@
  * Some code borrowed from the Linux EHCI driver.
  */
 
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
 #include <linux/pci.h>
 #include <linux/slab.h>
 #include <linux/module.h>
@@ -103,6 +105,114 @@ static int xhci_pci_run(struct usb_hcd *hcd);
 static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev,
 				      struct usb_tt *tt, gfp_t mem_flags);
 
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV)
+static const struct {
+	struct pci_device_id id;
+	const char *aux_dev_name;
+} pci_ids_have_aux[] = {
+	{
+		.id = { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x43fd) }, /* PROM21 xHCI */
+		.aux_dev_name = "hwmon",
+	},
+	{ /* end: all zeroes */ }
+};
+
+struct xhci_pci_aux_devres {
+	struct auxiliary_device *auxdev;
+};
+
+static bool xhci_pci_aux_match_id(const struct pci_device_id *id,
+				  const struct pci_dev *pdev)
+{
+	if (id->vendor != PCI_ANY_ID && id->vendor != pdev->vendor)
+		return false;
+	if (id->device != PCI_ANY_ID && id->device != pdev->device)
+		return false;
+	if (id->subvendor != PCI_ANY_ID &&
+	    id->subvendor != pdev->subsystem_vendor)
+		return false;
+	if (id->subdevice != PCI_ANY_ID &&
+	    id->subdevice != pdev->subsystem_device)
+		return false;
+
+	return !((id->class ^ pdev->class) & id->class_mask);
+}
+
+static const char *xhci_pci_aux_dev_name(struct pci_dev *pdev)
+{
+	int i;
+
+	for (i = 0; pci_ids_have_aux[i].aux_dev_name; i++) {
+		if (xhci_pci_aux_match_id(&pci_ids_have_aux[i].id, pdev))
+			return pci_ids_have_aux[i].aux_dev_name;
+	}
+
+	return NULL;
+}
+
+static void xhci_pci_aux_devres_release(struct device *dev, void *res)
+{
+	struct xhci_pci_aux_devres *devres = res;
+
+	if (devres->auxdev)
+		auxiliary_device_destroy(devres->auxdev);
+}
+
+static void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+	struct xhci_pci_aux_devres *devres;
+	struct auxiliary_device *auxdev;
+	const char *aux_dev_name;
+
+	aux_dev_name = xhci_pci_aux_dev_name(pdev);
+	if (!aux_dev_name)
+		return;
+
+	devres = devres_alloc(xhci_pci_aux_devres_release, sizeof(*devres),
+			      GFP_KERNEL);
+	if (!devres) {
+		dev_warn(&pdev->dev,
+			 "failed to allocate auxiliary device state\n");
+		return;
+	}
+
+	auxdev = auxiliary_device_create(&pdev->dev, KBUILD_MODNAME,
+					 aux_dev_name, NULL,
+					 (pci_domain_nr(pdev->bus) << 16) |
+						 pci_dev_id(pdev));
+	if (!auxdev) {
+		devres_free(devres);
+		dev_warn(&pdev->dev, "failed to add %s auxiliary device\n",
+			 aux_dev_name);
+		return;
+	}
+
+	devres->auxdev = auxdev;
+	devres_add(&pdev->dev, devres);
+}
+
+static void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+	struct xhci_pci_aux_devres *devres;
+
+	devres = devres_find(&pdev->dev, xhci_pci_aux_devres_release, NULL,
+			     NULL);
+	if (!devres || !devres->auxdev)
+		return;
+
+	auxiliary_device_destroy(devres->auxdev);
+	devres->auxdev = NULL;
+}
+#else
+static inline void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+}
+#endif
+
 static const struct xhci_driver_overrides xhci_pci_overrides __initconst = {
 	.reset = xhci_pci_setup,
 	.start = xhci_pci_run,

drivers/usb/host/xhci-pci.c int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) · line 790

Quoted 4 lines
drivers/usb/host/xhci-pci.c +1 -0
@@ -677,6 +787,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
 		pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
 
+	xhci_pci_try_add_aux_device(dev);

Is it considered acceptable to add

if (IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV))

before this call, remove #ifdef around the definitions of the auxdev functions and rely on dead code elimination to clean them up?

We already have a similar trick with CONFIG_USB_XHCI_PCI_RENESAS here and it seemed to be working, though the amount of conditional code is much lower and so is the potential risk.

The reason for doing it this way was because Greg doesn't like #ifdefs in .c files, and neither do static analyzers like them.

Quoted 16 lines
drivers/usb/host/xhci-pci.c +3 -0
+
 	return 0;
 
 put_usb3_hcd:
@@ -713,6 +825,8 @@ void xhci_pci_remove(struct pci_dev *dev)
 	xhci = hcd_to_xhci(pci_get_drvdata(dev));
 	set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
 
+	xhci_pci_try_remove_aux_device(dev);
+
 	xhci->xhc_state |= XHCI_STATE_REMOVING;
 
 	if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
Signature · 1 line
2.53.0
#10 lore raw

Re: [PATCH v2 1/2] usb: xhci-pci: add generic auxiliary device interface

Thank you.

I've changed the code by removing the preprocessor macros and protecting the two entry points with if (IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV)), for v3.

Jihong Min

Quoted 207 lines · Michal Pecio
Quoted 172 lines · Jihong Min

Some xHCI PCI controllers expose controller-specific functionality that is not part of generic xHCI operation and is better handled by optional child drivers in other subsystems. Add a small auxiliary device registration path for selected xHCI PCI controllers.

The initial table creates an xhci_pci.hwmon auxiliary device for AMD 1022:43fd controllers. Store the created auxiliary device in devres so the xhci-pci remove path destroys it before HCD teardown. Use a PCI-domain-qualified id so auxiliary device names remain unique across PCI domains.

This keeps xhci-pci responsible only for publishing selected controller functions while allowing subsystem-specific drivers to bind through the auxiliary bus.

Assisted-by Codex:gpt-5.5
---
  drivers/usb/host/Kconfig    |  10 ++++
  drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++
  2 files changed, 124 insertions(+)
drivers/usb/host/Kconfig +10 -0
index 0a277a07cf70..e0c2c7ac5c97 100644
@@ -42,6 +42,16 @@ config USB_XHCI_PCI
  	depends on USB_PCI
  	default y
 
+config USB_XHCI_PCI_AUXDEV
+	bool "xHCI PCI auxiliary device support"
+	depends on USB_XHCI_PCI
+	select AUXILIARY_BUS
+	help
+	  This enables xHCI PCI support for registering auxiliary devices
+	  for selected controllers. It is used by optional child drivers
+	  that bind to xHCI PCI controller-specific functionality through
+	  the auxiliary bus.
+
  config USB_XHCI_PCI_RENESAS
  	tristate "Support for additional Renesas xHCI controller with firmware"
  	depends on USB_XHCI_PCI
drivers/usb/host/xhci-pci.c +110 -0
index 585b2f3117b0..1ab27d2182eb 100644
@@ -8,6 +8,8 @@
   * Some code borrowed from the Linux EHCI driver.
   */
 
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
  #include <linux/pci.h>
  #include <linux/slab.h>
  #include <linux/module.h>
@@ -103,6 +105,114 @@ static int xhci_pci_run(struct usb_hcd *hcd);
  static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev,
  				      struct usb_tt *tt, gfp_t mem_flags);
 
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV)
+static const struct {
+	struct pci_device_id id;
+	const char *aux_dev_name;
+} pci_ids_have_aux[] = {
+	{
+		.id = { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x43fd) }, /* PROM21 xHCI */
+		.aux_dev_name = "hwmon",
+	},
+	{ /* end: all zeroes */ }
+};
+
+struct xhci_pci_aux_devres {
+	struct auxiliary_device *auxdev;
+};
+
+static bool xhci_pci_aux_match_id(const struct pci_device_id *id,
+				  const struct pci_dev *pdev)
+{
+	if (id->vendor != PCI_ANY_ID && id->vendor != pdev->vendor)
+		return false;
+	if (id->device != PCI_ANY_ID && id->device != pdev->device)
+		return false;
+	if (id->subvendor != PCI_ANY_ID &&
+	    id->subvendor != pdev->subsystem_vendor)
+		return false;
+	if (id->subdevice != PCI_ANY_ID &&
+	    id->subdevice != pdev->subsystem_device)
+		return false;
+
+	return !((id->class ^ pdev->class) & id->class_mask);
+}
+
+static const char *xhci_pci_aux_dev_name(struct pci_dev *pdev)
+{
+	int i;
+
+	for (i = 0; pci_ids_have_aux[i].aux_dev_name; i++) {
+		if (xhci_pci_aux_match_id(&pci_ids_have_aux[i].id, pdev))
+			return pci_ids_have_aux[i].aux_dev_name;
+	}
+
+	return NULL;
+}
+
+static void xhci_pci_aux_devres_release(struct device *dev, void *res)
+{
+	struct xhci_pci_aux_devres *devres = res;
+
+	if (devres->auxdev)
+		auxiliary_device_destroy(devres->auxdev);
+}
+
+static void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+	struct xhci_pci_aux_devres *devres;
+	struct auxiliary_device *auxdev;
+	const char *aux_dev_name;
+
+	aux_dev_name = xhci_pci_aux_dev_name(pdev);
+	if (!aux_dev_name)
+		return;
+
+	devres = devres_alloc(xhci_pci_aux_devres_release, sizeof(*devres),
+			      GFP_KERNEL);
+	if (!devres) {
+		dev_warn(&pdev->dev,
+			 "failed to allocate auxiliary device state\n");
+		return;
+	}
+
+	auxdev = auxiliary_device_create(&pdev->dev, KBUILD_MODNAME,
+					 aux_dev_name, NULL,
+					 (pci_domain_nr(pdev->bus) << 16) |
+						 pci_dev_id(pdev));
+	if (!auxdev) {
+		devres_free(devres);
+		dev_warn(&pdev->dev, "failed to add %s auxiliary device\n",
+			 aux_dev_name);
+		return;
+	}
+
+	devres->auxdev = auxdev;
+	devres_add(&pdev->dev, devres);
+}
+
+static void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+	struct xhci_pci_aux_devres *devres;
+
+	devres = devres_find(&pdev->dev, xhci_pci_aux_devres_release, NULL,
+			     NULL);
+	if (!devres || !devres->auxdev)
+		return;
+
+	auxiliary_device_destroy(devres->auxdev);
+	devres->auxdev = NULL;
+}
+#else
+static inline void xhci_pci_try_add_aux_device(struct pci_dev *pdev)
+{
+}
+
+static inline void xhci_pci_try_remove_aux_device(struct pci_dev *pdev)
+{
+}
+#endif
+
  static const struct xhci_driver_overrides xhci_pci_overrides __initconst = {
  	.reset = xhci_pci_setup,
  	.start = xhci_pci_run,

drivers/usb/host/xhci-pci.c int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) · line 790

Quoted 4 lines
drivers/usb/host/xhci-pci.c +1 -0
@@ -677,6 +787,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
  	if (device_property_read_bool(&dev->dev, "ti,pwron-active-high"))
  		pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22);
 
+	xhci_pci_try_add_aux_device(dev);

Is it considered acceptable to add

if (IS_ENABLED(CONFIG_USB_XHCI_PCI_AUXDEV))

before this call, remove #ifdef around the definitions of the auxdev functions and rely on dead code elimination to clean them up?

We already have a similar trick with CONFIG_USB_XHCI_PCI_RENESAS here and it seemed to be working, though the amount of conditional code is much lower and so is the potential risk.

The reason for doing it this way was because Greg doesn't like #ifdefs in .c files, and neither do static analyzers like them.

Quoted 16 lines
drivers/usb/host/xhci-pci.c +3 -0
+
  	return 0;
 
  put_usb3_hcd:
@@ -713,6 +825,8 @@ void xhci_pci_remove(struct pci_dev *dev)
  	xhci = hcd_to_xhci(pci_get_drvdata(dev));
  	set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
 
+	xhci_pci_try_remove_aux_device(dev);
+
  	xhci->xhc_state |= XHCI_STATE_REMOVING;
 
  	if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
Signature · 1 line
2.53.0
author
#11 lore raw

PATCH v2 2/2 hwmon: add initial support for AMD PROM21 xHCI temperature sensor

In reply to Jihong Min

AMD PROM21 xHCI controllers expose an 8-bit temperature value through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add an auxiliary hwmon driver for AMD 1022:43fd controllers and bind it to the xhci_pci.hwmon auxiliary device created by xhci-pci.

The read path verifies the parent PCI function and uses the initialized xHCI HCD MMIO mapping. The vendor index register is at byte offset 0x3000 from the xHCI MMIO BAR base and the vendor data register is at byte offset 0x3008. The driver writes register selector 0x0001e520 to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Expose temp1_input and an xHCI label through hwmon.

Register the hwmon device under the parent PCI function so userspace reports it as a PCI adapter, while the auxiliary driver still owns the hwmon lifetime and unregisters it from the auxiliary remove path.

No public AMD reference is available for this value. The conversion formula is derived from observed temperature readings:

temp[C] = raw * 0.9066 - 78.624

The temperature register did not return a valid value while the xHCI PCI function was suspended in testing. Keep the existing behavior by default and allow temperature reads to wake the xHCI PCI device. Add an allow_pm_switch module parameter so users can disable that behavior; when disabled, reads do not wake the device and return -EAGAIN if it is suspended.

Document the supported device, register access, conversion formula, module parameter, sysfs attributes, and sysfs lookup method.

Assisted-by Codex:gpt-5.5
---
 Documentation/hwmon/index.rst        |   1 +
 Documentation/hwmon/prom21-hwmon.rst |  84 ++++++++
 drivers/hwmon/Kconfig                |  11 +
 drivers/hwmon/Makefile               |   1 +
 drivers/hwmon/prom21-hwmon.c         | 300 +++++++++++++++++++++++++++
 5 files changed, 397 insertions(+)
 create mode 100644 Documentation/hwmon/prom21-hwmon.rst
 create mode 100644 drivers/hwmon/prom21-hwmon.c
Documentation/hwmon/index.rst +1 -0
index 8b655e5d6b68..0d85b78596cf 100644
@@ -215,6 +215,7 @@ Hardware Monitoring Kernel Drivers
    peci-dimmtemp
    pmbus
    powerz
+   prom21-hwmon
    powr1220
    pt5161l
    pxe1610
Documentation/hwmon/prom21-hwmon.rst new file +84 -0

84 lines not on this page · raw message

drivers/hwmon/Kconfig +11 -0
index 14e4cea48acc..da2266807f06 100644
@@ -940,6 +940,17 @@ config SENSORS_POWERZ
 	  This driver can also be built as a module. If so, the module
 	  will be called powerz.
 
+config SENSORS_PROM21
+	tristate "AMD PROM21 xHCI temperature sensor"
+	depends on USB_XHCI_PCI
+	select USB_XHCI_PCI_AUXDEV
+	help
+	  If you say yes here you get support for the AMD PROM21 xHCI
+	  temperature sensor.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called prom21-hwmon.
+
 config SENSORS_POWR1220
 	tristate "Lattice POWR1220 Power Monitoring"
 	depends on I2C
drivers/hwmon/Makefile +1 -0
index 4788996aa137..7693ed3b3f72 100644
@@ -196,6 +196,7 @@ obj-$(CONFIG_SENSORS_PC87427)	+= pc87427.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_SENSORS_POWERZ)	+= powerz.o
 obj-$(CONFIG_SENSORS_POWR1220)  += powr1220.o
+obj-$(CONFIG_SENSORS_PROM21)	+= prom21-hwmon.o
 obj-$(CONFIG_SENSORS_PT5161L)	+= pt5161l.o
 obj-$(CONFIG_SENSORS_PWM_FAN)	+= pwm-fan.o
 obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON)	+= qnap-mcu-hwmon.o
drivers/hwmon/prom21-hwmon.c new file +300 -0

300 lines not on this page · raw message

Signature · 1 line
2.53.0
#12 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 1 line · Jihong Min

AMD PROM21 xHCI controllers expose an 8-bit temperature value

I think this commit message and certainly the Kconfig help text should include full name of the chip and perhaps its official marketing names too, so that people better understand what hardware is supported.

So: "AMD Promontory 21 chipset" and "AM5 6xx/8xx series chipsets", or whatever they are called by AMD and motherboard vendors.

Quoted 13 lines

through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add an auxiliary hwmon driver for AMD 1022:43fd controllers and bind it to the xhci_pci.hwmon auxiliary device created by xhci-pci.

The read path verifies the parent PCI function and uses the initialized xHCI HCD MMIO mapping. The vendor index register is at byte offset 0x3000 from the xHCI MMIO BAR base and the vendor data register is at byte offset 0x3008. The driver writes register selector 0x0001e520 to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Expose temp1_input and an xHCI label through hwmon.

Quoted 5 lines

Register the hwmon device under the parent PCI function so userspace reports it as a PCI adapter, while the auxiliary driver still owns the hwmon lifetime and unregisters it from the auxiliary remove path.

No public AMD reference is available for this value.

Is there any documentation of the index/data registers themselves?

Any potential danger that something else (FW? SMM?) uses them too?

Quoted 3 lines

The conversion formula is derived from observed temperature readings:

temp[C] = raw * 0.9066 - 78.624

Could make sense to describe methodology, particularly in case some people would come and question the formula.

How was actual chip temperature measured?
Was the output compared with any other (Windows?) utilities?

People will be comparing these results and possibly trying to draw some conclusions, like OMG Linux runs this chip 8°C hotter, should I demand a full refund of my free Ubuntu download????!!!?

Quoted 6 lines

The temperature register did not return a valid value while the xHCI PCI function was suspended in testing. Keep the existing behavior by default and allow temperature reads to wake the xHCI PCI device. Add an allow_pm_switch module parameter so users can disable that behavior; when disabled, reads do not wake the device and return -EAGAIN if it is suspended.

Is such behavior useful?

Maybe the driver could just disable runtime PM while it's loaded.

Quoted 6 lines

Document the supported device, register access, conversion formula, module parameter, sysfs attributes, and sysfs lookup method.

Assisted-by Codex:gpt-5.5
#13 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 54 lines · Michal Pecio
Quoted 1 line · Jihong Min

AMD PROM21 xHCI controllers expose an 8-bit temperature value

I think this commit message and certainly the Kconfig help text should include full name of the chip and perhaps its official marketing names too, so that people better understand what hardware is supported.

So: "AMD Promontory 21 chipset" and "AM5 6xx/8xx series chipsets", or whatever they are called by AMD and motherboard vendors.

Quoted 13 lines

through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add an auxiliary hwmon driver for AMD 1022:43fd controllers and bind it to the xhci_pci.hwmon auxiliary device created by xhci-pci.

The read path verifies the parent PCI function and uses the initialized xHCI HCD MMIO mapping. The vendor index register is at byte offset 0x3000 from the xHCI MMIO BAR base and the vendor data register is at byte offset 0x3008. The driver writes register selector 0x0001e520 to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Expose temp1_input and an xHCI label through hwmon.

Quoted 5 lines

Register the hwmon device under the parent PCI function so userspace reports it as a PCI adapter, while the auxiliary driver still owns the hwmon lifetime and unregisters it from the auxiliary remove path.

No public AMD reference is available for this value.

Is there any documentation of the index/data registers themselves?

Any potential danger that something else (FW? SMM?) uses them too?

Quoted 3 lines

The conversion formula is derived from observed temperature readings:

   temp[C] = raw * 0.9066 - 78.624

Could make sense to describe methodology, particularly in case some people would come and question the formula.

How was actual chip temperature measured?
Was the output compared with any other (Windows?) utilities?

People will be comparing these results and possibly trying to draw some conclusions, like OMG Linux runs this chip 8°C hotter, should I demand a full refund of my free Ubuntu download????!!!?

Quoted 6 lines

The temperature register did not return a valid value while the xHCI PCI function was suspended in testing. Keep the existing behavior by default and allow temperature reads to wake the xHCI PCI device. Add an allow_pm_switch module parameter so users can disable that behavior; when disabled, reads do not wake the device and return -EAGAIN if it is suspended.

Quoted 3 lines

Is such behavior useful?

Maybe the driver could just disable runtime PM while it's loaded.

I'd encourage what we do in amdgpu for dGPUs. The hwmon files will return an error code (I forget which code) when the device is in runtime PM when called. Don't explicitly wake it otherwise.

This prevents someone installing a sensor monitoring application and that application "being the only thing" keeping the dGPU awake. If it's awake already for other reasons (like being used) then return valid data to the applications.

Quoted 7 lines
Quoted 7 lines

Document the supported device, register access, conversion formula, module parameter, sysfs attributes, and sysfs lookup method.

Assisted-by Codex:gpt-5.5
#14 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 11 lines · Mario Limonciello
Quoted 10 lines
Quoted 6 lines

The temperature register did not return a valid value while the xHCI PCI function was suspended in testing. Keep the existing behavior by default and allow temperature reads to wake the xHCI PCI device. Add an allow_pm_switch module parameter so users can disable that behavior; when disabled, reads do not wake the device and return -EAGAIN if it is suspended.

Is such behavior useful?

Maybe the driver could just disable runtime PM while it's loaded.

Quoted 8 lines

I'd encourage what we do in amdgpu for dGPUs. The hwmon files will return an error code (I forget which code) when the device is in runtime PM when called. Don't explicitly wake it otherwise.

This prevents someone installing a sensor monitoring application and that application "being the only thing" keeping the dGPU awake. If it's awake already for other reasons (like being used) then return valid data to the applications

Well, that's not a dGPU but an xHCI controller embedded in the chipset, which chipset is more or less active all the time (includes bridges to PCIe ports, some SATA controllers and mabe other things I forgot). Is the saving from disabling xHCI significant for a desktop system?

Users may be interested in monitoring chipset temperature even while not actively using USB.

I don't know what are the conditions to put GPUs into runtime suspend, but a USB HC will be going in and out quite randomly, depending on connected devices and their workload. You may end up needing to answer people why their sensor only works when they turn on a webcam :)

Alternatively, would it be possible to bring a suspendend HC into D0, read the temperature register and then put it back into D3hot without bothering the USB layer to fully resume and suspend xHCI logic?

Regards,
Michal

#15 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 26 lines · Michal Pecio
Quoted 11 lines · Mario Limonciello
Quoted 10 lines
Quoted 6 lines

The temperature register did not return a valid value while the xHCI PCI function was suspended in testing. Keep the existing behavior by default and allow temperature reads to wake the xHCI PCI device. Add an allow_pm_switch module parameter so users can disable that behavior; when disabled, reads do not wake the device and return -EAGAIN if it is suspended.

Is such behavior useful?

Maybe the driver could just disable runtime PM while it's loaded.

Quoted 8 lines

I'd encourage what we do in amdgpu for dGPUs. The hwmon files will return an error code (I forget which code) when the device is in runtime PM when called. Don't explicitly wake it otherwise.

This prevents someone installing a sensor monitoring application and that application "being the only thing" keeping the dGPU awake. If it's awake already for other reasons (like being used) then return valid data to the applications

Well, that's not a dGPU but an xHCI controller embedded in the chipset, which chipset is more or less active all the time (includes bridges to PCIe ports, some SATA controllers and mabe other things I forgot). Is the saving from disabling xHCI significant for a desktop system?

Quoted 7 lines

Users may be interested in monitoring chipset temperature even while not actively using USB.

I don't know what are the conditions to put GPUs into runtime suspend, but a USB HC will be going in and out quite randomly, depending on connected devices and their workload. You may end up needing to answer people why their sensor only works when they turn on a webcam :)

Heh. You have a good point here. The reason for this policy in dGPUs came from a lot of people using them "headless" (for example plugging in a display to the iGPU on the board) and wondering why randomly their power shot up and fans spun up on the GPU. It took a while to narrow down that it was specifically sensor software doing it.

For a sensor accessed through registers on XHCI this is a lot less of a big deal for all the reasons you point out.

Quoted 5 lines

Alternatively, would it be possible to bring a suspendend HC into D0, read the temperature register and then put it back into D3hot without bothering the USB layer to fully resume and suspend xHCI logic?

This sounds better to me if it's feasible.

#16 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

In reply to Michal Pecio
Quoted 6 lines

I think this commit message and certainly the Kconfig help text should include full name of the chip and perhaps its official marketing names too, so that people better understand what hardware is supported.

So: "AMD Promontory 21 chipset" and "AM5 6xx/8xx series chipsets", or whatever they are called by AMD and motherboard vendors.

Addressed locally for v3. I changed the commit message, Kconfig prompt/help text, and hwmon documentation to use the full name, "AMD Promontory 21 (PROM21)".

I avoided putting chipset marketing names directly into the Kconfig text and commit subject because some AMD 6xx/8xx series chipsets can be built from more than one PROM21 IP in a daisy-chained configuration, including more than one PROM21 xHCI controller. I described that relationship in the hwmon documentation instead.

Quoted 1 line

Is there any documentation of the index/data registers themselves?

I am not aware of public AMD documentation for the PROM21 vendor index/data registers or the temperature selector.

For my initial validation on an X870E system with two PROM21 xHCI controllers, I passed one PROM21 xHCI controller through to a Windows VM, left USB traffic idle, and let a Windows monitoring utility poll the controller temperature. From the Linux host I monitored that controller's PCI MMIO BAR with read-only accesses. The vendor index register repeatedly held the same selector value, and the adjacent data register exposed a stable low 8-bit value. That value matched the reported PROM21 xHCI temperature after the conversion formula below.

Quoted 1 line

Any potential danger that something else (FW? SMM?) uses them too?

I did not find any other in-kernel user of this PROM21 vendor index/data register pair or the temperature selector.

The driver only serializes its own accesses. It saves the previous index, selects the temperature register, reads the data register, restores the previous index, and keeps the sequence short. This avoids leaving the vendor index/data pair in a different state for other OS code.

This cannot synchronize against firmware or SMM. I cannot rule out such access without AMD documentation or confirmation, so I would appreciate AMD feedback on whether this PROM21 vendor index/data register pair is safe for OS polling.

I also tested concurrent temperature reads while USB traffic was active on the same controller, including fio against an external USB storage device and isochronous USB audio playback/capture. I did not observe USB transfer errors or audio glitches in those tests.

Quoted 9 lines

Could make sense to describe methodology, particularly in case some people would come and question the formula.

How was actual chip temperature measured?
Was the output compared with any other (Windows?) utilities?

People will be comparing these results and possibly trying to draw some conclusions, like OMG Linux runs this chip 8°C hotter, should I demand a full refund of my free Ubuntu download????!!!?

After identifying the register pair, I used the same validation setup to derive the conversion formula by comparing the observed raw MMIO register value against HWiNFO64's reported PROM21 xHCI temperature on Windows.

This worked because the PROM21 xHCI temperature is very stable at idle and the sensor appears to have low resolution, so the value usually stays unchanged long enough to compare both sides reliably.

Quoted 3 lines

Is such behavior useful?

Maybe the driver could just disable runtime PM while it's loaded.

and:

Quoted 8 lines

I'd encourage what we do in amdgpu for dGPUs. The hwmon files will return an error code (I forget which code) when the device is in runtime PM when called.  Don't explicitly wake it otherwise.

This prevents someone installing a sensor monitoring application and that application "being the only thing" keeping the dGPU awake.  If it's awake already for other reasons (like being used) then return valid data to the applications.

I changed the local v3 branch to follow the amdgpu-style behavior by default. The driver now does not wake the xHCI PCI device for a normal hwmon read. If the device is already active, the read returns valid data. If the device is suspended, the read returns -EPERM, matching the error code currently returned by amdgpu's pm_runtime_get_if_active() based path. I used -EPERM to match the current amdgpu precedent, but I can change the errno if maintainers prefer a different one for this driver.

I kept the allow_pm_switch module parameter for now, but changed its default to false. With the default setting, hwmon reads do not change the device runtime PM state. Setting allow_pm_switch=Y explicitly opts into waking the xHCI PCI device for temperature reads.

Thank you,
Jihong Min

Quoted 59 lines · Michal Pecio
Quoted 1 line · Jihong Min

AMD PROM21 xHCI controllers expose an 8-bit temperature value

I think this commit message and certainly the Kconfig help text should include full name of the chip and perhaps its official marketing names too, so that people better understand what hardware is supported.

So: "AMD Promontory 21 chipset" and "AM5 6xx/8xx series chipsets", or whatever they are called by AMD and motherboard vendors.

Quoted 13 lines

through a vendor-specific index/data register pair in the xHCI PCI MMIO BAR region. Add an auxiliary hwmon driver for AMD 1022:43fd controllers and bind it to the xhci_pci.hwmon auxiliary device created by xhci-pci.

The read path verifies the parent PCI function and uses the initialized xHCI HCD MMIO mapping. The vendor index register is at byte offset 0x3000 from the xHCI MMIO BAR base and the vendor data register is at byte offset 0x3008. The driver writes register selector 0x0001e520 to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Expose temp1_input and an xHCI label through hwmon.

Quoted 5 lines

Register the hwmon device under the parent PCI function so userspace reports it as a PCI adapter, while the auxiliary driver still owns the hwmon lifetime and unregisters it from the auxiliary remove path.

No public AMD reference is available for this value.

Is there any documentation of the index/data registers themselves?

Any potential danger that something else (FW? SMM?) uses them too?

Quoted 3 lines

The conversion formula is derived from observed temperature readings:

   temp[C] = raw * 0.9066 - 78.624

Could make sense to describe methodology, particularly in case some people would come and question the formula.

How was actual chip temperature measured?
Was the output compared with any other (Windows?) utilities?

People will be comparing these results and possibly trying to draw some conclusions, like OMG Linux runs this chip 8°C hotter, should I demand a full refund of my free Ubuntu download????!!!?

Quoted 6 lines

The temperature register did not return a valid value while the xHCI PCI function was suspended in testing. Keep the existing behavior by default and allow temperature reads to wake the xHCI PCI device. Add an allow_pm_switch module parameter so users can disable that behavior; when disabled, reads do not wake the device and return -EAGAIN if it is suspended.

Is such behavior useful?

Maybe the driver could just disable runtime PM while it's loaded.

Quoted 5 lines

Document the supported device, register access, conversion formula, module parameter, sysfs attributes, and sysfs lookup method.

Assisted-by Codex:gpt-5.5
#17 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 8 lines · Jihong Min
Quoted 7 lines

I think this commit message and certainly the Kconfig help text should include full name of the chip and perhaps its official marketing names too, so that people better understand what hardware is supported.

So: "AMD Promontory 21 chipset" and "AM5 6xx/8xx series chipsets", or whatever they are called by AMD and motherboard vendors.

Quoted 8 lines

Addressed locally for v3. I changed the commit message, Kconfig prompt/help text, and hwmon documentation to use the full name, "AMD Promontory 21 (PROM21)".

I avoided putting chipset marketing names directly into the Kconfig text and commit subject because some AMD 6xx/8xx series chipsets can be built from more than one PROM21 IP in a daisy-chained configuration, including more than one PROM21 xHCI controller.

Not sure how is this a problem?
The driver is still applicable to those daisy-chained chipsets.

Configuring the kernel is tedious enough already, the help text should ideally tell me whether I need this driver or not without searching for obscure code names.

I would even go as far as to mention that it's for AM5 chipsets, because AMD recycles chipset numbers every few generations.

Quoted 12 lines
Quoted 1 line

Is there any documentation of the index/data registers themselves?

I am not aware of public AMD documentation for the PROM21 vendor index/data registers or the temperature selector.

For my initial validation on an X870E system with two decade or so.PROM21 xHCI controllers, I passed one PROM21 xHCI controller through to a Windows VM, left USB traffic idle, and let a Windows monitoring utility poll the controller temperature. From the Linux host I monitored that controller's PCI MMIO BAR with read-only accesses.

Quoted 8 lines

The vendor index register repeatedly held the same selector value, and the adjacent data register exposed a stable low 8-bit value. [...]

After identifying the register pair, I used the same validation setup to derive the conversion formula by comparing the observed raw MMIO register value against HWiNFO64's reported PROM21 xHCI temperature on Windows.

Looks good, unless there are gotchas like the actual formula being dependent on other factors you haven't noticed HWiNFO reading.

It would be good idea to record this methodology in the commit message or docs so that others can repeat your experiment in case of issues.

Regards,
Michal

reviewer
#18 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

In reply to Jihong Min

Documentation/hwmon/index.rst Hardware Monitoring Kernel Drivers · line 221

Quoted 12 lines · Jihong Min
Documentation/hwmon/index.rst +1 -0
index 8b655e5d6b68..0d85b78596cf 100644
@@ -215,6 +215,7 @@ Hardware Monitoring Kernel Drivers
    peci-dimmtemp
    pmbus
    powerz
+   prom21-hwmon
    powr1220
    pt5161l
    pxe1610

It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.

Signature · 1 line
~Randy
#19 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

Quoted 2 lines

It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.

Yes, you are right. I also noticed that but it seems I pasted it in the wrong place. Fixed this locally for v3 by moving prom21-hwmon after powr1220 and next to pt5161l.

Thank you,
Jihong Min

Quoted 17 lines · Randy Dunlap

Documentation/hwmon/index.rst Hardware Monitoring Kernel Drivers · line 221

Quoted 12 lines · Jihong Min
Documentation/hwmon/index.rst +1 -0
index 8b655e5d6b68..0d85b78596cf 100644
@@ -215,6 +215,7 @@ Hardware Monitoring Kernel Drivers
     peci-dimmtemp
     pmbus
     powerz
+   prom21-hwmon
     powr1220
     pt5161l
     pxe1610

It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.

#20 lore raw

Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor

I noticed that my v2 series was accidentally sent as a reply to the v1 discussion, which made the patch series harder to track. It also appears that Sashiko did not pick up v2 as a separate patchset because of the threading.

I will send v3 as a clean new thread with the review feedback addressed so far.

Thank you,
Jihong Min

Quoted 27 lines · Jihong Min
Quoted 2 lines

It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.

Yes, you are right. I also noticed that but it seems I pasted it in the wrong place. Fixed this locally for v3 by moving prom21-hwmon after powr1220 and next to pt5161l.

Thank you,
Jihong Min

Quoted 17 lines · Randy Dunlap
Quoted 5 lines · Jihong Min
Documentation/hwmon/index.rst +0 -0
index 8b655e5d6b68..0d85b78596cf 100644
@@ -215,6 +215,7 @@ Hardware Monitoring Kernel Drivers
Quoted 7 lines

     peci-dimmtemp
     pmbus
     powerz
+   prom21-hwmon
     powr1220
     pt5161l
     pxe1610

It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.