Commit 6baa9b20 authored by Sam Ravnborg's avatar Sam Ravnborg Committed by David S. Miller
Browse files

sparc32: genirq support



The conversion of sparc32 to genirq is based on original work done
by David S. Miller.
Daniel Hellstrom has helped in the conversion and implemented
the shutdowm functionality.
Marcel van Nies <morcles@gmail.com> has tested this on Sparc Station 20

Test status:
sun4c      - not tested
sun4m,pci  - not tested
sun4m,sbus - tested (Sparc Classic, Sparc Station 5, Sparc Station 20)
sun4d      - not tested
leon       - tested on various combinations of leon boards,
             including SMP variants

generic
   Introduce use of GENERIC_HARDIRQS and GENERIC_IRQ_SHOW
   Allocate 64 IRQs - which is enough even for SS2000
   Use a table of irq_bucket to maintain uses IRQs
      irq_bucket is also used to chain several irq's that
      must be called when the same intrrupt is asserted
   Use irq_link to link a interrupt source to the irq
   All plafforms must now supply their own build_device_irq method
   handler_irq rewriten to use generic irq support

floppy
   Read FLOPPY_IRQ from platform device
   Use generic request_irq to register the floppy interrupt
   Rewrote sparc_floppy_irq to use the generic irq support

pcic:
   Introduce irq_chip
   Store mask in chip_data for use in mask/unmask functions
   Add build_device_irq for pcic
   Use pcic_build_device_irq in pci_time_init
   allocate virtual irqs in pcic_fill_irq

sun4c:
   Introduce irq_chip
   Store mask in chip_data for use in mask/unmask functions
   Add build_device_irq for sun4c
   Use sun4c_build_device_irq in sun4c_init_timers

sun4m:
   Introduce irq_chip
   Introduce dedicated mask/unmask methods
   Introduce sun4m_handler_data that allow easy access to necessary
     data in the mask/unmask functions
   Add a helper method to enable profile_timer (used from smp)
   Added sun4m_build_device_irq
   Use sun4m_build_device_irq in sun4m_init_timers

   TODO:
      There is no replacement for smp_rotate that always scheduled
      next CPU as interrupt target upon an interrupt

sun4d:
   Introduce irq_chip
   Introduce dedicated mask/unmask methods
   Introduce sun4d_handler_data that allow easy access to
   necessary data in mask/unmask fuctions
   Rewrote sun4d_handler_irq to use generic irq support

   TODO:
      The original implmentation of enable/disable had:

          if (irq < NR_IRQS)
               return;

      The new implmentation does not distingush between SBUS and cpu
      interrupts.
      I am no sure what is right here. I assume we need to do
      something for the cpu interrupts.

      I have not succeeded booting my sun4d box (with or without this patch)
      and my understanding of this platfrom is limited.
      So I would be a bit suprised if this works.

leon:
   Introduce irq_chip
   Store mask in chip_data for use in mask/unmask functions
   Add build_device_irq for leon
   Use leon_build_device_irq in leon_init_timers
Signed-off-by: default avatarSam Ravnborg <sam@ravnborg.org>
Acked-by: default avatarDaniel Hellstrom <daniel@gaisler.com>
Tested-by: default avatarDaniel Hellstrom <daniel@gaisler.com>
Tested-by: default avatarMarcel van Nies <morcles@gmail.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 06010fb5
......@@ -25,6 +25,10 @@ config SPARC
select HAVE_DMA_ATTRS
select HAVE_DMA_API_DEBUG
select HAVE_ARCH_JUMP_LABEL
select HAVE_GENERIC_HARDIRQS
select GENERIC_HARDIRQS_NO_DEPRECATED
select GENERIC_IRQ_SHOW
config SPARC32
def_bool !64BIT
......@@ -50,8 +54,6 @@ config SPARC64
select RTC_DRV_STARFIRE
select HAVE_PERF_EVENTS
select PERF_USE_VMALLOC
select HAVE_GENERIC_HARDIRQS
select GENERIC_IRQ_SHOW
select IRQ_PREFLOW_FASTEOI
config ARCH_DEFCONFIG
......
......@@ -281,28 +281,27 @@ static inline void sun_fd_enable_dma(void)
pdma_areasize = pdma_size;
}
/* Our low-level entry point in arch/sparc/kernel/entry.S */
extern int sparc_floppy_request_irq(int irq, unsigned long flags,
irq_handler_t irq_handler);
extern int sparc_floppy_request_irq(unsigned int irq,
irq_handler_t irq_handler);
static int sun_fd_request_irq(void)
{
static int once = 0;
int error;
if(!once) {
if (!once) {
once = 1;
error = sparc_floppy_request_irq(FLOPPY_IRQ,
IRQF_DISABLED,
floppy_interrupt);
return ((error == 0) ? 0 : -1);
} else return 0;
return sparc_floppy_request_irq(FLOPPY_IRQ, floppy_interrupt);
} else {
return 0;
}
}
static struct linux_prom_registers fd_regs[2];
static int sun_floppy_init(void)
{
struct platform_device *op;
struct device_node *dp;
char state[128];
phandle tnode, fd_node;
int num_regs;
......@@ -310,7 +309,6 @@ static int sun_floppy_init(void)
use_virtual_dma = 1;
FLOPPY_IRQ = 11;
/* Forget it if we aren't on a machine that could possibly
* ever have a floppy drive.
*/
......@@ -349,6 +347,26 @@ static int sun_floppy_init(void)
sun_fdc = (struct sun_flpy_controller *)
of_ioremap(&r, 0, fd_regs[0].reg_size, "floppy");
/* Look up irq in platform_device.
* We try "SUNW,fdtwo" and "fd"
*/
for_each_node_by_name(dp, "SUNW,fdtwo") {
op = of_find_device_by_node(dp);
if (op)
break;
}
if (!op) {
for_each_node_by_name(dp, "fd") {
op = of_find_device_by_node(dp);
if (op)
break;
}
}
if (!op)
goto no_sun_fdc;
FLOPPY_IRQ = op->archdata.irqs[0];
/* Last minute sanity check... */
if(sun_fdc->status_82072 == 0xff) {
sun_fdc = NULL;
......
......@@ -6,7 +6,11 @@
#ifndef _SPARC_IRQ_H
#define _SPARC_IRQ_H
#define NR_IRQS 16
/* Allocated number of logical irq numbers.
* sun4d boxes (ss2000e) should be OK with ~32.
* Be on the safe side and make room for 64
*/
#define NR_IRQS 64
#include <linux/interrupt.h>
......
......@@ -15,11 +15,6 @@
#include <linux/irqflags.h>
static inline unsigned int probe_irq_mask(unsigned long val)
{
return 0;
}
/*
* Sparc (general) CPU types
*/
......
......@@ -71,10 +71,6 @@ obj-$(CONFIG_SPARC64) += pcr.o
obj-$(CONFIG_SPARC64) += nmi.o
obj-$(CONFIG_SPARC64_SMP) += cpumap.o
# sparc32 do not use GENERIC_HARDIRQS but uses the generic devres implementation
obj-$(CONFIG_SPARC32) += devres.o
devres-y := ../../../kernel/irq/devres.o
obj-y += dma.o
obj-$(CONFIG_SPARC32_PCI) += pcic.o
......
......@@ -2,6 +2,23 @@
#include <asm/btfixup.h>
struct irq_bucket {
struct irq_bucket *next;
unsigned int real_irq;
unsigned int irq;
unsigned int pil;
};
#define SUN4D_MAX_BOARD 10
#define SUN4D_MAX_IRQ ((SUN4D_MAX_BOARD + 2) << 5)
/* Map between the irq identifier used in hw to the
* irq_bucket. The map is sufficient large to hold
* the sun4d hw identifiers.
*/
extern struct irq_bucket *irq_map[SUN4D_MAX_IRQ];
/* sun4m specific type definitions */
/* This maps direct to CPU specific interrupt registers */
......@@ -35,6 +52,10 @@ struct sparc_irq_config {
};
extern struct sparc_irq_config sparc_irq_config;
unsigned int irq_alloc(unsigned int real_irq, unsigned int pil);
void irq_link(unsigned int irq);
void irq_unlink(unsigned int irq);
void handler_irq(unsigned int pil, struct pt_regs *regs);
/* Dave Redman (djhr@tadpole.co.uk)
* changed these to function pointers.. it saves cycles and will allow
......@@ -44,33 +65,9 @@ extern struct sparc_irq_config sparc_irq_config;
* Changed these to btfixup entities... It saves cycles :)
*/
BTFIXUPDEF_CALL(void, disable_irq, unsigned int)
BTFIXUPDEF_CALL(void, enable_irq, unsigned int)
BTFIXUPDEF_CALL(void, disable_pil_irq, unsigned int)
BTFIXUPDEF_CALL(void, enable_pil_irq, unsigned int)
BTFIXUPDEF_CALL(void, clear_clock_irq, void)
BTFIXUPDEF_CALL(void, load_profile_irq, int, unsigned int)
static inline void __disable_irq(unsigned int irq)
{
BTFIXUP_CALL(disable_irq)(irq);
}
static inline void __enable_irq(unsigned int irq)
{
BTFIXUP_CALL(enable_irq)(irq);
}
static inline void disable_pil_irq(unsigned int irq)
{
BTFIXUP_CALL(disable_pil_irq)(irq);
}
static inline void enable_pil_irq(unsigned int irq)
{
BTFIXUP_CALL(enable_pil_irq)(irq);
}
static inline void clear_clock_irq(void)
{
BTFIXUP_CALL(clear_clock_irq)();
......
......@@ -15,6 +15,7 @@
#include <linux/seq_file.h>
#include <asm/cacheflush.h>
#include <asm/cpudata.h>
#include <asm/pcic.h>
#include <asm/leon.h>
......@@ -101,284 +102,163 @@ EXPORT_SYMBOL(arch_local_irq_restore);
* directed CPU interrupts using the existing enable/disable irq code
* with tweaks.
*
* Sun4d complicates things even further. IRQ numbers are arbitrary
* 32-bit values in that case. Since this is similar to sparc64,
* we adopt a virtual IRQ numbering scheme as is done there.
* Virutal interrupt numbers are allocated by build_irq(). So NR_IRQS
* just becomes a limit of how many interrupt sources we can handle in
* a single system. Even fully loaded SS2000 machines top off at
* about 32 interrupt sources or so, therefore a NR_IRQS value of 64
* is more than enough.
*
* We keep a map of per-PIL enable interrupts. These get wired
* up via the irq_chip->startup() method which gets invoked by
* the generic IRQ layer during request_irq().
*/
/* Table of allocated irqs. Unused entries has irq == 0 */
static struct irq_bucket irq_table[NR_IRQS];
/* Protect access to irq_table */
static DEFINE_SPINLOCK(irq_table_lock);
/*
* Dave Redman (djhr@tadpole.co.uk)
*
* There used to be extern calls and hard coded values here.. very sucky!
* instead, because some of the devices attach very early, I do something
* equally sucky but at least we'll never try to free statically allocated
* space or call kmalloc before kmalloc_init :(.
*
* In fact it's the timer10 that attaches first.. then timer14
* then kmalloc_init is called.. then the tty interrupts attach.
* hmmm....
*
*/
#define MAX_STATIC_ALLOC 4
struct irqaction static_irqaction[MAX_STATIC_ALLOC];
int static_irq_count;
static struct {
struct irqaction *action;
int flags;
} sparc_irq[NR_IRQS];
#define SPARC_IRQ_INPROGRESS 1
/* Used to protect the IRQ action lists */
DEFINE_SPINLOCK(irq_action_lock);
/* Map between the irq identifier used in hw to the irq_bucket. */
struct irq_bucket *irq_map[SUN4D_MAX_IRQ];
/* Protect access to irq_map */
static DEFINE_SPINLOCK(irq_map_lock);
int show_interrupts(struct seq_file *p, void *v)
/* Allocate a new irq from the irq_table */
unsigned int irq_alloc(unsigned int real_irq, unsigned int pil)
{
int i = *(loff_t *)v;
struct irqaction *action;
unsigned long flags;
#ifdef CONFIG_SMP
int j;
#endif
unsigned int i;
spin_lock_irqsave(&irq_table_lock, flags);
for (i = 1; i < NR_IRQS; i++) {
if (irq_table[i].real_irq == real_irq && irq_table[i].pil == pil)
goto found;
}
if (sparc_cpu_model == sun4d)
return show_sun4d_interrupts(p, v);
for (i = 1; i < NR_IRQS; i++) {
if (!irq_table[i].irq)
break;
}
spin_lock_irqsave(&irq_action_lock, flags);
if (i < NR_IRQS) {
action = sparc_irq[i].action;
if (!action)
goto out_unlock;
seq_printf(p, "%3d: ", i);
#ifndef CONFIG_SMP
seq_printf(p, "%10u ", kstat_irqs(i));
#else
for_each_online_cpu(j) {
seq_printf(p, "%10u ",
kstat_cpu(j).irqs[i]);
}
#endif
seq_printf(p, " %c %s",
(action->flags & IRQF_DISABLED) ? '+' : ' ',
action->name);
for (action = action->next; action; action = action->next) {
seq_printf(p, ",%s %s",
(action->flags & IRQF_DISABLED) ? " +" : "",
action->name);
}
seq_putc(p, '\n');
irq_table[i].real_irq = real_irq;
irq_table[i].irq = i;
irq_table[i].pil = pil;
} else {
printk(KERN_ERR "IRQ: Out of virtual IRQs.\n");
i = 0;
}
out_unlock:
spin_unlock_irqrestore(&irq_action_lock, flags);
return 0;
found:
spin_unlock_irqrestore(&irq_table_lock, flags);
return i;
}
void free_irq(unsigned int irq, void *dev_id)
/* Based on a single pil handler_irq may need to call several
* interrupt handlers. Use irq_map as entry to irq_table,
* and let each entry in irq_table point to the next entry.
*/
void irq_link(unsigned int irq)
{
struct irqaction *action;
struct irqaction **actionp;
struct irq_bucket *p;
unsigned long flags;
unsigned int cpu_irq;
unsigned int pil;
if (sparc_cpu_model == sun4d) {
sun4d_free_irq(irq, dev_id);
return;
}
cpu_irq = irq & (NR_IRQS - 1);
if (cpu_irq > 14) { /* 14 irq levels on the sparc */
printk(KERN_ERR "Trying to free bogus IRQ %d\n", irq);
return;
}
BUG_ON(irq >= NR_IRQS);
spin_lock_irqsave(&irq_action_lock, flags);
spin_lock_irqsave(&irq_map_lock, flags);
actionp = &sparc_irq[cpu_irq].action;
action = *actionp;
p = &irq_table[irq];
pil = p->pil;
BUG_ON(pil > SUN4D_MAX_IRQ);
p->next = irq_map[pil];
irq_map[pil] = p;
if (!action->handler) {
printk(KERN_ERR "Trying to free free IRQ%d\n", irq);
goto out_unlock;
}
if (dev_id) {
for (; action; action = action->next) {
if (action->dev_id == dev_id)
break;
actionp = &action->next;
}
if (!action) {
printk(KERN_ERR "Trying to free free shared IRQ%d\n",
irq);
goto out_unlock;
}
} else if (action->flags & IRQF_SHARED) {
printk(KERN_ERR "Trying to free shared IRQ%d with NULL device ID\n",
irq);
goto out_unlock;
}
if (action->flags & SA_STATIC_ALLOC) {
/*
* This interrupt is marked as specially allocated
* so it is a bad idea to free it.
*/
printk(KERN_ERR "Attempt to free statically allocated IRQ%d (%s)\n",
irq, action->name);
goto out_unlock;
}
*actionp = action->next;
spin_unlock_irqrestore(&irq_action_lock, flags);
spin_unlock_irqrestore(&irq_map_lock, flags);
}
synchronize_irq(irq);
void irq_unlink(unsigned int irq)
{
struct irq_bucket *p, **pnext;
unsigned long flags;
spin_lock_irqsave(&irq_action_lock, flags);
BUG_ON(irq >= NR_IRQS);
kfree(action);
spin_lock_irqsave(&irq_map_lock, flags);
if (!sparc_irq[cpu_irq].action)
__disable_irq(irq);
p = &irq_table[irq];
BUG_ON(p->pil > SUN4D_MAX_IRQ);
pnext = &irq_map[p->pil];
while (*pnext != p)
pnext = &(*pnext)->next;
*pnext = p->next;
out_unlock:
spin_unlock_irqrestore(&irq_action_lock, flags);
spin_unlock_irqrestore(&irq_map_lock, flags);
}
EXPORT_SYMBOL(free_irq);
/*
* This is called when we want to synchronize with
* interrupts. We may for example tell a device to
* stop sending interrupts: but to make sure there
* are no interrupts that are executing on another
* CPU we need to call this function.
*/
#ifdef CONFIG_SMP
void synchronize_irq(unsigned int irq)
{
unsigned int cpu_irq;
cpu_irq = irq & (NR_IRQS - 1);
while (sparc_irq[cpu_irq].flags & SPARC_IRQ_INPROGRESS)
cpu_relax();
}
EXPORT_SYMBOL(synchronize_irq);
#endif /* SMP */
void unexpected_irq(int irq, void *dev_id, struct pt_regs *regs)
/* /proc/interrupts printing */
int arch_show_interrupts(struct seq_file *p, int prec)
{
int i;
struct irqaction *action;
unsigned int cpu_irq;
int j;
cpu_irq = irq & (NR_IRQS - 1);
action = sparc_irq[cpu_irq].action;
printk(KERN_ERR "IO device interrupt, irq = %d\n", irq);
printk(KERN_ERR "PC = %08lx NPC = %08lx FP=%08lx\n", regs->pc,
regs->npc, regs->u_regs[14]);
if (action) {
printk(KERN_ERR "Expecting: ");
for (i = 0; i < 16; i++)
if (action->handler)
printk(KERN_CONT "[%s:%d:0x%x] ", action->name,
i, (unsigned int)action->handler);
}
printk(KERN_ERR "AIEEE\n");
panic("bogus interrupt received");
seq_printf(p, "NMI: ");
for_each_online_cpu(j)
seq_printf(p, "%10u ", cpu_data(j).counter);
seq_printf(p, " Non-maskable interrupts\n");
return 0;
}
void handler_irq(int pil, struct pt_regs *regs)
void handler_irq(unsigned int pil, struct pt_regs *regs)
{
struct pt_regs *old_regs;
struct irqaction *action;
int cpu = smp_processor_id();
struct irq_bucket *p;
BUG_ON(pil > 15);
old_regs = set_irq_regs(regs);
irq_enter();
disable_pil_irq(pil);
#ifdef CONFIG_SMP
/* Only rotate on lower priority IRQs (scsi, ethernet, etc.). */
if ((sparc_cpu_model==sun4m) && (pil < 10))
smp4m_irq_rotate(cpu);
#endif
action = sparc_irq[pil].action;
sparc_irq[pil].flags |= SPARC_IRQ_INPROGRESS;
kstat_cpu(cpu).irqs[pil]++;
do {
if (!action || !action->handler)
unexpected_irq(pil, NULL, regs);
action->handler(pil, action->dev_id);
action = action->next;
} while (action);
sparc_irq[pil].flags &= ~SPARC_IRQ_INPROGRESS;
enable_pil_irq(pil);
p = irq_map[pil];
while (p) {
struct irq_bucket *next = p->next;
generic_handle_irq(p->irq);
p = next;
}
irq_exit();
set_irq_regs(old_regs);
}
#if defined(CONFIG_BLK_DEV_FD) || defined(CONFIG_BLK_DEV_FD_MODULE)
static unsigned int floppy_irq;
/*
* Fast IRQs on the Sparc can only have one routine attached to them,
* thus no sharing possible.
*/
static int request_fast_irq(unsigned int irq,
void (*handler)(void),
unsigned long irqflags, const char *devname)
int sparc_floppy_request_irq(unsigned int irq, irq_handler_t irq_handler)
{
struct irqaction *action;
unsigned long flags;
unsigned int cpu_irq;
int ret;
int err;
#if defined CONFIG_SMP && !defined CONFIG_SPARC_LEON
struct tt_entry *trap_table;
#endif
cpu_irq = irq & (NR_IRQS - 1);
if (cpu_irq > 14) {
ret = -EINVAL;
goto out;
}
if (!handler) {
ret = -EINVAL;
goto out;
}
spin_lock_irqsave(&irq_action_lock, flags);
err = request_irq(irq, irq_handler, 0, "floppy", NULL);
if (err)
return -1;
action = sparc_irq[cpu_irq].action;
if (action) {
if (action->flags & IRQF_SHARED)
panic("Trying to register fast irq when already shared.\n");
if (irqflags & IRQF_SHARED)
panic("Trying to register fast irq as shared.\n");
/* Save for later use in floppy interrupt handler */
floppy_irq = irq;
/* Anyway, someone already owns it so cannot be made fast. */
printk(KERN_ERR "request_fast_irq: Trying to register yet already owned.\n");
ret = -EBUSY;
goto out_unlock;
}
/*
* If this is flagged as statically allocated then we use our
* private struct which is never freed.
*/
if (irqflags & SA_STATIC_ALLOC) {
if (static_irq_count < MAX_STATIC_ALLOC)
action = &static_irqaction[static_irq_count++];
else
printk(KERN_ERR "Fast IRQ%d (%s) SA_STATIC_ALLOC failed using kmalloc\n",
irq, devname);
}
if (action == NULL)
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
if (!action) {
ret = -ENOMEM;
goto out_unlock;
}
cpu_irq = (irq & (NR_IRQS - 1));
/* Dork with trap table if we get this far. */
#define INSTANTIATE(table) \
table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_one = SPARC_RD_PSR_L0; \
table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_two = \
SPARC_BRANCH((unsigned long) handler, \
SPARC_BRANCH((unsigned long) floppy_hardint, \
(unsigned long) &table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_two);\
table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_three = SPARC_RD_WIM_L3; \
table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_four = SPARC_NOP;
......@@ -399,22 +279,9 @@ static int request_fast_irq(unsigned int irq,
* writing we have no CPU-neutral interface to fine-grained flushes.
*/
flush_cache_all();
action->flags = irqflags;
action->name = devname;
action->dev_id = NULL;
action->next = NULL;
sparc_irq[cpu_irq].action = action;
__enable_irq(irq);
ret = 0;
out_unlock:
spin_unlock_irqrestore(&irq_action_lock, flags);
out:
return ret;
return 0;
}
EXPORT_SYMBOL(sparc_floppy_request_irq);
/*
* These variables are used to access state from the assembler
......@@ -440,154 +307,23 @@ EXPORT_SYMBOL(pdma_base);
unsigned long pdma_areasize;
EXPORT_SYMBOL(pdma_areasize);