1*7e916914SJihong Min // SPDX-License-Identifier: GPL-2.0 2*7e916914SJihong Min /* 3*7e916914SJihong Min * AMD Promontory 21 xHCI Hwmon Implementation 4*7e916914SJihong Min * (only temperature monitoring is supported) 5*7e916914SJihong Min * 6*7e916914SJihong Min * This can be effectively used as the alternative chipset temperature monitor. 7*7e916914SJihong Min * 8*7e916914SJihong Min * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com> 9*7e916914SJihong Min */ 10*7e916914SJihong Min 11*7e916914SJihong Min #include <linux/auxiliary_bus.h> 12*7e916914SJihong Min #include <linux/device.h> 13*7e916914SJihong Min #include <linux/err.h> 14*7e916914SJihong Min #include <linux/errno.h> 15*7e916914SJihong Min #include <linux/hwmon.h> 16*7e916914SJihong Min #include <linux/io.h> 17*7e916914SJihong Min #include <linux/math.h> 18*7e916914SJihong Min #include <linux/module.h> 19*7e916914SJihong Min #include <linux/pci.h> 20*7e916914SJihong Min #include <linux/platform_data/usb-xhci-prom21.h> 21*7e916914SJihong Min #include <linux/pm_runtime.h> 22*7e916914SJihong Min 23*7e916914SJihong Min #define PROM21_XHCI_INDEX_OFFSET 0x3000 24*7e916914SJihong Min #define PROM21_XHCI_DATA_OFFSET 0x3008 25*7e916914SJihong Min #define PROM21_XHCI_TEMP_SELECTOR 0x0001e520 26*7e916914SJihong Min 27*7e916914SJihong Min struct prom21_xhci { 28*7e916914SJihong Min struct pci_dev *pdev; 29*7e916914SJihong Min struct device *hwmon_dev; 30*7e916914SJihong Min void __iomem *regs; 31*7e916914SJihong Min }; 32*7e916914SJihong Min 33*7e916914SJihong Min static int prom21_xhci_pm_get(struct prom21_xhci *hwmon) 34*7e916914SJihong Min { 35*7e916914SJihong Min struct device *dev = &hwmon->pdev->dev; 36*7e916914SJihong Min int ret; 37*7e916914SJihong Min 38*7e916914SJihong Min /* 39*7e916914SJihong Min * PROM21 temperature register access does not return a valid value while 40*7e916914SJihong Min * the parent xHCI PCI function is suspended. Do not wake the device from 41*7e916914SJihong Min * a hwmon read. On success, hold a usage reference without changing the 42*7e916914SJihong Min * runtime PM state; if runtime PM is disabled, allow the read unless the 43*7e916914SJihong Min * device is still marked suspended. 44*7e916914SJihong Min */ 45*7e916914SJihong Min ret = pm_runtime_get_if_active(dev); 46*7e916914SJihong Min if (ret > 0) 47*7e916914SJihong Min return 0; 48*7e916914SJihong Min 49*7e916914SJihong Min if (ret == -EINVAL) { 50*7e916914SJihong Min if (pm_runtime_status_suspended(dev)) 51*7e916914SJihong Min return -ENODATA; 52*7e916914SJihong Min 53*7e916914SJihong Min pm_runtime_get_noresume(dev); 54*7e916914SJihong Min return 0; 55*7e916914SJihong Min } 56*7e916914SJihong Min 57*7e916914SJihong Min if (!ret) 58*7e916914SJihong Min return -ENODATA; 59*7e916914SJihong Min 60*7e916914SJihong Min return ret; 61*7e916914SJihong Min } 62*7e916914SJihong Min 63*7e916914SJihong Min /* 64*7e916914SJihong Min * This is not a pure MMIO read. The PROM21 vendor data register is selected 65*7e916914SJihong Min * by temporarily writing PROM21_XHCI_TEMP_SELECTOR to the vendor index 66*7e916914SJihong Min * register. 67*7e916914SJihong Min * The hwmon core already serializes this driver's callbacks, so this driver 68*7e916914SJihong Min * does not need an additional private lock. That does not synchronize with 69*7e916914SJihong Min * firmware, SMM, ACPI, or other possible users. Keep the sequence short and 70*7e916914SJihong Min * restore the previous index before returning. 71*7e916914SJihong Min */ 72*7e916914SJihong Min static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon, 73*7e916914SJihong Min u8 *raw) 74*7e916914SJihong Min { 75*7e916914SJihong Min struct device *dev = &hwmon->pdev->dev; 76*7e916914SJihong Min u32 index; 77*7e916914SJihong Min u8 data; 78*7e916914SJihong Min int ret; 79*7e916914SJihong Min 80*7e916914SJihong Min ret = prom21_xhci_pm_get(hwmon); 81*7e916914SJihong Min if (ret) 82*7e916914SJihong Min return ret; 83*7e916914SJihong Min 84*7e916914SJihong Min index = readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET); 85*7e916914SJihong Min /* Select the PROM21 temperature register through the vendor index. */ 86*7e916914SJihong Min writel(PROM21_XHCI_TEMP_SELECTOR, 87*7e916914SJihong Min hwmon->regs + PROM21_XHCI_INDEX_OFFSET); 88*7e916914SJihong Min /* Use a 32-bit read for PCI MMIO register access. */ 89*7e916914SJihong Min data = readl(hwmon->regs + PROM21_XHCI_DATA_OFFSET) & 0xff; 90*7e916914SJihong Min /* Restore the previous vendor index register value. */ 91*7e916914SJihong Min writel(index, hwmon->regs + PROM21_XHCI_INDEX_OFFSET); 92*7e916914SJihong Min readl(hwmon->regs + PROM21_XHCI_INDEX_OFFSET); 93*7e916914SJihong Min 94*7e916914SJihong Min /* 95*7e916914SJihong Min * Drop the usage reference taken by prom21_xhci_pm_get(). This is 96*7e916914SJihong Min * enough because the read path never resumes the device; use the normal 97*7e916914SJihong Min * put path so the PM core can re-evaluate idle state after the read. 98*7e916914SJihong Min * Otherwise, a racing xHCI autosuspend attempt can see a nonzero 99*7e916914SJihong Min * runtime PM usage count and skip autosuspend, and a later 100*7e916914SJihong Min * pm_runtime_put_noidle(), which does not check for an idle device, 101*7e916914SJihong Min * would leave the device active. 102*7e916914SJihong Min */ 103*7e916914SJihong Min pm_runtime_put(dev); 104*7e916914SJihong Min 105*7e916914SJihong Min if (!data) 106*7e916914SJihong Min return -ENODATA; 107*7e916914SJihong Min 108*7e916914SJihong Min *raw = data; 109*7e916914SJihong Min return 0; 110*7e916914SJihong Min } 111*7e916914SJihong Min 112*7e916914SJihong Min static long prom21_xhci_raw_to_millicelsius(u8 raw) 113*7e916914SJihong Min { 114*7e916914SJihong Min /* 115*7e916914SJihong Min * No public AMD reference is available for this value. 116*7e916914SJihong Min * The scale was derived from observed PROM21 xHCI temperature readings: 117*7e916914SJihong Min * temp[C] = raw * 0.9066 - 78.624 118*7e916914SJihong Min */ 119*7e916914SJihong Min return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624; 120*7e916914SJihong Min } 121*7e916914SJihong Min 122*7e916914SJihong Min static umode_t prom21_xhci_is_visible(const void *drvdata, 123*7e916914SJihong Min enum hwmon_sensor_types type, u32 attr, 124*7e916914SJihong Min int channel) 125*7e916914SJihong Min { 126*7e916914SJihong Min if (type != hwmon_temp) 127*7e916914SJihong Min return 0; 128*7e916914SJihong Min 129*7e916914SJihong Min switch (attr) { 130*7e916914SJihong Min case hwmon_temp_input: 131*7e916914SJihong Min return 0444; 132*7e916914SJihong Min default: 133*7e916914SJihong Min return 0; 134*7e916914SJihong Min } 135*7e916914SJihong Min } 136*7e916914SJihong Min 137*7e916914SJihong Min static int prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type, 138*7e916914SJihong Min u32 attr, int channel, long *val) 139*7e916914SJihong Min { 140*7e916914SJihong Min struct prom21_xhci *hwmon = dev_get_drvdata(dev); 141*7e916914SJihong Min u8 raw; 142*7e916914SJihong Min int ret; 143*7e916914SJihong Min 144*7e916914SJihong Min if (type != hwmon_temp || attr != hwmon_temp_input) 145*7e916914SJihong Min return -EOPNOTSUPP; 146*7e916914SJihong Min 147*7e916914SJihong Min ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw); 148*7e916914SJihong Min if (ret) 149*7e916914SJihong Min return ret; 150*7e916914SJihong Min 151*7e916914SJihong Min *val = prom21_xhci_raw_to_millicelsius(raw); 152*7e916914SJihong Min return 0; 153*7e916914SJihong Min } 154*7e916914SJihong Min 155*7e916914SJihong Min static const struct hwmon_ops prom21_xhci_ops = { 156*7e916914SJihong Min .is_visible = prom21_xhci_is_visible, 157*7e916914SJihong Min .read = prom21_xhci_read, 158*7e916914SJihong Min }; 159*7e916914SJihong Min 160*7e916914SJihong Min static const struct hwmon_channel_info *const prom21_xhci_info[] = { 161*7e916914SJihong Min HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 162*7e916914SJihong Min NULL, 163*7e916914SJihong Min }; 164*7e916914SJihong Min 165*7e916914SJihong Min static const struct hwmon_chip_info prom21_xhci_chip_info = { 166*7e916914SJihong Min .ops = &prom21_xhci_ops, 167*7e916914SJihong Min .info = prom21_xhci_info, 168*7e916914SJihong Min }; 169*7e916914SJihong Min 170*7e916914SJihong Min static int prom21_xhci_probe(struct auxiliary_device *auxdev, 171*7e916914SJihong Min const struct auxiliary_device_id *id) 172*7e916914SJihong Min { 173*7e916914SJihong Min struct device *dev = &auxdev->dev; 174*7e916914SJihong Min const struct prom21_xhci_pdata *pdata = dev_get_platdata(dev); 175*7e916914SJihong Min struct prom21_xhci *hwmon; 176*7e916914SJihong Min 177*7e916914SJihong Min if (!pdata) 178*7e916914SJihong Min return dev_err_probe(dev, -ENODEV, 179*7e916914SJihong Min "platform data unavailable\n"); 180*7e916914SJihong Min 181*7e916914SJihong Min if (!pdata->regs || 182*7e916914SJihong Min pdata->rsrc_len < PROM21_XHCI_DATA_OFFSET + sizeof(u32)) 183*7e916914SJihong Min return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n"); 184*7e916914SJihong Min 185*7e916914SJihong Min hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); 186*7e916914SJihong Min if (!hwmon) 187*7e916914SJihong Min return -ENOMEM; 188*7e916914SJihong Min 189*7e916914SJihong Min hwmon->pdev = pdata->pdev; 190*7e916914SJihong Min hwmon->regs = pdata->regs; 191*7e916914SJihong Min auxiliary_set_drvdata(auxdev, hwmon); 192*7e916914SJihong Min 193*7e916914SJihong Min /* 194*7e916914SJihong Min * Parent the hwmon device to the PCI function because the temperature 195*7e916914SJihong Min * value is read from that function's MMIO BAR, and systems may contain 196*7e916914SJihong Min * multiple PROM21 xHCI functions. This lets userspace identify the PCI 197*7e916914SJihong Min * endpoint for each reading. The auxiliary driver still owns the hwmon 198*7e916914SJihong Min * lifetime and unregisters it before HCD teardown. 199*7e916914SJihong Min */ 200*7e916914SJihong Min hwmon->hwmon_dev = 201*7e916914SJihong Min hwmon_device_register_with_info(&pdata->pdev->dev, "prom21_xhci", 202*7e916914SJihong Min hwmon, &prom21_xhci_chip_info, 203*7e916914SJihong Min NULL); 204*7e916914SJihong Min if (IS_ERR(hwmon->hwmon_dev)) 205*7e916914SJihong Min return PTR_ERR(hwmon->hwmon_dev); 206*7e916914SJihong Min 207*7e916914SJihong Min return 0; 208*7e916914SJihong Min } 209*7e916914SJihong Min 210*7e916914SJihong Min static void prom21_xhci_remove(struct auxiliary_device *auxdev) 211*7e916914SJihong Min { 212*7e916914SJihong Min struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev); 213*7e916914SJihong Min 214*7e916914SJihong Min /* 215*7e916914SJihong Min * The PROM21 PCI glue destroys the auxiliary device before HCD teardown. 216*7e916914SJihong Min * Unregister the hwmon device here so sysfs removes the attributes, 217*7e916914SJihong Min * stops new reads, and drains active hwmon callbacks before the xHCI 218*7e916914SJihong Min * MMIO mapping is released. 219*7e916914SJihong Min */ 220*7e916914SJihong Min hwmon_device_unregister(hwmon->hwmon_dev); 221*7e916914SJihong Min } 222*7e916914SJihong Min 223*7e916914SJihong Min static const struct auxiliary_device_id prom21_xhci_id_table[] = { 224*7e916914SJihong Min { .name = "xhci_pci_prom21.hwmon" }, 225*7e916914SJihong Min {} 226*7e916914SJihong Min }; 227*7e916914SJihong Min MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table); 228*7e916914SJihong Min 229*7e916914SJihong Min static struct auxiliary_driver prom21_xhci_driver = { 230*7e916914SJihong Min .name = "prom21-xhci", 231*7e916914SJihong Min .probe = prom21_xhci_probe, 232*7e916914SJihong Min .remove = prom21_xhci_remove, 233*7e916914SJihong Min .id_table = prom21_xhci_id_table, 234*7e916914SJihong Min }; 235*7e916914SJihong Min module_auxiliary_driver(prom21_xhci_driver); 236*7e916914SJihong Min 237*7e916914SJihong Min MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>"); 238*7e916914SJihong Min MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver"); 239*7e916914SJihong Min MODULE_LICENSE("GPL"); 240