2007-09-14 15:41:56 -05:00
/*
* PQ2 ADS - style PCI interrupt controller
*
* Copyright 2007 Freescale Semiconductor , Inc .
* Author : Scott Wood < scottwood @ freescale . com >
*
* Loosely based on mpc82xx ADS support by Vitaly Bordug < vbordug @ ru . mvista . com >
* Copyright ( c ) 2006 MontaVista Software , Inc .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation .
*/
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/irq.h>
# include <linux/types.h>
# include <linux/bootmem.h>
# include <asm/io.h>
# include <asm/prom.h>
# include <asm/cpm2.h>
# include "pq2.h"
static DEFINE_SPINLOCK ( pci_pic_lock ) ;
struct pq2ads_pci_pic {
struct device_node * node ;
struct irq_host * host ;
struct {
u32 stat ;
u32 mask ;
} __iomem * regs ;
} ;
# define NUM_IRQS 32
static void pq2ads_pci_mask_irq ( unsigned int virq )
{
struct pq2ads_pci_pic * priv = get_irq_chip_data ( virq ) ;
int irq = NUM_IRQS - virq_to_hw ( virq ) - 1 ;
if ( irq ! = - 1 ) {
unsigned long flags ;
spin_lock_irqsave ( & pci_pic_lock , flags ) ;
setbits32 ( & priv - > regs - > mask , 1 < < irq ) ;
mb ( ) ;
spin_unlock_irqrestore ( & pci_pic_lock , flags ) ;
}
}
static void pq2ads_pci_unmask_irq ( unsigned int virq )
{
struct pq2ads_pci_pic * priv = get_irq_chip_data ( virq ) ;
int irq = NUM_IRQS - virq_to_hw ( virq ) - 1 ;
if ( irq ! = - 1 ) {
unsigned long flags ;
spin_lock_irqsave ( & pci_pic_lock , flags ) ;
clrbits32 ( & priv - > regs - > mask , 1 < < irq ) ;
spin_unlock_irqrestore ( & pci_pic_lock , flags ) ;
}
}
static struct irq_chip pq2ads_pci_ic = {
. typename = " PQ2 ADS PCI " ,
. name = " PQ2 ADS PCI " ,
. end = pq2ads_pci_unmask_irq ,
. mask = pq2ads_pci_mask_irq ,
. mask_ack = pq2ads_pci_mask_irq ,
. ack = pq2ads_pci_mask_irq ,
. unmask = pq2ads_pci_unmask_irq ,
. enable = pq2ads_pci_unmask_irq ,
. disable = pq2ads_pci_mask_irq
} ;
static void pq2ads_pci_irq_demux ( unsigned int irq , struct irq_desc * desc )
{
struct pq2ads_pci_pic * priv = desc - > handler_data ;
u32 stat , mask , pend ;
int bit ;
for ( ; ; ) {
stat = in_be32 ( & priv - > regs - > stat ) ;
mask = in_be32 ( & priv - > regs - > mask ) ;
pend = stat & ~ mask ;
if ( ! pend )
break ;
for ( bit = 0 ; pend ! = 0 ; + + bit , pend < < = 1 ) {
if ( pend & 0x80000000 ) {
int virq = irq_linear_revmap ( priv - > host , bit ) ;
generic_handle_irq ( virq ) ;
}
}
}
}
static int pci_pic_host_map ( struct irq_host * h , unsigned int virq ,
irq_hw_number_t hw )
{
get_irq_desc ( virq ) - > status | = IRQ_LEVEL ;
set_irq_chip_data ( virq , h - > host_data ) ;
2008-05-20 14:28:57 -05:00
set_irq_chip_and_handler ( virq , & pq2ads_pci_ic , handle_level_irq ) ;
2007-09-14 15:41:56 -05:00
return 0 ;
}
static void pci_host_unmap ( struct irq_host * h , unsigned int virq )
{
/* remove chip and handler */
set_irq_chip_data ( virq , NULL ) ;
set_irq_chip ( virq , NULL ) ;
}
static struct irq_host_ops pci_pic_host_ops = {
. map = pci_pic_host_map ,
. unmap = pci_host_unmap ,
} ;
int __init pq2ads_pci_init_irq ( void )
{
struct pq2ads_pci_pic * priv ;
struct irq_host * host ;
struct device_node * np ;
int ret = - ENODEV ;
int irq ;
np = of_find_compatible_node ( NULL , NULL , " fsl,pq2ads-pci-pic " ) ;
if ( ! np ) {
printk ( KERN_ERR " No pci pic node in device tree. \n " ) ;
of_node_put ( np ) ;
goto out ;
}
irq = irq_of_parse_and_map ( np , 0 ) ;
if ( irq = = NO_IRQ ) {
printk ( KERN_ERR " No interrupt in pci pic node. \n " ) ;
of_node_put ( np ) ;
goto out ;
}
priv = alloc_bootmem ( sizeof ( struct pq2ads_pci_pic ) ) ;
if ( ! priv ) {
of_node_put ( np ) ;
ret = - ENOMEM ;
goto out_unmap_irq ;
}
/* PCI interrupt controller registers: status and mask */
priv - > regs = of_iomap ( np , 0 ) ;
if ( ! priv - > regs ) {
printk ( KERN_ERR " Cannot map PCI PIC registers. \n " ) ;
goto out_free_bootmem ;
}
/* mask all PCI interrupts */
out_be32 ( & priv - > regs - > mask , ~ 0 ) ;
mb ( ) ;
host = irq_alloc_host ( np , IRQ_HOST_MAP_LINEAR , NUM_IRQS ,
& pci_pic_host_ops , NUM_IRQS ) ;
if ( ! host ) {
ret = - ENOMEM ;
goto out_unmap_regs ;
}
host - > host_data = priv ;
priv - > host = host ;
host - > host_data = priv ;
set_irq_data ( irq , priv ) ;
set_irq_chained_handler ( irq , pq2ads_pci_irq_demux ) ;
of_node_put ( np ) ;
return 0 ;
out_unmap_regs :
iounmap ( priv - > regs ) ;
out_free_bootmem :
free_bootmem ( ( unsigned long ) priv ,
2009-02-04 22:43:04 +01:00
sizeof ( struct pq2ads_pci_pic ) ) ;
2007-09-14 15:41:56 -05:00
of_node_put ( np ) ;
out_unmap_irq :
irq_dispose_mapping ( irq ) ;
out :
return ret ;
}