[ubicom32]: move new files out from platform support patch
[openwrt.git] / target / linux / ubicom32 / files / arch / ubicom32 / kernel / irq.c
diff --git a/target/linux/ubicom32/files/arch/ubicom32/kernel/irq.c b/target/linux/ubicom32/files/arch/ubicom32/kernel/irq.c
new file mode 100644 (file)
index 0000000..c041f23
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * arch/ubicom32/kernel/irq.c
+ *   Ubicom32 architecture IRQ support.
+ *
+ * (C) Copyright 2009, Ubicom, Inc.
+ * (C) Copyright 2007, Greg Ungerer <gerg@snapgear.com>
+ *
+ * This file is part of the Ubicom32 Linux Kernel Port.
+ *
+ * The Ubicom32 Linux Kernel Port 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.
+ *
+ * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port.  If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Ubicom32 implementation derived from (with many thanks):
+ *   arch/m68knommu
+ *   arch/blackfin
+ *   arch/parisc
+ */
+
+#include <linux/types.h>
+#include <linux/irq.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kernel_stat.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/proc_fs.h>
+#include <asm/system.h>
+#include <asm/traps.h>
+#include <asm/ldsr.h>
+#include <asm/ip5000.h>
+#include <asm/machdep.h>
+#include <asm/asm-offsets.h>
+#include <asm/thread.h>
+#include <asm/devtree.h>
+
+unsigned int irq_soft_avail;
+static struct irqaction ubicom32_reserve_action[NR_IRQS];
+
+#if !defined(CONFIG_DEBUG_IRQMEASURE)
+#define IRQ_DECLARE_MEASUREMENT
+#define IRQ_MEASUREMENT_START()
+#define IRQ_MEASUREMENT_END(irq)
+#else
+#define IRQ_DECLARE_MEASUREMENT \
+       int __diff;             \
+       unsigned int __tstart;
+
+#define IRQ_MEASUREMENT_START() \
+       __tstart = UBICOM32_IO_TIMER->sysval;
+
+#define IRQ_MEASUREMENT_END(irq) \
+       __diff = (int)UBICOM32_IO_TIMER->sysval - (int)__tstart; \
+       irq_measurement_update((irq), __diff);
+
+/*
+ * We keep track of the time spent in both irq_enter()
+ * and irq_exit().
+ */
+#define IRQ_WEIGHT 32
+
+struct irq_measurement {
+       volatile unsigned int min;
+       volatile unsigned int avg;
+       volatile unsigned int max;
+};
+
+static DEFINE_SPINLOCK(irq_measurement_lock);
+
+/*
+ *  Add 1 in for softirq (irq_exit());
+ */
+static struct irq_measurement irq_measurements[NR_IRQS + 1];
+
+/*
+ * irq_measurement_update()
+ *     Update an entry in the measurement array for this irq.
+ */
+static void irq_measurement_update(int irq, int sample)
+{
+       struct irq_measurement *im = &irq_measurements[irq];
+       spin_lock(&irq_measurement_lock);
+       if ((im->min == 0) || (im->min > sample)) {
+               im->min = sample;
+       }
+       if (im->max < sample) {
+               im->max = sample;
+       }
+       im->avg = ((im->avg * (IRQ_WEIGHT - 1)) + sample) / IRQ_WEIGHT;
+       spin_unlock(&irq_measurement_lock);
+}
+#endif
+
+/*
+ * irq_kernel_stack_check()
+ *     See if the kernel stack is within STACK_WARN of the end.
+ */
+static void irq_kernel_stack_check(int irq, struct pt_regs *regs)
+{
+#ifdef CONFIG_DEBUG_STACKOVERFLOW
+       unsigned long sp;
+
+       /*
+        * Make sure that we are not close to the top of the stack and thus
+        * can not really service this interrupt.
+        */
+       asm volatile (
+               "and.4          %0, SP, %1 \n\t"
+               : "=d" (sp)
+               : "d" (THREAD_SIZE - 1)
+               : "cc"
+       );
+
+       if (sp < (sizeof(struct thread_info) + STACK_WARN)) {
+               printk(KERN_WARNING
+                       "cpu[%d]: possible overflow detected sp remain: %p, "
+                      "irq: %d, regs: %p\n",
+                       thread_get_self(), (void *)sp, irq, regs);
+               dump_stack();
+       }
+
+       if (sp < (sizeof(struct thread_info) + 16)) {
+               THREAD_STALL;
+       }
+#endif
+}
+
+/*
+ * irq_get_lsb()
+ *     Get the LSB set in value
+ */
+static int irq_get_lsb(unsigned int value)
+{
+       static unsigned char irq_bits[8] = {
+               3, 0, 1, 0, 2, 0, 1, 0
+       };
+       u32_t nextbit = 0;
+
+       value = (value >> nextbit) | (value << ((sizeof(value) * 8) - nextbit));
+
+       /*
+        * It's unlikely that we find that we execute the body of this while
+        * loop.  50% of the time we won't take this at all and then of the
+        * cases where we do about 50% of those we only execute once.
+        */
+       if (!(value & 0xffff)) {
+               nextbit += 0x10;
+               value >>= 16;
+       }
+
+       if (!(value & 0xff)) {
+               nextbit += 0x08;
+               value >>= 8;
+       }
+
+       if (!(value & 0xf)) {
+               nextbit += 0x04;
+               value >>= 4;
+       }
+
+       nextbit += irq_bits[value & 0x7];
+       if (nextbit > 63) {
+               panic("nextbit out of range: %d\n", nextbit);
+       }
+       return nextbit;
+}
+
+/*
+ * ubicom32_reserve_handler()
+ *     Bogus handler associated with pre-reserved IRQ(s).
+ */
+static irqreturn_t ubicom32_reserve_handler(int irq, void *dev_id)
+{
+       BUG();
+       return IRQ_HANDLED;
+}
+
+/*
+ * __irq_disable_vector()
+ *     Disable the interrupt by clearing the appropriate bit in the
+ *     LDSR Mask Register.
+ */
+static void __irq_disable_vector(unsigned int irq)
+{
+       ldsr_disable_vector(irq);
+}
+
+/*
+ * __irq_ack_vector()
+ *     Acknowledge the specific interrupt by clearing the associate bit in
+ *     hardware
+ */
+static void __irq_ack_vector(unsigned int irq)
+{
+       if (irq < 32) {
+               asm volatile ("move.4 INT_CLR0, %0" : : "d" (1 << irq));
+       } else {
+               asm volatile ("move.4 INT_CLR1, %0" : : "d" (1 << (irq - 32)));
+       }
+}
+
+/*
+ * __irq_enable_vector()
+ *     Clean and then enable the interrupt by setting the appropriate bit in
+ *     the LDSR Mask Register.
+ */
+static void __irq_enable_vector(unsigned int irq)
+{
+       /*
+        * Acknowledge, really clear the vector.
+        */
+       __irq_ack_vector(irq);
+       ldsr_enable_vector(irq);
+}
+
+/*
+ * __irq_mask_vector()
+ */
+static void __irq_mask_vector(unsigned int irq)
+{
+       ldsr_mask_vector(irq);
+}
+
+/*
+ * __irq_unmask_vector()
+ */
+static void __irq_unmask_vector(unsigned int irq)
+{
+       ldsr_unmask_vector(irq);
+}
+
+/*
+ * __irq_end_vector()
+ *     Called once an interrupt is completed (reset the LDSR mask).
+ */
+static void __irq_end_vector(unsigned int irq)
+{
+       ldsr_unmask_vector(irq);
+}
+
+#if defined(CONFIG_SMP)
+/*
+ * __irq_set_affinity()
+ *     Set the cpu affinity for this interrupt.
+ *     affinity container allocated at boot
+ */
+static void __irq_set_affinity(unsigned int irq, const struct cpumask *dest)
+{
+       smp_set_affinity(irq, dest);
+       cpumask_copy(irq_desc[irq].affinity, dest);
+}
+#endif
+
+/*
+ * On-Chip Generic Interrupt function handling.
+ */
+static struct irq_chip ubicom32_irq_chip = {
+       .name           = "Ubicom32",
+       .startup        = NULL,
+       .shutdown       = NULL,
+       .enable         = __irq_enable_vector,
+       .disable        = __irq_disable_vector,
+       .ack            = __irq_ack_vector,
+       .mask           = __irq_mask_vector,
+       .unmask         = __irq_unmask_vector,
+       .end            = __irq_end_vector,
+#if defined(CONFIG_SMP)
+       .set_affinity   = __irq_set_affinity,
+#endif
+};
+
+/*
+ * do_IRQ()
+ *     Primary interface for handling IRQ() requests.
+ */
+asmlinkage void do_IRQ(int irq, struct pt_regs *regs)
+{
+       struct pt_regs *oldregs;
+       struct thread_info *ti = current_thread_info();
+
+       IRQ_DECLARE_MEASUREMENT;
+
+       /*
+        * Mark that we are inside of an interrupt and
+        * that interrupts are disabled.
+        */
+       oldregs = set_irq_regs(regs);
+       ti->interrupt_nesting++;
+       trace_hardirqs_off();
+       irq_kernel_stack_check(irq, regs);
+
+       /*
+        * Start the interrupt sequence
+        */
+       irq_enter();
+
+       /*
+        * Execute the IRQ handler and any pending SoftIRQ requests.
+        */
+       BUG_ON(!irqs_disabled());
+       IRQ_MEASUREMENT_START();
+       __do_IRQ(irq);
+       IRQ_MEASUREMENT_END(irq);
+       BUG_ON(!irqs_disabled());
+
+       /*
+        * TODO: Since IRQ's are disabled when calling irq_exit()
+        * modify Kconfig to set __ARCH_IRQ_EXIT_IRQS_DISABLED flag.
+        * This will slightly improve performance by enabling
+        * softirq handling to avoid disabling/disabled interrupts.
+        */
+       IRQ_MEASUREMENT_START();
+       irq_exit();
+       IRQ_MEASUREMENT_END(NR_IRQS);
+       BUG_ON(!irqs_disabled());
+
+       /*
+        * Outside of an interrupt (or nested exit).
+        */
+       set_irq_regs(oldregs);
+       trace_hardirqs_on();
+       ti->interrupt_nesting--;
+}
+
+/*
+ * irq_soft_alloc()
+ *     Allocate a soft IRQ.
+ */
+int irq_soft_alloc(unsigned int *soft)
+{
+       if (irq_soft_avail == 0) {
+               printk(KERN_NOTICE "no soft irqs to allocate\n");
+               return -EFAULT;
+       }
+
+       *soft = irq_get_lsb(irq_soft_avail);
+       irq_soft_avail &= ~(1 << *soft);
+       return 0;
+}
+
+/*
+ * ack_bad_irq()
+ *     Called to handle an bad irq request.
+ */
+void ack_bad_irq(unsigned int irq)
+{
+       printk(KERN_ERR "IRQ: unexpected irq=%d\n", irq);
+       __irq_end_vector(irq);
+}
+
+/*
+ * show_interrupts()
+ *     Return a string that displays the state of each of the interrupts.
+ */
+int show_interrupts(struct seq_file *p, void *v)
+{
+       struct irqaction *ap;
+       int irq = *((loff_t *) v);
+       int j;
+
+       if (irq >= NR_IRQS) {
+               return 0;
+       }
+
+       if (irq == 0) {
+               seq_puts(p, "           ");
+               for_each_online_cpu(j) {
+                       seq_printf(p, "CPU%d       ", j);
+               }
+               seq_putc(p, '\n');
+       }
+
+       ap = irq_desc[irq].action;
+       if (ap) {
+               seq_printf(p, "%3d: ", irq);
+               for_each_online_cpu(j) {
+                       seq_printf(p, "%10u ", kstat_irqs_cpu(irq, j));
+               }
+               seq_printf(p, "%14s  ", irq_desc[irq].chip->name);
+               seq_printf(p, "%s", ap->name);
+               for (ap = ap->next; ap; ap = ap->next) {
+                       seq_printf(p, ", %s", ap->name);
+               }
+               seq_putc(p, '\n');
+       }
+       return 0;
+}
+
+#if defined(CONFIG_DEBUG_IRQMEASURE)
+static unsigned int irq_cycles_to_micro(unsigned int cycles, unsigned int frequency)
+{
+       unsigned int micro = (cycles / (frequency / 1000000));
+       return micro;
+}
+
+/*
+ * irq_measurement_show()
+ *     Print out the min, avg, max values for each IRQ
+ *
+ * By request, the max value is reset after each dump.
+ */
+static int irq_measurement_show(struct seq_file *p, void *v)
+{
+       struct irqaction *ap;
+       unsigned int freq = processor_frequency();
+       int irq = *((loff_t *) v);
+
+
+       if (irq == 0) {
+               seq_puts(p, "\tmin\tavg\tmax\t(micro-seconds)\n");
+       }
+
+       if (irq > NR_IRQS) {
+               return 0;
+       }
+
+       if (irq == NR_IRQS) {
+               unsigned int min, avg, max;
+               spin_lock(&irq_measurement_lock);
+               min = irq_cycles_to_micro(irq_measurements[irq].min, freq);
+               avg = irq_cycles_to_micro(irq_measurements[irq].avg, freq);
+               max = irq_cycles_to_micro(irq_measurements[irq].max, freq);
+               irq_measurements[irq].max = 0;
+               spin_unlock(&irq_measurement_lock);
+               seq_printf(p, "   \t%u\t%u\t%u\tsoftirq\n", min, avg, max);
+               return 0;
+       }
+
+       ap = irq_desc[irq].action;
+       if (ap) {
+               unsigned int min, avg, max;
+               spin_lock(&irq_measurement_lock);
+               min = irq_cycles_to_micro(irq_measurements[irq].min, freq);
+               avg = irq_cycles_to_micro(irq_measurements[irq].avg, freq);
+               max = irq_cycles_to_micro(irq_measurements[irq].max, freq);
+               irq_measurements[irq].max = 0;
+               spin_unlock(&irq_measurement_lock);
+               seq_printf(p, "%2u:\t%u\t%u\t%u\t%s\n", irq, min, avg, max, ap->name);
+       }
+       return 0;
+}
+
+static void *irq_measurement_start(struct seq_file *f, loff_t *pos)
+{
+       return (*pos <= NR_IRQS) ? pos : NULL;
+}
+
+static void *irq_measurement_next(struct seq_file *f, void *v, loff_t *pos)
+{
+       (*pos)++;
+       if (*pos > NR_IRQS)
+               return NULL;
+       return pos;
+}
+
+static void irq_measurement_stop(struct seq_file *f, void *v)
+{
+       /* Nothing to do */
+}
+
+static const struct seq_operations irq_measurement_seq_ops = {
+       .start = irq_measurement_start,
+       .next  = irq_measurement_next,
+       .stop  = irq_measurement_stop,
+       .show  = irq_measurement_show,
+};
+
+static int irq_measurement_open(struct inode *inode, struct file *filp)
+{
+       return seq_open(filp, &irq_measurement_seq_ops);
+}
+
+static const struct file_operations irq_measurement_fops = {
+       .open           = irq_measurement_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
+
+static int __init irq_measurement_init(void)
+{
+       proc_create("irq_measurements", 0, NULL, &irq_measurement_fops);
+       return 0;
+}
+module_init(irq_measurement_init);
+#endif
+
+/*
+ * init_IRQ(void)
+ *     Initialize the on-chip IRQ subsystem.
+ */
+void __init init_IRQ(void)
+{
+       int irq;
+       struct devtree_node *p = NULL;
+       struct devtree_node *iter = NULL;
+       unsigned int mask = 0;
+       unsigned int reserved = 0;
+
+       /*
+        * Pull out the list of software interrupts that are avialable to
+        * Linux and provide an allocation function for them.  The first
+        * 24 interrupts of INT0 are software interrupts.
+        */
+       irq_soft_avail = 0;
+       if (processor_interrupts(&irq_soft_avail, NULL) < 0) {
+               printk(KERN_WARNING "No Soft IRQ(s) available\n");
+       }
+       irq_soft_avail &= ((1 << 24) - 1);
+
+       /*
+        * Initialize all of the on-chip interrupt handling
+        * to use a common set of interrupt functions.
+        */
+       for (irq = 0; irq < NR_IRQS; irq++) {
+               irq_desc[irq].status = IRQ_DISABLED;
+               irq_desc[irq].action = NULL;
+               irq_desc[irq].depth = 1;
+               set_irq_chip(irq, &ubicom32_irq_chip);
+       }
+
+       /*
+        * The sendirq of a devnode is not registered within Linux but instead
+        * is used by the software I/O thread.  These interrupts are reserved.
+        * The recvirq is used by Linux and registered by a device driver, these
+        * are not reserved.
+        *
+        * recvirq(s) that are in the software interrupt range are not supposed
+        * to be marked as reserved.  We track this while we scan the device
+        * nodes.
+        */
+       p = devtree_find_next(&iter);
+       while (p) {
+               unsigned char sendirq, recvirq;
+               devtree_irq(p, &sendirq, &recvirq);
+
+               /*
+                * If the sendirq is valid, mark that irq as taken by the
+                * devtree node.
+                */
+               if (sendirq < NR_IRQS) {
+                       ubicom32_reserve_action[sendirq].handler =
+                               ubicom32_reserve_handler;
+                       ubicom32_reserve_action[sendirq].name = p->name;
+                       irq_desc[sendirq].action =
+                               &ubicom32_reserve_action[sendirq];
+                       mask |= (1 << sendirq);
+               }
+
+               /*
+                * Track the relevant recieve IRQ(s)
+                */
+               if (recvirq < 24) {
+                       mask |= (1 << recvirq);
+               }
+
+               /*
+                * Move to the next node.
+                */
+               p = devtree_find_next(&iter);
+       }
+
+       /*
+        * Remove these bits from the irq_soft_avail list and then use the
+        * result as the list of pre-reserved IRQ(s).
+        */
+       reserved = ~irq_soft_avail & ~mask;
+       for (irq = 0; irq < 24; irq++) {
+               if ((reserved & (1 << irq))) {
+                       ubicom32_reserve_action[irq].handler =
+                               ubicom32_reserve_handler;
+                       ubicom32_reserve_action[irq].name = "reserved";
+                       irq_desc[irq].action = &ubicom32_reserve_action[irq];
+               }
+       }
+
+       /*
+        * Initialize the LDSR which is the Ubicom32 programmable
+        * interrupt controller.
+        */
+       ldsr_init();
+
+       /*
+        * The Ubicom trap code needs a 2nd init after IRQ(s) are setup.
+        */
+       trap_init_interrupt();
+}