kernel-fxtec-pro1x/arch/powerpc/platforms/cell/spider-pic.c
Benjamin Herrenschmidt 0ebfff1491 [POWERPC] Add new interrupt mapping core and change platforms to use it
This adds the new irq remapper core and removes the old one.  Because
there are some fundamental conflicts with the old code, like the value
of NO_IRQ which I'm now setting to 0 (as per discussions with Linus),
etc..., this commit also changes the relevant platform and driver code
over to use the new remapper (so as not to cause difficulties later
in bisecting).

This patch removes the old pre-parsing of the open firmware interrupt
tree along with all the bogus assumptions it made to try to renumber
interrupts according to the platform. This is all to be handled by the
new code now.

For the pSeries XICS interrupt controller, a single remapper host is
created for the whole machine regardless of how many interrupt
presentation and source controllers are found, and it's set to match
any device node that isn't a 8259.  That works fine on pSeries and
avoids having to deal with some of the complexities of split source
controllers vs. presentation controllers in the pSeries device trees.

The powerpc i8259 PIC driver now always requests the legacy interrupt
range. It also has the feature of being able to match any device node
(including NULL) if passed no device node as an input. That will help
porting over platforms with broken device-trees like Pegasos who don't
have a proper interrupt tree.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
2006-07-03 21:36:01 +10:00

369 lines
11 KiB
C

/*
* External Interrupt Controller on Spider South Bridge
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/ioport.h>
#include <asm/pgtable.h>
#include <asm/prom.h>
#include <asm/io.h>
#include "interrupt.h"
/* register layout taken from Spider spec, table 7.4-4 */
enum {
TIR_DEN = 0x004, /* Detection Enable Register */
TIR_MSK = 0x084, /* Mask Level Register */
TIR_EDC = 0x0c0, /* Edge Detection Clear Register */
TIR_PNDA = 0x100, /* Pending Register A */
TIR_PNDB = 0x104, /* Pending Register B */
TIR_CS = 0x144, /* Current Status Register */
TIR_LCSA = 0x150, /* Level Current Status Register A */
TIR_LCSB = 0x154, /* Level Current Status Register B */
TIR_LCSC = 0x158, /* Level Current Status Register C */
TIR_LCSD = 0x15c, /* Level Current Status Register D */
TIR_CFGA = 0x200, /* Setting Register A0 */
TIR_CFGB = 0x204, /* Setting Register B0 */
/* 0x208 ... 0x3ff Setting Register An/Bn */
TIR_PPNDA = 0x400, /* Packet Pending Register A */
TIR_PPNDB = 0x404, /* Packet Pending Register B */
TIR_PIERA = 0x408, /* Packet Output Error Register A */
TIR_PIERB = 0x40c, /* Packet Output Error Register B */
TIR_PIEN = 0x444, /* Packet Output Enable Register */
TIR_PIPND = 0x454, /* Packet Output Pending Register */
TIRDID = 0x484, /* Spider Device ID Register */
REISTIM = 0x500, /* Reissue Command Timeout Time Setting */
REISTIMEN = 0x504, /* Reissue Command Timeout Setting */
REISWAITEN = 0x508, /* Reissue Wait Control*/
};
#define SPIDER_CHIP_COUNT 4
#define SPIDER_SRC_COUNT 64
#define SPIDER_IRQ_INVALID 63
struct spider_pic {
struct irq_host *host;
struct device_node *of_node;
void __iomem *regs;
unsigned int node_id;
};
static struct spider_pic spider_pics[SPIDER_CHIP_COUNT];
static struct spider_pic *spider_virq_to_pic(unsigned int virq)
{
return irq_map[virq].host->host_data;
}
static void __iomem *spider_get_irq_config(struct spider_pic *pic,
unsigned int src)
{
return pic->regs + TIR_CFGA + 8 * src;
}
static void spider_unmask_irq(unsigned int virq)
{
struct spider_pic *pic = spider_virq_to_pic(virq);
void __iomem *cfg = spider_get_irq_config(pic, irq_map[virq].hwirq);
/* We use no locking as we should be covered by the descriptor lock
* for access to invidual source configuration registers
*/
out_be32(cfg, in_be32(cfg) | 0x30000000u);
}
static void spider_mask_irq(unsigned int virq)
{
struct spider_pic *pic = spider_virq_to_pic(virq);
void __iomem *cfg = spider_get_irq_config(pic, irq_map[virq].hwirq);
/* We use no locking as we should be covered by the descriptor lock
* for access to invidual source configuration registers
*/
out_be32(cfg, in_be32(cfg) & ~0x30000000u);
}
static void spider_ack_irq(unsigned int virq)
{
struct spider_pic *pic = spider_virq_to_pic(virq);
unsigned int src = irq_map[virq].hwirq;
/* Reset edge detection logic if necessary
*/
if (get_irq_desc(virq)->status & IRQ_LEVEL)
return;
/* Only interrupts 47 to 50 can be set to edge */
if (src < 47 || src > 50)
return;
/* Perform the clear of the edge logic */
out_be32(pic->regs + TIR_EDC, 0x100 | (src & 0xf));
}
static struct irq_chip spider_pic = {
.typename = " SPIDER ",
.unmask = spider_unmask_irq,
.mask = spider_mask_irq,
.ack = spider_ack_irq,
};
static int spider_host_match(struct irq_host *h, struct device_node *node)
{
struct spider_pic *pic = h->host_data;
return node == pic->of_node;
}
static int spider_host_map(struct irq_host *h, unsigned int virq,
irq_hw_number_t hw, unsigned int flags)
{
unsigned int sense = flags & IRQ_TYPE_SENSE_MASK;
struct spider_pic *pic = h->host_data;
void __iomem *cfg = spider_get_irq_config(pic, hw);
int level = 0;
u32 ic;
/* Note that only level high is supported for most interrupts */
if (sense != IRQ_TYPE_NONE && sense != IRQ_TYPE_LEVEL_HIGH &&
(hw < 47 || hw > 50))
return -EINVAL;
/* Decode sense type */
switch(sense) {
case IRQ_TYPE_EDGE_RISING:
ic = 0x3;
break;
case IRQ_TYPE_EDGE_FALLING:
ic = 0x2;
break;
case IRQ_TYPE_LEVEL_LOW:
ic = 0x0;
level = 1;
break;
case IRQ_TYPE_LEVEL_HIGH:
case IRQ_TYPE_NONE:
ic = 0x1;
level = 1;
break;
default:
return -EINVAL;
}
/* Configure the source. One gross hack that was there before and
* that I've kept around is the priority to the BE which I set to
* be the same as the interrupt source number. I don't know wether
* that's supposed to make any kind of sense however, we'll have to
* decide that, but for now, I'm not changing the behaviour.
*/
out_be32(cfg, (ic << 24) | (0x7 << 16) | (pic->node_id << 4) | 0xe);
out_be32(cfg + 4, (0x2 << 16) | (hw & 0xff));
if (level)
get_irq_desc(virq)->status |= IRQ_LEVEL;
set_irq_chip_and_handler(virq, &spider_pic, handle_level_irq);
return 0;
}
static int spider_host_xlate(struct irq_host *h, struct device_node *ct,
u32 *intspec, unsigned int intsize,
irq_hw_number_t *out_hwirq, unsigned int *out_flags)
{
/* Spider interrupts have 2 cells, first is the interrupt source,
* second, well, I don't know for sure yet ... We mask the top bits
* because old device-trees encode a node number in there
*/
*out_hwirq = intspec[0] & 0x3f;
*out_flags = IRQ_TYPE_LEVEL_HIGH;
return 0;
}
static struct irq_host_ops spider_host_ops = {
.match = spider_host_match,
.map = spider_host_map,
.xlate = spider_host_xlate,
};
static void spider_irq_cascade(unsigned int irq, struct irq_desc *desc,
struct pt_regs *regs)
{
struct spider_pic *pic = desc->handler_data;
unsigned int cs, virq;
cs = in_be32(pic->regs + TIR_CS) >> 24;
if (cs == SPIDER_IRQ_INVALID)
virq = NO_IRQ;
else
virq = irq_linear_revmap(pic->host, cs);
if (virq != NO_IRQ)
generic_handle_irq(virq, regs);
desc->chip->eoi(irq);
}
/* For hooking up the cascace we have a problem. Our device-tree is
* crap and we don't know on which BE iic interrupt we are hooked on at
* least not the "standard" way. We can reconstitute it based on two
* informations though: which BE node we are connected to and wether
* we are connected to IOIF0 or IOIF1. Right now, we really only care
* about the IBM cell blade and we know that its firmware gives us an
* interrupt-map property which is pretty strange.
*/
static unsigned int __init spider_find_cascade_and_node(struct spider_pic *pic)
{
unsigned int virq;
u32 *imap, *tmp;
int imaplen, intsize, unit;
struct device_node *iic;
struct irq_host *iic_host;
#if 0 /* Enable that when we have a way to retreive the node as well */
/* First, we check wether we have a real "interrupts" in the device
* tree in case the device-tree is ever fixed
*/
struct of_irq oirq;
if (of_irq_map_one(pic->of_node, 0, &oirq) == 0) {
virq = irq_create_of_mapping(oirq.controller, oirq.specifier,
oirq.size);
goto bail;
}
#endif
/* Now do the horrible hacks */
tmp = (u32 *)get_property(pic->of_node, "#interrupt-cells", NULL);
if (tmp == NULL)
return NO_IRQ;
intsize = *tmp;
imap = (u32 *)get_property(pic->of_node, "interrupt-map", &imaplen);
if (imap == NULL || imaplen < (intsize + 1))
return NO_IRQ;
iic = of_find_node_by_phandle(imap[intsize]);
if (iic == NULL)
return NO_IRQ;
imap += intsize + 1;
tmp = (u32 *)get_property(iic, "#interrupt-cells", NULL);
if (tmp == NULL)
return NO_IRQ;
intsize = *tmp;
/* Assume unit is last entry of interrupt specifier */
unit = imap[intsize - 1];
/* Ok, we have a unit, now let's try to get the node */
tmp = (u32 *)get_property(iic, "ibm,interrupt-server-ranges", NULL);
if (tmp == NULL) {
of_node_put(iic);
return NO_IRQ;
}
/* ugly as hell but works for now */
pic->node_id = (*tmp) >> 1;
of_node_put(iic);
/* Ok, now let's get cracking. You may ask me why I just didn't match
* the iic host from the iic OF node, but that way I'm still compatible
* with really really old old firmwares for which we don't have a node
*/
iic_host = iic_get_irq_host(pic->node_id);
if (iic_host == NULL)
return NO_IRQ;
/* Manufacture an IIC interrupt number of class 2 */
virq = irq_create_mapping(iic_host, 0x20 | unit, 0);
if (virq == NO_IRQ)
printk(KERN_ERR "spider_pic: failed to map cascade !");
return virq;
}
static void __init spider_init_one(struct device_node *of_node, int chip,
unsigned long addr)
{
struct spider_pic *pic = &spider_pics[chip];
int i, virq;
/* Map registers */
pic->regs = ioremap(addr, 0x1000);
if (pic->regs == NULL)
panic("spider_pic: can't map registers !");
/* Allocate a host */
pic->host = irq_alloc_host(IRQ_HOST_MAP_LINEAR, SPIDER_SRC_COUNT,
&spider_host_ops, SPIDER_IRQ_INVALID);
if (pic->host == NULL)
panic("spider_pic: can't allocate irq host !");
pic->host->host_data = pic;
/* Fill out other bits */
pic->of_node = of_node_get(of_node);
/* Go through all sources and disable them */
for (i = 0; i < SPIDER_SRC_COUNT; i++) {
void __iomem *cfg = pic->regs + TIR_CFGA + 8 * i;
out_be32(cfg, in_be32(cfg) & ~0x30000000u);
}
/* do not mask any interrupts because of level */
out_be32(pic->regs + TIR_MSK, 0x0);
/* enable interrupt packets to be output */
out_be32(pic->regs + TIR_PIEN, in_be32(pic->regs + TIR_PIEN) | 0x1);
/* Hook up the cascade interrupt to the iic and nodeid */
virq = spider_find_cascade_and_node(pic);
if (virq == NO_IRQ)
return;
set_irq_data(virq, pic);
set_irq_chained_handler(virq, spider_irq_cascade);
printk(KERN_INFO "spider_pic: node %d, addr: 0x%lx %s\n",
pic->node_id, addr, of_node->full_name);
/* Enable the interrupt detection enable bit. Do this last! */
out_be32(pic->regs + TIR_DEN, in_be32(pic->regs + TIR_DEN) | 0x1);
}
void __init spider_init_IRQ(void)
{
struct resource r;
struct device_node *dn;
int chip = 0;
/* XXX node numbers are totally bogus. We _hope_ we get the device
* nodes in the right order here but that's definitely not guaranteed,
* we need to get the node from the device tree instead.
* There is currently no proper property for it (but our whole
* device-tree is bogus anyway) so all we can do is pray or maybe test
* the address and deduce the node-id
*/
for (dn = NULL;
(dn = of_find_node_by_name(dn, "interrupt-controller"));) {
if (device_is_compatible(dn, "CBEA,platform-spider-pic")) {
if (of_address_to_resource(dn, 0, &r)) {
printk(KERN_WARNING "spider-pic: Failed\n");
continue;
}
} else if (device_is_compatible(dn, "sti,platform-spider-pic")
&& (chip < 2)) {
static long hard_coded_pics[] =
{ 0x24000008000, 0x34000008000 };
r.start = hard_coded_pics[chip];
} else
continue;
spider_init_one(dn, chip++, r.start);
}
}