aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/host/pcie-designware.c
diff options
context:
space:
mode:
authorJingoo Han <jg1.han@samsung.com>2013-09-06 02:54:59 -0400
committerBjorn Helgaas <bhelgaas@google.com>2013-09-25 18:31:27 -0400
commitf342d940ee0e3a2b5197fd4fbade1cb6bbc960b7 (patch)
tree04152ee8ca07e664560a370bec2c535fb20ccdb0 /drivers/pci/host/pcie-designware.c
parent4af8225567065f1825baed259822b0d26c343fad (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.c240
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
148static 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 */
157void 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
177void 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
187static 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
211static 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
262no_valid_irq:
263 *pos = pos0;
264 return -ENOSPC;
265}
266
267static 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
297static 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
336static void dw_msi_teardown_irq(struct msi_chip *chip, unsigned int irq)
337{
338 clear_irq(irq);
339}
340
341static struct msi_chip dw_pcie_msi_chip = {
342 .setup_irq = dw_msi_setup_irq,
343 .teardown_irq = dw_msi_teardown_irq,
344};
345
145int dw_pcie_link_up(struct pcie_port *pp) 346int 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
354static 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
364static const struct irq_domain_ops msi_domain_ops = {
365 .map = dw_pcie_msi_map,
366};
367
153int __init dw_pcie_host_init(struct pcie_port *pp) 368int __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
717static 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
488static struct hw_pci dw_pci = { 727static 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
494void dw_pcie_setup_rc(struct pcie_port *pp) 734void dw_pcie_setup_rc(struct pcie_port *pp)