2014-03-06 07:52:28 +04:00
/*
* MSI support for PPC4xx SoCs using High Speed Transfer Assist ( HSTA ) for
* generation of the interrupt .
*
* Copyright © 2013 Alistair Popple < alistair @ popple . id . au > 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 ; either version 2 of the License , or ( at your
* option ) any later version .
*/
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/msi.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/pci.h>
# include <linux/semaphore.h>
# include <asm/msi_bitmap.h>
struct ppc4xx_hsta_msi {
struct device * dev ;
/* The ioremapped HSTA MSI IO space */
u32 __iomem * data ;
/* Physical address of HSTA MSI IO space */
u64 address ;
struct msi_bitmap bmp ;
/* An array mapping offsets to hardware IRQs */
int * irq_map ;
/* Number of hwirqs supported */
int irq_count ;
} ;
static struct ppc4xx_hsta_msi ppc4xx_hsta_msi ;
static int hsta_setup_msi_irqs ( struct pci_dev * dev , int nvec , int type )
{
struct msi_msg msg ;
struct msi_desc * entry ;
int irq , hwirq ;
u64 addr ;
2014-09-07 22:57:53 +04:00
/* We don't support MSI-X */
if ( type = = PCI_CAP_ID_MSIX ) {
pr_debug ( " %s: MSI-X not supported. \n " , __func__ ) ;
return - EINVAL ;
}
2014-03-06 07:52:28 +04:00
list_for_each_entry ( entry , & dev - > msi_list , list ) {
irq = msi_bitmap_alloc_hwirqs ( & ppc4xx_hsta_msi . bmp , 1 ) ;
if ( irq < 0 ) {
pr_debug ( " %s: Failed to allocate msi interrupt \n " ,
__func__ ) ;
return irq ;
}
hwirq = ppc4xx_hsta_msi . irq_map [ irq ] ;
if ( hwirq = = NO_IRQ ) {
pr_err ( " %s: Failed mapping irq %d \n " , __func__ , irq ) ;
return - EINVAL ;
}
/*
* HSTA generates interrupts on writes to 128 - bit aligned
* addresses .
*/
addr = ppc4xx_hsta_msi . address + irq * 0x10 ;
msg . address_hi = upper_32_bits ( addr ) ;
msg . address_lo = lower_32_bits ( addr ) ;
/* Data is not used by the HSTA. */
msg . data = 0 ;
pr_debug ( " %s: Setup irq %d (0x%0llx) \n " , __func__ , hwirq ,
( ( ( u64 ) msg . address_hi ) < < 32 ) | msg . address_lo ) ;
if ( irq_set_msi_desc ( hwirq , entry ) ) {
pr_err (
" %s: Invalid hwirq %d specified in device tree \n " ,
__func__ , hwirq ) ;
msi_bitmap_free_hwirqs ( & ppc4xx_hsta_msi . bmp , irq , 1 ) ;
return - EINVAL ;
}
2014-11-09 18:10:34 +03:00
pci_write_msi_msg ( hwirq , & msg ) ;
2014-03-06 07:52:28 +04:00
}
return 0 ;
}
static int hsta_find_hwirq_offset ( int hwirq )
{
int irq ;
/* Find the offset given the hwirq */
for ( irq = 0 ; irq < ppc4xx_hsta_msi . irq_count ; irq + + )
if ( ppc4xx_hsta_msi . irq_map [ irq ] = = hwirq )
return irq ;
return - EINVAL ;
}
static void hsta_teardown_msi_irqs ( struct pci_dev * dev )
{
struct msi_desc * entry ;
int irq ;
list_for_each_entry ( entry , & dev - > msi_list , list ) {
if ( entry - > irq = = NO_IRQ )
continue ;
irq = hsta_find_hwirq_offset ( entry - > irq ) ;
/* entry->irq should always be in irq_map */
BUG_ON ( irq < 0 ) ;
irq_set_msi_desc ( entry - > irq , NULL ) ;
msi_bitmap_free_hwirqs ( & ppc4xx_hsta_msi . bmp , irq , 1 ) ;
pr_debug ( " %s: Teardown IRQ %u (index %u) \n " , __func__ ,
entry - > irq , irq ) ;
}
}
static int hsta_msi_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct resource * mem ;
int irq , ret , irq_count ;
2015-04-14 07:28:00 +03:00
struct pci_controller * phb ;
2014-03-06 07:52:28 +04:00
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( IS_ERR ( mem ) ) {
dev_err ( dev , " Unable to get mmio space \n " ) ;
return - EINVAL ;
}
irq_count = of_irq_count ( dev - > of_node ) ;
if ( ! irq_count ) {
dev_err ( dev , " Unable to find IRQ range \n " ) ;
return - EINVAL ;
}
ppc4xx_hsta_msi . dev = dev ;
ppc4xx_hsta_msi . address = mem - > start ;
ppc4xx_hsta_msi . data = ioremap ( mem - > start , resource_size ( mem ) ) ;
ppc4xx_hsta_msi . irq_count = irq_count ;
2014-07-20 11:20:59 +04:00
if ( ! ppc4xx_hsta_msi . data ) {
2014-03-06 07:52:28 +04:00
dev_err ( dev , " Unable to map memory \n " ) ;
return - ENOMEM ;
}
ret = msi_bitmap_alloc ( & ppc4xx_hsta_msi . bmp , irq_count , dev - > of_node ) ;
if ( ret )
goto out ;
ppc4xx_hsta_msi . irq_map = kmalloc ( sizeof ( int ) * irq_count , GFP_KERNEL ) ;
if ( IS_ERR ( ppc4xx_hsta_msi . irq_map ) ) {
ret = - ENOMEM ;
goto out1 ;
}
/* Setup a mapping from irq offsets to hardware irq numbers */
for ( irq = 0 ; irq < irq_count ; irq + + ) {
ppc4xx_hsta_msi . irq_map [ irq ] =
irq_of_parse_and_map ( dev - > of_node , irq ) ;
if ( ppc4xx_hsta_msi . irq_map [ irq ] = = NO_IRQ ) {
dev_err ( dev , " Unable to map IRQ \n " ) ;
ret = - EINVAL ;
goto out2 ;
}
}
2015-04-14 07:28:00 +03:00
list_for_each_entry ( phb , & hose_list , list_node ) {
phb - > controller_ops . setup_msi_irqs = hsta_setup_msi_irqs ;
phb - > controller_ops . teardown_msi_irqs = hsta_teardown_msi_irqs ;
}
2014-03-06 07:52:28 +04:00
return 0 ;
out2 :
kfree ( ppc4xx_hsta_msi . irq_map ) ;
out1 :
msi_bitmap_free ( & ppc4xx_hsta_msi . bmp ) ;
out :
iounmap ( ppc4xx_hsta_msi . data ) ;
return ret ;
}
static const struct of_device_id hsta_msi_ids [ ] = {
{
. compatible = " ibm,hsta-msi " ,
} ,
{ }
} ;
static struct platform_driver hsta_msi_driver = {
. probe = hsta_msi_probe ,
. driver = {
. name = " hsta-msi " ,
. of_match_table = hsta_msi_ids ,
} ,
} ;
static int hsta_msi_init ( void )
{
return platform_driver_register ( & hsta_msi_driver ) ;
}
subsys_initcall ( hsta_msi_init ) ;