Merge tag 'drm-misc-next-2019-04-18' of git://anongit.freedesktop.org/drm/drm-misc...
[sfrench/cifs-2.6.git] / drivers / pci / pcie / bw_notification.c
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
20 static 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
29 static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev)
30 {
31         u16 lnk_ctl;
32
33         pcie_capability_write_word(dev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
34
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
40 static 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
49 static irqreturn_t pcie_bw_notification_irq(int irq, void *context)
50 {
51         struct pcie_device *srv = context;
52         struct pci_dev *port = srv->port;
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
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
67 static 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
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)
79                 pcie_report_downtraining(dev);
80         up_read(&pci_bus_sem);
81
82         return IRQ_HANDLED;
83 }
84
85 static 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
93         ret = request_threaded_irq(srv->irq, pcie_bw_notification_irq,
94                                    pcie_bw_notification_handler,
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
104 static 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
110 static struct pcie_port_service_driver pcie_bandwidth_notification_driver = {
111         .name           = "pcie_bw_notification",
112         .port_type      = PCIE_ANY_PORT,
113         .service        = PCIE_PORT_SERVICE_BWNOTIF,
114         .probe          = pcie_bandwidth_notification_probe,
115         .remove         = pcie_bandwidth_notification_remove,
116 };
117
118 int __init pcie_bandwidth_notification_init(void)
119 {
120         return pcie_port_service_register(&pcie_bandwidth_notification_driver);
121 }