2007-07-20 23:39:28 +04:00
/*
* Copyright 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 ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/kernel.h>
# include <linux/pci.h>
# include <linux/msi.h>
2008-01-25 08:59:14 +03:00
# include <linux/of_platform.h>
2007-07-20 23:39:28 +04:00
# include <asm/dcr.h>
# include <asm/machdep.h>
# include <asm/prom.h>
/*
* MSIC registers , specified as offsets from dcr_base
*/
# define MSIC_CTRL_REG 0x0
/* Base Address registers specify FIFO location in BE memory */
# define MSIC_BASE_ADDR_HI_REG 0x3
# define MSIC_BASE_ADDR_LO_REG 0x4
/* Hold the read/write offsets into the FIFO */
# define MSIC_READ_OFFSET_REG 0x5
# define MSIC_WRITE_OFFSET_REG 0x6
/* MSIC control register flags */
# define MSIC_CTRL_ENABLE 0x0001
# define MSIC_CTRL_FIFO_FULL_ENABLE 0x0002
# define MSIC_CTRL_IRQ_ENABLE 0x0008
# define MSIC_CTRL_FULL_STOP_ENABLE 0x0010
/*
* The MSIC can be configured to use a FIFO of 32 KB , 64 KB , 128 KB or 256 KB .
* Currently we ' re using a 64 KB FIFO size .
*/
# define MSIC_FIFO_SIZE_SHIFT 16
# define MSIC_FIFO_SIZE_BYTES (1 << MSIC_FIFO_SIZE_SHIFT)
/*
* To configure the FIFO size as ( 1 < < n ) bytes , we write ( n - 15 ) into bits
* 8 - 9 of the MSIC control reg .
*/
# define MSIC_CTRL_FIFO_SIZE (((MSIC_FIFO_SIZE_SHIFT - 15) << 8) & 0x300)
/*
* We need to mask the read / write offsets to make sure they stay within
* the bounds of the FIFO . Also they should always be 16 - byte aligned .
*/
# define MSIC_FIFO_SIZE_MASK ((MSIC_FIFO_SIZE_BYTES - 1) & ~0xFu)
/* Each entry in the FIFO is 16 bytes, the first 4 bytes hold the irq # */
# define MSIC_FIFO_ENTRY_SIZE 0x10
struct axon_msic {
struct irq_host * irq_host ;
__le32 * fifo ;
dcr_host_t dcr_host ;
u32 read_offset ;
} ;
static void msic_dcr_write ( struct axon_msic * msic , unsigned int dcr_n , u32 val )
{
pr_debug ( " axon_msi: dcr_write(0x%x, 0x%x) \n " , val , dcr_n ) ;
2007-10-15 13:34:36 +04:00
dcr_write ( msic - > dcr_host , dcr_n , val ) ;
2007-07-20 23:39:28 +04:00
}
static void axon_msi_cascade ( unsigned int irq , struct irq_desc * desc )
{
struct axon_msic * msic = get_irq_data ( irq ) ;
u32 write_offset , msi ;
int idx ;
2007-10-15 13:34:38 +04:00
write_offset = dcr_read ( msic - > dcr_host , MSIC_WRITE_OFFSET_REG ) ;
2007-07-20 23:39:28 +04:00
pr_debug ( " axon_msi: original write_offset 0x%x \n " , write_offset ) ;
/* write_offset doesn't wrap properly, so we have to mask it */
write_offset & = MSIC_FIFO_SIZE_MASK ;
while ( msic - > read_offset ! = write_offset ) {
idx = msic - > read_offset / sizeof ( __le32 ) ;
msi = le32_to_cpu ( msic - > fifo [ idx ] ) ;
msi & = 0xFFFF ;
pr_debug ( " axon_msi: woff %x roff %x msi %x \n " ,
write_offset , msic - > read_offset , msi ) ;
msic - > read_offset + = MSIC_FIFO_ENTRY_SIZE ;
msic - > read_offset & = MSIC_FIFO_SIZE_MASK ;
if ( msi < NR_IRQS & & irq_map [ msi ] . host = = msic - > irq_host )
generic_handle_irq ( msi ) ;
else
pr_debug ( " axon_msi: invalid irq 0x%x! \n " , msi ) ;
}
desc - > chip - > eoi ( irq ) ;
}
static struct axon_msic * find_msi_translator ( struct pci_dev * dev )
{
struct irq_host * irq_host ;
struct device_node * dn , * tmp ;
const phandle * ph ;
struct axon_msic * msic = NULL ;
2007-09-17 10:03:45 +04:00
dn = of_node_get ( pci_device_to_OF_node ( dev ) ) ;
2007-07-20 23:39:28 +04:00
if ( ! dn ) {
dev_dbg ( & dev - > dev , " axon_msi: no pci_dn found \n " ) ;
return NULL ;
}
for ( ; dn ; tmp = of_get_parent ( dn ) , of_node_put ( dn ) , dn = tmp ) {
ph = of_get_property ( dn , " msi-translator " , NULL ) ;
if ( ph )
break ;
}
if ( ! ph ) {
dev_dbg ( & dev - > dev ,
" axon_msi: no msi-translator property found \n " ) ;
goto out_error ;
}
tmp = dn ;
dn = of_find_node_by_phandle ( * ph ) ;
if ( ! dn ) {
dev_dbg ( & dev - > dev ,
" axon_msi: msi-translator doesn't point to a node \n " ) ;
goto out_error ;
}
irq_host = irq_find_host ( dn ) ;
if ( ! irq_host ) {
dev_dbg ( & dev - > dev , " axon_msi: no irq_host found for node %s \n " ,
dn - > full_name ) ;
goto out_error ;
}
msic = irq_host - > host_data ;
out_error :
of_node_put ( dn ) ;
of_node_put ( tmp ) ;
return msic ;
}
static int axon_msi_check_device ( struct pci_dev * dev , int nvec , int type )
{
if ( ! find_msi_translator ( dev ) )
return - ENODEV ;
return 0 ;
}
static int setup_msi_msg_address ( struct pci_dev * dev , struct msi_msg * msg )
{
struct device_node * dn , * tmp ;
struct msi_desc * entry ;
int len ;
const u32 * prop ;
2007-09-17 10:03:45 +04:00
dn = of_node_get ( pci_device_to_OF_node ( dev ) ) ;
2007-07-20 23:39:28 +04:00
if ( ! dn ) {
dev_dbg ( & dev - > dev , " axon_msi: no pci_dn found \n " ) ;
return - ENODEV ;
}
entry = list_first_entry ( & dev - > msi_list , struct msi_desc , list ) ;
for ( ; dn ; tmp = of_get_parent ( dn ) , of_node_put ( dn ) , dn = tmp ) {
if ( entry - > msi_attrib . is_64 ) {
prop = of_get_property ( dn , " msi-address-64 " , & len ) ;
if ( prop )
break ;
}
prop = of_get_property ( dn , " msi-address-32 " , & len ) ;
if ( prop )
break ;
}
if ( ! prop ) {
dev_dbg ( & dev - > dev ,
" axon_msi: no msi-address-(32|64) properties found \n " ) ;
return - ENOENT ;
}
switch ( len ) {
case 8 :
msg - > address_hi = prop [ 0 ] ;
msg - > address_lo = prop [ 1 ] ;
break ;
case 4 :
msg - > address_hi = 0 ;
msg - > address_lo = prop [ 0 ] ;
break ;
default :
dev_dbg ( & dev - > dev ,
" axon_msi: malformed msi-address-(32|64) property \n " ) ;
of_node_put ( dn ) ;
return - EINVAL ;
}
of_node_put ( dn ) ;
return 0 ;
}
static int axon_msi_setup_msi_irqs ( struct pci_dev * dev , int nvec , int type )
{
unsigned int virq , rc ;
struct msi_desc * entry ;
struct msi_msg msg ;
struct axon_msic * msic ;
msic = find_msi_translator ( dev ) ;
if ( ! msic )
return - ENODEV ;
rc = setup_msi_msg_address ( dev , & msg ) ;
if ( rc )
return rc ;
/* We rely on being able to stash a virq in a u16 */
BUILD_BUG_ON ( NR_IRQS > 65536 ) ;
list_for_each_entry ( entry , & dev - > msi_list , list ) {
virq = irq_create_direct_mapping ( msic - > irq_host ) ;
if ( virq = = NO_IRQ ) {
dev_warn ( & dev - > dev ,
" axon_msi: virq allocation failed! \n " ) ;
return - 1 ;
}
dev_dbg ( & dev - > dev , " axon_msi: allocated virq 0x%x \n " , virq ) ;
set_irq_msi ( virq , entry ) ;
msg . data = virq ;
write_msi_msg ( virq , & msg ) ;
}
return 0 ;
}
static void axon_msi_teardown_msi_irqs ( struct pci_dev * dev )
{
struct msi_desc * entry ;
dev_dbg ( & dev - > dev , " axon_msi: tearing down msi irqs \n " ) ;
list_for_each_entry ( entry , & dev - > msi_list , list ) {
if ( entry - > irq = = NO_IRQ )
continue ;
set_irq_msi ( entry - > irq , NULL ) ;
irq_dispose_mapping ( entry - > irq ) ;
}
}
static struct irq_chip msic_irq_chip = {
. mask = mask_msi_irq ,
. unmask = unmask_msi_irq ,
. shutdown = unmask_msi_irq ,
. typename = " AXON-MSI " ,
} ;
static int msic_host_map ( struct irq_host * h , unsigned int virq ,
irq_hw_number_t hw )
{
set_irq_chip_and_handler ( virq , & msic_irq_chip , handle_simple_irq ) ;
return 0 ;
}
static struct irq_host_ops msic_host_ops = {
. map = msic_host_map ,
} ;
2008-01-25 08:59:14 +03:00
static int axon_msi_shutdown ( struct of_device * device )
2007-07-20 23:39:28 +04:00
{
2008-01-25 08:59:14 +03:00
struct axon_msic * msic = device - > dev . platform_data ;
2007-07-20 23:39:28 +04:00
u32 tmp ;
2008-01-25 08:59:14 +03:00
pr_debug ( " axon_msi: disabling %s \n " ,
msic - > irq_host - > of_node - > full_name ) ;
tmp = dcr_read ( msic - > dcr_host , MSIC_CTRL_REG ) ;
tmp & = ~ MSIC_CTRL_ENABLE & ~ MSIC_CTRL_IRQ_ENABLE ;
msic_dcr_write ( msic , MSIC_CTRL_REG , tmp ) ;
2007-07-20 23:39:28 +04:00
return 0 ;
}
2008-01-25 08:59:14 +03:00
static int axon_msi_probe ( struct of_device * device ,
const struct of_device_id * device_id )
2007-07-20 23:39:28 +04:00
{
struct page * page ;
2008-01-25 08:59:14 +03:00
struct device_node * dn = device - > node ;
2007-07-20 23:39:28 +04:00
struct axon_msic * msic ;
unsigned int virq ;
2007-09-17 10:05:02 +04:00
int dcr_base , dcr_len ;
2007-07-20 23:39:28 +04:00
pr_debug ( " axon_msi: setting up dn %s \n " , dn - > full_name ) ;
msic = kzalloc ( sizeof ( struct axon_msic ) , GFP_KERNEL ) ;
if ( ! msic ) {
printk ( KERN_ERR " axon_msi: couldn't allocate msic for %s \n " ,
dn - > full_name ) ;
goto out ;
}
2007-09-17 10:05:02 +04:00
dcr_base = dcr_resource_start ( dn , 0 ) ;
2007-07-20 23:39:28 +04:00
dcr_len = dcr_resource_len ( dn , 0 ) ;
2007-09-17 10:05:02 +04:00
if ( dcr_base = = 0 | | dcr_len = = 0 ) {
2007-07-20 23:39:28 +04:00
printk ( KERN_ERR
" axon_msi: couldn't parse dcr properties on %s \n " ,
dn - > full_name ) ;
goto out ;
}
2007-09-17 10:05:02 +04:00
msic - > dcr_host = dcr_map ( dn , dcr_base , dcr_len ) ;
2007-07-20 23:39:28 +04:00
if ( ! DCR_MAP_OK ( msic - > dcr_host ) ) {
printk ( KERN_ERR " axon_msi: dcr_map failed for %s \n " ,
dn - > full_name ) ;
goto out_free_msic ;
}
page = alloc_pages_node ( of_node_to_nid ( dn ) , GFP_KERNEL ,
get_order ( MSIC_FIFO_SIZE_BYTES ) ) ;
if ( ! page ) {
printk ( KERN_ERR " axon_msi: couldn't allocate fifo for %s \n " ,
dn - > full_name ) ;
goto out_free_msic ;
}
msic - > fifo = page_address ( page ) ;
2007-08-28 12:47:54 +04:00
msic - > irq_host = irq_alloc_host ( of_node_get ( dn ) , IRQ_HOST_MAP_NOMAP ,
NR_IRQS , & msic_host_ops , 0 ) ;
2007-07-20 23:39:28 +04:00
if ( ! msic - > irq_host ) {
printk ( KERN_ERR " axon_msi: couldn't allocate irq_host for %s \n " ,
dn - > full_name ) ;
goto out_free_fifo ;
}
msic - > irq_host - > host_data = msic ;
virq = irq_of_parse_and_map ( dn , 0 ) ;
if ( virq = = NO_IRQ ) {
printk ( KERN_ERR " axon_msi: irq parse and map failed for %s \n " ,
dn - > full_name ) ;
goto out_free_host ;
}
set_irq_data ( virq , msic ) ;
set_irq_chained_handler ( virq , axon_msi_cascade ) ;
pr_debug ( " axon_msi: irq 0x%x setup for axon_msi \n " , virq ) ;
/* Enable the MSIC hardware */
msic_dcr_write ( msic , MSIC_BASE_ADDR_HI_REG , ( u64 ) msic - > fifo > > 32 ) ;
msic_dcr_write ( msic , MSIC_BASE_ADDR_LO_REG ,
( u64 ) msic - > fifo & 0xFFFFFFFF ) ;
msic_dcr_write ( msic , MSIC_CTRL_REG ,
MSIC_CTRL_IRQ_ENABLE | MSIC_CTRL_ENABLE |
MSIC_CTRL_FIFO_SIZE ) ;
2008-01-25 08:59:14 +03:00
device - > dev . platform_data = msic ;
ppc_md . setup_msi_irqs = axon_msi_setup_msi_irqs ;
ppc_md . teardown_msi_irqs = axon_msi_teardown_msi_irqs ;
ppc_md . msi_check_device = axon_msi_check_device ;
2007-07-20 23:39:28 +04:00
printk ( KERN_DEBUG " axon_msi: setup MSIC on %s \n " , dn - > full_name ) ;
return 0 ;
out_free_host :
kfree ( msic - > irq_host ) ;
out_free_fifo :
__free_pages ( virt_to_page ( msic - > fifo ) , get_order ( MSIC_FIFO_SIZE_BYTES ) ) ;
out_free_msic :
kfree ( msic ) ;
out :
return - 1 ;
}
2008-01-25 08:59:14 +03:00
static const struct of_device_id axon_msi_device_id [ ] = {
{
. compatible = " ibm,axon-msic "
} ,
{ }
} ;
2007-07-20 23:39:28 +04:00
2008-01-25 08:59:14 +03:00
static struct of_platform_driver axon_msi_driver = {
. match_table = axon_msi_device_id ,
. probe = axon_msi_probe ,
. shutdown = axon_msi_shutdown ,
. driver = {
. name = " axon-msi "
} ,
} ;
2007-07-20 23:39:28 +04:00
2008-01-25 08:59:14 +03:00
static int __init axon_msi_init ( void )
{
return of_register_platform_driver ( & axon_msi_driver ) ;
2007-07-20 23:39:28 +04:00
}
2008-01-25 08:59:14 +03:00
subsys_initcall ( axon_msi_init ) ;