Commit | Line | Data |
---|---|---|
3fb79338 RS |
1 | /* |
2 | * Adding PCI-E MSI support for PPC4XX SoCs. | |
3 | * | |
4 | * Copyright (c) 2010, Applied Micro Circuits Corporation | |
5 | * Authors: Tirumala R Marri <tmarri@apm.com> | |
6 | * Feng Kan <fkan@apm.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License as | |
10 | * published by the Free Software Foundation; either version 2 of | |
11 | * the License, or (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
21 | * MA 02111-1307 USA | |
22 | */ | |
23 | ||
24 | #include <linux/irq.h> | |
25 | #include <linux/bootmem.h> | |
26 | #include <linux/pci.h> | |
27 | #include <linux/msi.h> | |
28 | #include <linux/of_platform.h> | |
29 | #include <linux/interrupt.h> | |
93087948 | 30 | #include <linux/export.h> |
247540b0 | 31 | #include <linux/kernel.h> |
3fb79338 RS |
32 | #include <asm/prom.h> |
33 | #include <asm/hw_irq.h> | |
34 | #include <asm/ppc-pci.h> | |
247540b0 | 35 | #include <asm/dcr.h> |
3fb79338 RS |
36 | #include <asm/dcr-regs.h> |
37 | #include <asm/msi_bitmap.h> | |
38 | ||
39 | #define PEIH_TERMADH 0x00 | |
40 | #define PEIH_TERMADL 0x08 | |
41 | #define PEIH_MSIED 0x10 | |
42 | #define PEIH_MSIMK 0x18 | |
43 | #define PEIH_MSIASS 0x20 | |
44 | #define PEIH_FLUSH0 0x30 | |
45 | #define PEIH_FLUSH1 0x38 | |
46 | #define PEIH_CNTRST 0x48 | |
247540b0 ML |
47 | |
48 | static int msi_irqs; | |
3fb79338 RS |
49 | |
50 | struct ppc4xx_msi { | |
51 | u32 msi_addr_lo; | |
52 | u32 msi_addr_hi; | |
53 | void __iomem *msi_regs; | |
247540b0 | 54 | int *msi_virqs; |
3fb79338 RS |
55 | struct msi_bitmap bitmap; |
56 | struct device_node *msi_dev; | |
57 | }; | |
58 | ||
59 | static struct ppc4xx_msi ppc4xx_msi; | |
60 | ||
61 | static int ppc4xx_msi_init_allocator(struct platform_device *dev, | |
62 | struct ppc4xx_msi *msi_data) | |
63 | { | |
64 | int err; | |
65 | ||
247540b0 | 66 | err = msi_bitmap_alloc(&msi_data->bitmap, msi_irqs, |
3fb79338 RS |
67 | dev->dev.of_node); |
68 | if (err) | |
69 | return err; | |
70 | ||
71 | err = msi_bitmap_reserve_dt_hwirqs(&msi_data->bitmap); | |
72 | if (err < 0) { | |
73 | msi_bitmap_free(&msi_data->bitmap); | |
74 | return err; | |
75 | } | |
76 | ||
77 | return 0; | |
78 | } | |
79 | ||
80 | static int ppc4xx_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) | |
81 | { | |
82 | int int_no = -ENOMEM; | |
83 | unsigned int virq; | |
84 | struct msi_msg msg; | |
85 | struct msi_desc *entry; | |
86 | struct ppc4xx_msi *msi_data = &ppc4xx_msi; | |
87 | ||
247540b0 ML |
88 | msi_data->msi_virqs = kmalloc((msi_irqs) * sizeof(int), |
89 | GFP_KERNEL); | |
90 | if (!msi_data->msi_virqs) | |
91 | return -ENOMEM; | |
92 | ||
3fb79338 RS |
93 | list_for_each_entry(entry, &dev->msi_list, list) { |
94 | int_no = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1); | |
95 | if (int_no >= 0) | |
96 | break; | |
97 | if (int_no < 0) { | |
98 | pr_debug("%s: fail allocating msi interrupt\n", | |
99 | __func__); | |
100 | } | |
101 | virq = irq_of_parse_and_map(msi_data->msi_dev, int_no); | |
102 | if (virq == NO_IRQ) { | |
103 | dev_err(&dev->dev, "%s: fail mapping irq\n", __func__); | |
104 | msi_bitmap_free_hwirqs(&msi_data->bitmap, int_no, 1); | |
105 | return -ENOSPC; | |
106 | } | |
107 | dev_dbg(&dev->dev, "%s: virq = %d\n", __func__, virq); | |
108 | ||
109 | /* Setup msi address space */ | |
110 | msg.address_hi = msi_data->msi_addr_hi; | |
111 | msg.address_lo = msi_data->msi_addr_lo; | |
112 | ||
113 | irq_set_msi_desc(virq, entry); | |
114 | msg.data = int_no; | |
115 | write_msi_msg(virq, &msg); | |
116 | } | |
117 | return 0; | |
118 | } | |
119 | ||
120 | void ppc4xx_teardown_msi_irqs(struct pci_dev *dev) | |
121 | { | |
122 | struct msi_desc *entry; | |
123 | struct ppc4xx_msi *msi_data = &ppc4xx_msi; | |
124 | ||
125 | dev_dbg(&dev->dev, "PCIE-MSI: tearing down msi irqs\n"); | |
126 | ||
127 | list_for_each_entry(entry, &dev->msi_list, list) { | |
128 | if (entry->irq == NO_IRQ) | |
129 | continue; | |
130 | irq_set_msi_desc(entry->irq, NULL); | |
131 | msi_bitmap_free_hwirqs(&msi_data->bitmap, | |
132 | virq_to_hw(entry->irq), 1); | |
133 | irq_dispose_mapping(entry->irq); | |
134 | } | |
135 | } | |
136 | ||
137 | static int ppc4xx_msi_check_device(struct pci_dev *pdev, int nvec, int type) | |
138 | { | |
139 | dev_dbg(&pdev->dev, "PCIE-MSI:%s called. vec %x type %d\n", | |
140 | __func__, nvec, type); | |
141 | if (type == PCI_CAP_ID_MSIX) | |
142 | pr_debug("ppc4xx msi: MSI-X untested, trying anyway.\n"); | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | static int ppc4xx_setup_pcieh_hw(struct platform_device *dev, | |
148 | struct resource res, struct ppc4xx_msi *msi) | |
149 | { | |
150 | const u32 *msi_data; | |
151 | const u32 *msi_mask; | |
152 | const u32 *sdr_addr; | |
153 | dma_addr_t msi_phys; | |
154 | void *msi_virt; | |
155 | ||
156 | sdr_addr = of_get_property(dev->dev.of_node, "sdr-base", NULL); | |
157 | if (!sdr_addr) | |
158 | return -1; | |
159 | ||
247540b0 ML |
160 | mtdcri(SDR0, *sdr_addr, upper_32_bits(res.start)); /*HIGH addr */ |
161 | mtdcri(SDR0, *sdr_addr + 1, lower_32_bits(res.start)); /* Low addr */ | |
3fb79338 RS |
162 | |
163 | msi->msi_dev = of_find_node_by_name(NULL, "ppc4xx-msi"); | |
247540b0 | 164 | if (!msi->msi_dev) |
3fb79338 RS |
165 | return -ENODEV; |
166 | ||
167 | msi->msi_regs = of_iomap(msi->msi_dev, 0); | |
168 | if (!msi->msi_regs) { | |
169 | dev_err(&dev->dev, "of_iomap problem failed\n"); | |
170 | return -ENOMEM; | |
171 | } | |
172 | dev_dbg(&dev->dev, "PCIE-MSI: msi register mapped 0x%x 0x%x\n", | |
173 | (u32) (msi->msi_regs + PEIH_TERMADH), (u32) (msi->msi_regs)); | |
174 | ||
175 | msi_virt = dma_alloc_coherent(&dev->dev, 64, &msi_phys, GFP_KERNEL); | |
247540b0 ML |
176 | if (!msi_virt) |
177 | return -ENOMEM; | |
dce4c92d JB |
178 | msi->msi_addr_hi = upper_32_bits(msi_phys); |
179 | msi->msi_addr_lo = lower_32_bits(msi_phys & 0xffffffff); | |
247540b0 ML |
180 | dev_dbg(&dev->dev, "PCIE-MSI: msi address high 0x%x, low 0x%x\n", |
181 | msi->msi_addr_hi, msi->msi_addr_lo); | |
3fb79338 RS |
182 | |
183 | /* Progam the Interrupt handler Termination addr registers */ | |
184 | out_be32(msi->msi_regs + PEIH_TERMADH, msi->msi_addr_hi); | |
185 | out_be32(msi->msi_regs + PEIH_TERMADL, msi->msi_addr_lo); | |
186 | ||
187 | msi_data = of_get_property(dev->dev.of_node, "msi-data", NULL); | |
188 | if (!msi_data) | |
189 | return -1; | |
190 | msi_mask = of_get_property(dev->dev.of_node, "msi-mask", NULL); | |
191 | if (!msi_mask) | |
192 | return -1; | |
193 | /* Program MSI Expected data and Mask bits */ | |
194 | out_be32(msi->msi_regs + PEIH_MSIED, *msi_data); | |
195 | out_be32(msi->msi_regs + PEIH_MSIMK, *msi_mask); | |
196 | ||
247540b0 ML |
197 | dma_free_coherent(&dev->dev, 64, msi_virt, msi_phys); |
198 | ||
3fb79338 RS |
199 | return 0; |
200 | } | |
201 | ||
202 | static int ppc4xx_of_msi_remove(struct platform_device *dev) | |
203 | { | |
204 | struct ppc4xx_msi *msi = dev->dev.platform_data; | |
205 | int i; | |
206 | int virq; | |
207 | ||
247540b0 | 208 | for (i = 0; i < msi_irqs; i++) { |
3fb79338 RS |
209 | virq = msi->msi_virqs[i]; |
210 | if (virq != NO_IRQ) | |
211 | irq_dispose_mapping(virq); | |
212 | } | |
213 | ||
214 | if (msi->bitmap.bitmap) | |
215 | msi_bitmap_free(&msi->bitmap); | |
216 | iounmap(msi->msi_regs); | |
217 | of_node_put(msi->msi_dev); | |
218 | kfree(msi); | |
219 | ||
220 | return 0; | |
221 | } | |
222 | ||
cad5cef6 | 223 | static int ppc4xx_msi_probe(struct platform_device *dev) |
3fb79338 RS |
224 | { |
225 | struct ppc4xx_msi *msi; | |
226 | struct resource res; | |
227 | int err = 0; | |
228 | ||
3fb79338 RS |
229 | dev_dbg(&dev->dev, "PCIE-MSI: Setting up MSI support...\n"); |
230 | ||
231 | msi = kzalloc(sizeof(struct ppc4xx_msi), GFP_KERNEL); | |
232 | if (!msi) { | |
233 | dev_err(&dev->dev, "No memory for MSI structure\n"); | |
234 | return -ENOMEM; | |
235 | } | |
236 | dev->dev.platform_data = msi; | |
237 | ||
238 | /* Get MSI ranges */ | |
239 | err = of_address_to_resource(dev->dev.of_node, 0, &res); | |
240 | if (err) { | |
241 | dev_err(&dev->dev, "%s resource error!\n", | |
242 | dev->dev.of_node->full_name); | |
243 | goto error_out; | |
244 | } | |
245 | ||
247540b0 ML |
246 | msi_irqs = of_irq_count(dev->dev.of_node); |
247 | if (!msi_irqs) | |
248 | return -ENODEV; | |
249 | ||
3fb79338 RS |
250 | if (ppc4xx_setup_pcieh_hw(dev, res, msi)) |
251 | goto error_out; | |
252 | ||
253 | err = ppc4xx_msi_init_allocator(dev, msi); | |
254 | if (err) { | |
255 | dev_err(&dev->dev, "Error allocating MSI bitmap\n"); | |
256 | goto error_out; | |
257 | } | |
247540b0 | 258 | ppc4xx_msi = *msi; |
3fb79338 RS |
259 | |
260 | ppc_md.setup_msi_irqs = ppc4xx_setup_msi_irqs; | |
261 | ppc_md.teardown_msi_irqs = ppc4xx_teardown_msi_irqs; | |
262 | ppc_md.msi_check_device = ppc4xx_msi_check_device; | |
263 | return err; | |
264 | ||
265 | error_out: | |
266 | ppc4xx_of_msi_remove(dev); | |
267 | return err; | |
268 | } | |
269 | static const struct of_device_id ppc4xx_msi_ids[] = { | |
270 | { | |
271 | .compatible = "amcc,ppc4xx-msi", | |
272 | }, | |
273 | {} | |
274 | }; | |
275 | static struct platform_driver ppc4xx_msi_driver = { | |
276 | .probe = ppc4xx_msi_probe, | |
277 | .remove = ppc4xx_of_msi_remove, | |
278 | .driver = { | |
279 | .name = "ppc4xx-msi", | |
280 | .owner = THIS_MODULE, | |
281 | .of_match_table = ppc4xx_msi_ids, | |
282 | }, | |
283 | ||
284 | }; | |
285 | ||
286 | static __init int ppc4xx_msi_init(void) | |
287 | { | |
288 | return platform_driver_register(&ppc4xx_msi_driver); | |
289 | } | |
290 | ||
291 | subsys_initcall(ppc4xx_msi_init); |