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