2007-05-08 06:58:37 +04:00
/*
* Copyright 2006 , Segher Boessenkool , IBM Corporation .
* Copyright 2006 - 2007 , Michael Ellerman , IBM Corporation .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; version 2 of the
* License .
*
*/
# include <linux/irq.h>
# include <linux/bootmem.h>
# include <linux/msi.h>
# include <asm/mpic.h>
# include <asm/prom.h>
# include <asm/hw_irq.h>
# include <asm/ppc-pci.h>
2008-08-06 03:10:03 +04:00
# include <asm/msi_bitmap.h>
2007-05-08 06:58:37 +04:00
# include "mpic.h"
/* A bit ugly, can we get this from the pci_dev somehow? */
static struct mpic * msi_mpic ;
2010-09-28 18:46:51 +04:00
static void mpic_u3msi_mask_irq ( struct irq_data * data )
2007-05-08 06:58:37 +04:00
{
2010-09-28 18:46:51 +04:00
mask_msi_irq ( data ) ;
2011-03-09 01:26:43 +03:00
mpic_mask_irq ( data ) ;
2007-05-08 06:58:37 +04:00
}
2010-09-28 18:46:51 +04:00
static void mpic_u3msi_unmask_irq ( struct irq_data * data )
2007-05-08 06:58:37 +04:00
{
2011-03-09 01:26:43 +03:00
mpic_unmask_irq ( data ) ;
2010-09-28 18:46:51 +04:00
unmask_msi_irq ( data ) ;
2007-05-08 06:58:37 +04:00
}
static struct irq_chip mpic_u3msi_chip = {
2011-03-09 01:26:43 +03:00
. irq_shutdown = mpic_u3msi_mask_irq ,
. irq_mask = mpic_u3msi_mask_irq ,
. irq_unmask = mpic_u3msi_unmask_irq ,
. irq_eoi = mpic_end_irq ,
. irq_set_type = mpic_set_irq_type ,
. irq_set_affinity = mpic_set_affinity ,
. name = " MPIC-U3MSI " ,
2007-05-08 06:58:37 +04:00
} ;
static u64 read_ht_magic_addr ( struct pci_dev * pdev , unsigned int pos )
{
u8 flags ;
u32 tmp ;
u64 addr ;
pci_read_config_byte ( pdev , pos + HT_MSI_FLAGS , & flags ) ;
if ( flags & HT_MSI_FLAGS_FIXED )
return HT_MSI_FIXED_ADDR ;
pci_read_config_dword ( pdev , pos + HT_MSI_ADDR_LO , & tmp ) ;
addr = tmp & HT_MSI_ADDR_LO_MASK ;
pci_read_config_dword ( pdev , pos + HT_MSI_ADDR_HI , & tmp ) ;
addr = addr | ( ( u64 ) tmp < < 32 ) ;
return addr ;
}
2009-12-14 18:31:13 +03:00
static u64 find_ht_magic_addr ( struct pci_dev * pdev , unsigned int hwirq )
2007-05-08 06:58:37 +04:00
{
struct pci_bus * bus ;
unsigned int pos ;
2009-12-14 18:31:13 +03:00
for ( bus = pdev - > bus ; bus & & bus - > self ; bus = bus - > parent ) {
2007-05-08 06:58:37 +04:00
pos = pci_find_ht_capability ( bus - > self , HT_CAPTYPE_MSI_MAPPING ) ;
if ( pos )
return read_ht_magic_addr ( bus - > self , pos ) ;
}
return 0 ;
}
2009-12-14 18:31:13 +03:00
static u64 find_u4_magic_addr ( struct pci_dev * pdev , unsigned int hwirq )
{
struct pci_controller * hose = pci_bus_to_host ( pdev - > bus ) ;
/* U4 PCIe MSIs need to write to the special register in
* the bridge that generates interrupts . There should be
* theorically a register at 0xf8005000 where you just write
* the MSI number and that triggers the right interrupt , but
* unfortunately , this is busted in HW , the bridge endian swaps
* the value and hits the wrong nibble in the register .
*
* So instead we use another register set which is used normally
* for converting HT interrupts to MPIC interrupts , which decodes
* the interrupt number as part of the low address bits
*
* This will not work if we ever use more than one legacy MSI in
* a block but we never do . For one MSI or multiple MSI - X where
* each interrupt address can be specified separately , it works
* just fine .
*/
if ( of_device_is_compatible ( hose - > dn , " u4-pcie " ) | |
of_device_is_compatible ( hose - > dn , " U4-pcie " ) )
return 0xf8004000 | ( hwirq < < 4 ) ;
return 0 ;
}
2007-05-08 06:58:37 +04:00
static int u3msi_msi_check_device ( struct pci_dev * pdev , int nvec , int type )
{
if ( type = = PCI_CAP_ID_MSIX )
pr_debug ( " u3msi: MSI-X untested, trying anyway. \n " ) ;
/* If we can't find a magic address then MSI ain't gonna work */
2009-12-14 18:31:13 +03:00
if ( find_ht_magic_addr ( pdev , 0 ) = = 0 & &
find_u4_magic_addr ( pdev , 0 ) = = 0 ) {
2007-05-08 06:58:37 +04:00
pr_debug ( " u3msi: no magic address found for %s \n " ,
pci_name ( pdev ) ) ;
return - ENXIO ;
}
return 0 ;
}
static void u3msi_teardown_msi_irqs ( struct pci_dev * pdev )
{
struct msi_desc * entry ;
list_for_each_entry ( entry , & pdev - > msi_list , list ) {
if ( entry - > irq = = NO_IRQ )
continue ;
2011-03-25 18:45:20 +03:00
irq_set_msi_desc ( entry - > irq , NULL ) ;
2008-08-06 03:10:03 +04:00
msi_bitmap_free_hwirqs ( & msi_mpic - > msi_bitmap ,
virq_to_hw ( entry - > irq ) , 1 ) ;
2007-05-08 06:58:37 +04:00
irq_dispose_mapping ( entry - > irq ) ;
}
return ;
}
static int u3msi_setup_msi_irqs ( struct pci_dev * pdev , int nvec , int type )
{
unsigned int virq ;
struct msi_desc * entry ;
struct msi_msg msg ;
2007-09-20 10:36:51 +04:00
u64 addr ;
2008-08-06 03:10:03 +04:00
int hwirq ;
2007-09-20 10:36:51 +04:00
2007-05-08 06:58:37 +04:00
list_for_each_entry ( entry , & pdev - > msi_list , list ) {
2008-08-06 03:10:03 +04:00
hwirq = msi_bitmap_alloc_hwirqs ( & msi_mpic - > msi_bitmap , 1 ) ;
if ( hwirq < 0 ) {
2007-05-08 06:58:37 +04:00
pr_debug ( " u3msi: failed allocating hwirq \n " ) ;
2008-08-06 03:10:03 +04:00
return hwirq ;
2007-05-08 06:58:37 +04:00
}
2009-12-14 18:31:13 +03:00
addr = find_ht_magic_addr ( pdev , hwirq ) ;
if ( addr = = 0 )
addr = find_u4_magic_addr ( pdev , hwirq ) ;
msg . address_lo = addr & 0xFFFFFFFF ;
msg . address_hi = addr > > 32 ;
2007-05-08 06:58:37 +04:00
virq = irq_create_mapping ( msi_mpic - > irqhost , hwirq ) ;
if ( virq = = NO_IRQ ) {
2008-08-06 03:10:03 +04:00
pr_debug ( " u3msi: failed mapping hwirq 0x%x \n " , hwirq ) ;
msi_bitmap_free_hwirqs ( & msi_mpic - > msi_bitmap , hwirq , 1 ) ;
2007-09-20 10:36:47 +04:00
return - ENOSPC ;
2007-05-08 06:58:37 +04:00
}
2011-03-25 18:45:20 +03:00
irq_set_msi_desc ( virq , entry ) ;
irq_set_chip ( virq , & mpic_u3msi_chip ) ;
irq_set_irq_type ( virq , IRQ_TYPE_EDGE_RISING ) ;
2007-05-08 06:58:37 +04:00
2008-08-06 03:10:03 +04:00
pr_debug ( " u3msi: allocated virq 0x%x (hw 0x%x) addr 0x%lx \n " ,
virq , hwirq , ( unsigned long ) addr ) ;
2007-09-20 10:36:51 +04:00
2009-12-14 18:31:13 +03:00
printk ( " u3msi: allocated virq 0x%x (hw 0x%x) addr 0x%lx \n " ,
virq , hwirq , ( unsigned long ) addr ) ;
2007-09-20 10:36:51 +04:00
msg . data = hwirq ;
2007-05-08 06:58:37 +04:00
write_msi_msg ( virq , & msg ) ;
hwirq + + ;
}
return 0 ;
}
int mpic_u3msi_init ( struct mpic * mpic )
{
int rc ;
rc = mpic_msi_init_allocator ( mpic ) ;
if ( rc ) {
pr_debug ( " u3msi: Error allocating bitmap! \n " ) ;
return rc ;
}
pr_debug ( " u3msi: Registering MPIC U3 MSI callbacks. \n " ) ;
BUG_ON ( msi_mpic ) ;
msi_mpic = mpic ;
WARN_ON ( ppc_md . setup_msi_irqs ) ;
ppc_md . setup_msi_irqs = u3msi_setup_msi_irqs ;
ppc_md . teardown_msi_irqs = u3msi_teardown_msi_irqs ;
ppc_md . msi_check_device = u3msi_msi_check_device ;
return 0 ;
}