diff options
author | Ley Foon Tan <lftan@altera.com> | 2015-10-23 06:27:13 -0400 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2015-11-03 09:36:58 -0500 |
commit | af1169b48b179c9db6b5d57e14552cceccbc04eb (patch) | |
tree | 98b6a1b9063851a6c9a3db6691d7e70f2afa65f3 | |
parent | eaa6111b70a7cb43b7536eacea8ef501fc4fc235 (diff) |
PCI: altera: Add Altera PCIe MSI driver
Add Altera PCIe MSI driver. This soft IP supports a configurable number of
vectors, which is a DTS parameter.
[bhelgaas: Kconfig depend on PCIE_ALTERA, typos, whitespace]
Signed-off-by: Ley Foon Tan <lftan@altera.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Marc Zyngier <marc.zyngier@arm.com>
Acked-by: Rob Herring <robh@kernel.org>
-rw-r--r-- | Documentation/devicetree/bindings/pci/altera-pcie-msi.txt | 28 | ||||
-rw-r--r-- | MAINTAINERS | 8 | ||||
-rw-r--r-- | drivers/pci/host/Kconfig | 8 | ||||
-rw-r--r-- | drivers/pci/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/pci/host/pcie-altera-msi.c | 312 |
5 files changed, 357 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/pci/altera-pcie-msi.txt b/Documentation/devicetree/bindings/pci/altera-pcie-msi.txt new file mode 100644 index 000000000000..09cd3bc4d038 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/altera-pcie-msi.txt | |||
@@ -0,0 +1,28 @@ | |||
1 | * Altera PCIe MSI controller | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: should contain "altr,msi-1.0" | ||
5 | - reg: specifies the physical base address of the controller and | ||
6 | the length of the memory mapped region. | ||
7 | - reg-names: must include the following entries: | ||
8 | "csr": CSR registers | ||
9 | "vector_slave": vectors slave port region | ||
10 | - interrupt-parent: interrupt source phandle. | ||
11 | - interrupts: specifies the interrupt source of the parent interrupt | ||
12 | controller. The format of the interrupt specifier depends on the | ||
13 | parent interrupt controller. | ||
14 | - num-vectors: number of vectors, range 1 to 32. | ||
15 | - msi-controller: indicates that this is MSI controller node | ||
16 | |||
17 | |||
18 | Example | ||
19 | msi0: msi@0xFF200000 { | ||
20 | compatible = "altr,msi-1.0"; | ||
21 | reg = <0xFF200000 0x00000010 | ||
22 | 0xFF200010 0x00000080>; | ||
23 | reg-names = "csr", "vector_slave"; | ||
24 | interrupt-parent = <&hps_0_arm_gic_0>; | ||
25 | interrupts = <0 42 4>; | ||
26 | msi-controller; | ||
27 | num-vectors = <32>; | ||
28 | }; | ||
diff --git a/MAINTAINERS b/MAINTAINERS index d8fc71ec8cb8..eeb9ec999cda 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -8047,6 +8047,14 @@ L: linux-pci@vger.kernel.org | |||
8047 | S: Maintained | 8047 | S: Maintained |
8048 | F: drivers/pci/host/*spear* | 8048 | F: drivers/pci/host/*spear* |
8049 | 8049 | ||
8050 | PCI MSI DRIVER FOR ALTERA MSI IP | ||
8051 | M: Ley Foon Tan <lftan@altera.com> | ||
8052 | L: rfi@lists.rocketboards.org (moderated for non-subscribers) | ||
8053 | L: linux-pci@vger.kernel.org | ||
8054 | S: Supported | ||
8055 | F: Documentation/devicetree/bindings/pci/altera-pcie-msi.txt | ||
8056 | F: drivers/pci/host/pcie-altera-msi.c | ||
8057 | |||
8050 | PCI MSI DRIVER FOR APPLIEDMICRO XGENE | 8058 | PCI MSI DRIVER FOR APPLIEDMICRO XGENE |
8051 | M: Duc Dang <dhdang@apm.com> | 8059 | M: Duc Dang <dhdang@apm.com> |
8052 | L: linux-pci@vger.kernel.org | 8060 | L: linux-pci@vger.kernel.org |
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index f48e6d004425..72d81457feaa 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig | |||
@@ -154,4 +154,12 @@ config PCIE_ALTERA | |||
154 | Say Y here if you want to enable PCIe controller support on Altera | 154 | Say Y here if you want to enable PCIe controller support on Altera |
155 | FPGA. | 155 | FPGA. |
156 | 156 | ||
157 | config PCIE_ALTERA_MSI | ||
158 | bool "Altera PCIe MSI feature" | ||
159 | depends on PCIE_ALTERA && PCI_MSI | ||
160 | select PCI_MSI_IRQ_DOMAIN | ||
161 | help | ||
162 | Say Y here if you want PCIe MSI support for the Altera FPGA. | ||
163 | This MSI driver supports Altera MSI to GIC controller IP. | ||
164 | |||
157 | endmenu | 165 | endmenu |
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 6954f7666ac1..6c4913dfd235 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile | |||
@@ -18,3 +18,4 @@ obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o | |||
18 | obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o | 18 | obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o |
19 | obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o | 19 | obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o |
20 | obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o | 20 | obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o |
21 | obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o | ||
diff --git a/drivers/pci/host/pcie-altera-msi.c b/drivers/pci/host/pcie-altera-msi.c new file mode 100644 index 000000000000..2c37e8620c37 --- /dev/null +++ b/drivers/pci/host/pcie-altera-msi.c | |||
@@ -0,0 +1,312 @@ | |||
1 | /* | ||
2 | * Copyright Altera Corporation (C) 2013-2015. All rights reserved | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms and conditions of the GNU General Public License, | ||
6 | * version 2, as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
11 | * more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public License along with | ||
14 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
15 | */ | ||
16 | |||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/irqchip/chained_irq.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/msi.h> | ||
21 | #include <linux/of_address.h> | ||
22 | #include <linux/of_irq.h> | ||
23 | #include <linux/of_pci.h> | ||
24 | #include <linux/pci.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/slab.h> | ||
27 | |||
28 | #define MSI_STATUS 0x0 | ||
29 | #define MSI_ERROR 0x4 | ||
30 | #define MSI_INTMASK 0x8 | ||
31 | |||
32 | #define MAX_MSI_VECTORS 32 | ||
33 | |||
34 | struct altera_msi { | ||
35 | DECLARE_BITMAP(used, MAX_MSI_VECTORS); | ||
36 | struct mutex lock; /* protect "used" bitmap */ | ||
37 | struct platform_device *pdev; | ||
38 | struct irq_domain *msi_domain; | ||
39 | struct irq_domain *inner_domain; | ||
40 | void __iomem *csr_base; | ||
41 | void __iomem *vector_base; | ||
42 | phys_addr_t vector_phy; | ||
43 | u32 num_of_vectors; | ||
44 | int irq; | ||
45 | }; | ||
46 | |||
47 | static inline void msi_writel(struct altera_msi *msi, const u32 value, | ||
48 | const u32 reg) | ||
49 | { | ||
50 | writel_relaxed(value, msi->csr_base + reg); | ||
51 | } | ||
52 | |||
53 | static inline u32 msi_readl(struct altera_msi *msi, const u32 reg) | ||
54 | { | ||
55 | return readl_relaxed(msi->csr_base + reg); | ||
56 | } | ||
57 | |||
58 | static void altera_msi_isr(struct irq_desc *desc) | ||
59 | { | ||
60 | struct irq_chip *chip = irq_desc_get_chip(desc); | ||
61 | struct altera_msi *msi; | ||
62 | unsigned long status; | ||
63 | u32 num_of_vectors; | ||
64 | u32 bit; | ||
65 | u32 virq; | ||
66 | |||
67 | chained_irq_enter(chip, desc); | ||
68 | msi = irq_desc_get_handler_data(desc); | ||
69 | num_of_vectors = msi->num_of_vectors; | ||
70 | |||
71 | while ((status = msi_readl(msi, MSI_STATUS)) != 0) { | ||
72 | for_each_set_bit(bit, &status, msi->num_of_vectors) { | ||
73 | /* Dummy read from vector to clear the interrupt */ | ||
74 | readl_relaxed(msi->vector_base + (bit * sizeof(u32))); | ||
75 | |||
76 | virq = irq_find_mapping(msi->inner_domain, bit); | ||
77 | if (virq) | ||
78 | generic_handle_irq(virq); | ||
79 | else | ||
80 | dev_err(&msi->pdev->dev, "unexpected MSI\n"); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | chained_irq_exit(chip, desc); | ||
85 | } | ||
86 | |||
87 | static struct irq_chip altera_msi_irq_chip = { | ||
88 | .name = "Altera PCIe MSI", | ||
89 | .irq_mask = pci_msi_mask_irq, | ||
90 | .irq_unmask = pci_msi_unmask_irq, | ||
91 | }; | ||
92 | |||
93 | static struct msi_domain_info altera_msi_domain_info = { | ||
94 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | ||
95 | MSI_FLAG_PCI_MSIX), | ||
96 | .chip = &altera_msi_irq_chip, | ||
97 | }; | ||
98 | |||
99 | static void altera_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) | ||
100 | { | ||
101 | struct altera_msi *msi = irq_data_get_irq_chip_data(data); | ||
102 | phys_addr_t addr = msi->vector_phy + (data->hwirq * sizeof(u32)); | ||
103 | |||
104 | msg->address_lo = lower_32_bits(addr); | ||
105 | msg->address_hi = upper_32_bits(addr); | ||
106 | msg->data = data->hwirq; | ||
107 | |||
108 | dev_dbg(&msi->pdev->dev, "msi#%d address_hi %#x address_lo %#x\n", | ||
109 | (int)data->hwirq, msg->address_hi, msg->address_lo); | ||
110 | } | ||
111 | |||
112 | static int altera_msi_set_affinity(struct irq_data *irq_data, | ||
113 | const struct cpumask *mask, bool force) | ||
114 | { | ||
115 | return -EINVAL; | ||
116 | } | ||
117 | |||
118 | static struct irq_chip altera_msi_bottom_irq_chip = { | ||
119 | .name = "Altera MSI", | ||
120 | .irq_compose_msi_msg = altera_compose_msi_msg, | ||
121 | .irq_set_affinity = altera_msi_set_affinity, | ||
122 | }; | ||
123 | |||
124 | static int altera_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, | ||
125 | unsigned int nr_irqs, void *args) | ||
126 | { | ||
127 | struct altera_msi *msi = domain->host_data; | ||
128 | unsigned long bit; | ||
129 | u32 mask; | ||
130 | |||
131 | WARN_ON(nr_irqs != 1); | ||
132 | mutex_lock(&msi->lock); | ||
133 | |||
134 | bit = find_first_zero_bit(msi->used, msi->num_of_vectors); | ||
135 | if (bit >= msi->num_of_vectors) { | ||
136 | mutex_unlock(&msi->lock); | ||
137 | return -ENOSPC; | ||
138 | } | ||
139 | |||
140 | set_bit(bit, msi->used); | ||
141 | |||
142 | mutex_unlock(&msi->lock); | ||
143 | |||
144 | irq_domain_set_info(domain, virq, bit, &altera_msi_bottom_irq_chip, | ||
145 | domain->host_data, handle_simple_irq, | ||
146 | NULL, NULL); | ||
147 | |||
148 | mask = msi_readl(msi, MSI_INTMASK); | ||
149 | mask |= 1 << bit; | ||
150 | msi_writel(msi, mask, MSI_INTMASK); | ||
151 | |||
152 | return 0; | ||
153 | } | ||
154 | |||
155 | static void altera_irq_domain_free(struct irq_domain *domain, | ||
156 | unsigned int virq, unsigned int nr_irqs) | ||
157 | { | ||
158 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | ||
159 | struct altera_msi *msi = irq_data_get_irq_chip_data(d); | ||
160 | u32 mask; | ||
161 | |||
162 | mutex_lock(&msi->lock); | ||
163 | |||
164 | if (!test_bit(d->hwirq, msi->used)) { | ||
165 | dev_err(&msi->pdev->dev, "trying to free unused MSI#%lu\n", | ||
166 | d->hwirq); | ||
167 | } else { | ||
168 | __clear_bit(d->hwirq, msi->used); | ||
169 | mask = msi_readl(msi, MSI_INTMASK); | ||
170 | mask &= ~(1 << d->hwirq); | ||
171 | msi_writel(msi, mask, MSI_INTMASK); | ||
172 | } | ||
173 | |||
174 | mutex_unlock(&msi->lock); | ||
175 | } | ||
176 | |||
177 | static const struct irq_domain_ops msi_domain_ops = { | ||
178 | .alloc = altera_irq_domain_alloc, | ||
179 | .free = altera_irq_domain_free, | ||
180 | }; | ||
181 | |||
182 | static int altera_allocate_domains(struct altera_msi *msi) | ||
183 | { | ||
184 | msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors, | ||
185 | &msi_domain_ops, msi); | ||
186 | if (!msi->inner_domain) { | ||
187 | dev_err(&msi->pdev->dev, "failed to create IRQ domain\n"); | ||
188 | return -ENOMEM; | ||
189 | } | ||
190 | |||
191 | msi->msi_domain = pci_msi_create_irq_domain(msi->pdev->dev.of_node, | ||
192 | &altera_msi_domain_info, msi->inner_domain); | ||
193 | if (!msi->msi_domain) { | ||
194 | dev_err(&msi->pdev->dev, "failed to create MSI domain\n"); | ||
195 | irq_domain_remove(msi->inner_domain); | ||
196 | return -ENOMEM; | ||
197 | } | ||
198 | |||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static void altera_free_domains(struct altera_msi *msi) | ||
203 | { | ||
204 | irq_domain_remove(msi->msi_domain); | ||
205 | irq_domain_remove(msi->inner_domain); | ||
206 | } | ||
207 | |||
208 | static int altera_msi_remove(struct platform_device *pdev) | ||
209 | { | ||
210 | struct altera_msi *msi = platform_get_drvdata(pdev); | ||
211 | |||
212 | msi_writel(msi, 0, MSI_INTMASK); | ||
213 | irq_set_chained_handler(msi->irq, NULL); | ||
214 | irq_set_handler_data(msi->irq, NULL); | ||
215 | |||
216 | altera_free_domains(msi); | ||
217 | |||
218 | platform_set_drvdata(pdev, NULL); | ||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | static int altera_msi_probe(struct platform_device *pdev) | ||
223 | { | ||
224 | struct altera_msi *msi; | ||
225 | struct device_node *np = pdev->dev.of_node; | ||
226 | struct resource *res; | ||
227 | int ret; | ||
228 | |||
229 | msi = devm_kzalloc(&pdev->dev, sizeof(struct altera_msi), | ||
230 | GFP_KERNEL); | ||
231 | if (!msi) | ||
232 | return -ENOMEM; | ||
233 | |||
234 | mutex_init(&msi->lock); | ||
235 | msi->pdev = pdev; | ||
236 | |||
237 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr"); | ||
238 | if (!res) { | ||
239 | dev_err(&pdev->dev, "no csr memory resource defined\n"); | ||
240 | return -ENODEV; | ||
241 | } | ||
242 | |||
243 | msi->csr_base = devm_ioremap_resource(&pdev->dev, res); | ||
244 | if (IS_ERR(msi->csr_base)) { | ||
245 | dev_err(&pdev->dev, "failed to map csr memory\n"); | ||
246 | return PTR_ERR(msi->csr_base); | ||
247 | } | ||
248 | |||
249 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, | ||
250 | "vector_slave"); | ||
251 | if (!res) { | ||
252 | dev_err(&pdev->dev, "no vector_slave memory resource defined\n"); | ||
253 | return -ENODEV; | ||
254 | } | ||
255 | |||
256 | msi->vector_base = devm_ioremap_resource(&pdev->dev, res); | ||
257 | if (IS_ERR(msi->vector_base)) { | ||
258 | dev_err(&pdev->dev, "failed to map vector_slave memory\n"); | ||
259 | return PTR_ERR(msi->vector_base); | ||
260 | } | ||
261 | |||
262 | msi->vector_phy = res->start; | ||
263 | |||
264 | if (of_property_read_u32(np, "num-vectors", &msi->num_of_vectors)) { | ||
265 | dev_err(&pdev->dev, "failed to parse the number of vectors\n"); | ||
266 | return -EINVAL; | ||
267 | } | ||
268 | |||
269 | ret = altera_allocate_domains(msi); | ||
270 | if (ret) | ||
271 | return ret; | ||
272 | |||
273 | msi->irq = platform_get_irq(pdev, 0); | ||
274 | if (msi->irq <= 0) { | ||
275 | dev_err(&pdev->dev, "failed to map IRQ: %d\n", msi->irq); | ||
276 | ret = -ENODEV; | ||
277 | goto err; | ||
278 | } | ||
279 | |||
280 | irq_set_chained_handler_and_data(msi->irq, altera_msi_isr, msi); | ||
281 | platform_set_drvdata(pdev, msi); | ||
282 | |||
283 | return 0; | ||
284 | |||
285 | err: | ||
286 | altera_msi_remove(pdev); | ||
287 | return ret; | ||
288 | } | ||
289 | |||
290 | static const struct of_device_id altera_msi_of_match[] = { | ||
291 | { .compatible = "altr,msi-1.0", NULL }, | ||
292 | { }, | ||
293 | }; | ||
294 | |||
295 | static struct platform_driver altera_msi_driver = { | ||
296 | .driver = { | ||
297 | .name = "altera-msi", | ||
298 | .of_match_table = altera_msi_of_match, | ||
299 | }, | ||
300 | .probe = altera_msi_probe, | ||
301 | .remove = altera_msi_remove, | ||
302 | }; | ||
303 | |||
304 | static int __init altera_msi_init(void) | ||
305 | { | ||
306 | return platform_driver_register(&altera_msi_driver); | ||
307 | } | ||
308 | subsys_initcall(altera_msi_init); | ||
309 | |||
310 | MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); | ||
311 | MODULE_DESCRIPTION("Altera PCIe MSI support"); | ||
312 | MODULE_LICENSE("GPL v2"); | ||