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