xref: /linux/drivers/hwmon/prom21-xhci.c (revision 364f4a55c661641c02c86a849f0608d8fc3c0006)
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