2011-03-29 23:10:24 +00:00
/*
* Adding PCI - E MSI support for PPC4XX SoCs .
*
* Copyright ( c ) 2010 , Applied Micro Circuits Corporation
* Authors : Tirumala R Marri < tmarri @ apm . com >
* Feng Kan < fkan @ apm . com >
*
* 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 ; either version 2 of
* the License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston ,
* MA 02111 - 1307 USA
*/
# include <linux/irq.h>
# include <linux/bootmem.h>
# include <linux/pci.h>
# include <linux/msi.h>
# include <linux/of_platform.h>
# include <linux/interrupt.h>
2011-07-29 16:19:31 +10:00
# include <linux/export.h>
2012-03-18 17:59:08 +00:00
# include <linux/kernel.h>
2011-03-29 23:10:24 +00:00
# include <asm/prom.h>
# include <asm/hw_irq.h>
# include <asm/ppc-pci.h>
2012-03-18 17:59:08 +00:00
# include <asm/dcr.h>
2011-03-29 23:10:24 +00:00
# include <asm/dcr-regs.h>
# include <asm/msi_bitmap.h>
# define PEIH_TERMADH 0x00
# define PEIH_TERMADL 0x08
# define PEIH_MSIED 0x10
# define PEIH_MSIMK 0x18
# define PEIH_MSIASS 0x20
# define PEIH_FLUSH0 0x30
# define PEIH_FLUSH1 0x38
# define PEIH_CNTRST 0x48
2012-03-18 17:59:08 +00:00
static int msi_irqs ;
2011-03-29 23:10:24 +00:00
struct ppc4xx_msi {
u32 msi_addr_lo ;
u32 msi_addr_hi ;
void __iomem * msi_regs ;
2012-03-18 17:59:08 +00:00
int * msi_virqs ;
2011-03-29 23:10:24 +00:00
struct msi_bitmap bitmap ;
struct device_node * msi_dev ;
} ;
static struct ppc4xx_msi ppc4xx_msi ;
static int ppc4xx_msi_init_allocator ( struct platform_device * dev ,
struct ppc4xx_msi * msi_data )
{
int err ;
2012-03-18 17:59:08 +00:00
err = msi_bitmap_alloc ( & msi_data - > bitmap , msi_irqs ,
2011-03-29 23:10:24 +00:00
dev - > dev . of_node ) ;
if ( err )
return err ;
err = msi_bitmap_reserve_dt_hwirqs ( & msi_data - > bitmap ) ;
if ( err < 0 ) {
msi_bitmap_free ( & msi_data - > bitmap ) ;
return err ;
}
return 0 ;
}
static int ppc4xx_setup_msi_irqs ( struct pci_dev * dev , int nvec , int type )
{
int int_no = - ENOMEM ;
unsigned int virq ;
struct msi_msg msg ;
struct msi_desc * entry ;
struct ppc4xx_msi * msi_data = & ppc4xx_msi ;
2012-03-18 17:59:08 +00:00
msi_data - > msi_virqs = kmalloc ( ( msi_irqs ) * sizeof ( int ) ,
GFP_KERNEL ) ;
if ( ! msi_data - > msi_virqs )
return - ENOMEM ;
2011-03-29 23:10:24 +00:00
list_for_each_entry ( entry , & dev - > msi_list , list ) {
int_no = msi_bitmap_alloc_hwirqs ( & msi_data - > bitmap , 1 ) ;
if ( int_no > = 0 )
break ;
if ( int_no < 0 ) {
pr_debug ( " %s: fail allocating msi interrupt \n " ,
__func__ ) ;
}
virq = irq_of_parse_and_map ( msi_data - > msi_dev , int_no ) ;
if ( virq = = NO_IRQ ) {
dev_err ( & dev - > dev , " %s: fail mapping irq \n " , __func__ ) ;
msi_bitmap_free_hwirqs ( & msi_data - > bitmap , int_no , 1 ) ;
return - ENOSPC ;
}
dev_dbg ( & dev - > dev , " %s: virq = %d \n " , __func__ , virq ) ;
/* Setup msi address space */
msg . address_hi = msi_data - > msi_addr_hi ;
msg . address_lo = msi_data - > msi_addr_lo ;
irq_set_msi_desc ( virq , entry ) ;
msg . data = int_no ;
write_msi_msg ( virq , & msg ) ;
}
return 0 ;
}
void ppc4xx_teardown_msi_irqs ( struct pci_dev * dev )
{
struct msi_desc * entry ;
struct ppc4xx_msi * msi_data = & ppc4xx_msi ;
dev_dbg ( & dev - > dev , " PCIE-MSI: tearing down msi irqs \n " ) ;
list_for_each_entry ( entry , & dev - > msi_list , list ) {
if ( entry - > irq = = NO_IRQ )
continue ;
irq_set_msi_desc ( entry - > irq , NULL ) ;
msi_bitmap_free_hwirqs ( & msi_data - > bitmap ,
virq_to_hw ( entry - > irq ) , 1 ) ;
irq_dispose_mapping ( entry - > irq ) ;
}
}
static int ppc4xx_msi_check_device ( struct pci_dev * pdev , int nvec , int type )
{
dev_dbg ( & pdev - > dev , " PCIE-MSI:%s called. vec %x type %d \n " ,
__func__ , nvec , type ) ;
if ( type = = PCI_CAP_ID_MSIX )
pr_debug ( " ppc4xx msi: MSI-X untested, trying anyway. \n " ) ;
return 0 ;
}
static int ppc4xx_setup_pcieh_hw ( struct platform_device * dev ,
struct resource res , struct ppc4xx_msi * msi )
{
const u32 * msi_data ;
const u32 * msi_mask ;
const u32 * sdr_addr ;
dma_addr_t msi_phys ;
void * msi_virt ;
sdr_addr = of_get_property ( dev - > dev . of_node , " sdr-base " , NULL ) ;
if ( ! sdr_addr )
return - 1 ;
2012-03-18 17:59:08 +00:00
mtdcri ( SDR0 , * sdr_addr , upper_32_bits ( res . start ) ) ; /*HIGH addr */
mtdcri ( SDR0 , * sdr_addr + 1 , lower_32_bits ( res . start ) ) ; /* Low addr */
2011-03-29 23:10:24 +00:00
msi - > msi_dev = of_find_node_by_name ( NULL , " ppc4xx-msi " ) ;
2012-03-18 17:59:08 +00:00
if ( ! msi - > msi_dev )
2011-03-29 23:10:24 +00:00
return - ENODEV ;
msi - > msi_regs = of_iomap ( msi - > msi_dev , 0 ) ;
if ( ! msi - > msi_regs ) {
dev_err ( & dev - > dev , " of_iomap problem failed \n " ) ;
return - ENOMEM ;
}
dev_dbg ( & dev - > dev , " PCIE-MSI: msi register mapped 0x%x 0x%x \n " ,
( u32 ) ( msi - > msi_regs + PEIH_TERMADH ) , ( u32 ) ( msi - > msi_regs ) ) ;
msi_virt = dma_alloc_coherent ( & dev - > dev , 64 , & msi_phys , GFP_KERNEL ) ;
2012-03-18 17:59:08 +00:00
if ( ! msi_virt )
return - ENOMEM ;
2012-05-03 20:13:13 -04:00
msi - > msi_addr_hi = upper_32_bits ( msi_phys ) ;
msi - > msi_addr_lo = lower_32_bits ( msi_phys & 0xffffffff ) ;
2012-03-18 17:59:08 +00:00
dev_dbg ( & dev - > dev , " PCIE-MSI: msi address high 0x%x, low 0x%x \n " ,
msi - > msi_addr_hi , msi - > msi_addr_lo ) ;
2011-03-29 23:10:24 +00:00
/* Progam the Interrupt handler Termination addr registers */
out_be32 ( msi - > msi_regs + PEIH_TERMADH , msi - > msi_addr_hi ) ;
out_be32 ( msi - > msi_regs + PEIH_TERMADL , msi - > msi_addr_lo ) ;
msi_data = of_get_property ( dev - > dev . of_node , " msi-data " , NULL ) ;
if ( ! msi_data )
return - 1 ;
msi_mask = of_get_property ( dev - > dev . of_node , " msi-mask " , NULL ) ;
if ( ! msi_mask )
return - 1 ;
/* Program MSI Expected data and Mask bits */
out_be32 ( msi - > msi_regs + PEIH_MSIED , * msi_data ) ;
out_be32 ( msi - > msi_regs + PEIH_MSIMK , * msi_mask ) ;
2012-03-18 17:59:08 +00:00
dma_free_coherent ( & dev - > dev , 64 , msi_virt , msi_phys ) ;
2011-03-29 23:10:24 +00:00
return 0 ;
}
static int ppc4xx_of_msi_remove ( struct platform_device * dev )
{
struct ppc4xx_msi * msi = dev - > dev . platform_data ;
int i ;
int virq ;
2012-03-18 17:59:08 +00:00
for ( i = 0 ; i < msi_irqs ; i + + ) {
2011-03-29 23:10:24 +00:00
virq = msi - > msi_virqs [ i ] ;
if ( virq ! = NO_IRQ )
irq_dispose_mapping ( virq ) ;
}
if ( msi - > bitmap . bitmap )
msi_bitmap_free ( & msi - > bitmap ) ;
iounmap ( msi - > msi_regs ) ;
of_node_put ( msi - > msi_dev ) ;
kfree ( msi ) ;
return 0 ;
}
2012-12-21 14:04:10 -08:00
static int ppc4xx_msi_probe ( struct platform_device * dev )
2011-03-29 23:10:24 +00:00
{
struct ppc4xx_msi * msi ;
struct resource res ;
int err = 0 ;
dev_dbg ( & dev - > dev , " PCIE-MSI: Setting up MSI support... \n " ) ;
msi = kzalloc ( sizeof ( struct ppc4xx_msi ) , GFP_KERNEL ) ;
if ( ! msi ) {
dev_err ( & dev - > dev , " No memory for MSI structure \n " ) ;
return - ENOMEM ;
}
dev - > dev . platform_data = msi ;
/* Get MSI ranges */
err = of_address_to_resource ( dev - > dev . of_node , 0 , & res ) ;
if ( err ) {
dev_err ( & dev - > dev , " %s resource error! \n " ,
dev - > dev . of_node - > full_name ) ;
goto error_out ;
}
2012-03-18 17:59:08 +00:00
msi_irqs = of_irq_count ( dev - > dev . of_node ) ;
if ( ! msi_irqs )
return - ENODEV ;
2011-03-29 23:10:24 +00:00
if ( ppc4xx_setup_pcieh_hw ( dev , res , msi ) )
goto error_out ;
err = ppc4xx_msi_init_allocator ( dev , msi ) ;
if ( err ) {
dev_err ( & dev - > dev , " Error allocating MSI bitmap \n " ) ;
goto error_out ;
}
2012-03-18 17:59:08 +00:00
ppc4xx_msi = * msi ;
2011-03-29 23:10:24 +00:00
ppc_md . setup_msi_irqs = ppc4xx_setup_msi_irqs ;
ppc_md . teardown_msi_irqs = ppc4xx_teardown_msi_irqs ;
ppc_md . msi_check_device = ppc4xx_msi_check_device ;
return err ;
error_out :
ppc4xx_of_msi_remove ( dev ) ;
return err ;
}
static const struct of_device_id ppc4xx_msi_ids [ ] = {
{
. compatible = " amcc,ppc4xx-msi " ,
} ,
{ }
} ;
static struct platform_driver ppc4xx_msi_driver = {
. probe = ppc4xx_msi_probe ,
. remove = ppc4xx_of_msi_remove ,
. driver = {
. name = " ppc4xx-msi " ,
. owner = THIS_MODULE ,
. of_match_table = ppc4xx_msi_ids ,
} ,
} ;
static __init int ppc4xx_msi_init ( void )
{
return platform_driver_register ( & ppc4xx_msi_driver ) ;
}
subsys_initcall ( ppc4xx_msi_init ) ;