From: Mario Limonciello <mario.limonciello@amd.com>
To: Jihong Min <hurryman2212@gmail.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Mathias Nyman <mathias.nyman@intel.com>
Cc: Guenter Roeck <linux@roeck-us.net>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Basavaraj Natikar <Basavaraj.Natikar@amd.com>,
linux-usb@vger.kernel.org, linux-hwmon@vger.kernel.org,
linux-doc@vger.kernel.org, linux-pci@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH v4 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support
Date: Fri, 8 May 2026 11:51:13 -0500 [thread overview]
Message-ID: <f682afbb-e816-44e6-9b18-fc7e2335706e@amd.com> (raw)
In-Reply-To: <20260508143910.14673-3-hurryman2212@gmail.com>
On 5/8/26 09:39, Jihong Min wrote:
> Add an auxiliary-bus hwmon driver for the temperature sensor exposed by
> AMD Promontory 21 (PROM21) xHCI PCI functions. The driver binds to the
> "hwmon" auxiliary device published by the PROM21 xHCI PCI glue and
> exposes the sensor as temp1_input under the prom21_xhci hwmon device.
>
> The sensor is accessed through a PROM21 vendor index/data register pair
> in the xHCI PCI MMIO BAR. The read path restores the previous vendor
> index value after sampling and does not runtime-resume the parent PCI
> device; reads from a suspended parent return -ENODATA.
>
> Document the supported device, register access, runtime PM behavior, and
> sysfs lookup method. The documentation also records the observation
> method used to identify the register pair and derive the conversion
> formula.
>
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
> ---
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/prom21-xhci.rst | 99 +++++++++++
> drivers/hwmon/Kconfig | 10 ++
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/prom21-xhci.c | 250 ++++++++++++++++++++++++++++
> 5 files changed, 361 insertions(+)
> create mode 100644 Documentation/hwmon/prom21-xhci.rst
> create mode 100644 drivers/hwmon/prom21-xhci.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8b655e5d6b68..324208f1faa2 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -216,6 +216,7 @@ Hardware Monitoring Kernel Drivers
> pmbus
> powerz
> powr1220
> + prom21-xhci
> pt5161l
> pxe1610
> pwm-fan
> diff --git a/Documentation/hwmon/prom21-xhci.rst b/Documentation/hwmon/prom21-xhci.rst
> new file mode 100644
> index 000000000000..10d03c4476c3
> --- /dev/null
> +++ b/Documentation/hwmon/prom21-xhci.rst
> @@ -0,0 +1,99 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver prom21-xhci
> +=========================
> +
> +Supported chips:
> +
> + * AMD Promontory 21 (PROM21) xHCI
> +
> + Prefix: 'prom21_xhci'
> +
> + PCI ID: 1022:43fd
> +
> +Author:
> +
> + - Jihong Min <hurryman2212@gmail.com>
> +
> +Description
> +-----------
> +
> +This driver exposes the temperature sensor in AMD PROM21 xHCI controllers.
> +
> +The driver binds to an auxiliary device created by the xHCI PCI driver for
> +supported controllers. The sensor value is accessed through a vendor-specific
> +index/data register pair in the controller's PCI MMIO BAR.
> +The auxiliary device is created by the ``xhci-pci-prom21`` PCI glue driver.
> +USB host operation is otherwise delegated to the common ``xhci-pci`` code.
> +
> +PROM21 is an AMD chipset IP used in single-chip or daisy-chained configurations
> +to build AMD 6xx/8xx series chipsets. Since the xHCI controllers are
> +integrated in PROM21, this temperature can also be used as a monitor for a
> +temperature close to the AMD chipset temperature.
> +
> +Register access
> +---------------
> +
> +The temperature value is read through a vendor-specific index/data register
> +pair in the xHCI PCI MMIO BAR. The driver uses the following byte offsets from
> +the MMIO BAR base:
> +
> +======================= =====================================================
> +0x3000 Vendor index register
> +0x3008 Vendor data register
> +======================= =====================================================
> +
> +The driver saves the current vendor index register value, writes the
> +temperature selector ``0x0001e520`` to the vendor index register, reads the
> +vendor data register, and restores the previous vendor index value before
> +returning. The raw temperature value is the low 8 bits of the vendor data
> +register value.
> +
> +No public AMD reference is available for the register pair or the raw value.
> +The register pair was identified on an X870E system with two PROM21 xHCI
> +controllers. One controller was passed through to a Windows VM, and the same
> +controller's PCI MMIO BAR was observed from the Linux host while HWiNFO64 was
> +reporting the PROM21 xHCI temperature. In the test environment, the reported
> +temperature was very stable at idle and the displayed sensor resolution was
> +low, which made it possible to look for a consistently repeating MMIO response
> +for the same reported temperature. During observation, offset 0x3000 repeatedly
> +contained selector ``0x0001e520``. Writing the same selector to offset 0x3000
> +from Linux and then reading offset 0x3008 reproduced the same raw value, so the
> +offsets are treated as a vendor index/data register pair.
> +
> +The conversion formula was empirically inferred by matching observed raw
> +8-bit values against HWiNFO64's reported PROM21 xHCI temperature for the same
> +controller. The observed mapping is:
> +
> + temp[C] = raw * 0.9066 - 78.624
> +
> +Runtime PM
> +----------
> +
> +The driver does not wake the xHCI PCI device for hwmon reads. It reads the
> +temperature only when the parent device is already active. A read from a
> +suspended device returns ``-ENODATA``. Sensor reads do not mark the xHCI PCI
> +device as busy or schedule autosuspend, so polling the sensor does not delay
> +runtime suspend.
> +
> +Sysfs entries
> +-------------
> +
> +======================= =====================================================
> +temp1_input Temperature in millidegrees Celsius
> +======================= =====================================================
> +
> +The hwmon device name is ``prom21_xhci``. The sysfs path depends on the hwmon
> +device number assigned by the kernel. Userspace can locate the device by
> +matching the ``name`` attribute:
> +
> +.. code-block:: sh
> +
> + for hwmon in /sys/class/hwmon/hwmon*; do
> + [ "$(cat "$hwmon/name")" = "prom21_xhci" ] || continue
> + cat "$hwmon/temp1_input"
> + done
> +
> +``temp1_input`` reports millidegrees Celsius, so a value of ``50113`` means
> +50.113 degrees Celsius. If the raw register value is invalid, ``temp1_input``
> +returns ``-ENODATA``.
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 14e4cea48acc..fe0f14e247b5 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -951,6 +951,16 @@ config SENSORS_POWR1220
> This driver can also be built as a module. If so, the module
> will be called powr1220.
>
> +config SENSORS_PROM21_XHCI
> + tristate "AMD Promontory 21 xHCI temperature sensor"
> + depends on USB_XHCI_PCI_PROM21
> + help
> + If you say yes here you get support for the AMD Promontory 21
> + (PROM21) xHCI temperature sensor.
> +
> + This driver can also be built as a module. If so, the module
> + will be called prom21-xhci.
> +
> config SENSORS_LAN966X
> tristate "Microchip LAN966x Hardware Monitoring"
> depends on SOC_LAN966 || COMPILE_TEST
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 4788996aa137..0bda542e8e2b 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -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_XHCI) += prom21-xhci.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
> diff --git a/drivers/hwmon/prom21-xhci.c b/drivers/hwmon/prom21-xhci.c
> new file mode 100644
> index 000000000000..f91303ce3428
> --- /dev/null
> +++ b/drivers/hwmon/prom21-xhci.c
> @@ -0,0 +1,250 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * AMD Promontory 21 xHCI Hwmon Implementation
> + * (only temperature monitoring is supported)
> + *
> + * This can be effectively used as the alternative chipset temperature monitor.
> + *
> + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/hwmon.h>
> +#include <linux/io.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +#include <linux/usb/hcd.h>
> +
> +#define PROM21_INDEX 0x3000
> +#define PROM21_DATA 0x3008
> +#define PROM21_TEMP_REG 0x0001e520
> +
> +struct prom21_xhci {
> + struct pci_dev *pdev;
> + struct device *hwmon_dev;
> + void __iomem *regs;
> + struct mutex lock; /* serializes index/data register access */
> +};
> +
> +static int prom21_xhci_pm_get(struct prom21_xhci *hwmon, bool *pm_ref)
> +{
> + struct device *dev = &hwmon->pdev->dev;
> + int ret;
> +
> + *pm_ref = false;
> +
> + /*
> + * PROM21 temperature register access does not return a valid value while
> + * the parent xHCI PCI function is suspended. Do not wake the device from
> + * a hwmon read; only read when runtime PM reports the device as active,
> + * or when runtime PM is disabled and the device is not marked as
> + * suspended.
> + */
> + ret = pm_runtime_get_if_active(dev);
> + if (ret > 0) {
> + *pm_ref = true;
> + return 0;
> + }
> +
> + if (ret == -EINVAL && !pm_runtime_status_suspended(dev))
> + return 0;
> +
> + if (!ret || pm_runtime_status_suspended(dev))
> + return -ENODATA;
> +
> + return ret;
> +}
> +
> +/*
> + * This is not a pure MMIO read. The PROM21 vendor data register is selected
> + * by temporarily writing PROM21_TEMP_REG to the vendor index register.
> + * Serialize the sequence, keep it short, and restore the previous index before
> + * returning so this driver does not leave the vendor index/data register pair
> + * in a different state for other possible users.
> + */
> +static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon,
> + u8 *raw)
> +{
> + struct device *dev = &hwmon->pdev->dev;
> + bool pm_ref;
> + u32 index;
> + u32 data;
> + int ret;
> +
> + ret = prom21_xhci_pm_get(hwmon, &pm_ref);
> + if (ret)
> + return ret;
Rather than using pm_ref as an output variable to indicate whether you
took a ref - how about you instead always take a ref on success and
return an error on fail? This would feel more logical to me.
> +
> + mutex_lock(&hwmon->lock);
guard(mutex) perhaps?
> + index = readl(hwmon->regs + PROM21_INDEX);
> + /* Select the PROM21 temperature register through the vendor index. */
> + writel(PROM21_TEMP_REG, hwmon->regs + PROM21_INDEX);
> + data = readl(hwmon->regs + PROM21_DATA);
You only care about the first byte, right? Just use readb() and make
data a u8.
> + /* Restore the previous vendor index register value. */
> + writel(index, hwmon->regs + PROM21_INDEX);
> + readl(hwmon->regs + PROM21_INDEX);
> + mutex_unlock(&hwmon->lock);
> +
> + if (pm_ref) {
> + /*
> + * Drop only the reference taken by pm_runtime_get_if_active().
> + * Do not mark the device busy or schedule autosuspend from the
> + * hwmon path; sensor polling must not keep the xHCI PCI device
> + * active.
> + */
> + pm_runtime_put_noidle(dev);
> + }
> +
> + *raw = data & 0xff;
I personally don't really like changing the pointer when there is
potentially an error case with it for -ENODATA.
> + if (!*raw || *raw == 0xff)
Does 0xff actually happen with your runtime PM handling? Between my
suggestion above to use readb() this can turn into:
if (!data)
return -ENODDATA;
*raw = data;
return 0;
> + return -ENODATA;
> +
> + return 0;
> +}
> +
> +static long prom21_xhci_raw_to_millicelsius(u8 raw)
> +{
> + /*
> + * No public AMD 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 prom21_xhci_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 prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct prom21_xhci *hwmon = dev_get_drvdata(dev);
> + u8 raw;
> + int ret;
> +
> + if (type != hwmon_temp || attr != hwmon_temp_input || channel)
> + return -EOPNOTSUPP;
> +
> + ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw);
> + if (ret)
> + return ret;
> +
> + *val = prom21_xhci_raw_to_millicelsius(raw);
> + return 0;
> +}
> +
> +static const struct hwmon_ops prom21_xhci_ops = {
> + .is_visible = prom21_xhci_is_visible,
> + .read = prom21_xhci_read,
> +};
> +
> +static const struct hwmon_channel_info *const prom21_xhci_info[] = {
> + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
> + NULL,
> +};
> +
> +static const struct hwmon_chip_info prom21_xhci_chip_info = {
> + .ops = &prom21_xhci_ops,
> + .info = prom21_xhci_info,
> +};
> +
> +static int prom21_xhci_probe(struct auxiliary_device *auxdev,
> + const struct auxiliary_device_id *id)
> +{
> + struct device *dev = &auxdev->dev;
> + struct device *parent = dev->parent;
> + struct prom21_xhci *hwmon;
> + struct pci_dev *pdev;
> + struct usb_hcd *hcd;
> + int ret;
> +
> + if (!parent || !dev_is_pci(parent))
> + return -ENODEV;
> +
> + pdev = to_pci_dev(parent);
> + hcd = pci_get_drvdata(pdev);
> + if (!hcd)
> + return dev_err_probe(dev, -ENODEV,
> + "xHCI HCD data unavailable\n");
> +
> + if (!hcd->regs || hcd->rsrc_len < PROM21_DATA + sizeof(u32))
> + return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n");
> +
> + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
> + if (!hwmon)
> + return -ENOMEM;
> +
> + ret = devm_mutex_init(dev, &hwmon->lock);
> + if (ret)
> + return ret;
> +
> + hwmon->pdev = pdev;
> + hwmon->regs = hcd->regs;
> + auxiliary_set_drvdata(auxdev, hwmon);
> +
> + /*
> + * Use the PCI function as the hwmon parent so user space reports it as
> + * a PCI adapter. Lifetime is still owned by this auxiliary driver;
> + * remove() unregisters the hwmon device before xhci-pci tears down the
> + * HCD.
> + */
> + hwmon->hwmon_dev =
> + hwmon_device_register_with_info(&pdev->dev, "prom21_xhci",
> + hwmon, &prom21_xhci_chip_info,
> + NULL);
> + if (IS_ERR(hwmon->hwmon_dev))
> + return PTR_ERR(hwmon->hwmon_dev);
> +
> + return 0;
> +}
> +
> +static void prom21_xhci_remove(struct auxiliary_device *auxdev)
> +{
> + struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev);
> +
> + /*
> + * The PROM21 PCI glue destroys the auxiliary device before HCD teardown.
> + * Unregister the hwmon device here so sysfs removes the attributes,
> + * stops new reads, and drains active hwmon callbacks before the xHCI
> + * MMIO mapping is released.
> + */
> + hwmon_device_unregister(hwmon->hwmon_dev);
> +}
> +
> +static const struct auxiliary_device_id prom21_xhci_id_table[] = {
> + { .name = "xhci_pci_prom21.hwmon" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table);
> +
> +static struct auxiliary_driver prom21_xhci_driver = {
> + .name = "prom21-xhci",
> + .probe = prom21_xhci_probe,
> + .remove = prom21_xhci_remove,
> + .id_table = prom21_xhci_id_table,
> +};
> +module_auxiliary_driver(prom21_xhci_driver);
> +
> +MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
> +MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver");
> +MODULE_LICENSE("GPL");
next prev parent reply other threads:[~2026-05-08 16:51 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-08 14:39 [PATCH v4 0/2] AMD Promontory 21 xHCI temperature sensor support Jihong Min
2026-05-08 14:39 ` [PATCH v4 1/2] usb: xhci-pci: add AMD Promontory 21 PCI glue Jihong Min
2026-05-08 16:40 ` Mario Limonciello
2026-05-08 17:39 ` Jihong Min
2026-05-08 17:42 ` Mario Limonciello
2026-05-08 17:48 ` Jihong Min
2026-05-08 18:11 ` Jihong Min
2026-05-08 18:15 ` Guenter Roeck
2026-05-08 18:39 ` Jihong Min
2026-05-09 5:34 ` Jihong Min
2026-05-09 5:52 ` Mario Limonciello
2026-05-09 6:54 ` Jihong Min
2026-05-08 14:39 ` [PATCH v4 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support Jihong Min
2026-05-08 15:45 ` Guenter Roeck
2026-05-08 17:37 ` Jihong Min
2026-05-08 16:51 ` Mario Limonciello [this message]
2026-05-08 17:40 ` Jihong Min
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=f682afbb-e816-44e6-9b18-fc7e2335706e@amd.com \
--to=mario.limonciello@amd.com \
--cc=Basavaraj.Natikar@amd.com \
--cc=corbet@lwn.net \
--cc=gregkh@linuxfoundation.org \
--cc=hurryman2212@gmail.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=mathias.nyman@intel.com \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox