diff options
Diffstat (limited to 'arch/mips/sgi-ip22')
-rw-r--r-- | arch/mips/sgi-ip22/Makefile | 11 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-berr.c | 114 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-eisa.c | 308 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-hpc.c | 63 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-int.c | 410 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-irq.S | 118 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-mc.c | 208 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-nvram.c | 119 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-reset.c | 247 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-setup.c | 144 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-time.c | 214 |
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 000000000000..eb0820fe50bd --- /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 000000000000..a28dc7800072 --- /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 000000000000..0ab4abf65d58 --- /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 000000000000..c0afeccb08c4 --- /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 000000000000..ea2844d29e6e --- /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 000000000000..6ccbd9e1d967 --- /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 000000000000..b58bd522262b --- /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 000000000000..de43e86fa17c --- /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 000000000000..ed5c60adce63 --- /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 000000000000..0e96a5d67993 --- /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 000000000000..173f76805ea3 --- /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; +} |