Merge branch 'work.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-microblaze.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 #define dev_fmt(fmt) "bw_notification: " fmt
18
19 #include "../pci.h"
20 #include "portdrv.h"
21
22 static bool pcie_link_bandwidth_notification_supported(struct pci_dev *dev)
23 {
24         int ret;
25         u32 lnk_cap;
26
27         ret = pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnk_cap);
28         return (ret == PCIBIOS_SUCCESSFUL) && (lnk_cap & PCI_EXP_LNKCAP_LBNC);
29 }
30
31 static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev)
32 {
33         u16 lnk_ctl;
34
35         pcie_capability_write_word(dev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
36
37         pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
38         lnk_ctl |= PCI_EXP_LNKCTL_LBMIE;
39         pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
40 }
41
42 static void pcie_disable_link_bandwidth_notification(struct pci_dev *dev)
43 {
44         u16 lnk_ctl;
45
46         pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
47         lnk_ctl &= ~PCI_EXP_LNKCTL_LBMIE;
48         pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
49 }
50
51 static irqreturn_t pcie_bw_notification_irq(int irq, void *context)
52 {
53         struct pcie_device *srv = context;
54         struct pci_dev *port = srv->port;
55         u16 link_status, events;
56         int ret;
57
58         ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
59         events = link_status & PCI_EXP_LNKSTA_LBMS;
60
61         if (ret != PCIBIOS_SUCCESSFUL || !events)
62                 return IRQ_NONE;
63
64         pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
65         pcie_update_link_speed(port->subordinate, link_status);
66         return IRQ_WAKE_THREAD;
67 }
68
69 static irqreturn_t pcie_bw_notification_handler(int irq, void *context)
70 {
71         struct pcie_device *srv = context;
72         struct pci_dev *port = srv->port;
73         struct pci_dev *dev;
74
75         /*
76          * Print status from downstream devices, not this root port or
77          * downstream switch port.
78          */
79         down_read(&pci_bus_sem);
80         list_for_each_entry(dev, &port->subordinate->devices, bus_list)
81                 pcie_report_downtraining(dev);
82         up_read(&pci_bus_sem);
83
84         return IRQ_HANDLED;
85 }
86
87 static int pcie_bandwidth_notification_probe(struct pcie_device *srv)
88 {
89         int ret;
90
91         /* Single-width or single-speed ports do not have to support this. */
92         if (!pcie_link_bandwidth_notification_supported(srv->port))
93                 return -ENODEV;
94
95         ret = request_threaded_irq(srv->irq, pcie_bw_notification_irq,
96                                    pcie_bw_notification_handler,
97                                    IRQF_SHARED, "PCIe BW notif", srv);
98         if (ret)
99                 return ret;
100
101         pcie_enable_link_bandwidth_notification(srv->port);
102         pci_info(srv->port, "enabled with IRQ %d\n", srv->irq);
103
104         return 0;
105 }
106
107 static void pcie_bandwidth_notification_remove(struct pcie_device *srv)
108 {
109         pcie_disable_link_bandwidth_notification(srv->port);
110         free_irq(srv->irq, srv);
111 }
112
113 static int pcie_bandwidth_notification_suspend(struct pcie_device *srv)
114 {
115         pcie_disable_link_bandwidth_notification(srv->port);
116         return 0;
117 }
118
119 static int pcie_bandwidth_notification_resume(struct pcie_device *srv)
120 {
121         pcie_enable_link_bandwidth_notification(srv->port);
122         return 0;
123 }
124
125 static struct pcie_port_service_driver pcie_bandwidth_notification_driver = {
126         .name           = "pcie_bw_notification",
127         .port_type      = PCIE_ANY_PORT,
128         .service        = PCIE_PORT_SERVICE_BWNOTIF,
129         .probe          = pcie_bandwidth_notification_probe,
130         .suspend        = pcie_bandwidth_notification_suspend,
131         .resume         = pcie_bandwidth_notification_resume,
132         .remove         = pcie_bandwidth_notification_remove,
133 };
134
135 int __init pcie_bandwidth_notification_init(void)
136 {
137         return pcie_port_service_register(&pcie_bandwidth_notification_driver);
138 }