summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-pci-prom21.c
blob: 6486f4a0934555ca14c3d3efca5516b4c695aa51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// SPDX-License-Identifier: GPL-2.0
/*
 * AMD Promontory 21 xHCI host controller PCI Bus Glue.
 *
 * This does not add any PROM21-specific USB or xHCI operation. It exists only
 * to publish an auxiliary device for integrated temperature sensor support.
 *
 * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
 */

#include <linux/auxiliary_bus.h>
#include <linux/device/devres.h>
#include <linux/errno.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_data/usb-xhci-prom21.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>

#include "xhci-pci.h"

struct prom21_xhci_auxdev {
	struct auxiliary_device *auxdev;
	struct prom21_xhci_pdata pdata;
	int id;
};

static DEFINE_IDA(prom21_xhci_auxdev_ida);

static void prom21_xhci_auxdev_release(struct device *dev, void *res)
{
	struct prom21_xhci_auxdev *prom21_auxdev = res;

	auxiliary_device_destroy(prom21_auxdev->auxdev);
	ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id);
}

static int prom21_xhci_create_auxdev(struct pci_dev *pdev)
{
	struct prom21_xhci_auxdev *prom21_auxdev;
	struct usb_hcd *hcd = pci_get_drvdata(pdev);
	int ret;

	prom21_auxdev = devres_alloc(prom21_xhci_auxdev_release,
				     sizeof(*prom21_auxdev), GFP_KERNEL);
	if (!prom21_auxdev)
		return -ENOMEM;

	prom21_auxdev->pdata.pdev = pdev;
	prom21_auxdev->pdata.regs = hcd->regs;
	prom21_auxdev->pdata.rsrc_len = hcd->rsrc_len;

	prom21_auxdev->id = ida_alloc(&prom21_xhci_auxdev_ida, GFP_KERNEL);
	if (prom21_auxdev->id < 0) {
		ret = prom21_auxdev->id;
		goto err_free_devres;
	}

	prom21_auxdev->auxdev = auxiliary_device_create(&pdev->dev,
							KBUILD_MODNAME, "hwmon",
							&prom21_auxdev->pdata,
							prom21_auxdev->id);
	if (!prom21_auxdev->auxdev) {
		ret = -ENOMEM;
		goto err_free_ida;
	}

	devres_add(&pdev->dev, prom21_auxdev);
	return 0;

err_free_ida:
	ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id);
err_free_devres:
	devres_free(prom21_auxdev);
	return ret;
}

static void prom21_xhci_destroy_auxdev(struct pci_dev *pdev)
{
	devres_release(&pdev->dev, prom21_xhci_auxdev_release, NULL, NULL);
}

static int prom21_xhci_probe(struct pci_dev *dev,
			     const struct pci_device_id *id)
{
	int retval;

	retval = xhci_pci_common_probe(dev, id);
	if (retval)
		return retval;

	retval = prom21_xhci_create_auxdev(dev);
	if (retval) {
		/*
		 * The auxiliary device only provides optional temperature sensor
		 * support. Keep the xHCI controller usable if it fails.
		 */
		dev_err(&dev->dev,
			"failed to create PROM21 hwmon auxiliary device: %d\n",
			retval);
	}

	return 0;
}

static void prom21_xhci_remove(struct pci_dev *dev)
{
	prom21_xhci_destroy_auxdev(dev);
	xhci_pci_remove(dev);
}

static const struct pci_device_id pci_ids[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) },
	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) },
	{ /* end: all zeroes */ }
};
MODULE_DEVICE_TABLE(pci, pci_ids);

static struct pci_driver prom21_xhci_driver = {
	.name = "xhci-pci-prom21",
	.id_table = pci_ids,

	.probe = prom21_xhci_probe,
	.remove = prom21_xhci_remove,

	.shutdown = usb_hcd_pci_shutdown,
	.driver = {
		.pm = pm_ptr(&usb_hcd_pci_pm_ops),
	},
};
module_pci_driver(prom21_xhci_driver);

MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver");
MODULE_IMPORT_NS("xhci");
MODULE_LICENSE("GPL");