]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blame - drivers/pci/pcie/bw_notification.c
Merge tag 'powerpc-5.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc...
[mirror_ubuntu-hirsute-kernel.git] / drivers / pci / pcie / bw_notification.c
CommitLineData
e8303bb7
AG
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * PCI Express Link Bandwidth Notification services driver
4 * Author: Alexandru Gagniuc <mr.nuke.me@gmail.com>
5 *
6 * Copyright (C) 2019, Dell Inc
7 *
8 * The PCIe Link Bandwidth Notification provides a way to notify the
9 * operating system when the link width or data rate changes. This
10 * capability is required for all root ports and downstream ports
11 * supporting links wider than x1 and/or multiple link speeds.
12 *
13 * This service port driver hooks into the bandwidth notification interrupt
14 * and warns when links become degraded in operation.
15 */
16
17#include "../pci.h"
18#include "portdrv.h"
19
20static bool pcie_link_bandwidth_notification_supported(struct pci_dev *dev)
21{
22 int ret;
23 u32 lnk_cap;
24
25 ret = pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnk_cap);
26 return (ret == PCIBIOS_SUCCESSFUL) && (lnk_cap & PCI_EXP_LNKCAP_LBNC);
27}
28
29static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev)
30{
31 u16 lnk_ctl;
32
55397ce8
LW
33 pcie_capability_write_word(dev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
34
e8303bb7
AG
35 pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
36 lnk_ctl |= PCI_EXP_LNKCTL_LBMIE;
37 pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
38}
39
40static void pcie_disable_link_bandwidth_notification(struct pci_dev *dev)
41{
42 u16 lnk_ctl;
43
44 pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
45 lnk_ctl &= ~PCI_EXP_LNKCTL_LBMIE;
46 pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
47}
48
3e82a7f9 49static irqreturn_t pcie_bw_notification_irq(int irq, void *context)
e8303bb7
AG
50{
51 struct pcie_device *srv = context;
52 struct pci_dev *port = srv->port;
e8303bb7
AG
53 u16 link_status, events;
54 int ret;
55
56 ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
57 events = link_status & PCI_EXP_LNKSTA_LBMS;
58
59 if (ret != PCIBIOS_SUCCESSFUL || !events)
60 return IRQ_NONE;
61
3e82a7f9
AG
62 pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
63 pcie_update_link_speed(port->subordinate, link_status);
64 return IRQ_WAKE_THREAD;
65}
66
67static irqreturn_t pcie_bw_notification_handler(int irq, void *context)
68{
69 struct pcie_device *srv = context;
70 struct pci_dev *port = srv->port;
71 struct pci_dev *dev;
72
e8303bb7
AG
73 /*
74 * Print status from downstream devices, not this root port or
75 * downstream switch port.
76 */
77 down_read(&pci_bus_sem);
78 list_for_each_entry(dev, &port->subordinate->devices, bus_list)
0fa635ae 79 pcie_report_downtraining(dev);
e8303bb7
AG
80 up_read(&pci_bus_sem);
81
e8303bb7
AG
82 return IRQ_HANDLED;
83}
84
85static int pcie_bandwidth_notification_probe(struct pcie_device *srv)
86{
87 int ret;
88
89 /* Single-width or single-speed ports do not have to support this. */
90 if (!pcie_link_bandwidth_notification_supported(srv->port))
91 return -ENODEV;
92
3e82a7f9
AG
93 ret = request_threaded_irq(srv->irq, pcie_bw_notification_irq,
94 pcie_bw_notification_handler,
e8303bb7
AG
95 IRQF_SHARED, "PCIe BW notif", srv);
96 if (ret)
97 return ret;
98
99 pcie_enable_link_bandwidth_notification(srv->port);
100
101 return 0;
102}
103
104static void pcie_bandwidth_notification_remove(struct pcie_device *srv)
105{
106 pcie_disable_link_bandwidth_notification(srv->port);
107 free_irq(srv->irq, srv);
108}
109
6056bed9
MW
110static int pcie_bandwidth_notification_suspend(struct pcie_device *srv)
111{
112 pcie_disable_link_bandwidth_notification(srv->port);
113 return 0;
114}
115
116static int pcie_bandwidth_notification_resume(struct pcie_device *srv)
117{
118 pcie_enable_link_bandwidth_notification(srv->port);
119 return 0;
120}
121
e8303bb7
AG
122static struct pcie_port_service_driver pcie_bandwidth_notification_driver = {
123 .name = "pcie_bw_notification",
124 .port_type = PCIE_ANY_PORT,
125 .service = PCIE_PORT_SERVICE_BWNOTIF,
126 .probe = pcie_bandwidth_notification_probe,
6056bed9
MW
127 .suspend = pcie_bandwidth_notification_suspend,
128 .resume = pcie_bandwidth_notification_resume,
e8303bb7
AG
129 .remove = pcie_bandwidth_notification_remove,
130};
131
132int __init pcie_bandwidth_notification_init(void)
133{
134 return pcie_port_service_register(&pcie_bandwidth_notification_driver);
135}