usb: xhci: add AMD PROM21 xHCI hwmon support for temperature monitoring
Subsystems (5)
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.
--- 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
@@ -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
@@ -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
@@ -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
@@ -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
[email protected]
AI reviewThank 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@@ -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
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.5Signed-off-by Jihong Min <[email protected]>--- 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.ccreate mode 100644drivers/usb/host/xhci-prom21-hwmon.hdrivers/usb/host/Kconfig +10
-0@@ -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_PCIdrivers/usb/host/Makefile +4
-0@@ -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.odrivers/usb/host/xhci-pci.c +9
-0@@ -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@@ -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@@ -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
Guenter Roeck
maintainerQuoted 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/hwmonbind 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
0x3000and0x3008from 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.5Signed-off-by Jihong Min <[email protected]>---
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.ccreate mode 100644drivers/usb/host/xhci-prom21-hwmon.hdrivers/usb/host/Kconfig +0
-0@@ -42,6 +42,16 @@ config USB_XHCI_PCIdepends on
USB_PCI
default ydrivers/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 onUSB_XHCI_PCIdrivers/usb/host/Makefile +0
-0@@ -36,6 +36,10 @@ ifneq ($(CONFIG_USB_XHCI_SIDEBAND),)xhci-hcd-y += xhci-sideband.o
endifdrivers/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.odrivers/usb/host/xhci-pci.c +5
-0@@ -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@@ -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@@ -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
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/hwmonbind 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_PCIis built-in (y) andCONFIG_HWMONis 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/hwmonbind 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
0x3000and0x3008from 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.5Signed-off-by Jihong Min <[email protected]>---
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.ccreate mode 100644drivers/usb/host/xhci-prom21-hwmon.hdrivers/usb/host/Kconfig +0
-0@@ -42,6 +42,16 @@ config USB_XHCI_PCIdepends on
USB_PCI
default ydrivers/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 onUSB_XHCI_PCIdrivers/usb/host/Makefile +0
-0@@ -36,6 +36,10 @@ ifneq ($(CONFIG_USB_XHCI_SIDEBAND),)xhci-hcd-y += xhci-sideband.o
endifdrivers/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.odrivers/usb/host/xhci-pci.c +5
-0@@ -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@@ -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/dataregisters */
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 writingXHCI_PROM21_TEMP_REGto 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 vendorindex. */
+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
+ */
+ returnDIV_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 *constxhci_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@@ -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
v2 cover AMD PROM21 xHCI temperature hwmon support
In reply to Jihong MinHi,
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
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.
--- drivers/usb/host/Kconfig | 10 ++++ drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+)
drivers/usb/host/Kconfig +10 -0
@@ -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
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.5Signed-off-by Jihong Min <[email protected]>--- drivers/usb/host/Kconfig | 10 ++++ drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+)drivers/usb/host/Kconfig +10
-0@@ -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_PCIdrivers/usb/host/xhci-pci.c +23
-0@@ -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
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.5Signed-off-by Jihong Min <[email protected]>---
drivers/usb/host/Kconfig | 10 ++++drivers/usb/host/xhci-pci.c| 114 ++++++++++++++++++++++++++++++++++++2 files changed, 124 insertions(+)drivers/usb/host/Kconfig +0
-0@@ -42,6 +42,16 @@ config USB_XHCI_PCIdepends on
USB_PCI
default y+config
USB_XHCI_PCI_AUXDEV
+ bool "xHCI PCI auxiliary device support"
+ depends onUSB_XHCI_PCI
+ selectAUXILIARY_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.
+
configUSB_XHCI_PCI_RENESAS
tristate "Support for additional Renesas xHCI controller with firmware" depends onUSB_XHCI_PCIdrivers/usb/host/xhci-pci.c +9
-0@@ -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_overridesxhci_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
Re: [PATCH v2 1/2] usb: xhci-pci: add generic auxiliary device interface
In reply to Jihong MinQuoted 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.5Signed-off-by Jihong Min <[email protected]>--- drivers/usb/host/Kconfig | 10 ++++ drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+)drivers/usb/host/Kconfig +10
-0@@ -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_PCIdrivers/usb/host/xhci-pci.c +110
-0@@ -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
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.5Signed-off-by Jihong Min <[email protected]>--- drivers/usb/host/Kconfig | 10 ++++ drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+)drivers/usb/host/Kconfig +10
-0@@ -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_PCIdrivers/usb/host/xhci-pci.c +110
-0@@ -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_RENESAShere 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
PATCH v2 2/2 hwmon: add initial support for AMD PROM21 xHCI temperature sensor
In reply to Jihong MinAMD 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.
--- 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
@@ -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
@@ -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
@@ -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
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
0x3000from the xHCI MMIO BAR base and the vendor data register is at byte offset0x3008. The driver writes register selector0x0001e520to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Exposetemp1_inputand 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_switchmodule parameter so users can disable that behavior; when disabled, reads do not wake the device and return-EAGAINif 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.5Signed-off-by Jihong Min <[email protected]>
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
Kconfighelp 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
0x3000from the xHCI MMIO BAR base and the vendor data register is at byte offset0x3008. The driver writes register selector0x0001e520to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Exposetemp1_inputand 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.624Could 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_switchmodule parameter so users can disable that behavior; when disabled, reads do not wake the device and return-EAGAINif 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.5Signed-off-by Jihong Min <[email protected]>
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_switchmodule parameter so users can disable that behavior; when disabled, reads do not wake the device and return-EAGAINif 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
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_switchmodule parameter so users can disable that behavior; when disabled, reads do not wake the device and return-EAGAINif 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.
Re: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor
In reply to Michal PecioQuoted 6 lines
I think this commit message and certainly the
Kconfighelp 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
Kconfighelp 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
0x3000from the xHCI MMIO BAR base and the vendor data register is at byte offset0x3008. The driver writes register selector0x0001e520to the index register, reads the raw temperature value from the low 8 bits of the data register, and restores the previous index before returning. Exposetemp1_inputand 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.624Could 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_switchmodule parameter so users can disable that behavior; when disabled, reads do not wake the device and return-EAGAINif 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.5Signed-off-by Jihong Min <[email protected]>
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
Kconfighelp 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,
Kconfigprompt/help text, and hwmon documentation to use the full name, "AMD Promontory 21 (PROM21)".I avoided putting chipset marketing names directly into the
Kconfigtext 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
Randy Dunlap
reviewerRe: [PATCH v2 2/2] hwmon: add initial support for AMD PROM21 xHCI temperature sensor
In reply to Jihong MinDocumentation/hwmon/index.rst Hardware Monitoring Kernel Drivers · line 221
Quoted 12 lines · Jihong Min
Documentation/hwmon/index.rst +1
-0@@ -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
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@@ -215,6 +215,7 @@ Hardware Monitoring Kernel Drivers peci-dimmtemp pmbus powerz + prom21-hwmon powr1220 pt5161l pxe1610It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.
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 MinQuoted 17 lines · Randy Dunlap
Quoted 5 lines · Jihong Min
Documentation/hwmon/index.rst +0
-0@@ -215,6 +215,7 @@ Hardware Monitoring Kernel DriversQuoted 7 lines
peci-dimmtemp
pmbus
powerz
+ prom21-hwmon
powr1220
pt5161l
pxe1610It sorta looks like these entries are supposed to be maintained in alphabetical order, but that new entry is not.