aboutsummaryrefslogtreecommitdiff
path: root/arch/mips/sgi-ip22
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/mips/sgi-ip22
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'arch/mips/sgi-ip22')
-rw-r--r--arch/mips/sgi-ip22/Makefile11
-rw-r--r--arch/mips/sgi-ip22/ip22-berr.c114
-rw-r--r--arch/mips/sgi-ip22/ip22-eisa.c308
-rw-r--r--arch/mips/sgi-ip22/ip22-hpc.c63
-rw-r--r--arch/mips/sgi-ip22/ip22-int.c410
-rw-r--r--arch/mips/sgi-ip22/ip22-irq.S118
-rw-r--r--arch/mips/sgi-ip22/ip22-mc.c208
-rw-r--r--arch/mips/sgi-ip22/ip22-nvram.c119
-rw-r--r--arch/mips/sgi-ip22/ip22-reset.c247
-rw-r--r--arch/mips/sgi-ip22/ip22-setup.c144
-rw-r--r--arch/mips/sgi-ip22/ip22-time.c214
11 files changed, 1956 insertions, 0 deletions
diff --git a/arch/mips/sgi-ip22/Makefile b/arch/mips/sgi-ip22/Makefile
new file mode 100644
index 00000000000..eb0820fe50b
--- /dev/null
+++ b/arch/mips/sgi-ip22/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for the SGI specific kernel interface routines
+# under Linux.
+#
+
+obj-y += ip22-mc.o ip22-hpc.o ip22-int.o ip22-irq.o ip22-berr.o \
+ ip22-time.o ip22-nvram.o ip22-reset.o ip22-setup.o
+
+obj-$(CONFIG_EISA) += ip22-eisa.o
+
+EXTRA_AFLAGS := $(CFLAGS)
diff --git a/arch/mips/sgi-ip22/ip22-berr.c b/arch/mips/sgi-ip22/ip22-berr.c
new file mode 100644
index 00000000000..a28dc780007
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-berr.c
@@ -0,0 +1,114 @@
+/*
+ * ip22-berr.c: Bus error handling.
+ *
+ * Copyright (C) 2002, 2003 Ladislav Michl (ladis@linux-mips.org)
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+
+#include <asm/addrspace.h>
+#include <asm/system.h>
+#include <asm/traps.h>
+#include <asm/branch.h>
+#include <asm/sgi/mc.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ioc.h>
+#include <asm/sgi/ip22.h>
+
+
+static unsigned int cpu_err_stat; /* Status reg for CPU */
+static unsigned int gio_err_stat; /* Status reg for GIO */
+static unsigned int cpu_err_addr; /* Error address reg for CPU */
+static unsigned int gio_err_addr; /* Error address reg for GIO */
+static unsigned int extio_stat;
+static unsigned int hpc3_berr_stat; /* Bus error interrupt status */
+
+static void save_and_clear_buserr(void)
+{
+ /* save status registers */
+ cpu_err_addr = sgimc->cerr;
+ cpu_err_stat = sgimc->cstat;
+ gio_err_addr = sgimc->gerr;
+ gio_err_stat = sgimc->gstat;
+ extio_stat = ip22_is_fullhouse() ? sgioc->extio : (sgint->errstat << 4);
+ hpc3_berr_stat = hpc3c0->bestat;
+
+ sgimc->cstat = sgimc->gstat = 0;
+}
+
+#define GIO_ERRMASK 0xff00
+#define CPU_ERRMASK 0x3f00
+
+static void print_buserr(void)
+{
+ if (extio_stat & EXTIO_MC_BUSERR)
+ printk(KERN_ERR "MC Bus Error\n");
+ if (extio_stat & EXTIO_HPC3_BUSERR)
+ printk(KERN_ERR "HPC3 Bus Error 0x%x:<id=0x%x,%s,lane=0x%x>\n",
+ hpc3_berr_stat,
+ (hpc3_berr_stat & HPC3_BESTAT_PIDMASK) >>
+ HPC3_BESTAT_PIDSHIFT,
+ (hpc3_berr_stat & HPC3_BESTAT_CTYPE) ? "PIO" : "DMA",
+ hpc3_berr_stat & HPC3_BESTAT_BLMASK);
+ if (extio_stat & EXTIO_EISA_BUSERR)
+ printk(KERN_ERR "EISA Bus Error\n");
+ if (cpu_err_stat & CPU_ERRMASK)
+ printk(KERN_ERR "CPU error 0x%x<%s%s%s%s%s%s> @ 0x%08x\n",
+ cpu_err_stat,
+ cpu_err_stat & SGIMC_CSTAT_RD ? "RD " : "",
+ cpu_err_stat & SGIMC_CSTAT_PAR ? "PAR " : "",
+ cpu_err_stat & SGIMC_CSTAT_ADDR ? "ADDR " : "",
+ cpu_err_stat & SGIMC_CSTAT_SYSAD_PAR ? "SYSAD " : "",
+ cpu_err_stat & SGIMC_CSTAT_SYSCMD_PAR ? "SYSCMD " : "",
+ cpu_err_stat & SGIMC_CSTAT_BAD_DATA ? "BAD_DATA " : "",
+ cpu_err_addr);
+ if (gio_err_stat & GIO_ERRMASK)
+ printk(KERN_ERR "GIO error 0x%x:<%s%s%s%s%s%s%s%s> @ 0x%08x\n",
+ gio_err_stat,
+ gio_err_stat & SGIMC_GSTAT_RD ? "RD " : "",
+ gio_err_stat & SGIMC_GSTAT_WR ? "WR " : "",
+ gio_err_stat & SGIMC_GSTAT_TIME ? "TIME " : "",
+ gio_err_stat & SGIMC_GSTAT_PROM ? "PROM " : "",
+ gio_err_stat & SGIMC_GSTAT_ADDR ? "ADDR " : "",
+ gio_err_stat & SGIMC_GSTAT_BC ? "BC " : "",
+ gio_err_stat & SGIMC_GSTAT_PIO_RD ? "PIO_RD " : "",
+ gio_err_stat & SGIMC_GSTAT_PIO_WR ? "PIO_WR " : "",
+ gio_err_addr);
+}
+
+/*
+ * MC sends an interrupt whenever bus or parity errors occur. In addition,
+ * if the error happened during a CPU read, it also asserts the bus error
+ * pin on the R4K. Code in bus error handler save the MC bus error registers
+ * and then clear the interrupt when this happens.
+ */
+
+void ip22_be_interrupt(int irq, struct pt_regs *regs)
+{
+ const int field = 2 * sizeof(unsigned long);
+
+ save_and_clear_buserr();
+ print_buserr();
+ printk(KERN_ALERT "%s bus error, epc == %0*lx, ra == %0*lx\n",
+ (regs->cp0_cause & 4) ? "Data" : "Instruction",
+ field, regs->cp0_epc, field, regs->regs[31]);
+ /* Assume it would be too dangerous to continue ... */
+ die_if_kernel("Oops", regs);
+ force_sig(SIGBUS, current);
+}
+
+static int ip22_be_handler(struct pt_regs *regs, int is_fixup)
+{
+ save_and_clear_buserr();
+ if (is_fixup)
+ return MIPS_BE_FIXUP;
+ print_buserr();
+ return MIPS_BE_FATAL;
+}
+
+void __init ip22_be_init(void)
+{
+ board_be_handler = ip22_be_handler;
+}
diff --git a/arch/mips/sgi-ip22/ip22-eisa.c b/arch/mips/sgi-ip22/ip22-eisa.c
new file mode 100644
index 00000000000..0ab4abf65d5
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-eisa.c
@@ -0,0 +1,308 @@
+/*
+ * Basic EISA bus support for the SGI Indigo-2.
+ *
+ * (C) 2002 Pascal Dameme <netinet@freesurf.fr>
+ * and Marc Zyngier <mzyngier@freesurf.fr>
+ *
+ * This code is released under both the GPL version 2 and BSD
+ * licenses. Either license may be used.
+ *
+ * This code offers a very basic support for this EISA bus present in
+ * the SGI Indigo-2. It currently only supports PIO (forget about DMA
+ * for the time being). This is enough for a low-end ethernet card,
+ * but forget about your favorite SCSI card...
+ *
+ * TODO :
+ * - Fix bugs...
+ * - Add ISA support
+ * - Add DMA (yeah, right...).
+ * - Fix more bugs.
+ */
+
+#include <linux/config.h>
+#include <linux/eisa.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/kernel_stat.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <asm/irq.h>
+#include <asm/mipsregs.h>
+#include <asm/addrspace.h>
+#include <asm/processor.h>
+#include <asm/sgi/ioc.h>
+#include <asm/sgi/mc.h>
+#include <asm/sgi/ip22.h>
+
+#define EISA_MAX_SLOTS 4
+#define EISA_MAX_IRQ 16
+
+#define EISA_TO_PHYS(x) (0x00080000 | (x))
+#define EISA_TO_KSEG1(x) ((void *) KSEG1ADDR(EISA_TO_PHYS((x))))
+
+#define EIU_MODE_REG 0x0009ffc0
+#define EIU_STAT_REG 0x0009ffc4
+#define EIU_PREMPT_REG 0x0009ffc8
+#define EIU_QUIET_REG 0x0009ffcc
+#define EIU_INTRPT_ACK 0x00090004
+
+#define EISA_DMA1_STATUS 8
+#define EISA_INT1_CTRL 0x20
+#define EISA_INT1_MASK 0x21
+#define EISA_INT2_CTRL 0xA0
+#define EISA_INT2_MASK 0xA1
+#define EISA_DMA2_STATUS 0xD0
+#define EISA_DMA2_WRITE_SINGLE 0xD4
+#define EISA_EXT_NMI_RESET_CTRL 0x461
+#define EISA_INT1_EDGE_LEVEL 0x4D0
+#define EISA_INT2_EDGE_LEVEL 0x4D1
+#define EISA_VENDOR_ID_OFFSET 0xC80
+
+#define EIU_WRITE_32(x,y) { *((u32 *) KSEG1ADDR(x)) = (u32) (y); mb(); }
+#define EIU_READ_8(x) *((u8 *) KSEG1ADDR(x))
+#define EISA_WRITE_8(x,y) { *((u8 *) EISA_TO_KSEG1(x)) = (u8) (y); mb(); }
+#define EISA_READ_8(x) *((u8 *) EISA_TO_KSEG1(x))
+
+static char *decode_eisa_sig(u8 * sig)
+{
+ static char sig_str[8];
+ u16 rev;
+
+ if (sig[0] & 0x80)
+ return NULL;
+
+ sig_str[0] = ((sig[0] >> 2) & 0x1f) + ('A' - 1);
+ sig_str[1] = (((sig[0] & 3) << 3) | (sig[1] >> 5)) + ('A' - 1);
+ sig_str[2] = (sig[1] & 0x1f) + ('A' - 1);
+ rev = (sig[2] << 8) | sig[3];
+ sprintf(sig_str + 3, "%04X", rev);
+
+ return sig_str;
+}
+
+static void ip22_eisa_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+ u8 eisa_irq;
+ u8 dma1, dma2;
+
+ eisa_irq = EIU_READ_8(EIU_INTRPT_ACK);
+ dma1 = EISA_READ_8(EISA_DMA1_STATUS);
+ dma2 = EISA_READ_8(EISA_DMA2_STATUS);
+
+ if (eisa_irq >= EISA_MAX_IRQ) {
+ /* Oops, Bad Stuff Happened... */
+ printk(KERN_ERR "eisa_irq %d out of bound\n", eisa_irq);
+
+ EISA_WRITE_8(EISA_INT2_CTRL, 0x20);
+ EISA_WRITE_8(EISA_INT1_CTRL, 0x20);
+ } else
+ do_IRQ(eisa_irq, regs);
+}
+
+static void enable_eisa1_irq(unsigned int irq)
+{
+ unsigned long flags;
+ u8 mask;
+
+ local_irq_save(flags);
+
+ mask = EISA_READ_8(EISA_INT1_MASK);
+ mask &= ~((u8) (1 << irq));
+ EISA_WRITE_8(EISA_INT1_MASK, mask);
+
+ local_irq_restore(flags);
+}
+
+static unsigned int startup_eisa1_irq(unsigned int irq)
+{
+ u8 edge;
+
+ /* Only use edge interrupts for EISA */
+
+ edge = EISA_READ_8(EISA_INT1_EDGE_LEVEL);
+ edge &= ~((u8) (1 << irq));
+ EISA_WRITE_8(EISA_INT1_EDGE_LEVEL, edge);
+
+ enable_eisa1_irq(irq);
+ return 0;
+}
+
+static void disable_eisa1_irq(unsigned int irq)
+{
+ u8 mask;
+
+ mask = EISA_READ_8(EISA_INT1_MASK);
+ mask |= ((u8) (1 << irq));
+ EISA_WRITE_8(EISA_INT1_MASK, mask);
+}
+
+#define shutdown_eisa1_irq disable_eisa1_irq
+
+static void mask_and_ack_eisa1_irq(unsigned int irq)
+{
+ disable_eisa1_irq(irq);
+
+ EISA_WRITE_8(EISA_INT1_CTRL, 0x20);
+}
+
+static void end_eisa1_irq(unsigned int irq)
+{
+ if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS)))
+ enable_eisa1_irq(irq);
+}
+
+static struct hw_interrupt_type ip22_eisa1_irq_type = {
+ .typename = "IP22 EISA",
+ .startup = startup_eisa1_irq,
+ .shutdown = shutdown_eisa1_irq,
+ .enable = enable_eisa1_irq,
+ .disable = disable_eisa1_irq,
+ .ack = mask_and_ack_eisa1_irq,
+ .end = end_eisa1_irq,
+};
+
+static void enable_eisa2_irq(unsigned int irq)
+{
+ unsigned long flags;
+ u8 mask;
+
+ local_irq_save(flags);
+
+ mask = EISA_READ_8(EISA_INT2_MASK);
+ mask &= ~((u8) (1 << (irq - 8)));
+ EISA_WRITE_8(EISA_INT2_MASK, mask);
+
+ local_irq_restore(flags);
+}
+
+static unsigned int startup_eisa2_irq(unsigned int irq)
+{
+ u8 edge;
+
+ /* Only use edge interrupts for EISA */
+
+ edge = EISA_READ_8(EISA_INT2_EDGE_LEVEL);
+ edge &= ~((u8) (1 << (irq - 8)));
+ EISA_WRITE_8(EISA_INT2_EDGE_LEVEL, edge);
+
+ enable_eisa2_irq(irq);
+ return 0;
+}
+
+static void disable_eisa2_irq(unsigned int irq)
+{
+ u8 mask;
+
+ mask = EISA_READ_8(EISA_INT2_MASK);
+ mask |= ((u8) (1 << (irq - 8)));
+ EISA_WRITE_8(EISA_INT2_MASK, mask);
+}
+
+#define shutdown_eisa2_irq disable_eisa2_irq
+
+static void mask_and_ack_eisa2_irq(unsigned int irq)
+{
+ disable_eisa2_irq(irq);
+
+ EISA_WRITE_8(EISA_INT2_CTRL, 0x20);
+ EISA_WRITE_8(EISA_INT1_CTRL, 0x20);
+}
+
+static void end_eisa2_irq(unsigned int irq)
+{
+ if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS)))
+ enable_eisa2_irq(irq);
+}
+
+static struct hw_interrupt_type ip22_eisa2_irq_type = {
+ .typename = "IP22 EISA",
+ .startup = startup_eisa2_irq,
+ .shutdown = shutdown_eisa2_irq,
+ .enable = enable_eisa2_irq,
+ .disable = disable_eisa2_irq,
+ .ack = mask_and_ack_eisa2_irq,
+ .end = end_eisa2_irq,
+};
+
+static struct irqaction eisa_action = {
+ .handler = ip22_eisa_intr,
+ .name = "EISA",
+};
+
+static struct irqaction cascade_action = {
+ .handler = no_action,
+ .name = "EISA cascade",
+};
+
+int __init ip22_eisa_init(void)
+{
+ int i, c;
+ char *str;
+ u8 *slot_addr;
+
+ if (!(sgimc->systemid & SGIMC_SYSID_EPRESENT)) {
+ printk(KERN_INFO "EISA: bus not present.\n");
+ return 1;
+ }
+
+ printk(KERN_INFO "EISA: Probing bus...\n");
+ for (c = 0, i = 1; i <= EISA_MAX_SLOTS; i++) {
+ slot_addr =
+ (u8 *) EISA_TO_KSEG1((0x1000 * i) +
+ EISA_VENDOR_ID_OFFSET);
+ if ((str = decode_eisa_sig(slot_addr))) {
+ printk(KERN_INFO "EISA: slot %d : %s detected.\n",
+ i, str);
+ c++;
+ }
+ }
+ printk(KERN_INFO "EISA: Detected %d card%s.\n", c, c < 2 ? "" : "s");
+#ifdef CONFIG_ISA
+ printk(KERN_INFO "ISA support compiled in.\n");
+#endif
+
+ /* Warning : BlackMagicAhead(tm).
+ Please wave your favorite dead chicken over the busses */
+
+ /* First say hello to the EIU */
+ EIU_WRITE_32(EIU_PREMPT_REG, 0x0000FFFF);
+ EIU_WRITE_32(EIU_QUIET_REG, 1);
+ EIU_WRITE_32(EIU_MODE_REG, 0x40f3c07F);
+
+ /* Now be nice to the EISA chipset */
+ EISA_WRITE_8(EISA_EXT_NMI_RESET_CTRL, 1);
+ for (i = 0; i < 10000; i++); /* Wait long enough for the dust to settle */
+ EISA_WRITE_8(EISA_EXT_NMI_RESET_CTRL, 0);
+ EISA_WRITE_8(EISA_INT1_CTRL, 0x11);
+ EISA_WRITE_8(EISA_INT2_CTRL, 0x11);
+ EISA_WRITE_8(EISA_INT1_MASK, 0);
+ EISA_WRITE_8(EISA_INT2_MASK, 8);
+ EISA_WRITE_8(EISA_INT1_MASK, 4);
+ EISA_WRITE_8(EISA_INT2_MASK, 2);
+ EISA_WRITE_8(EISA_INT1_MASK, 1);
+ EISA_WRITE_8(EISA_INT2_MASK, 1);
+ EISA_WRITE_8(EISA_INT1_MASK, 0xfb);
+ EISA_WRITE_8(EISA_INT2_MASK, 0xff);
+ EISA_WRITE_8(EISA_DMA2_WRITE_SINGLE, 0);
+
+ for (i = SGINT_EISA; i < (SGINT_EISA + EISA_MAX_IRQ); i++) {
+ irq_desc[i].status = IRQ_DISABLED;
+ irq_desc[i].action = 0;
+ irq_desc[i].depth = 1;
+ if (i < (SGINT_EISA + 8))
+ irq_desc[i].handler = &ip22_eisa1_irq_type;
+ else
+ irq_desc[i].handler = &ip22_eisa2_irq_type;
+ }
+
+ /* Cannot use request_irq because of kmalloc not being ready at such
+ * an early stage. Yes, I've been bitten... */
+ setup_irq(SGI_EISA_IRQ, &eisa_action);
+ setup_irq(SGINT_EISA + 2, &cascade_action);
+
+ EISA_bus = 1;
+ return 0;
+}
diff --git a/arch/mips/sgi-ip22/ip22-hpc.c b/arch/mips/sgi-ip22/ip22-hpc.c
new file mode 100644
index 00000000000..c0afeccb08c
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-hpc.c
@@ -0,0 +1,63 @@
+/*
+ * ip22-hpc.c: Routines for generic manipulation of the HPC controllers.
+ *
+ * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
+ * Copyright (C) 1998 Ralf Baechle
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <asm/io.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ioc.h>
+#include <asm/sgi/ip22.h>
+
+struct hpc3_regs *hpc3c0, *hpc3c1;
+
+EXPORT_SYMBOL(hpc3c0);
+EXPORT_SYMBOL(hpc3c1);
+
+struct sgioc_regs *sgioc;
+
+EXPORT_SYMBOL(sgioc);
+
+/* We need software copies of these because they are write only. */
+u8 sgi_ioc_reset, sgi_ioc_write;
+
+extern char *system_type;
+
+void __init sgihpc_init(void)
+{
+ /* ioremap can't fail */
+ hpc3c0 = (struct hpc3_regs *)
+ ioremap(HPC3_CHIP0_BASE, sizeof(struct hpc3_regs));
+ hpc3c1 = (struct hpc3_regs *)
+ ioremap(HPC3_CHIP1_BASE, sizeof(struct hpc3_regs));
+ /* IOC lives in PBUS PIO channel 6 */
+ sgioc = (struct sgioc_regs *)hpc3c0->pbus_extregs[6];
+
+ hpc3c0->pbus_piocfg[6][0] |= HPC3_PIOCFG_DS16;
+ if (ip22_is_fullhouse()) {
+ /* Full House comes with INT2 which lives in PBUS PIO
+ * channel 4 */
+ sgint = (struct sgint_regs *)hpc3c0->pbus_extregs[4];
+ system_type = "SGI Indigo2";
+ } else {
+ /* Guiness comes with INT3 which is part of IOC */
+ sgint = &sgioc->int3;
+ system_type = "SGI Indy";
+ }
+
+ sgi_ioc_reset = (SGIOC_RESET_PPORT | SGIOC_RESET_KBDMOUSE |
+ SGIOC_RESET_EISA | SGIOC_RESET_ISDN |
+ SGIOC_RESET_LC0OFF);
+
+ sgi_ioc_write = (SGIOC_WRITE_EASEL | SGIOC_WRITE_NTHRESH |
+ SGIOC_WRITE_TPSPEED | SGIOC_WRITE_EPSEL |
+ SGIOC_WRITE_U0AMODE | SGIOC_WRITE_U1AMODE);
+
+ sgioc->reset = sgi_ioc_reset;
+ sgioc->write = sgi_ioc_write;
+}
diff --git a/arch/mips/sgi-ip22/ip22-int.c b/arch/mips/sgi-ip22/ip22-int.c
new file mode 100644
index 00000000000..ea2844d29e6
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-int.c
@@ -0,0 +1,410 @@
+/*
+ * ip22-int.c: Routines for generic manipulation of the INT[23] ASIC
+ * found on INDY and Indigo2 workstations.
+ *
+ * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
+ * Copyright (C) 1997, 1998 Ralf Baechle (ralf@gnu.org)
+ * Copyright (C) 1999 Andrew R. Baker (andrewb@uab.edu)
+ * - Indigo2 changes
+ * - Interrupt handling fixes
+ * Copyright (C) 2001, 2003 Ladislav Michl (ladis@linux-mips.org)
+ */
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel_stat.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <asm/mipsregs.h>
+#include <asm/addrspace.h>
+
+#include <asm/sgi/ioc.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ip22.h>
+
+/* #define DEBUG_SGINT */
+
+/* So far nothing hangs here */
+#undef USE_LIO3_IRQ
+
+struct sgint_regs *sgint;
+
+static char lc0msk_to_irqnr[256];
+static char lc1msk_to_irqnr[256];
+static char lc2msk_to_irqnr[256];
+static char lc3msk_to_irqnr[256];
+
+extern asmlinkage void indyIRQ(void);
+extern int ip22_eisa_init(void);
+
+static void enable_local0_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ /* don't allow mappable interrupt to be enabled from setup_irq,
+ * we have our own way to do so */
+ if (irq != SGI_MAP_0_IRQ)
+ sgint->imask0 |= (1 << (irq - SGINT_LOCAL0));
+ local_irq_restore(flags);
+}
+
+static unsigned int startup_local0_irq(unsigned int irq)
+{
+ enable_local0_irq(irq);
+ return 0; /* Never anything pending */
+}
+
+static void disable_local0_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sgint->imask0 &= ~(1 << (irq - SGINT_LOCAL0));
+ local_irq_restore(flags);
+}
+
+#define shutdown_local0_irq disable_local0_irq
+#define mask_and_ack_local0_irq disable_local0_irq
+
+static void end_local0_irq (unsigned int irq)
+{
+ if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
+ enable_local0_irq(irq);
+}
+
+static struct hw_interrupt_type ip22_local0_irq_type = {
+ .typename = "IP22 local 0",
+ .startup = startup_local0_irq,
+ .shutdown = shutdown_local0_irq,
+ .enable = enable_local0_irq,
+ .disable = disable_local0_irq,
+ .ack = mask_and_ack_local0_irq,
+ .end = end_local0_irq,
+};
+
+static void enable_local1_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ /* don't allow mappable interrupt to be enabled from setup_irq,
+ * we have our own way to do so */
+ if (irq != SGI_MAP_1_IRQ)
+ sgint->imask1 |= (1 << (irq - SGINT_LOCAL1));
+ local_irq_restore(flags);
+}
+
+static unsigned int startup_local1_irq(unsigned int irq)
+{
+ enable_local1_irq(irq);
+ return 0; /* Never anything pending */
+}
+
+void disable_local1_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sgint->imask1 &= ~(1 << (irq - SGINT_LOCAL1));
+ local_irq_restore(flags);
+}
+
+#define shutdown_local1_irq disable_local1_irq
+#define mask_and_ack_local1_irq disable_local1_irq
+
+static void end_local1_irq (unsigned int irq)
+{
+ if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
+ enable_local1_irq(irq);
+}
+
+static struct hw_interrupt_type ip22_local1_irq_type = {
+ .typename = "IP22 local 1",
+ .startup = startup_local1_irq,
+ .shutdown = shutdown_local1_irq,
+ .enable = enable_local1_irq,
+ .disable = disable_local1_irq,
+ .ack = mask_and_ack_local1_irq,
+ .end = end_local1_irq,
+};
+
+static void enable_local2_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sgint->imask0 |= (1 << (SGI_MAP_0_IRQ - SGINT_LOCAL0));
+ sgint->cmeimask0 |= (1 << (irq - SGINT_LOCAL2));
+ local_irq_restore(flags);
+}
+
+static unsigned int startup_local2_irq(unsigned int irq)
+{
+ enable_local2_irq(irq);
+ return 0; /* Never anything pending */
+}
+
+void disable_local2_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sgint->cmeimask0 &= ~(1 << (irq - SGINT_LOCAL2));
+ if (!sgint->cmeimask0)
+ sgint->imask0 &= ~(1 << (SGI_MAP_0_IRQ - SGINT_LOCAL0));
+ local_irq_restore(flags);
+}
+
+#define shutdown_local2_irq disable_local2_irq
+#define mask_and_ack_local2_irq disable_local2_irq
+
+static void end_local2_irq (unsigned int irq)
+{
+ if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
+ enable_local2_irq(irq);
+}
+
+static struct hw_interrupt_type ip22_local2_irq_type = {
+ .typename = "IP22 local 2",
+ .startup = startup_local2_irq,
+ .shutdown = shutdown_local2_irq,
+ .enable = enable_local2_irq,
+ .disable = disable_local2_irq,
+ .ack = mask_and_ack_local2_irq,
+ .end = end_local2_irq,
+};
+
+static void enable_local3_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sgint->imask1 |= (1 << (SGI_MAP_1_IRQ - SGINT_LOCAL1));
+ sgint->cmeimask1 |= (1 << (irq - SGINT_LOCAL3));
+ local_irq_restore(flags);
+}
+
+static unsigned int startup_local3_irq(unsigned int irq)
+{
+ enable_local3_irq(irq);
+ return 0; /* Never anything pending */
+}
+
+void disable_local3_irq(unsigned int irq)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sgint->cmeimask1 &= ~(1 << (irq - SGINT_LOCAL3));
+ if (!sgint->cmeimask1)
+ sgint->imask1 &= ~(1 << (SGI_MAP_1_IRQ - SGINT_LOCAL1));
+ local_irq_restore(flags);
+}
+
+#define shutdown_local3_irq disable_local3_irq
+#define mask_and_ack_local3_irq disable_local3_irq
+
+static void end_local3_irq (unsigned int irq)
+{
+ if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
+ enable_local3_irq(irq);
+}
+
+static struct hw_interrupt_type ip22_local3_irq_type = {
+ .typename = "IP22 local 3",
+ .startup = startup_local3_irq,
+ .shutdown = shutdown_local3_irq,
+ .enable = enable_local3_irq,
+ .disable = disable_local3_irq,
+ .ack = mask_and_ack_local3_irq,
+ .end = end_local3_irq,
+};
+
+void indy_local0_irqdispatch(struct pt_regs *regs)
+{
+ u8 mask = sgint->istat0 & sgint->imask0;
+ u8 mask2;
+ int irq;
+
+ if (mask & SGINT_ISTAT0_LIO2) {
+ mask2 = sgint->vmeistat & sgint->cmeimask0;
+ irq = lc2msk_to_irqnr[mask2];
+ } else
+ irq = lc0msk_to_irqnr[mask];
+
+ /* if irq == 0, then the interrupt has already been cleared */
+ if (irq)
+ do_IRQ(irq, regs);
+ return;
+}
+
+void indy_local1_irqdispatch(struct pt_regs *regs)
+{
+ u8 mask = sgint->istat1 & sgint->imask1;
+ u8 mask2;
+ int irq;
+
+ if (mask & SGINT_ISTAT1_LIO3) {
+ mask2 = sgint->vmeistat & sgint->cmeimask1;
+ irq = lc3msk_to_irqnr[mask2];
+ } else
+ irq = lc1msk_to_irqnr[mask];
+
+ /* if irq == 0, then the interrupt has already been cleared */
+ if (irq)
+ do_IRQ(irq, regs);
+ return;
+}
+
+extern void ip22_be_interrupt(int irq, struct pt_regs *regs);
+
+void indy_buserror_irq(struct pt_regs *regs)
+{
+ int irq = SGI_BUSERR_IRQ;
+
+ irq_enter();
+ kstat_this_cpu.irqs[irq]++;
+ ip22_be_interrupt(irq, regs);
+ irq_exit();
+}
+
+static struct irqaction local0_cascade = {
+ .handler = no_action,
+ .flags = SA_INTERRUPT,
+ .name = "local0 cascade",
+};
+
+static struct irqaction local1_cascade = {
+ .handler = no_action,
+ .flags = SA_INTERRUPT,
+ .name = "local1 cascade",
+};
+
+static struct irqaction buserr = {
+ .handler = no_action,
+ .flags = SA_INTERRUPT,
+ .name = "Bus Error",
+};
+
+static struct irqaction map0_cascade = {
+ .handler = no_action,
+ .flags = SA_INTERRUPT,
+ .name = "mapable0 cascade",
+};
+
+#ifdef USE_LIO3_IRQ
+static struct irqaction map1_cascade = {
+ .handler = no_action,
+ .flags = SA_INTERRUPT,
+ .name = "mapable1 cascade",
+};
+#define SGI_INTERRUPTS SGINT_END
+#else
+#define SGI_INTERRUPTS SGINT_LOCAL3
+#endif
+
+extern void mips_cpu_irq_init(unsigned int irq_base);
+
+void __init arch_init_irq(void)
+{
+ int i;
+
+ /* Init local mask --> irq tables. */
+ for (i = 0; i < 256; i++) {
+ if (i & 0x80) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 7;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 7;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 7;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 7;
+ } else if (i & 0x40) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 6;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 6;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 6;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 6;
+ } else if (i & 0x20) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 5;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 5;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 5;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 5;
+ } else if (i & 0x10) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 4;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 4;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 4;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 4;
+ } else if (i & 0x08) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 3;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 3;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 3;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 3;
+ } else if (i & 0x04) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 2;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 2;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 2;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 2;
+ } else if (i & 0x02) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 1;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 1;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 1;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 1;
+ } else if (i & 0x01) {
+ lc0msk_to_irqnr[i] = SGINT_LOCAL0 + 0;
+ lc1msk_to_irqnr[i] = SGINT_LOCAL1 + 0;
+ lc2msk_to_irqnr[i] = SGINT_LOCAL2 + 0;
+ lc3msk_to_irqnr[i] = SGINT_LOCAL3 + 0;
+ } else {
+ lc0msk_to_irqnr[i] = 0;
+ lc1msk_to_irqnr[i] = 0;
+ lc2msk_to_irqnr[i] = 0;
+ lc3msk_to_irqnr[i] = 0;
+ }
+ }
+
+ /* Mask out all interrupts. */
+ sgint->imask0 = 0;
+ sgint->imask1 = 0;
+ sgint->cmeimask0 = 0;
+ sgint->cmeimask1 = 0;
+
+ set_except_vector(0, indyIRQ);
+
+ /* init CPU irqs */
+ mips_cpu_irq_init(SGINT_CPU);
+
+ for (i = SGINT_LOCAL0; i < SGI_INTERRUPTS; i++) {
+ hw_irq_controller *handler;
+
+ if (i < SGINT_LOCAL1)
+ handler = &ip22_local0_irq_type;
+ else if (i < SGINT_LOCAL2)
+ handler = &ip22_local1_irq_type;
+ else if (i < SGINT_LOCAL3)
+ handler = &ip22_local2_irq_type;
+ else
+ handler = &ip22_local3_irq_type;
+
+ irq_desc[i].status = IRQ_DISABLED;
+ irq_desc[i].action = 0;
+ irq_desc[i].depth = 1;
+ irq_desc[i].handler = handler;
+ }
+
+ /* vector handler. this register the IRQ as non-sharable */
+ setup_irq(SGI_LOCAL_0_IRQ, &local0_cascade);
+ setup_irq(SGI_LOCAL_1_IRQ, &local1_cascade);
+ setup_irq(SGI_BUSERR_IRQ, &buserr);
+
+ /* cascade in cascade. i love Indy ;-) */
+ setup_irq(SGI_MAP_0_IRQ, &map0_cascade);
+#ifdef USE_LIO3_IRQ
+ setup_irq(SGI_MAP_1_IRQ, &map1_cascade);
+#endif
+
+#ifdef CONFIG_EISA
+ if (ip22_is_fullhouse()) /* Only Indigo-2 has EISA stuff */
+ ip22_eisa_init ();
+#endif
+}
diff --git a/arch/mips/sgi-ip22/ip22-irq.S b/arch/mips/sgi-ip22/ip22-irq.S
new file mode 100644
index 00000000000..6ccbd9e1d96
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-irq.S
@@ -0,0 +1,118 @@
+/*
+ * ip22-irq.S: Interrupt exception dispatch code for FullHouse and
+ * Guiness.
+ *
+ * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
+ */
+
+#include <asm/asm.h>
+#include <asm/mipsregs.h>
+#include <asm/regdef.h>
+#include <asm/stackframe.h>
+
+/* A lot of complication here is taken away because:
+ *
+ * 1) We handle one interrupt and return, sitting in a loop and moving across
+ * all the pending IRQ bits in the cause register is _NOT_ the answer, the
+ * common case is one pending IRQ so optimize in that direction.
+ *
+ * 2) We need not check against bits in the status register IRQ mask, that
+ * would make this routine slow as hell.
+ *
+ * 3) Linux only thinks in terms of all IRQs on or all IRQs off, nothing in
+ * between like BSD spl() brain-damage.
+ *
+ * Furthermore, the IRQs on the INDY look basically (barring software IRQs
+ * which we don't use at all) like:
+ *
+ * MIPS IRQ Source
+ * -------- ------
+ * 0 Software (ignored)
+ * 1 Software (ignored)
+ * 2 Local IRQ level zero
+ * 3 Local IRQ level one
+ * 4 8254 Timer zero
+ * 5 8254 Timer one
+ * 6 Bus Error
+ * 7 R4k timer (what we use)
+ *
+ * We handle the IRQ according to _our_ priority which is:
+ *
+ * Highest ---- R4k Timer
+ * Local IRQ zero
+ * Local IRQ one
+ * Bus Error
+ * 8254 Timer zero
+ * Lowest ---- 8254 Timer one
+ *
+ * then we just return, if multiple IRQs are pending then we will just take
+ * another exception, big deal.
+ */
+
+ .text
+ .set noreorder
+ .set noat
+ .align 5
+ NESTED(indyIRQ, PT_SIZE, sp)
+ SAVE_ALL
+ CLI
+ .set at
+ mfc0 s0, CP0_CAUSE # get irq mask
+
+ /* First we check for r4k counter/timer IRQ. */
+ andi a0, s0, CAUSEF_IP7
+ beq a0, zero, 1f
+ andi a0, s0, CAUSEF_IP2 # delay slot, check local level zero
+
+ /* Wheee, a timer interrupt. */
+ jal indy_r4k_timer_interrupt
+ move a0, sp # delay slot
+ j ret_from_irq
+ nop # delay slot
+
+1:
+ beq a0, zero, 1f
+ andi a0, s0, CAUSEF_IP3 # delay slot, check local level one
+
+ /* Wheee, local level zero interrupt. */
+ jal indy_local0_irqdispatch
+ move a0, sp # delay slot
+
+ j ret_from_irq
+ nop # delay slot
+
+1:
+ beq a0, zero, 1f
+ andi a0, s0, CAUSEF_IP6 # delay slot, check bus error
+
+ /* Wheee, local level one interrupt. */
+ jal indy_local1_irqdispatch
+ move a0, sp # delay slot
+ j ret_from_irq
+ nop # delay slot
+
+1:
+ beq a0, zero, 1f
+ andi a0, s0, (CAUSEF_IP4 | CAUSEF_IP5) # delay slot
+
+ /* Wheee, an asynchronous bus error... */
+ jal indy_buserror_irq
+ move a0, sp # delay slot
+ j ret_from_irq
+ nop # delay slot
+
+1:
+ /* Here by mistake? It is possible, that by the time we take
+ * the exception the IRQ pin goes low, so just leave if this
+ * is the case.
+ */
+ beq a0, zero, 1f
+ nop # delay slot
+
+ /* Must be one of the 8254 timers... */
+ jal indy_8254timer_irq
+ move a0, sp # delay slot
+1:
+ j ret_from_irq
+ nop # delay slot
+ END(indyIRQ)
diff --git a/arch/mips/sgi-ip22/ip22-mc.c b/arch/mips/sgi-ip22/ip22-mc.c
new file mode 100644
index 00000000000..b58bd522262
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-mc.c
@@ -0,0 +1,208 @@
+/*
+ * ip22-mc.c: Routines for manipulating SGI Memory Controller.
+ *
+ * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
+ * Copyright (C) 1999 Andrew R. Baker (andrewb@uab.edu) - Indigo2 changes
+ * Copyright (C) 2003 Ladislav Michl (ladis@linux-mips.org)
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <asm/io.h>
+#include <asm/bootinfo.h>
+#include <asm/sgialib.h>
+#include <asm/sgi/mc.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ip22.h>
+
+struct sgimc_regs *sgimc;
+
+EXPORT_SYMBOL(sgimc);
+
+static inline unsigned long get_bank_addr(unsigned int memconfig)
+{
+ return ((memconfig & SGIMC_MCONFIG_BASEADDR) <<
+ ((sgimc->systemid & SGIMC_SYSID_MASKREV) >= 5 ? 24 : 22));
+}
+
+static inline unsigned long get_bank_size(unsigned int memconfig)
+{
+ return ((memconfig & SGIMC_MCONFIG_RMASK) + 0x0100) <<
+ ((sgimc->systemid & SGIMC_SYSID_MASKREV) >= 5 ? 16 : 14);
+}
+
+static inline unsigned int get_bank_config(int bank)
+{
+ unsigned int res = bank > 1 ? sgimc->mconfig1 : sgimc->mconfig0;
+ return bank % 2 ? res & 0xffff : res >> 16;
+}
+
+struct mem {
+ unsigned long addr;
+ unsigned long size;
+};
+
+/*
+ * Detect installed memory, do some sanity checks and notify kernel about it
+ */
+static void probe_memory(void)
+{
+ int i, j, found, cnt = 0;
+ struct mem bank[4];
+ struct mem space[2] = {{SGIMC_SEG0_BADDR, 0}, {SGIMC_SEG1_BADDR, 0}};
+
+ printk(KERN_INFO "MC: Probing memory configuration:\n");
+ for (i = 0; i < ARRAY_SIZE(bank); i++) {
+ unsigned int tmp = get_bank_config(i);
+ if (!(tmp & SGIMC_MCONFIG_BVALID))
+ continue;
+
+ bank[cnt].size = get_bank_size(tmp);
+ bank[cnt].addr = get_bank_addr(tmp);
+ printk(KERN_INFO " bank%d: %3ldM @ %08lx\n",
+ i, bank[cnt].size / 1024 / 1024, bank[cnt].addr);
+ cnt++;
+ }
+
+ /* And you thought bubble sort is dead algorithm... */
+ do {
+ unsigned long addr, size;
+
+ found = 0;
+ for (i = 1; i < cnt; i++)
+ if (bank[i-1].addr > bank[i].addr) {
+ addr = bank[i].addr;
+ size = bank[i].size;
+ bank[i].addr = bank[i-1].addr;
+ bank[i].size = bank[i-1].size;
+ bank[i-1].addr = addr;
+ bank[i-1].size = size;
+ found = 1;
+ }
+ } while (found);
+
+ /* Figure out how are memory banks mapped into spaces */
+ for (i = 0; i < cnt; i++) {
+ found = 0;
+ for (j = 0; j < ARRAY_SIZE(space) && !found; j++)
+ if (space[j].addr + space[j].size == bank[i].addr) {
+ space[j].size += bank[i].size;
+ found = 1;
+ }
+ /* There is either hole or overlapping memory */
+ if (!found)
+ printk(KERN_CRIT "MC: Memory configuration mismatch "
+ "(%08lx), expect Bus Error soon\n",
+ bank[i].addr);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(space); i++)
+ if (space[i].size)
+ add_memory_region(space[i].addr, space[i].size,
+ BOOT_MEM_RAM);
+}
+
+void __init sgimc_init(void)
+{
+ u32 tmp;
+
+ /* ioremap can't fail */
+ sgimc = (struct sgimc_regs *)
+ ioremap(SGIMC_BASE, sizeof(struct sgimc_regs));
+
+ printk(KERN_INFO "MC: SGI memory controller Revision %d\n",
+ (int) sgimc->systemid & SGIMC_SYSID_MASKREV);
+
+ /* Place the MC into a known state. This must be done before
+ * interrupts are first enabled etc.
+ */
+
+ /* Step 0: Make sure we turn off the watchdog in case it's
+ * still running (which might be the case after a
+ * soft reboot).
+ */
+ tmp = sgimc->cpuctrl0;
+ tmp &= ~SGIMC_CCTRL0_WDOG;
+ sgimc->cpuctrl0 = tmp;
+
+ /* Step 1: The CPU/GIO error status registers will not latch
+ * up a new error status until the register has been
+ * cleared by the cpu. These status registers are
+ * cleared by writing any value to them.
+ */
+ sgimc->cstat = sgimc->gstat = 0;
+
+ /* Step 2: Enable all parity checking in cpu control register
+ * zero.
+ */
+ tmp = sgimc->cpuctrl0;
+ tmp |= (SGIMC_CCTRL0_EPERRGIO | SGIMC_CCTRL0_EPERRMEM |
+ SGIMC_CCTRL0_R4KNOCHKPARR);
+ sgimc->cpuctrl0 = tmp;
+
+ /* Step 3: Setup the MC write buffer depth, this is controlled
+ * in cpu control register 1 in the lower 4 bits.
+ */
+ tmp = sgimc->cpuctrl1;
+ tmp &= ~0xf;
+ tmp |= 0xd;
+ sgimc->cpuctrl1 = tmp;
+
+ /* Step 4: Initialize the RPSS divider register to run as fast
+ * as it can correctly operate. The register is laid
+ * out as follows:
+ *
+ * ----------------------------------------
+ * | RESERVED | INCREMENT | DIVIDER |
+ * ----------------------------------------
+ * 31 16 15 8 7 0
+ *
+ * DIVIDER determines how often a 'tick' happens,
+ * INCREMENT determines by how the RPSS increment
+ * registers value increases at each 'tick'. Thus,
+ * for IP22 we get INCREMENT=1, DIVIDER=1 == 0x101
+ */
+ sgimc->divider = 0x101;
+
+ /* Step 5: Initialize GIO64 arbitrator configuration register.
+ *
+ * NOTE: HPC init code in sgihpc_init() must run before us because
+ * we need to know Guiness vs. FullHouse and the board
+ * revision on this machine. You have been warned.
+ */
+
+ /* First the basic invariants across all GIO64 implementations. */
+ tmp = SGIMC_GIOPAR_HPC64; /* All 1st HPC's interface at 64bits */
+ tmp |= SGIMC_GIOPAR_ONEBUS; /* Only one physical GIO bus exists */
+
+ if (ip22_is_fullhouse()) {
+ /* Fullhouse specific settings. */
+ if (SGIOC_SYSID_BOARDREV(sgioc->sysid) < 2) {
+ tmp |= SGIMC_GIOPAR_HPC264; /* 2nd HPC at 64bits */
+ tmp |= SGIMC_GIOPAR_PLINEEXP0; /* exp0 pipelines */
+ tmp |= SGIMC_GIOPAR_MASTEREXP1; /* exp1 masters */
+ tmp |= SGIMC_GIOPAR_RTIMEEXP0; /* exp0 is realtime */
+ } else {
+ tmp |= SGIMC_GIOPAR_HPC264; /* 2nd HPC 64bits */
+ tmp |= SGIMC_GIOPAR_PLINEEXP0; /* exp[01] pipelined */
+ tmp |= SGIMC_GIOPAR_PLINEEXP1;
+ tmp |= SGIMC_GIOPAR_MASTEREISA; /* EISA masters */
+ tmp |= SGIMC_GIOPAR_GFX64; /* GFX at 64 bits */
+ }
+ } else {
+ /* Guiness specific settings. */
+ tmp |= SGIMC_GIOPAR_EISA64; /* MC talks to EISA at 64bits */
+ tmp |= SGIMC_GIOPAR_MASTEREISA; /* EISA bus can act as master */
+ }
+ sgimc->giopar = tmp; /* poof */
+
+ probe_memory();
+}
+
+void __init prom_meminit(void) {}
+unsigned long __init prom_free_prom_memory(void)
+{
+ return 0;
+}
diff --git a/arch/mips/sgi-ip22/ip22-nvram.c b/arch/mips/sgi-ip22/ip22-nvram.c
new file mode 100644
index 00000000000..de43e86fa17
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-nvram.c
@@ -0,0 +1,119 @@
+/*
+ * ip22-nvram.c: NVRAM and serial EEPROM handling.
+ *
+ * Copyright (C) 2003 Ladislav Michl (ladis@linux-mips.org)
+ */
+#include <linux/module.h>
+
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ip22.h>
+
+/* Control opcode for serial eeprom */
+#define EEPROM_READ 0xc000 /* serial memory read */
+#define EEPROM_WEN 0x9800 /* write enable before prog modes */
+#define EEPROM_WRITE 0xa000 /* serial memory write */
+#define EEPROM_WRALL 0x8800 /* write all registers */
+#define EEPROM_WDS 0x8000 /* disable all programming */
+#define EEPROM_PRREAD 0xc000 /* read protect register */
+#define EEPROM_PREN 0x9800 /* enable protect register mode */
+#define EEPROM_PRCLEAR 0xffff /* clear protect register */
+#define EEPROM_PRWRITE 0xa000 /* write protect register */
+#define EEPROM_PRDS 0x8000 /* disable protect register, forever */
+
+#define EEPROM_EPROT 0x01 /* Protect register enable */
+#define EEPROM_CSEL 0x02 /* Chip select */
+#define EEPROM_ECLK 0x04 /* EEPROM clock */
+#define EEPROM_DATO 0x08 /* Data out */
+#define EEPROM_DATI 0x10 /* Data in */
+
+/* We need to use these functions early... */
+#define delay() ({ \
+ int x; \
+ for (x=0; x<100000; x++) __asm__ __volatile__(""); })
+
+#define eeprom_cs_on(ptr) ({ \
+ *ptr &= ~EEPROM_DATO; \
+ *ptr &= ~EEPROM_ECLK; \
+ *ptr &= ~EEPROM_EPROT; \
+ delay(); \
+ *ptr |= EEPROM_CSEL; \
+ *ptr |= EEPROM_ECLK; })
+
+
+#define eeprom_cs_off(ptr) ({ \
+ *ptr &= ~EEPROM_ECLK; \
+ *ptr &= ~EEPROM_CSEL; \
+ *ptr |= EEPROM_EPROT; \
+ *ptr |= EEPROM_ECLK; })
+
+#define BITS_IN_COMMAND 11
+/*
+ * clock in the nvram command and the register number. For the
+ * national semiconductor nv ram chip the op code is 3 bits and
+ * the address is 6/8 bits.
+ */
+static inline void eeprom_cmd(volatile unsigned int *ctrl, unsigned cmd,
+ unsigned reg)
+{
+ unsigned short ser_cmd;
+ int i;
+
+ ser_cmd = cmd | (reg << (16 - BITS_IN_COMMAND));
+ for (i = 0; i < BITS_IN_COMMAND; i++) {
+ if (ser_cmd & (1<<15)) /* if high order bit set */
+ *ctrl |= EEPROM_DATO;
+ else
+ *ctrl &= ~EEPROM_DATO;
+ *ctrl &= ~EEPROM_ECLK;
+ *ctrl |= EEPROM_ECLK;
+ ser_cmd <<= 1;
+ }
+ *ctrl &= ~EEPROM_DATO; /* see data sheet timing diagram */
+}
+
+unsigned short ip22_eeprom_read(volatile unsigned int *ctrl, int reg)
+{
+ unsigned short res = 0;
+ int i;
+
+ *ctrl &= ~EEPROM_EPROT;
+ eeprom_cs_on(ctrl);
+ eeprom_cmd(ctrl, EEPROM_READ, reg);
+
+ /* clock the data ouf of serial mem */
+ for (i = 0; i < 16; i++) {
+ *ctrl &= ~EEPROM_ECLK;
+ delay();
+ *ctrl |= EEPROM_ECLK;
+ delay();
+ res <<= 1;
+ if (*ctrl & EEPROM_DATI)
+ res |= 1;
+ }
+
+ eeprom_cs_off(ctrl);
+
+ return res;
+}
+
+EXPORT_SYMBOL(ip22_eeprom_read);
+
+/*
+ * Read specified register from main NVRAM
+ */
+unsigned short ip22_nvram_read(int reg)
+{
+ if (ip22_is_fullhouse())
+ /* IP22 (Indigo2 aka FullHouse) stores env variables into
+ * 93CS56 Microwire Bus EEPROM 2048 Bit (128x16) */
+ return ip22_eeprom_read(&hpc3c0->eeprom, reg);
+ else {
+ unsigned short tmp;
+ /* IP24 (Indy aka Guiness) uses DS1386 8K version */
+ reg <<= 1;
+ tmp = hpc3c0->bbram[reg++] & 0xff;
+ return (tmp << 8) | (hpc3c0->bbram[reg] & 0xff);
+ }
+}
+
+EXPORT_SYMBOL(ip22_nvram_read);
diff --git a/arch/mips/sgi-ip22/ip22-reset.c b/arch/mips/sgi-ip22/ip22-reset.c
new file mode 100644
index 00000000000..ed5c60adce6
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-reset.c
@@ -0,0 +1,247 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1997, 1998, 2001, 2003 by Ralf Baechle
+ */
+#include <linux/init.h>
+#include <linux/ds1286.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/notifier.h>
+#include <linux/timer.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/reboot.h>
+#include <asm/sgialib.h>
+#include <asm/sgi/ioc.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/mc.h>
+#include <asm/sgi/ip22.h>
+
+/*
+ * Just powerdown if init hasn't done after POWERDOWN_TIMEOUT seconds.
+ * I'm not sure if this feature is a good idea, for now it's here just to
+ * make the power button make behave just like under IRIX.
+ */
+#define POWERDOWN_TIMEOUT 120
+
+/*
+ * Blink frequency during reboot grace period and when paniced.
+ */
+#define POWERDOWN_FREQ (HZ / 4)
+#define PANIC_FREQ (HZ / 8)
+
+static struct timer_list power_timer, blink_timer, debounce_timer, volume_timer;
+
+#define MACHINE_PANICED 1
+#define MACHINE_SHUTTING_DOWN 2
+static int machine_state = 0;
+
+static void sgi_machine_restart(char *command) __attribute__((noreturn));
+static void sgi_machine_halt(void) __attribute__((noreturn));
+static void sgi_machine_power_off(void) __attribute__((noreturn));
+
+static void sgi_machine_restart(char *command)
+{
+ if (machine_state & MACHINE_SHUTTING_DOWN)
+ sgi_machine_power_off();
+ sgimc->cpuctrl0 |= SGIMC_CCTRL0_SYSINIT;
+ while (1);
+}
+
+static void sgi_machine_halt(void)
+{
+ if (machine_state & MACHINE_SHUTTING_DOWN)
+ sgi_machine_power_off();
+ ArcEnterInteractiveMode();
+}
+
+static void sgi_machine_power_off(void)
+{
+ unsigned int tmp;
+
+ local_irq_disable();
+
+ /* Disable watchdog */
+ tmp = hpc3c0->rtcregs[RTC_CMD] & 0xff;
+ hpc3c0->rtcregs[RTC_CMD] = tmp | RTC_WAM;
+ hpc3c0->rtcregs[RTC_WSEC] = 0;
+ hpc3c0->rtcregs[RTC_WHSEC] = 0;
+
+ while (1) {
+ sgioc->panel = ~SGIOC_PANEL_POWERON;
+ /* Good bye cruel world ... */
+
+ /* If we're still running, we probably got sent an alarm
+ interrupt. Read the flag to clear it. */
+ tmp = hpc3c0->rtcregs[RTC_HOURS_ALARM];
+ }
+}
+
+static void power_timeout(unsigned long data)
+{
+ sgi_machine_power_off();
+}
+
+static void blink_timeout(unsigned long data)
+{
+ /* XXX fix this for fullhouse */
+ sgi_ioc_reset ^= (SGIOC_RESET_LC0OFF|SGIOC_RESET_LC1OFF);
+ sgioc->reset = sgi_ioc_reset;
+
+ mod_timer(&blink_timer, jiffies+data);
+}
+
+static void debounce(unsigned long data)
+{
+ del_timer(&debounce_timer);
+ if (sgint->istat1 & SGINT_ISTAT1_PWR) {
+ /* Interrupt still being sent. */
+ debounce_timer.expires = jiffies + 5; /* 0.05s */
+ add_timer(&debounce_timer);
+
+ sgioc->panel = SGIOC_PANEL_POWERON | SGIOC_PANEL_POWERINTR |
+ SGIOC_PANEL_VOLDNINTR | SGIOC_PANEL_VOLDNHOLD |
+ SGIOC_PANEL_VOLUPINTR | SGIOC_PANEL_VOLUPHOLD;
+
+ return;
+ }
+
+ if (machine_state & MACHINE_PANICED)
+ sgimc->cpuctrl0 |= SGIMC_CCTRL0_SYSINIT;
+
+ enable_irq(SGI_PANEL_IRQ);
+}
+
+static inline void power_button(void)
+{
+ if (machine_state & MACHINE_PANICED)
+ return;
+
+ if ((machine_state & MACHINE_SHUTTING_DOWN) || kill_proc(1,SIGINT,1)) {
+ /* No init process or button pressed twice. */
+ sgi_machine_power_off();
+ }
+
+ machine_state |= MACHINE_SHUTTING_DOWN;
+ blink_timer.data = POWERDOWN_FREQ;
+ blink_timeout(POWERDOWN_FREQ);
+
+ init_timer(&power_timer);
+ power_timer.function = power_timeout;
+ power_timer.expires = jiffies + POWERDOWN_TIMEOUT * HZ;
+ add_timer(&power_timer);
+}
+
+void (*indy_volume_button)(int) = NULL;
+
+EXPORT_SYMBOL(indy_volume_button);
+
+static inline void volume_up_button(unsigned long data)
+{
+ del_timer(&volume_timer);
+
+ if (indy_volume_button)
+ indy_volume_button(1);
+
+ if (sgint->istat1 & SGINT_ISTAT1_PWR) {
+ volume_timer.expires = jiffies + 1;
+ add_timer(&volume_timer);
+ }
+}
+
+static inline void volume_down_button(unsigned long data)
+{
+ del_timer(&volume_timer);
+
+ if (indy_volume_button)
+ indy_volume_button(-1);
+
+ if (sgint->istat1 & SGINT_ISTAT1_PWR) {
+ volume_timer.expires = jiffies + 1;
+ add_timer(&volume_timer);
+ }
+}
+
+static irqreturn_t panel_int(int irq, void *dev_id, struct pt_regs *regs)
+{
+ unsigned int buttons;
+
+ buttons = sgioc->panel;
+ sgioc->panel = SGIOC_PANEL_POWERON | SGIOC_PANEL_POWERINTR;
+
+ if (sgint->istat1 & SGINT_ISTAT1_PWR) {
+ /* Wait until interrupt goes away */
+ disable_irq(SGI_PANEL_IRQ);
+ init_timer(&debounce_timer);
+ debounce_timer.function = debounce;
+ debounce_timer.expires = jiffies + 5;
+ add_timer(&debounce_timer);
+ }
+
+ /* Power button was pressed
+ * ioc.ps page 22: "The Panel Register is called Power Control by Full
+ * House. Only lowest 2 bits are used. Guiness uses upper four bits
+ * for volume control". This is not true, all bits are pulled high
+ * on fullhouse */
+ if (ip22_is_fullhouse() || !(buttons & SGIOC_PANEL_POWERINTR)) {
+ power_button();
+ return IRQ_HANDLED;
+ }
+ /* TODO: mute/unmute */
+ /* Volume up button was pressed */
+ if (!(buttons & SGIOC_PANEL_VOLUPINTR)) {
+ init_timer(&volume_timer);
+ volume_timer.function = volume_up_button;
+ volume_timer.expires = jiffies + 1;
+ add_timer(&volume_timer);
+ }
+ /* Volume down button was pressed */
+ if (!(buttons & SGIOC_PANEL_VOLDNINTR)) {
+ init_timer(&volume_timer);
+ volume_timer.function = volume_down_button;
+ volume_timer.expires = jiffies + 1;
+ add_timer(&volume_timer);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int panic_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ if (machine_state & MACHINE_PANICED)
+ return NOTIFY_DONE;
+ machine_state |= MACHINE_PANICED;
+
+ blink_timer.data = PANIC_FREQ;
+ blink_timeout(PANIC_FREQ);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_block = {
+ .notifier_call = panic_event,
+};
+
+static int __init reboot_setup(void)
+{
+ _machine_restart = sgi_machine_restart;
+ _machine_halt = sgi_machine_halt;
+ _machine_power_off = sgi_machine_power_off;
+
+ request_irq(SGI_PANEL_IRQ, panel_int, 0, "Front Panel", NULL);
+ init_timer(&blink_timer);
+ blink_timer.function = blink_timeout;
+ notifier_chain_register(&panic_notifier_list, &panic_block);
+
+ return 0;
+}
+
+subsys_initcall(reboot_setup);
diff --git a/arch/mips/sgi-ip22/ip22-setup.c b/arch/mips/sgi-ip22/ip22-setup.c
new file mode 100644
index 00000000000..0e96a5d6799
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-setup.c
@@ -0,0 +1,144 @@
+/*
+ * ip22-setup.c: SGI specific setup, including init of the feature struct.
+ *
+ * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
+ * Copyright (C) 1997, 1998 Ralf Baechle (ralf@gnu.org)
+ */
+#include <linux/config.h>
+#include <linux/ds1286.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kdev_t.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+
+#include <asm/addrspace.h>
+#include <asm/bcache.h>
+#include <asm/bootinfo.h>
+#include <asm/irq.h>
+#include <asm/reboot.h>
+#include <asm/time.h>
+#include <asm/gdb-stub.h>
+#include <asm/io.h>
+#include <asm/traps.h>
+#include <asm/sgialib.h>
+#include <asm/sgi/mc.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ip22.h>
+
+unsigned long sgi_gfxaddr;
+
+/*
+ * Stop-A is originally a Sun thing that isn't standard on IP22 so to avoid
+ * accidents it's disabled by default on IP22.
+ *
+ * FIXME: provide a mechanism to change the value of stop_a_enabled.
+ */
+int stop_a_enabled;
+
+void ip22_do_break(void)
+{
+ if (!stop_a_enabled)
+ return;
+
+ printk("\n");
+ ArcEnterInteractiveMode();
+}
+
+EXPORT_SYMBOL(ip22_do_break);
+
+extern void ip22_be_init(void) __init;
+extern void ip22_time_init(void) __init;
+
+static int __init ip22_setup(void)
+{
+ char *ctype;
+
+ board_be_init = ip22_be_init;
+ ip22_time_init();
+
+ /* Init the INDY HPC I/O controller. Need to call this before
+ * fucking with the memory controller because it needs to know the
+ * boardID and whether this is a Guiness or a FullHouse machine.
+ */
+ sgihpc_init();
+
+ /* Init INDY memory controller. */
+ sgimc_init();
+
+#ifdef CONFIG_BOARD_SCACHE
+ /* Now enable boardcaches, if any. */
+ indy_sc_init();
+#endif
+
+ /* Set EISA IO port base for Indigo2
+ * ioremap cannot fail */
+ set_io_port_base((unsigned long)ioremap(0x00080000,
+ 0x1fffffff - 0x00080000));
+ /* ARCS console environment variable is set to "g?" for
+ * graphics console, it is set to "d" for the first serial
+ * line and "d2" for the second serial line.
+ */
+ ctype = ArcGetEnvironmentVariable("console");
+ if (ctype && *ctype == 'd') {
+ static char options[8];
+ char *baud = ArcGetEnvironmentVariable("dbaud");
+ if (baud)
+ strcpy(options, baud);
+ add_preferred_console("ttyS", *(ctype + 1) == '2' ? 1 : 0,
+ baud ? options : NULL);
+ } else if (!ctype || *ctype != 'g') {
+ /* Use ARC if we don't want serial ('d') or Newport ('g'). */
+ prom_flags |= PROM_FLAG_USE_AS_CONSOLE;
+ add_preferred_console("arc", 0, NULL);
+ }
+
+#ifdef CONFIG_KGDB
+ {
+ char *kgdb_ttyd = prom_getcmdline();
+
+ if ((kgdb_ttyd = strstr(kgdb_ttyd, "kgdb=ttyd")) != NULL) {
+ int line;
+ kgdb_ttyd += strlen("kgdb=ttyd");
+ if (*kgdb_ttyd != '1' && *kgdb_ttyd != '2')
+ printk(KERN_INFO "KGDB: Uknown serial line /dev/ttyd%c"
+ ", falling back to /dev/ttyd1\n", *kgdb_ttyd);
+ line = *kgdb_ttyd == '2' ? 0 : 1;
+ printk(KERN_INFO "KGDB: Using serial line /dev/ttyd%d for "
+ "session\n", line ? 1 : 2);
+ rs_kgdb_hook(line);
+
+ printk(KERN_INFO "KGDB: Using serial line /dev/ttyd%d for "
+ "session, please connect your debugger\n", line ? 1:2);
+
+ kgdb_enabled = 1;
+ /* Breakpoints and stuff are in sgi_irq_setup() */
+ }
+ }
+#endif
+
+#if defined(CONFIG_VT) && defined(CONFIG_SGI_NEWPORT_CONSOLE)
+ {
+ ULONG *gfxinfo;
+ ULONG * (*__vec)(void) = (void *) (long)
+ *((_PULONG *)(long)((PROMBLOCK)->pvector + 0x20));
+
+ gfxinfo = __vec();
+ sgi_gfxaddr = ((gfxinfo[1] >= 0xa0000000
+ && gfxinfo[1] <= 0xc0000000)
+ ? gfxinfo[1] - 0xa0000000 : 0);
+
+ /* newport addresses? */
+ if (sgi_gfxaddr == 0x1f0f0000 || sgi_gfxaddr == 0x1f4f0000) {
+ conswitchp = &newport_con;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+early_initcall(ip22_setup);
diff --git a/arch/mips/sgi-ip22/ip22-time.c b/arch/mips/sgi-ip22/ip22-time.c
new file mode 100644
index 00000000000..173f76805ea
--- /dev/null
+++ b/arch/mips/sgi-ip22/ip22-time.c
@@ -0,0 +1,214 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Time operations for IP22 machines. Original code may come from
+ * Ralf Baechle or David S. Miller (sorry guys, i'm really not sure)
+ *
+ * Copyright (C) 2001 by Ladislav Michl
+ * Copyright (C) 2003 Ralf Baechle (ralf@linux-mips.org)
+ */
+#include <linux/bcd.h>
+#include <linux/ds1286.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/kernel_stat.h>
+#include <linux/time.h>
+
+#include <asm/cpu.h>
+#include <asm/mipsregs.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/time.h>
+#include <asm/sgialib.h>
+#include <asm/sgi/ioc.h>
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ip22.h>
+
+/*
+ * note that mktime uses month from 1 to 12 while to_tm
+ * uses 0 to 11.
+ */
+static unsigned long indy_rtc_get_time(void)
+{
+ unsigned int yrs, mon, day, hrs, min, sec;
+ unsigned int save_control;
+
+ save_control = hpc3c0->rtcregs[RTC_CMD] & 0xff;
+ hpc3c0->rtcregs[RTC_CMD] = save_control | RTC_TE;
+
+ sec = BCD2BIN(hpc3c0->rtcregs[RTC_SECONDS] & 0xff);
+ min = BCD2BIN(hpc3c0->rtcregs[RTC_MINUTES] & 0xff);
+ hrs = BCD2BIN(hpc3c0->rtcregs[RTC_HOURS] & 0x3f);
+ day = BCD2BIN(hpc3c0->rtcregs[RTC_DATE] & 0xff);
+ mon = BCD2BIN(hpc3c0->rtcregs[RTC_MONTH] & 0x1f);
+ yrs = BCD2BIN(hpc3c0->rtcregs[RTC_YEAR] & 0xff);
+
+ hpc3c0->rtcregs[RTC_CMD] = save_control;
+
+ if (yrs < 45)
+ yrs += 30;
+ if ((yrs += 40) < 70)
+ yrs += 100;
+
+ return mktime(yrs + 1900, mon, day, hrs, min, sec);
+}
+
+static int indy_rtc_set_time(unsigned long tim)
+{
+ struct rtc_time tm;
+ unsigned int save_control;
+
+ to_tm(tim, &tm);
+
+ tm.tm_mon += 1; /* tm_mon starts at zero */
+ tm.tm_year -= 1940;
+ if (tm.tm_year >= 100)
+ tm.tm_year -= 100;
+
+ save_control = hpc3c0->rtcregs[RTC_CMD] & 0xff;
+ hpc3c0->rtcregs[RTC_CMD] = save_control | RTC_TE;
+
+ hpc3c0->rtcregs[RTC_YEAR] = BIN2BCD(tm.tm_sec);
+ hpc3c0->rtcregs[RTC_MONTH] = BIN2BCD(tm.tm_mon);
+ hpc3c0->rtcregs[RTC_DATE] = BIN2BCD(tm.tm_mday);
+ hpc3c0->rtcregs[RTC_HOURS] = BIN2BCD(tm.tm_hour);
+ hpc3c0->rtcregs[RTC_MINUTES] = BIN2BCD(tm.tm_min);
+ hpc3c0->rtcregs[RTC_SECONDS] = BIN2BCD(tm.tm_sec);
+ hpc3c0->rtcregs[RTC_HUNDREDTH_SECOND] = 0;
+
+ hpc3c0->rtcregs[RTC_CMD] = save_control;
+
+ return 0;
+}
+
+static unsigned long dosample(void)
+{
+ u32 ct0, ct1;
+ volatile u8 msb, lsb;
+
+ /* Start the counter. */
+ sgint->tcword = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL |
+ SGINT_TCWORD_MRGEN);
+ sgint->tcnt2 = SGINT_TCSAMP_COUNTER & 0xff;
+ sgint->tcnt2 = SGINT_TCSAMP_COUNTER >> 8;
+
+ /* Get initial counter invariant */
+ ct0 = read_c0_count();
+
+ /* Latch and spin until top byte of counter2 is zero */
+ do {
+ sgint->tcword = SGINT_TCWORD_CNT2 | SGINT_TCWORD_CLAT;
+ lsb = sgint->tcnt2;
+ msb = sgint->tcnt2;
+ ct1 = read_c0_count();
+ } while (msb);
+
+ /* Stop the counter. */
+ sgint->tcword = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL |
+ SGINT_TCWORD_MSWST);
+ /*
+ * Return the difference, this is how far the r4k counter increments
+ * for every 1/HZ seconds. We round off the nearest 1 MHz of master
+ * clock (= 1000000 / HZ / 2).
+ */
+ /*return (ct1 - ct0 + (500000/HZ/2)) / (500000/HZ) * (500000/HZ);*/
+ return (ct1 - ct0) / (500000/HZ) * (500000/HZ);
+}
+
+/*
+ * Here we need to calibrate the cycle counter to at least be close.
+ */
+static __init void indy_time_init(void)
+{
+ unsigned long r4k_ticks[3];
+ unsigned long r4k_tick;
+
+ /*
+ * Figure out the r4k offset, the algorithm is very simple and works in
+ * _all_ cases as long as the 8254 counter register itself works ok (as
+ * an interrupt driving timer it does not because of bug, this is why
+ * we are using the onchip r4k counter/compare register to serve this
+ * purpose, but for r4k_offset calculation it will work ok for us).
+ * There are other very complicated ways of performing this calculation
+ * but this one works just fine so I am not going to futz around. ;-)
+ */
+ printk(KERN_INFO "Calibrating system timer... ");
+ dosample(); /* Prime cache. */
+ dosample(); /* Prime cache. */
+ /* Zero is NOT an option. */
+ do {
+ r4k_ticks[0] = dosample();
+ } while (!r4k_ticks[0]);
+ do {
+ r4k_ticks[1] = dosample();
+ } while (!r4k_ticks[1]);
+
+ if (r4k_ticks[0] != r4k_ticks[1]) {
+ printk("warning: timer counts differ, retrying... ");
+ r4k_ticks[2] = dosample();
+ if (r4k_ticks[2] == r4k_ticks[0]
+ || r4k_ticks[2] == r4k_ticks[1])
+ r4k_tick = r4k_ticks[2];
+ else {
+ printk("disagreement, using average... ");
+ r4k_tick = (r4k_ticks[0] + r4k_ticks[1]
+ + r4k_ticks[2]) / 3;
+ }
+ } else
+ r4k_tick = r4k_ticks[0];
+
+ printk("%d [%d.%04d MHz CPU]\n", (int) r4k_tick,
+ (int) (r4k_tick / (500000 / HZ)),
+ (int) (r4k_tick % (500000 / HZ)));
+
+ mips_hpt_frequency = r4k_tick * HZ;
+}
+
+/* Generic SGI handler for (spurious) 8254 interrupts */
+void indy_8254timer_irq(struct pt_regs *regs)
+{
+ int irq = SGI_8254_0_IRQ;
+ ULONG cnt;
+ char c;
+
+ irq_enter();
+ kstat_this_cpu.irqs[irq]++;
+ printk(KERN_ALERT "Oops, got 8254 interrupt.\n");
+ ArcRead(0, &c, 1, &cnt);
+ ArcEnterInteractiveMode();
+ irq_exit();
+}
+
+void indy_r4k_timer_interrupt(struct pt_regs *regs)
+{
+ int irq = SGI_TIMER_IRQ;
+
+ irq_enter();
+ kstat_this_cpu.irqs[irq]++;
+ timer_interrupt(irq, NULL, regs);
+ irq_exit();
+}
+
+extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
+
+static void indy_timer_setup(struct irqaction *irq)
+{
+ /* over-write the handler, we use our own way */
+ irq->handler = no_action;
+
+ /* setup irqaction */
+ setup_irq(SGI_TIMER_IRQ, irq);
+}
+
+void __init ip22_time_init(void)
+{
+ /* setup hookup functions */
+ rtc_get_time = indy_rtc_get_time;
+ rtc_set_time = indy_rtc_set_time;
+
+ board_time_init = indy_time_init;
+ board_timer_setup = indy_timer_setup;
+}