]>
Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
3fb79338 RS |
2 | /* |
3 | * Adding PCI-E MSI support for PPC4XX SoCs. | |
4 | * | |
5 | * Copyright (c) 2010, Applied Micro Circuits Corporation | |
6 | * Authors: Tirumala R Marri <tmarri@apm.com> | |
7 | * Feng Kan <fkan@apm.com> | |
3fb79338 RS |
8 | */ |
9 | ||
10 | #include <linux/irq.h> | |
3fb79338 RS |
11 | #include <linux/pci.h> |
12 | #include <linux/msi.h> | |
13 | #include <linux/of_platform.h> | |
14 | #include <linux/interrupt.h> | |
93087948 | 15 | #include <linux/export.h> |
247540b0 | 16 | #include <linux/kernel.h> |
3fb79338 RS |
17 | #include <asm/prom.h> |
18 | #include <asm/hw_irq.h> | |
19 | #include <asm/ppc-pci.h> | |
247540b0 | 20 | #include <asm/dcr.h> |
3fb79338 RS |
21 | #include <asm/dcr-regs.h> |
22 | #include <asm/msi_bitmap.h> | |
23 | ||
24 | #define PEIH_TERMADH 0x00 | |
25 | #define PEIH_TERMADL 0x08 | |
26 | #define PEIH_MSIED 0x10 | |
27 | #define PEIH_MSIMK 0x18 | |
28 | #define PEIH_MSIASS 0x20 | |
29 | #define PEIH_FLUSH0 0x30 | |
30 | #define PEIH_FLUSH1 0x38 | |
31 | #define PEIH_CNTRST 0x48 | |
247540b0 ML |
32 | |
33 | static int msi_irqs; | |
3fb79338 RS |
34 | |
35 | struct ppc4xx_msi { | |
36 | u32 msi_addr_lo; | |
37 | u32 msi_addr_hi; | |
38 | void __iomem *msi_regs; | |
247540b0 | 39 | int *msi_virqs; |
3fb79338 RS |
40 | struct msi_bitmap bitmap; |
41 | struct device_node *msi_dev; | |
42 | }; | |
43 | ||
44 | static struct ppc4xx_msi ppc4xx_msi; | |
45 | ||
46 | static int ppc4xx_msi_init_allocator(struct platform_device *dev, | |
47 | struct ppc4xx_msi *msi_data) | |
48 | { | |
49 | int err; | |
50 | ||
247540b0 | 51 | err = msi_bitmap_alloc(&msi_data->bitmap, msi_irqs, |
3fb79338 RS |
52 | dev->dev.of_node); |
53 | if (err) | |
54 | return err; | |
55 | ||
56 | err = msi_bitmap_reserve_dt_hwirqs(&msi_data->bitmap); | |
57 | if (err < 0) { | |
58 | msi_bitmap_free(&msi_data->bitmap); | |
59 | return err; | |
60 | } | |
61 | ||
62 | return 0; | |
63 | } | |
64 | ||
65 | static int ppc4xx_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) | |
66 | { | |
67 | int int_no = -ENOMEM; | |
68 | unsigned int virq; | |
69 | struct msi_msg msg; | |
70 | struct msi_desc *entry; | |
71 | struct ppc4xx_msi *msi_data = &ppc4xx_msi; | |
72 | ||
6b2fd7ef AG |
73 | dev_dbg(&dev->dev, "PCIE-MSI:%s called. vec %x type %d\n", |
74 | __func__, nvec, type); | |
75 | if (type == PCI_CAP_ID_MSIX) | |
76 | pr_debug("ppc4xx msi: MSI-X untested, trying anyway.\n"); | |
77 | ||
6da2ec56 | 78 | msi_data->msi_virqs = kmalloc_array(msi_irqs, sizeof(int), GFP_KERNEL); |
247540b0 ML |
79 | if (!msi_data->msi_virqs) |
80 | return -ENOMEM; | |
81 | ||
2921d179 | 82 | for_each_pci_msi_entry(entry, dev) { |
3fb79338 RS |
83 | int_no = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1); |
84 | if (int_no >= 0) | |
85 | break; | |
86 | if (int_no < 0) { | |
87 | pr_debug("%s: fail allocating msi interrupt\n", | |
88 | __func__); | |
89 | } | |
90 | virq = irq_of_parse_and_map(msi_data->msi_dev, int_no); | |
ef24ba70 | 91 | if (!virq) { |
3fb79338 RS |
92 | dev_err(&dev->dev, "%s: fail mapping irq\n", __func__); |
93 | msi_bitmap_free_hwirqs(&msi_data->bitmap, int_no, 1); | |
94 | return -ENOSPC; | |
95 | } | |
96 | dev_dbg(&dev->dev, "%s: virq = %d\n", __func__, virq); | |
97 | ||
98 | /* Setup msi address space */ | |
99 | msg.address_hi = msi_data->msi_addr_hi; | |
100 | msg.address_lo = msi_data->msi_addr_lo; | |
101 | ||
102 | irq_set_msi_desc(virq, entry); | |
103 | msg.data = int_no; | |
83a18912 | 104 | pci_write_msi_msg(virq, &msg); |
3fb79338 RS |
105 | } |
106 | return 0; | |
107 | } | |
108 | ||
109 | void ppc4xx_teardown_msi_irqs(struct pci_dev *dev) | |
110 | { | |
111 | struct msi_desc *entry; | |
112 | struct ppc4xx_msi *msi_data = &ppc4xx_msi; | |
e297c939 | 113 | irq_hw_number_t hwirq; |
3fb79338 RS |
114 | |
115 | dev_dbg(&dev->dev, "PCIE-MSI: tearing down msi irqs\n"); | |
116 | ||
2921d179 | 117 | for_each_pci_msi_entry(entry, dev) { |
ef24ba70 | 118 | if (!entry->irq) |
3fb79338 | 119 | continue; |
e297c939 | 120 | hwirq = virq_to_hw(entry->irq); |
3fb79338 | 121 | irq_set_msi_desc(entry->irq, NULL); |
3fb79338 | 122 | irq_dispose_mapping(entry->irq); |
e297c939 | 123 | msi_bitmap_free_hwirqs(&msi_data->bitmap, hwirq, 1); |
3fb79338 RS |
124 | } |
125 | } | |
126 | ||
3fb79338 RS |
127 | static int ppc4xx_setup_pcieh_hw(struct platform_device *dev, |
128 | struct resource res, struct ppc4xx_msi *msi) | |
129 | { | |
130 | const u32 *msi_data; | |
131 | const u32 *msi_mask; | |
132 | const u32 *sdr_addr; | |
133 | dma_addr_t msi_phys; | |
134 | void *msi_virt; | |
6e0495c2 | 135 | int err; |
3fb79338 RS |
136 | |
137 | sdr_addr = of_get_property(dev->dev.of_node, "sdr-base", NULL); | |
138 | if (!sdr_addr) | |
6e0495c2 | 139 | return -EINVAL; |
3fb79338 | 140 | |
6e0495c2 GR |
141 | msi_data = of_get_property(dev->dev.of_node, "msi-data", NULL); |
142 | if (!msi_data) | |
143 | return -EINVAL; | |
144 | ||
145 | msi_mask = of_get_property(dev->dev.of_node, "msi-mask", NULL); | |
146 | if (!msi_mask) | |
147 | return -EINVAL; | |
3fb79338 RS |
148 | |
149 | msi->msi_dev = of_find_node_by_name(NULL, "ppc4xx-msi"); | |
247540b0 | 150 | if (!msi->msi_dev) |
3fb79338 RS |
151 | return -ENODEV; |
152 | ||
153 | msi->msi_regs = of_iomap(msi->msi_dev, 0); | |
154 | if (!msi->msi_regs) { | |
6e0495c2 GR |
155 | dev_err(&dev->dev, "of_iomap failed\n"); |
156 | err = -ENOMEM; | |
157 | goto node_put; | |
3fb79338 RS |
158 | } |
159 | dev_dbg(&dev->dev, "PCIE-MSI: msi register mapped 0x%x 0x%x\n", | |
160 | (u32) (msi->msi_regs + PEIH_TERMADH), (u32) (msi->msi_regs)); | |
161 | ||
162 | msi_virt = dma_alloc_coherent(&dev->dev, 64, &msi_phys, GFP_KERNEL); | |
6e0495c2 GR |
163 | if (!msi_virt) { |
164 | err = -ENOMEM; | |
165 | goto iounmap; | |
166 | } | |
dce4c92d JB |
167 | msi->msi_addr_hi = upper_32_bits(msi_phys); |
168 | msi->msi_addr_lo = lower_32_bits(msi_phys & 0xffffffff); | |
247540b0 ML |
169 | dev_dbg(&dev->dev, "PCIE-MSI: msi address high 0x%x, low 0x%x\n", |
170 | msi->msi_addr_hi, msi->msi_addr_lo); | |
3fb79338 | 171 | |
6e0495c2 GR |
172 | mtdcri(SDR0, *sdr_addr, upper_32_bits(res.start)); /*HIGH addr */ |
173 | mtdcri(SDR0, *sdr_addr + 1, lower_32_bits(res.start)); /* Low addr */ | |
174 | ||
3fb79338 RS |
175 | /* Progam the Interrupt handler Termination addr registers */ |
176 | out_be32(msi->msi_regs + PEIH_TERMADH, msi->msi_addr_hi); | |
177 | out_be32(msi->msi_regs + PEIH_TERMADL, msi->msi_addr_lo); | |
178 | ||
3fb79338 RS |
179 | /* Program MSI Expected data and Mask bits */ |
180 | out_be32(msi->msi_regs + PEIH_MSIED, *msi_data); | |
181 | out_be32(msi->msi_regs + PEIH_MSIMK, *msi_mask); | |
182 | ||
247540b0 ML |
183 | dma_free_coherent(&dev->dev, 64, msi_virt, msi_phys); |
184 | ||
3fb79338 | 185 | return 0; |
6e0495c2 GR |
186 | |
187 | iounmap: | |
188 | iounmap(msi->msi_regs); | |
189 | node_put: | |
190 | of_node_put(msi->msi_dev); | |
191 | return err; | |
3fb79338 RS |
192 | } |
193 | ||
194 | static int ppc4xx_of_msi_remove(struct platform_device *dev) | |
195 | { | |
196 | struct ppc4xx_msi *msi = dev->dev.platform_data; | |
197 | int i; | |
198 | int virq; | |
199 | ||
247540b0 | 200 | for (i = 0; i < msi_irqs; i++) { |
3fb79338 | 201 | virq = msi->msi_virqs[i]; |
ef24ba70 | 202 | if (virq) |
3fb79338 RS |
203 | irq_dispose_mapping(virq); |
204 | } | |
205 | ||
206 | if (msi->bitmap.bitmap) | |
207 | msi_bitmap_free(&msi->bitmap); | |
208 | iounmap(msi->msi_regs); | |
209 | of_node_put(msi->msi_dev); | |
3fb79338 RS |
210 | |
211 | return 0; | |
212 | } | |
213 | ||
cad5cef6 | 214 | static int ppc4xx_msi_probe(struct platform_device *dev) |
3fb79338 RS |
215 | { |
216 | struct ppc4xx_msi *msi; | |
217 | struct resource res; | |
218 | int err = 0; | |
b7eba2ff | 219 | struct pci_controller *phb; |
3fb79338 | 220 | |
3fb79338 RS |
221 | dev_dbg(&dev->dev, "PCIE-MSI: Setting up MSI support...\n"); |
222 | ||
6e0495c2 GR |
223 | msi = devm_kzalloc(&dev->dev, sizeof(*msi), GFP_KERNEL); |
224 | if (!msi) | |
3fb79338 | 225 | return -ENOMEM; |
3fb79338 RS |
226 | dev->dev.platform_data = msi; |
227 | ||
228 | /* Get MSI ranges */ | |
229 | err = of_address_to_resource(dev->dev.of_node, 0, &res); | |
230 | if (err) { | |
b7c670d6 | 231 | dev_err(&dev->dev, "%pOF resource error!\n", dev->dev.of_node); |
6e0495c2 | 232 | return err; |
3fb79338 RS |
233 | } |
234 | ||
247540b0 ML |
235 | msi_irqs = of_irq_count(dev->dev.of_node); |
236 | if (!msi_irqs) | |
237 | return -ENODEV; | |
238 | ||
9a2c1d31 WY |
239 | err = ppc4xx_setup_pcieh_hw(dev, res, msi); |
240 | if (err) | |
6e0495c2 | 241 | return err; |
3fb79338 RS |
242 | |
243 | err = ppc4xx_msi_init_allocator(dev, msi); | |
244 | if (err) { | |
245 | dev_err(&dev->dev, "Error allocating MSI bitmap\n"); | |
246 | goto error_out; | |
247 | } | |
247540b0 | 248 | ppc4xx_msi = *msi; |
3fb79338 | 249 | |
b7eba2ff DA |
250 | list_for_each_entry(phb, &hose_list, list_node) { |
251 | phb->controller_ops.setup_msi_irqs = ppc4xx_setup_msi_irqs; | |
252 | phb->controller_ops.teardown_msi_irqs = ppc4xx_teardown_msi_irqs; | |
253 | } | |
6e0495c2 | 254 | return 0; |
3fb79338 RS |
255 | |
256 | error_out: | |
257 | ppc4xx_of_msi_remove(dev); | |
258 | return err; | |
259 | } | |
260 | static const struct of_device_id ppc4xx_msi_ids[] = { | |
261 | { | |
262 | .compatible = "amcc,ppc4xx-msi", | |
263 | }, | |
264 | {} | |
265 | }; | |
266 | static struct platform_driver ppc4xx_msi_driver = { | |
267 | .probe = ppc4xx_msi_probe, | |
268 | .remove = ppc4xx_of_msi_remove, | |
269 | .driver = { | |
270 | .name = "ppc4xx-msi", | |
3fb79338 RS |
271 | .of_match_table = ppc4xx_msi_ids, |
272 | }, | |
273 | ||
274 | }; | |
275 | ||
276 | static __init int ppc4xx_msi_init(void) | |
277 | { | |
278 | return platform_driver_register(&ppc4xx_msi_driver); | |
279 | } | |
280 | ||
281 | subsys_initcall(ppc4xx_msi_init); |