diff options
author | Jingoo Han <jg1.han@samsung.com> | 2013-09-06 02:54:59 -0400 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2013-09-25 18:31:27 -0400 |
commit | f342d940ee0e3a2b5197fd4fbade1cb6bbc960b7 (patch) | |
tree | 04152ee8ca07e664560a370bec2c535fb20ccdb0 /drivers/pci/host/pcie-designware.c | |
parent | 4af8225567065f1825baed259822b0d26c343fad (diff) |
PCI: exynos: Add support for MSI
This patch adds support for Message Signaled Interrupt in the
Exynos PCIe driver using Synopsys designware PCIe core IP.
Signed-off-by: Siva Reddy Kallam <siva.kallam@samsung.com>
Signed-off-by: Srikanth T Shivanand <ts.srikanth@samsung.com>
Signed-off-by: Jingoo Han <jg1.han@samsung.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Pratyush Anand <pratyush.anand@st.com>
Cc: Mohit KUMAR <Mohit.KUMAR@st.com>
Diffstat (limited to 'drivers/pci/host/pcie-designware.c')
-rw-r--r-- | drivers/pci/host/pcie-designware.c | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index c10e9ac9bbbc..896301788e9d 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c | |||
@@ -11,8 +11,11 @@ | |||
11 | * published by the Free Software Foundation. | 11 | * published by the Free Software Foundation. |
12 | */ | 12 | */ |
13 | 13 | ||
14 | #include <linux/irq.h> | ||
15 | #include <linux/irqdomain.h> | ||
14 | #include <linux/kernel.h> | 16 | #include <linux/kernel.h> |
15 | #include <linux/module.h> | 17 | #include <linux/module.h> |
18 | #include <linux/msi.h> | ||
16 | #include <linux/of_address.h> | 19 | #include <linux/of_address.h> |
17 | #include <linux/pci.h> | 20 | #include <linux/pci.h> |
18 | #include <linux/pci_regs.h> | 21 | #include <linux/pci_regs.h> |
@@ -142,6 +145,204 @@ int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, | |||
142 | return ret; | 145 | return ret; |
143 | } | 146 | } |
144 | 147 | ||
148 | static struct irq_chip dw_msi_irq_chip = { | ||
149 | .name = "PCI-MSI", | ||
150 | .irq_enable = unmask_msi_irq, | ||
151 | .irq_disable = mask_msi_irq, | ||
152 | .irq_mask = mask_msi_irq, | ||
153 | .irq_unmask = unmask_msi_irq, | ||
154 | }; | ||
155 | |||
156 | /* MSI int handler */ | ||
157 | void dw_handle_msi_irq(struct pcie_port *pp) | ||
158 | { | ||
159 | unsigned long val; | ||
160 | int i, pos; | ||
161 | |||
162 | for (i = 0; i < MAX_MSI_CTRLS; i++) { | ||
163 | dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, | ||
164 | (u32 *)&val); | ||
165 | if (val) { | ||
166 | pos = 0; | ||
167 | while ((pos = find_next_bit(&val, 32, pos)) != 32) { | ||
168 | generic_handle_irq(pp->msi_irq_start | ||
169 | + (i * 32) + pos); | ||
170 | pos++; | ||
171 | } | ||
172 | } | ||
173 | dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, val); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | void dw_pcie_msi_init(struct pcie_port *pp) | ||
178 | { | ||
179 | pp->msi_data = __get_free_pages(GFP_KERNEL, 0); | ||
180 | |||
181 | /* program the msi_data */ | ||
182 | dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, | ||
183 | virt_to_phys((void *)pp->msi_data)); | ||
184 | dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, 0); | ||
185 | } | ||
186 | |||
187 | static int find_valid_pos0(struct pcie_port *pp, int msgvec, int pos, int *pos0) | ||
188 | { | ||
189 | int flag = 1; | ||
190 | |||
191 | do { | ||
192 | pos = find_next_zero_bit(pp->msi_irq_in_use, | ||
193 | MAX_MSI_IRQS, pos); | ||
194 | /*if you have reached to the end then get out from here.*/ | ||
195 | if (pos == MAX_MSI_IRQS) | ||
196 | return -ENOSPC; | ||
197 | /* | ||
198 | * Check if this position is at correct offset.nvec is always a | ||
199 | * power of two. pos0 must be nvec bit alligned. | ||
200 | */ | ||
201 | if (pos % msgvec) | ||
202 | pos += msgvec - (pos % msgvec); | ||
203 | else | ||
204 | flag = 0; | ||
205 | } while (flag); | ||
206 | |||
207 | *pos0 = pos; | ||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos) | ||
212 | { | ||
213 | int res, bit, irq, pos0, pos1, i; | ||
214 | u32 val; | ||
215 | struct pcie_port *pp = sys_to_pcie(desc->dev->bus->sysdata); | ||
216 | |||
217 | if (!pp) { | ||
218 | BUG(); | ||
219 | return -EINVAL; | ||
220 | } | ||
221 | |||
222 | pos0 = find_first_zero_bit(pp->msi_irq_in_use, | ||
223 | MAX_MSI_IRQS); | ||
224 | if (pos0 % no_irqs) { | ||
225 | if (find_valid_pos0(pp, no_irqs, pos0, &pos0)) | ||
226 | goto no_valid_irq; | ||
227 | } | ||
228 | if (no_irqs > 1) { | ||
229 | pos1 = find_next_bit(pp->msi_irq_in_use, | ||
230 | MAX_MSI_IRQS, pos0); | ||
231 | /* there must be nvec number of consecutive free bits */ | ||
232 | while ((pos1 - pos0) < no_irqs) { | ||
233 | if (find_valid_pos0(pp, no_irqs, pos1, &pos0)) | ||
234 | goto no_valid_irq; | ||
235 | pos1 = find_next_bit(pp->msi_irq_in_use, | ||
236 | MAX_MSI_IRQS, pos0); | ||
237 | } | ||
238 | } | ||
239 | |||
240 | irq = (pp->msi_irq_start + pos0); | ||
241 | |||
242 | if ((irq + no_irqs) > (pp->msi_irq_start + MAX_MSI_IRQS-1)) | ||
243 | goto no_valid_irq; | ||
244 | |||
245 | i = 0; | ||
246 | while (i < no_irqs) { | ||
247 | set_bit(pos0 + i, pp->msi_irq_in_use); | ||
248 | irq_alloc_descs((irq + i), (irq + i), 1, 0); | ||
249 | irq_set_msi_desc(irq + i, desc); | ||
250 | /*Enable corresponding interrupt in MSI interrupt controller */ | ||
251 | res = ((pos0 + i) / 32) * 12; | ||
252 | bit = (pos0 + i) % 32; | ||
253 | dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); | ||
254 | val |= 1 << bit; | ||
255 | dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); | ||
256 | i++; | ||
257 | } | ||
258 | |||
259 | *pos = pos0; | ||
260 | return irq; | ||
261 | |||
262 | no_valid_irq: | ||
263 | *pos = pos0; | ||
264 | return -ENOSPC; | ||
265 | } | ||
266 | |||
267 | static void clear_irq(unsigned int irq) | ||
268 | { | ||
269 | int res, bit, val, pos; | ||
270 | struct irq_desc *desc; | ||
271 | struct msi_desc *msi; | ||
272 | struct pcie_port *pp; | ||
273 | |||
274 | /* get the port structure */ | ||
275 | desc = irq_to_desc(irq); | ||
276 | msi = irq_desc_get_msi_desc(desc); | ||
277 | pp = sys_to_pcie(msi->dev->bus->sysdata); | ||
278 | if (!pp) { | ||
279 | BUG(); | ||
280 | return; | ||
281 | } | ||
282 | |||
283 | pos = irq - pp->msi_irq_start; | ||
284 | |||
285 | irq_free_desc(irq); | ||
286 | |||
287 | clear_bit(pos, pp->msi_irq_in_use); | ||
288 | |||
289 | /* Disable corresponding interrupt on MSI interrupt controller */ | ||
290 | res = (pos / 32) * 12; | ||
291 | bit = pos % 32; | ||
292 | dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); | ||
293 | val &= ~(1 << bit); | ||
294 | dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); | ||
295 | } | ||
296 | |||
297 | static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, | ||
298 | struct msi_desc *desc) | ||
299 | { | ||
300 | int irq, pos, msgvec; | ||
301 | u16 msg_ctr; | ||
302 | struct msi_msg msg; | ||
303 | struct pcie_port *pp = sys_to_pcie(pdev->bus->sysdata); | ||
304 | |||
305 | if (!pp) { | ||
306 | BUG(); | ||
307 | return -EINVAL; | ||
308 | } | ||
309 | |||
310 | pci_read_config_word(pdev, desc->msi_attrib.pos+PCI_MSI_FLAGS, | ||
311 | &msg_ctr); | ||
312 | msgvec = (msg_ctr&PCI_MSI_FLAGS_QSIZE) >> 4; | ||
313 | if (msgvec == 0) | ||
314 | msgvec = (msg_ctr & PCI_MSI_FLAGS_QMASK) >> 1; | ||
315 | if (msgvec > 5) | ||
316 | msgvec = 0; | ||
317 | |||
318 | irq = assign_irq((1 << msgvec), desc, &pos); | ||
319 | if (irq < 0) | ||
320 | return irq; | ||
321 | |||
322 | msg_ctr &= ~PCI_MSI_FLAGS_QSIZE; | ||
323 | msg_ctr |= msgvec << 4; | ||
324 | pci_write_config_word(pdev, desc->msi_attrib.pos + PCI_MSI_FLAGS, | ||
325 | msg_ctr); | ||
326 | desc->msi_attrib.multiple = msgvec; | ||
327 | |||
328 | msg.address_lo = virt_to_phys((void *)pp->msi_data); | ||
329 | msg.address_hi = 0x0; | ||
330 | msg.data = pos; | ||
331 | write_msi_msg(irq, &msg); | ||
332 | |||
333 | return 0; | ||
334 | } | ||
335 | |||
336 | static void dw_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) | ||
337 | { | ||
338 | clear_irq(irq); | ||
339 | } | ||
340 | |||
341 | static struct msi_chip dw_pcie_msi_chip = { | ||
342 | .setup_irq = dw_msi_setup_irq, | ||
343 | .teardown_irq = dw_msi_teardown_irq, | ||
344 | }; | ||
345 | |||
145 | int dw_pcie_link_up(struct pcie_port *pp) | 346 | int dw_pcie_link_up(struct pcie_port *pp) |
146 | { | 347 | { |
147 | if (pp->ops->link_up) | 348 | if (pp->ops->link_up) |
@@ -150,6 +351,20 @@ int dw_pcie_link_up(struct pcie_port *pp) | |||
150 | return 0; | 351 | return 0; |
151 | } | 352 | } |
152 | 353 | ||
354 | static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, | ||
355 | irq_hw_number_t hwirq) | ||
356 | { | ||
357 | irq_set_chip_and_handler(irq, &dw_msi_irq_chip, handle_simple_irq); | ||
358 | irq_set_chip_data(irq, domain->host_data); | ||
359 | set_irq_flags(irq, IRQF_VALID); | ||
360 | |||
361 | return 0; | ||
362 | } | ||
363 | |||
364 | static const struct irq_domain_ops msi_domain_ops = { | ||
365 | .map = dw_pcie_msi_map, | ||
366 | }; | ||
367 | |||
153 | int __init dw_pcie_host_init(struct pcie_port *pp) | 368 | int __init dw_pcie_host_init(struct pcie_port *pp) |
154 | { | 369 | { |
155 | struct device_node *np = pp->dev->of_node; | 370 | struct device_node *np = pp->dev->of_node; |
@@ -157,6 +372,8 @@ int __init dw_pcie_host_init(struct pcie_port *pp) | |||
157 | struct of_pci_range_parser parser; | 372 | struct of_pci_range_parser parser; |
158 | u32 val; | 373 | u32 val; |
159 | 374 | ||
375 | struct irq_domain *irq_domain; | ||
376 | |||
160 | if (of_pci_range_parser_init(&parser, np)) { | 377 | if (of_pci_range_parser_init(&parser, np)) { |
161 | dev_err(pp->dev, "missing ranges property\n"); | 378 | dev_err(pp->dev, "missing ranges property\n"); |
162 | return -EINVAL; | 379 | return -EINVAL; |
@@ -223,6 +440,18 @@ int __init dw_pcie_host_init(struct pcie_port *pp) | |||
223 | return -EINVAL; | 440 | return -EINVAL; |
224 | } | 441 | } |
225 | 442 | ||
443 | if (IS_ENABLED(CONFIG_PCI_MSI)) { | ||
444 | irq_domain = irq_domain_add_linear(pp->dev->of_node, | ||
445 | MAX_MSI_IRQS, &msi_domain_ops, | ||
446 | &dw_pcie_msi_chip); | ||
447 | if (!irq_domain) { | ||
448 | dev_err(pp->dev, "irq domain init failed\n"); | ||
449 | return -ENXIO; | ||
450 | } | ||
451 | |||
452 | pp->msi_irq_start = irq_find_mapping(irq_domain, 0); | ||
453 | } | ||
454 | |||
226 | if (pp->ops->host_init) | 455 | if (pp->ops->host_init) |
227 | pp->ops->host_init(pp); | 456 | pp->ops->host_init(pp); |
228 | 457 | ||
@@ -485,10 +714,21 @@ int dw_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) | |||
485 | return pp->irq; | 714 | return pp->irq; |
486 | } | 715 | } |
487 | 716 | ||
717 | static void dw_pcie_add_bus(struct pci_bus *bus) | ||
718 | { | ||
719 | if (IS_ENABLED(CONFIG_PCI_MSI)) { | ||
720 | struct pcie_port *pp = sys_to_pcie(bus->sysdata); | ||
721 | |||
722 | dw_pcie_msi_chip.dev = pp->dev; | ||
723 | bus->msi = &dw_pcie_msi_chip; | ||
724 | } | ||
725 | } | ||
726 | |||
488 | static struct hw_pci dw_pci = { | 727 | static struct hw_pci dw_pci = { |
489 | .setup = dw_pcie_setup, | 728 | .setup = dw_pcie_setup, |
490 | .scan = dw_pcie_scan_bus, | 729 | .scan = dw_pcie_scan_bus, |
491 | .map_irq = dw_pcie_map_irq, | 730 | .map_irq = dw_pcie_map_irq, |
731 | .add_bus = dw_pcie_add_bus, | ||
492 | }; | 732 | }; |
493 | 733 | ||
494 | void dw_pcie_setup_rc(struct pcie_port *pp) | 734 | void dw_pcie_setup_rc(struct pcie_port *pp) |