diff options
Diffstat (limited to 'arch/mips/sgi-ip22')
-rw-r--r-- | arch/mips/sgi-ip22/Makefile | 12 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/Platform | 34 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-berr.c | 116 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-eisa.c | 145 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-gio.c | 430 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-hpc.c | 64 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-int.c | 339 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-mc.c | 237 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-nvram.c | 122 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-platform.c | 210 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-reset.c | 203 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-setup.c | 78 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip22-time.c | 131 | ||||
-rw-r--r-- | arch/mips/sgi-ip22/ip28-berr.c | 504 |
14 files changed, 2625 insertions, 0 deletions
diff --git a/arch/mips/sgi-ip22/Makefile b/arch/mips/sgi-ip22/Makefile new file mode 100644 index 000000000..45f42fa08 --- /dev/null +++ b/arch/mips/sgi-ip22/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the SGI specific kernel interface routines +# under Linux. +# + +obj-y += ip22-mc.o ip22-hpc.o ip22-int.o ip22-time.o ip22-nvram.o \ + ip22-platform.o ip22-reset.o ip22-setup.o ip22-gio.o + +obj-$(CONFIG_SGI_IP22) += ip22-berr.o +obj-$(CONFIG_SGI_IP28) += ip28-berr.o +obj-$(CONFIG_EISA) += ip22-eisa.o diff --git a/arch/mips/sgi-ip22/Platform b/arch/mips/sgi-ip22/Platform new file mode 100644 index 000000000..e8f6b3a42 --- /dev/null +++ b/arch/mips/sgi-ip22/Platform @@ -0,0 +1,34 @@ +# +# SGI IP22 (Indy/Indigo2) +# +# Set the load address to >= 0xffffffff88069000 if you want to leave space for +# symmon, 0xffffffff80002000 for production kernels. Note that the value must +# be aligned to a multiple of the kernel stack size or the handling of the +# current variable will break so for 64-bit kernels we have to raise the start +# address by 8kb. +# +platform-$(CONFIG_SGI_IP22) += sgi-ip22/ +cflags-$(CONFIG_SGI_IP22) += -I$(srctree)/arch/mips/include/asm/mach-ip22 +ifdef CONFIG_32BIT +load-$(CONFIG_SGI_IP22) += 0xffffffff88002000 +endif +ifdef CONFIG_64BIT +load-$(CONFIG_SGI_IP22) += 0xffffffff88004000 +endif + +# +# SGI IP28 (Indigo2 R10k) +# +# Set the load address to >= 0xa800000020080000 if you want to leave space for +# symmon, 0xa800000020004000 for production kernels ? Note that the value must +# be 16kb aligned or the handling of the current variable will break. +# Simplified: what IP22 does at 128MB+ in ksegN, IP28 does at 512MB+ in xkphys +# +ifdef CONFIG_SGI_IP28 + ifeq ($(call cc-option-yn,-march=r10000 -mr10k-cache-barrier=store), n) + $(error gcc doesn't support needed option -mr10k-cache-barrier=store) + endif +endif +platform-$(CONFIG_SGI_IP28) += sgi-ip22/ +cflags-$(CONFIG_SGI_IP28) += -mr10k-cache-barrier=store -I$(srctree)/arch/mips/include/asm/mach-ip28 +load-$(CONFIG_SGI_IP28) += 0xa800000020004000 diff --git a/arch/mips/sgi-ip22/ip22-berr.c b/arch/mips/sgi-ip22/ip22-berr.c new file mode 100644 index 000000000..34bb9801d --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-berr.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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/signal.h> + +#include <asm/addrspace.h> +#include <asm/traps.h> +#include <asm/branch.h> +#include <asm/irq_regs.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) +{ + const int field = 2 * sizeof(unsigned long); + struct pt_regs *regs = get_irq_regs(); + + 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 000000000..a0a79222c --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-eisa.c @@ -0,0 +1,145 @@ +/* + * 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/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/io.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> +#include <asm/i8259.h> + +/* I2 has four EISA slots. */ +#define IP22_EISA_MAX_SLOTS 4 +#define EISA_MAX_IRQ 16 + +#define EIU_MODE_REG 0x0001ffc0 +#define EIU_STAT_REG 0x0001ffc4 +#define EIU_PREMPT_REG 0x0001ffc8 +#define EIU_QUIET_REG 0x0001ffcc +#define EIU_INTRPT_ACK 0x00010004 + +static char __init *decode_eisa_sig(unsigned long addr) +{ + static char sig_str[EISA_SIG_LEN] __initdata; + u8 sig[4]; + u16 rev; + int i; + + for (i = 0; i < 4; i++) { + sig[i] = inb(addr + i); + + if (!i && (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 irqreturn_t ip22_eisa_intr(int irq, void *dev_id) +{ + u8 eisa_irq = inb(EIU_INTRPT_ACK); + + inb(EISA_DMA1_STATUS); + inb(EISA_DMA2_STATUS); + + if (eisa_irq < EISA_MAX_IRQ) { + do_IRQ(eisa_irq); + return IRQ_HANDLED; + } + + /* Oops, Bad Stuff Happened... */ + printk(KERN_ERR "eisa_irq %d out of bound\n", eisa_irq); + + outb(0x20, EISA_INT2_CTRL); + outb(0x20, EISA_INT1_CTRL); + + return IRQ_NONE; +} + +static struct irqaction eisa_action = { + .handler = ip22_eisa_intr, + .name = "EISA", +}; + +int __init ip22_eisa_init(void) +{ + int i, c; + char *str; + + 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 <= IP22_EISA_MAX_SLOTS; i++) { + if ((str = decode_eisa_sig(0x1000 * i + EISA_VENDOR_ID_OFFSET))) { + 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 */ + outl(0x0000FFFF, EIU_PREMPT_REG); + outl(1, EIU_QUIET_REG); + outl(0x40f3c07F, EIU_MODE_REG); + + /* Now be nice to the EISA chipset */ + outb(1, EISA_EXT_NMI_RESET_CTRL); + udelay(50); /* Wait long enough for the dust to settle */ + outb(0, EISA_EXT_NMI_RESET_CTRL); + outb(0, EISA_DMA2_WRITE_SINGLE); + + init_i8259_irqs(); + + /* 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); + + EISA_bus = 1; + return 0; +} diff --git a/arch/mips/sgi-ip22/ip22-gio.c b/arch/mips/sgi-ip22/ip22-gio.c new file mode 100644 index 000000000..b225033aa --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-gio.c @@ -0,0 +1,430 @@ +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> + +#include <asm/addrspace.h> +#include <asm/paccess.h> +#include <asm/gio_device.h> +#include <asm/sgi/gio.h> +#include <asm/sgi/hpc3.h> +#include <asm/sgi/mc.h> +#include <asm/sgi/ip22.h> + +static struct bus_type gio_bus_type; + +static struct { + const char *name; + __u8 id; +} gio_name_table[] = { + { .name = "SGI Impact", .id = 0x10 }, + { .name = "Phobos G160", .id = 0x35 }, + { .name = "Phobos G130", .id = 0x36 }, + { .name = "Phobos G100", .id = 0x37 }, + { .name = "Set Engineering GFE", .id = 0x38 }, + /* fake IDs */ + { .name = "SGI Newport", .id = 0x7e }, + { .name = "SGI GR2/GR3", .id = 0x7f }, +}; + +static void gio_bus_release(struct device *dev) +{ + kfree(dev); +} + +static struct device gio_bus = { + .init_name = "gio", + .release = &gio_bus_release, +}; + +/** + * gio_match_device - Tell if an of_device structure has a matching + * gio_match structure + * @ids: array of of device match structures to search in + * @dev: the of device structure to match against + * + * Used by a driver to check whether an of_device present in the + * system is in its list of supported devices. + */ +const struct gio_device_id *gio_match_device(const struct gio_device_id *match, + const struct gio_device *dev) +{ + const struct gio_device_id *ids; + + for (ids = match; ids->id != 0xff; ids++) + if (ids->id == dev->id.id) + return ids; + + return NULL; +} +EXPORT_SYMBOL_GPL(gio_match_device); + +struct gio_device *gio_dev_get(struct gio_device *dev) +{ + struct device *tmp; + + if (!dev) + return NULL; + tmp = get_device(&dev->dev); + if (tmp) + return to_gio_device(tmp); + else + return NULL; +} +EXPORT_SYMBOL_GPL(gio_dev_get); + +void gio_dev_put(struct gio_device *dev) +{ + if (dev) + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(gio_dev_put); + +/** + * gio_release_dev - free an gio device structure when all users of it are finished. + * @dev: device that's been disconnected + * + * Will be called only by the device core when all users of this gio device are + * done. + */ +void gio_release_dev(struct device *dev) +{ + struct gio_device *giodev; + + giodev = to_gio_device(dev); + kfree(giodev); +} +EXPORT_SYMBOL_GPL(gio_release_dev); + +int gio_device_register(struct gio_device *giodev) +{ + giodev->dev.bus = &gio_bus_type; + giodev->dev.parent = &gio_bus; + return device_register(&giodev->dev); +} +EXPORT_SYMBOL_GPL(gio_device_register); + +void gio_device_unregister(struct gio_device *giodev) +{ + device_unregister(&giodev->dev); +} +EXPORT_SYMBOL_GPL(gio_device_unregister); + +static int gio_bus_match(struct device *dev, struct device_driver *drv) +{ + struct gio_device *gio_dev = to_gio_device(dev); + struct gio_driver *gio_drv = to_gio_driver(drv); + + return gio_match_device(gio_drv->id_table, gio_dev) != NULL; +} + +static int gio_device_probe(struct device *dev) +{ + int error = -ENODEV; + struct gio_driver *drv; + struct gio_device *gio_dev; + const struct gio_device_id *match; + + drv = to_gio_driver(dev->driver); + gio_dev = to_gio_device(dev); + + if (!drv->probe) + return error; + + gio_dev_get(gio_dev); + + match = gio_match_device(drv->id_table, gio_dev); + if (match) + error = drv->probe(gio_dev, match); + if (error) + gio_dev_put(gio_dev); + + return error; +} + +static int gio_device_remove(struct device *dev) +{ + struct gio_device *gio_dev = to_gio_device(dev); + struct gio_driver *drv = to_gio_driver(dev->driver); + + if (dev->driver && drv->remove) + drv->remove(gio_dev); + return 0; +} + +static void gio_device_shutdown(struct device *dev) +{ + struct gio_device *gio_dev = to_gio_device(dev); + struct gio_driver *drv = to_gio_driver(dev->driver); + + if (dev->driver && drv->shutdown) + drv->shutdown(gio_dev); +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct gio_device *gio_dev = to_gio_device(dev); + int len = snprintf(buf, PAGE_SIZE, "gio:%x\n", gio_dev->id.id); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} +static DEVICE_ATTR_RO(modalias); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gio_device *giodev; + + giodev = to_gio_device(dev); + return sprintf(buf, "%s", giodev->name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gio_device *giodev; + + giodev = to_gio_device(dev); + return sprintf(buf, "%x", giodev->id.id); +} +static DEVICE_ATTR_RO(id); + +static struct attribute *gio_dev_attrs[] = { + &dev_attr_modalias.attr, + &dev_attr_name.attr, + &dev_attr_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(gio_dev); + +static int gio_device_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct gio_device *gio_dev = to_gio_device(dev); + + add_uevent_var(env, "MODALIAS=gio:%x", gio_dev->id.id); + return 0; +} + +int gio_register_driver(struct gio_driver *drv) +{ + /* initialize common driver fields */ + if (!drv->driver.name) + drv->driver.name = drv->name; + if (!drv->driver.owner) + drv->driver.owner = drv->owner; + drv->driver.bus = &gio_bus_type; + + /* register with core */ + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(gio_register_driver); + +void gio_unregister_driver(struct gio_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(gio_unregister_driver); + +void gio_set_master(struct gio_device *dev) +{ + u32 tmp = sgimc->giopar; + + switch (dev->slotno) { + case 0: + tmp |= SGIMC_GIOPAR_MASTERGFX; + break; + case 1: + tmp |= SGIMC_GIOPAR_MASTEREXP0; + break; + case 2: + tmp |= SGIMC_GIOPAR_MASTEREXP1; + break; + } + sgimc->giopar = tmp; +} +EXPORT_SYMBOL_GPL(gio_set_master); + +void ip22_gio_set_64bit(int slotno) +{ + u32 tmp = sgimc->giopar; + + switch (slotno) { + case 0: + tmp |= SGIMC_GIOPAR_GFX64; + break; + case 1: + tmp |= SGIMC_GIOPAR_EXP064; + break; + case 2: + tmp |= SGIMC_GIOPAR_EXP164; + break; + } + sgimc->giopar = tmp; +} + +static int ip22_gio_id(unsigned long addr, u32 *res) +{ + u8 tmp8; + u8 tmp16; + u32 tmp32; + u8 *ptr8; + u16 *ptr16; + u32 *ptr32; + + ptr32 = (void *)CKSEG1ADDR(addr); + if (!get_dbe(tmp32, ptr32)) { + /* + * We got no DBE, but this doesn't mean anything. + * If GIO is pipelined (which can't be disabled + * for GFX slot) we don't get a DBE, but we see + * the transfer size as data. So we do an 8bit + * and a 16bit access and check whether the common + * data matches + */ + ptr8 = (void *)CKSEG1ADDR(addr + 3); + if (get_dbe(tmp8, ptr8)) { + /* + * 32bit access worked, but 8bit doesn't + * so we don't see phantom reads on + * a pipelined bus, but a real card which + * doesn't support 8 bit reads + */ + *res = tmp32; + return 1; + } + ptr16 = (void *)CKSEG1ADDR(addr + 2); + get_dbe(tmp16, ptr16); + if (tmp8 == (tmp16 & 0xff) && + tmp8 == (tmp32 & 0xff) && + tmp16 == (tmp32 & 0xffff)) { + *res = tmp32; + return 1; + } + } + return 0; /* nothing here */ +} + +#define HQ2_MYSTERY_OFFS 0x6A07C +#define NEWPORT_USTATUS_OFFS 0xF133C + +static int ip22_is_gr2(unsigned long addr) +{ + u32 tmp; + u32 *ptr; + + /* HQ2 only allows 32bit accesses */ + ptr = (void *)CKSEG1ADDR(addr + HQ2_MYSTERY_OFFS); + if (!get_dbe(tmp, ptr)) { + if (tmp == 0xdeadbeef) + return 1; + } + return 0; +} + + +static void ip22_check_gio(int slotno, unsigned long addr, int irq) +{ + const char *name = "Unknown"; + struct gio_device *gio_dev; + u32 tmp; + __u8 id; + int i; + + /* first look for GR2/GR3 by checking mystery register */ + if (ip22_is_gr2(addr)) + tmp = 0x7f; + else { + if (!ip22_gio_id(addr, &tmp)) { + /* + * no GIO signature at start address of slot + * since Newport doesn't have one, we check if + * user status register is readable + */ + if (ip22_gio_id(addr + NEWPORT_USTATUS_OFFS, &tmp)) + tmp = 0x7e; + else + tmp = 0; + } + } + if (tmp) { + id = GIO_ID(tmp); + if (tmp & GIO_32BIT_ID) { + if (tmp & GIO_64BIT_IFACE) + ip22_gio_set_64bit(slotno); + } + for (i = 0; i < ARRAY_SIZE(gio_name_table); i++) { + if (id == gio_name_table[i].id) { + name = gio_name_table[i].name; + break; + } + } + printk(KERN_INFO "GIO: slot %d : %s (id %x)\n", + slotno, name, id); + gio_dev = kzalloc(sizeof *gio_dev, GFP_KERNEL); + gio_dev->name = name; + gio_dev->slotno = slotno; + gio_dev->id.id = id; + gio_dev->resource.start = addr; + gio_dev->resource.end = addr + 0x3fffff; + gio_dev->resource.flags = IORESOURCE_MEM; + gio_dev->irq = irq; + dev_set_name(&gio_dev->dev, "%d", slotno); + gio_device_register(gio_dev); + } else + printk(KERN_INFO "GIO: slot %d : Empty\n", slotno); +} + +static struct bus_type gio_bus_type = { + .name = "gio", + .dev_groups = gio_dev_groups, + .match = gio_bus_match, + .probe = gio_device_probe, + .remove = gio_device_remove, + .shutdown = gio_device_shutdown, + .uevent = gio_device_uevent, +}; + +static struct resource gio_bus_resource = { + .start = GIO_SLOT_GFX_BASE, + .end = GIO_SLOT_GFX_BASE + 0x9fffff, + .name = "GIO Bus", + .flags = IORESOURCE_MEM, +}; + +int __init ip22_gio_init(void) +{ + unsigned int pbdma __maybe_unused; + int ret; + + ret = device_register(&gio_bus); + if (ret) { + put_device(&gio_bus); + return ret; + } + + ret = bus_register(&gio_bus_type); + if (!ret) { + request_resource(&iomem_resource, &gio_bus_resource); + printk(KERN_INFO "GIO: Probing bus...\n"); + + if (ip22_is_fullhouse()) { + /* Indigo2 */ + ip22_check_gio(0, GIO_SLOT_GFX_BASE, SGI_GIO_1_IRQ); + ip22_check_gio(1, GIO_SLOT_EXP0_BASE, SGI_GIO_1_IRQ); + } else { + /* Indy/Challenge S */ + if (get_dbe(pbdma, (unsigned int *)&hpc3c1->pbdma[1])) + ip22_check_gio(0, GIO_SLOT_GFX_BASE, + SGI_GIO_0_IRQ); + ip22_check_gio(1, GIO_SLOT_EXP0_BASE, SGI_GIOEXP0_IRQ); + ip22_check_gio(2, GIO_SLOT_EXP1_BASE, SGI_GIOEXP1_IRQ); + } + } else + device_unregister(&gio_bus); + + return ret; +} + +subsys_initcall(ip22_gio_init); diff --git a/arch/mips/sgi-ip22/ip22-hpc.c b/arch/mips/sgi-ip22/ip22-hpc.c new file mode 100644 index 000000000..49922e86c --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-hpc.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ip22-hpc.c: Routines for generic manipulation of the HPC controllers. + * + * Copyright (C) 1996 David S. Miller (davem@davemloft.net) + * Copyright (C) 1998 Ralf Baechle + */ + +#include <linux/export.h> +#include <linux/init.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 000000000..3804895fa --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-int.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ip22-int.c: Routines for generic manipulation of the INT[23] ASIC + * found on INDY and Indigo2 workstations. + * + * Copyright (C) 1996 David S. Miller (davem@davemloft.net) + * 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/types.h> +#include <linux/init.h> +#include <linux/kernel_stat.h> +#include <linux/interrupt.h> +#include <linux/ftrace.h> + +#include <asm/irq_cpu.h> +#include <asm/sgi/hpc3.h> +#include <asm/sgi/ip22.h> + +/* 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 int ip22_eisa_init(void); + +static void enable_local0_irq(struct irq_data *d) +{ + /* don't allow mappable interrupt to be enabled from setup_irq, + * we have our own way to do so */ + if (d->irq != SGI_MAP_0_IRQ) + sgint->imask0 |= (1 << (d->irq - SGINT_LOCAL0)); +} + +static void disable_local0_irq(struct irq_data *d) +{ + sgint->imask0 &= ~(1 << (d->irq - SGINT_LOCAL0)); +} + +static struct irq_chip ip22_local0_irq_type = { + .name = "IP22 local 0", + .irq_mask = disable_local0_irq, + .irq_unmask = enable_local0_irq, +}; + +static void enable_local1_irq(struct irq_data *d) +{ + /* don't allow mappable interrupt to be enabled from setup_irq, + * we have our own way to do so */ + if (d->irq != SGI_MAP_1_IRQ) + sgint->imask1 |= (1 << (d->irq - SGINT_LOCAL1)); +} + +static void disable_local1_irq(struct irq_data *d) +{ + sgint->imask1 &= ~(1 << (d->irq - SGINT_LOCAL1)); +} + +static struct irq_chip ip22_local1_irq_type = { + .name = "IP22 local 1", + .irq_mask = disable_local1_irq, + .irq_unmask = enable_local1_irq, +}; + +static void enable_local2_irq(struct irq_data *d) +{ + sgint->imask0 |= (1 << (SGI_MAP_0_IRQ - SGINT_LOCAL0)); + sgint->cmeimask0 |= (1 << (d->irq - SGINT_LOCAL2)); +} + +static void disable_local2_irq(struct irq_data *d) +{ + sgint->cmeimask0 &= ~(1 << (d->irq - SGINT_LOCAL2)); + if (!sgint->cmeimask0) + sgint->imask0 &= ~(1 << (SGI_MAP_0_IRQ - SGINT_LOCAL0)); +} + +static struct irq_chip ip22_local2_irq_type = { + .name = "IP22 local 2", + .irq_mask = disable_local2_irq, + .irq_unmask = enable_local2_irq, +}; + +static void enable_local3_irq(struct irq_data *d) +{ + sgint->imask1 |= (1 << (SGI_MAP_1_IRQ - SGINT_LOCAL1)); + sgint->cmeimask1 |= (1 << (d->irq - SGINT_LOCAL3)); +} + +static void disable_local3_irq(struct irq_data *d) +{ + sgint->cmeimask1 &= ~(1 << (d->irq - SGINT_LOCAL3)); + if (!sgint->cmeimask1) + sgint->imask1 &= ~(1 << (SGI_MAP_1_IRQ - SGINT_LOCAL1)); +} + +static struct irq_chip ip22_local3_irq_type = { + .name = "IP22 local 3", + .irq_mask = disable_local3_irq, + .irq_unmask = enable_local3_irq, +}; + +static void indy_local0_irqdispatch(void) +{ + 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]; + + /* + * workaround for INT2 bug; if irq == 0, INT2 has seen a fifo full + * irq, but failed to latch it into status register + */ + if (irq) + do_IRQ(irq); + else + do_IRQ(SGINT_LOCAL0 + 0); +} + +static void indy_local1_irqdispatch(void) +{ + 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); +} + +extern void ip22_be_interrupt(int irq); + +static void __irq_entry indy_buserror_irq(void) +{ + int irq = SGI_BUSERR_IRQ; + + irq_enter(); + kstat_incr_irq_this_cpu(irq); + ip22_be_interrupt(irq); + irq_exit(); +} + +static struct irqaction local0_cascade = { + .handler = no_action, + .flags = IRQF_NO_THREAD, + .name = "local0 cascade", +}; + +static struct irqaction local1_cascade = { + .handler = no_action, + .flags = IRQF_NO_THREAD, + .name = "local1 cascade", +}; + +static struct irqaction buserr = { + .handler = no_action, + .flags = IRQF_NO_THREAD, + .name = "Bus Error", +}; + +static struct irqaction map0_cascade = { + .handler = no_action, + .flags = IRQF_NO_THREAD, + .name = "mapable0 cascade", +}; + +#ifdef USE_LIO3_IRQ +static struct irqaction map1_cascade = { + .handler = no_action, + .flags = IRQF_NO_THREAD, + .name = "mapable1 cascade", +}; +#define SGI_INTERRUPTS SGINT_END +#else +#define SGI_INTERRUPTS SGINT_LOCAL3 +#endif + +extern void indy_8254timer_irq(void); + +/* + * 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. + */ + +asmlinkage void plat_irq_dispatch(void) +{ + unsigned int pending = read_c0_status() & read_c0_cause(); + + /* + * First we check for r4k counter/timer IRQ. + */ + if (pending & CAUSEF_IP7) + do_IRQ(SGI_TIMER_IRQ); + else if (pending & CAUSEF_IP2) + indy_local0_irqdispatch(); + else if (pending & CAUSEF_IP3) + indy_local1_irqdispatch(); + else if (pending & CAUSEF_IP6) + indy_buserror_irq(); + else if (pending & (CAUSEF_IP4 | CAUSEF_IP5)) + indy_8254timer_irq(); +} + +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; + + /* init CPU irqs */ + mips_cpu_irq_init(); + + for (i = SGINT_LOCAL0; i < SGI_INTERRUPTS; i++) { + struct irq_chip *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_set_chip_and_handler(i, handler, handle_level_irq); + } + + /* 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-mc.c b/arch/mips/sgi-ip22/ip22-mc.c new file mode 100644 index 000000000..1944d4150 --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-mc.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ip22-mc.c: Routines for manipulating SGI Memory Controller. + * + * Copyright (C) 1996 David S. Miller (davem@davemloft.net) + * Copyright (C) 1999 Andrew R. Baker (andrewb@uab.edu) - Indigo2 changes + * Copyright (C) 2003 Ladislav Michl (ladis@linux-mips.org) + * Copyright (C) 2004 Peter Fuerst (pf@net.alphadv.de) - IP28 + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/spinlock.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 __init 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. + */ + /* don't touch parity settings for IP28 */ + tmp = sgimc->cpuctrl0; +#ifndef CONFIG_SGI_IP28 + tmp |= SGIMC_CCTRL0_EPERRGIO | SGIMC_CCTRL0_EPERRMEM; +#endif + tmp |= 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 & SGIMC_GIOPAR_GFX64; /* keep gfx 64bit settings */ + 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 */ + } + } 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) {} +void __init prom_free_prom_memory(void) +{ +#ifdef CONFIG_SGI_IP28 + u32 mconfig1; + unsigned long flags; + spinlock_t lock; + + /* + * because ARCS accesses memory uncached we wait until ARCS + * isn't needed any longer, before we switch from slow to + * normal mode + */ + spin_lock_irqsave(&lock, flags); + mconfig1 = sgimc->mconfig1; + /* map ECC register */ + sgimc->mconfig1 = (mconfig1 & 0xffff0000) | 0x2060; + iob(); + /* switch to normal mode */ + *(unsigned long *)PHYS_TO_XKSEG_UNCACHED(0x60000000) = 0; + iob(); + /* reduce WR_COL */ + sgimc->cmacc = (sgimc->cmacc & ~0xf) | 4; + iob(); + /* restore old config */ + sgimc->mconfig1 = mconfig1; + iob(); + spin_unlock_irqrestore(&lock, flags); +#endif +} diff --git a/arch/mips/sgi-ip22/ip22-nvram.c b/arch/mips/sgi-ip22/ip22-nvram.c new file mode 100644 index 000000000..e727ef519 --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-nvram.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ip22-nvram.c: NVRAM and serial EEPROM handling. + * + * Copyright (C) 2003 Ladislav Michl (ladis@linux-mips.org) + */ +#include <linux/export.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) ({ \ + __raw_writel(__raw_readl(ptr) & ~EEPROM_DATO, ptr); \ + __raw_writel(__raw_readl(ptr) & ~EEPROM_ECLK, ptr); \ + __raw_writel(__raw_readl(ptr) & ~EEPROM_EPROT, ptr); \ + delay(); \ + __raw_writel(__raw_readl(ptr) | EEPROM_CSEL, ptr); \ + __raw_writel(__raw_readl(ptr) | EEPROM_ECLK, ptr); }) + + +#define eeprom_cs_off(ptr) ({ \ + __raw_writel(__raw_readl(ptr) & ~EEPROM_ECLK, ptr); \ + __raw_writel(__raw_readl(ptr) & ~EEPROM_CSEL, ptr); \ + __raw_writel(__raw_readl(ptr) | EEPROM_EPROT, ptr); \ + __raw_writel(__raw_readl(ptr) | EEPROM_ECLK, ptr); }) + +#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(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 */ + __raw_writel(__raw_readl(ctrl) | EEPROM_DATO, ctrl); + else + __raw_writel(__raw_readl(ctrl) & ~EEPROM_DATO, ctrl); + __raw_writel(__raw_readl(ctrl) & ~EEPROM_ECLK, ctrl); + delay(); + __raw_writel(__raw_readl(ctrl) | EEPROM_ECLK, ctrl); + delay(); + ser_cmd <<= 1; + } + /* see data sheet timing diagram */ + __raw_writel(__raw_readl(ctrl) & ~EEPROM_DATO, ctrl); +} + +unsigned short ip22_eeprom_read(unsigned int *ctrl, int reg) +{ + unsigned short res = 0; + int i; + + __raw_writel(__raw_readl(ctrl) & ~EEPROM_EPROT, ctrl); + eeprom_cs_on(ctrl); + eeprom_cmd(ctrl, EEPROM_READ, reg); + + /* clock the data ouf of serial mem */ + for (i = 0; i < 16; i++) { + __raw_writel(__raw_readl(ctrl) & ~EEPROM_ECLK, ctrl); + delay(); + __raw_writel(__raw_readl(ctrl) | EEPROM_ECLK, ctrl); + delay(); + res <<= 1; + if (__raw_readl(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-platform.c b/arch/mips/sgi-ip22/ip22-platform.c new file mode 100644 index 000000000..37ad26716 --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-platform.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/init.h> +#include <linux/if_ether.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> + +#include <asm/paccess.h> +#include <asm/sgi/ip22.h> +#include <asm/sgi/hpc3.h> +#include <asm/sgi/mc.h> +#include <asm/sgi/seeq.h> +#include <asm/sgi/wd.h> + +static struct resource sgiwd93_0_resources[] = { + { + .name = "eth0 irq", + .start = SGI_WD93_0_IRQ, + .end = SGI_WD93_0_IRQ, + .flags = IORESOURCE_IRQ + } +}; + +static struct sgiwd93_platform_data sgiwd93_0_pd = { + .unit = 0, + .irq = SGI_WD93_0_IRQ, +}; + +static struct platform_device sgiwd93_0_device = { + .name = "sgiwd93", + .id = 0, + .num_resources = ARRAY_SIZE(sgiwd93_0_resources), + .resource = sgiwd93_0_resources, + .dev = { + .platform_data = &sgiwd93_0_pd, + }, +}; + +static struct resource sgiwd93_1_resources[] = { + { + .name = "eth0 irq", + .start = SGI_WD93_1_IRQ, + .end = SGI_WD93_1_IRQ, + .flags = IORESOURCE_IRQ + } +}; + +static struct sgiwd93_platform_data sgiwd93_1_pd = { + .unit = 1, + .irq = SGI_WD93_1_IRQ, +}; + +static struct platform_device sgiwd93_1_device = { + .name = "sgiwd93", + .id = 1, + .num_resources = ARRAY_SIZE(sgiwd93_1_resources), + .resource = sgiwd93_1_resources, + .dev = { + .platform_data = &sgiwd93_1_pd, + }, +}; + +/* + * Create a platform device for the GPI port that receives the + * image data from the embedded camera. + */ +static int __init sgiwd93_devinit(void) +{ + int res; + + sgiwd93_0_pd.hregs = &hpc3c0->scsi_chan0; + sgiwd93_0_pd.wdregs = (unsigned char *) hpc3c0->scsi0_ext; + + res = platform_device_register(&sgiwd93_0_device); + if (res) + return res; + + if (!ip22_is_fullhouse()) + return 0; + + sgiwd93_1_pd.hregs = &hpc3c0->scsi_chan1; + sgiwd93_1_pd.wdregs = (unsigned char *) hpc3c0->scsi1_ext; + + return platform_device_register(&sgiwd93_1_device); +} + +device_initcall(sgiwd93_devinit); + +static struct resource sgiseeq_0_resources[] = { + { + .name = "eth0 irq", + .start = SGI_ENET_IRQ, + .end = SGI_ENET_IRQ, + .flags = IORESOURCE_IRQ + } +}; + +static struct sgiseeq_platform_data eth0_pd; + +static struct platform_device eth0_device = { + .name = "sgiseeq", + .id = 0, + .num_resources = ARRAY_SIZE(sgiseeq_0_resources), + .resource = sgiseeq_0_resources, + .dev = { + .platform_data = ð0_pd, + }, +}; + +static struct resource sgiseeq_1_resources[] = { + { + .name = "eth1 irq", + .start = SGI_GIO_0_IRQ, + .end = SGI_GIO_0_IRQ, + .flags = IORESOURCE_IRQ + } +}; + +static struct sgiseeq_platform_data eth1_pd; + +static struct platform_device eth1_device = { + .name = "sgiseeq", + .id = 1, + .num_resources = ARRAY_SIZE(sgiseeq_1_resources), + .resource = sgiseeq_1_resources, + .dev = { + .platform_data = ð1_pd, + }, +}; + +/* + * Create a platform device for the GPI port that receives the + * image data from the embedded camera. + */ +static int __init sgiseeq_devinit(void) +{ + unsigned int pbdma __maybe_unused; + int res, i; + + eth0_pd.hpc = hpc3c0; + eth0_pd.irq = SGI_ENET_IRQ; +#define EADDR_NVOFS 250 + for (i = 0; i < 3; i++) { + unsigned short tmp = ip22_nvram_read(EADDR_NVOFS / 2 + i); + + eth0_pd.mac[2 * i] = tmp >> 8; + eth0_pd.mac[2 * i + 1] = tmp & 0xff; + } + + res = platform_device_register(ð0_device); + if (res) + return res; + + /* Second HPC is missing? */ + if (ip22_is_fullhouse() || + get_dbe(pbdma, (unsigned int *)&hpc3c1->pbdma[1])) + return 0; + + sgimc->giopar |= SGIMC_GIOPAR_MASTEREXP1 | SGIMC_GIOPAR_EXP164 | + SGIMC_GIOPAR_HPC264; + hpc3c1->pbus_piocfg[0][0] = 0x3ffff; + /* interrupt/config register on Challenge S Mezz board */ + hpc3c1->pbus_extregs[0][0] = 0x30; + + eth1_pd.hpc = hpc3c1; + eth1_pd.irq = SGI_GIO_0_IRQ; +#define EADDR_NVOFS 250 + for (i = 0; i < 3; i++) { + unsigned short tmp = ip22_eeprom_read(&hpc3c1->eeprom, + EADDR_NVOFS / 2 + i); + + eth1_pd.mac[2 * i] = tmp >> 8; + eth1_pd.mac[2 * i + 1] = tmp & 0xff; + } + + return platform_device_register(ð1_device); +} + +device_initcall(sgiseeq_devinit); + +static int __init sgi_hal2_devinit(void) +{ + return IS_ERR(platform_device_register_simple("sgihal2", 0, NULL, 0)); +} + +device_initcall(sgi_hal2_devinit); + +static int __init sgi_button_devinit(void) +{ + if (ip22_is_fullhouse()) + return 0; /* full house has no volume buttons */ + + return IS_ERR(platform_device_register_simple("sgibtns", -1, NULL, 0)); +} + +device_initcall(sgi_button_devinit); + +static int __init sgi_ds1286_devinit(void) +{ + struct resource res; + + memset(&res, 0, sizeof(res)); + res.start = HPC3_CHIP0_BASE + offsetof(struct hpc3_regs, rtcregs); + res.end = res.start + sizeof(hpc3c0->rtcregs) - 1; + res.flags = IORESOURCE_MEM; + + return IS_ERR(platform_device_register_simple("rtc-ds1286", -1, + &res, 1)); +} + +device_initcall(sgi_ds1286_devinit); diff --git a/arch/mips/sgi-ip22/ip22-reset.c b/arch/mips/sgi-ip22/ip22-reset.c new file mode 100644 index 000000000..c374f3cee --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-reset.c @@ -0,0 +1,203 @@ +/* + * 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, 03, 05, 06 by Ralf Baechle + */ +#include <linux/linkage.h> +#include <linux/init.h> +#include <linux/rtc/ds1286.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/notifier.h> +#include <linux/pm.h> +#include <linux/timer.h> + +#include <asm/io.h> +#include <asm/irq.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 panicked. + */ +#define POWERDOWN_FREQ (HZ / 4) +#define PANIC_FREQ (HZ / 8) + +static struct timer_list power_timer, blink_timer, debounce_timer; +static unsigned long blink_timer_timeout; + +#define MACHINE_PANICED 1 +#define MACHINE_SHUTTING_DOWN 2 + +static int machine_state; + +static void __noreturn 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 __noreturn sgi_machine_restart(char *command) +{ + if (machine_state & MACHINE_SHUTTING_DOWN) + sgi_machine_power_off(); + sgimc->cpuctrl0 |= SGIMC_CCTRL0_SYSINIT; + while (1); +} + +static void __noreturn sgi_machine_halt(void) +{ + if (machine_state & MACHINE_SHUTTING_DOWN) + sgi_machine_power_off(); + ArcEnterInteractiveMode(); +} + +static void power_timeout(struct timer_list *unused) +{ + sgi_machine_power_off(); +} + +static void blink_timeout(struct timer_list *unused) +{ + /* XXX fix this for fullhouse */ + sgi_ioc_reset ^= (SGIOC_RESET_LC0OFF|SGIOC_RESET_LC1OFF); + sgioc->reset = sgi_ioc_reset; + + mod_timer(&blink_timer, jiffies + blink_timer_timeout); +} + +static void debounce(struct timer_list *unused) +{ + del_timer(&debounce_timer); + if (sgint->istat1 & SGINT_ISTAT1_PWR) { + /* Interrupt still being sent. */ + debounce_timer.expires = jiffies + (HZ / 20); /* 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_cad_pid(SIGINT, 1)) { + /* No init process or button pressed twice. */ + sgi_machine_power_off(); + } + + machine_state |= MACHINE_SHUTTING_DOWN; + blink_timer_timeout = POWERDOWN_FREQ; + blink_timeout(&blink_timer); + + timer_setup(&power_timer, power_timeout, 0); + power_timer.expires = jiffies + POWERDOWN_TIMEOUT * HZ; + add_timer(&power_timer); +} + +static irqreturn_t panel_int(int irq, void *dev_id) +{ + 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_nosync(SGI_PANEL_IRQ); + timer_setup(&debounce_timer, debounce, 0); + 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 (!(buttons & SGIOC_PANEL_POWERINTR)) + power_button(); + + 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_timeout = PANIC_FREQ; + blink_timeout(&blink_timer); + + return NOTIFY_DONE; +} + +static struct notifier_block panic_block = { + .notifier_call = panic_event, +}; + +static int __init reboot_setup(void) +{ + int res; + + _machine_restart = sgi_machine_restart; + _machine_halt = sgi_machine_halt; + pm_power_off = sgi_machine_power_off; + + res = request_irq(SGI_PANEL_IRQ, panel_int, 0, "Front Panel", NULL); + if (res) { + printk(KERN_ERR "Allocation of front panel IRQ failed\n"); + return res; + } + + timer_setup(&blink_timer, blink_timeout, 0); + atomic_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 000000000..b69daa024 --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-setup.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ip22-setup.c: SGI specific setup, including init of the feature struct. + * + * Copyright (C) 1996 David S. Miller (davem@davemloft.net) + * Copyright (C) 1997, 1998 Ralf Baechle (ralf@gnu.org) + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/kdev_t.h> +#include <linux/types.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/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> + +extern void ip22_be_init(void) __init; + +void __init plat_mem_setup(void) +{ + char *ctype; + char *cserial; + + board_be_init = ip22_be_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. + * + * Need to check if the case is 'g' but no keyboard: + * (ConsoleIn/Out = serial) + */ + ctype = ArcGetEnvironmentVariable("console"); + cserial = ArcGetEnvironmentVariable("ConsoleOut"); + + if ((ctype && *ctype == 'd') || (cserial && *cserial == 's')) { + static char options[8] __initdata; + 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 graphics ('g'). */ + prom_flags |= PROM_FLAG_USE_AS_CONSOLE; + add_preferred_console("arc", 0, NULL); + } +} diff --git a/arch/mips/sgi-ip22/ip22-time.c b/arch/mips/sgi-ip22/ip22-time.c new file mode 100644 index 000000000..045aa89f2 --- /dev/null +++ b/arch/mips/sgi-ip22/ip22-time.c @@ -0,0 +1,131 @@ +/* + * 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, 06 Ralf Baechle (ralf@linux-mips.org) + */ +#include <linux/bcd.h> +#include <linux/i8253.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/time.h> +#include <linux/ftrace.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> + +static unsigned long dosample(void) +{ + u32 ct0, ct1; + u8 msb; + + /* 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 { + writeb(SGINT_TCWORD_CNT2 | SGINT_TCWORD_CLAT, &sgint->tcword); + (void) readb(&sgint->tcnt2); + msb = readb(&sgint->tcnt2); + ct1 = read_c0_count(); + } while (msb); + + /* Stop the counter. */ + writeb(SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL | SGINT_TCWORD_MSWST, + &sgint->tcword); + /* + * 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) * (500000/HZ); +} + +/* + * Here we need to calibrate the cycle counter to at least be close. + */ +__init void plat_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; + + if (ip22_is_fullhouse()) + setup_pit_timer(); +} + +/* Generic SGI handler for (spurious) 8254 interrupts */ +void __irq_entry indy_8254timer_irq(void) +{ + int irq = SGI_8254_0_IRQ; + ULONG cnt; + char c; + + irq_enter(); + kstat_incr_irq_this_cpu(irq); + printk(KERN_ALERT "Oops, got 8254 interrupt.\n"); + ArcRead(0, &c, 1, &cnt); + ArcEnterInteractiveMode(); + irq_exit(); +} diff --git a/arch/mips/sgi-ip22/ip28-berr.c b/arch/mips/sgi-ip22/ip28-berr.c new file mode 100644 index 000000000..2ed8e4990 --- /dev/null +++ b/arch/mips/sgi-ip22/ip28-berr.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ip28-berr.c: Bus error handling. + * + * Copyright (C) 2002, 2003 Ladislav Michl (ladis@linux-mips.org) + * Copyright (C) 2005 Peter Fuerst (pf@net.alphadv.de) - IP28 + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/sched/debug.h> +#include <linux/sched/signal.h> +#include <linux/seq_file.h> + +#include <asm/addrspace.h> +#include <asm/traps.h> +#include <asm/branch.h> +#include <asm/irq_regs.h> +#include <asm/sgi/mc.h> +#include <asm/sgi/hpc3.h> +#include <asm/sgi/ioc.h> +#include <asm/sgi/ip22.h> +#include <asm/r4kcache.h> +#include <linux/uaccess.h> +#include <asm/bootinfo.h> + +static unsigned int count_be_is_fixup; +static unsigned int count_be_handler; +static unsigned int count_be_interrupt; +static int debug_be_interrupt; + +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 */ + +struct hpc3_stat { + unsigned long addr; + unsigned int ctrl; + unsigned int cbp; + unsigned int ndptr; +}; + +static struct { + struct hpc3_stat pbdma[8]; + struct hpc3_stat scsi[2]; + struct hpc3_stat ethrx, ethtx; +} hpc3; + +static struct { + unsigned long err_addr; + struct { + u32 lo; + u32 hi; + } tags[1][2], tagd[4][2], tagi[4][2]; /* Way 0/1 */ +} cache_tags; + +static inline void save_cache_tags(unsigned busaddr) +{ + unsigned long addr = CAC_BASE | busaddr; + int i; + cache_tags.err_addr = addr; + + /* + * Starting with a bus-address, save secondary cache (indexed by + * PA[23..18:7..6]) tags first. + */ + addr &= ~1L; +#define tag cache_tags.tags[0] + cache_op(Index_Load_Tag_S, addr); + tag[0].lo = read_c0_taglo(); /* PA[35:18], VA[13:12] */ + tag[0].hi = read_c0_taghi(); /* PA[39:36] */ + cache_op(Index_Load_Tag_S, addr | 1L); + tag[1].lo = read_c0_taglo(); /* PA[35:18], VA[13:12] */ + tag[1].hi = read_c0_taghi(); /* PA[39:36] */ +#undef tag + + /* + * Save all primary data cache (indexed by VA[13:5]) tags which + * might fit to this bus-address, knowing that VA[11:0] == PA[11:0]. + * Saving all tags and evaluating them later is easier and safer + * than relying on VA[13:12] from the secondary cache tags to pick + * matching primary tags here already. + */ + addr &= (0xffL << 56) | ((1 << 12) - 1); +#define tag cache_tags.tagd[i] + for (i = 0; i < 4; ++i, addr += (1 << 12)) { + cache_op(Index_Load_Tag_D, addr); + tag[0].lo = read_c0_taglo(); /* PA[35:12] */ + tag[0].hi = read_c0_taghi(); /* PA[39:36] */ + cache_op(Index_Load_Tag_D, addr | 1L); + tag[1].lo = read_c0_taglo(); /* PA[35:12] */ + tag[1].hi = read_c0_taghi(); /* PA[39:36] */ + } +#undef tag + + /* + * Save primary instruction cache (indexed by VA[13:6]) tags + * the same way. + */ + addr &= (0xffL << 56) | ((1 << 12) - 1); +#define tag cache_tags.tagi[i] + for (i = 0; i < 4; ++i, addr += (1 << 12)) { + cache_op(Index_Load_Tag_I, addr); + tag[0].lo = read_c0_taglo(); /* PA[35:12] */ + tag[0].hi = read_c0_taghi(); /* PA[39:36] */ + cache_op(Index_Load_Tag_I, addr | 1L); + tag[1].lo = read_c0_taglo(); /* PA[35:12] */ + tag[1].hi = read_c0_taghi(); /* PA[39:36] */ + } +#undef tag +} + +#define GIO_ERRMASK 0xff00 +#define CPU_ERRMASK 0x3f00 + +static void save_and_clear_buserr(void) +{ + int i; + + /* 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 = sgioc->extio; + hpc3_berr_stat = hpc3c0->bestat; + + hpc3.scsi[0].addr = (unsigned long)&hpc3c0->scsi_chan0; + hpc3.scsi[0].ctrl = hpc3c0->scsi_chan0.ctrl; /* HPC3_SCTRL_ACTIVE ? */ + hpc3.scsi[0].cbp = hpc3c0->scsi_chan0.cbptr; + hpc3.scsi[0].ndptr = hpc3c0->scsi_chan0.ndptr; + + hpc3.scsi[1].addr = (unsigned long)&hpc3c0->scsi_chan1; + hpc3.scsi[1].ctrl = hpc3c0->scsi_chan1.ctrl; /* HPC3_SCTRL_ACTIVE ? */ + hpc3.scsi[1].cbp = hpc3c0->scsi_chan1.cbptr; + hpc3.scsi[1].ndptr = hpc3c0->scsi_chan1.ndptr; + + hpc3.ethrx.addr = (unsigned long)&hpc3c0->ethregs.rx_cbptr; + hpc3.ethrx.ctrl = hpc3c0->ethregs.rx_ctrl; /* HPC3_ERXCTRL_ACTIVE ? */ + hpc3.ethrx.cbp = hpc3c0->ethregs.rx_cbptr; + hpc3.ethrx.ndptr = hpc3c0->ethregs.rx_ndptr; + + hpc3.ethtx.addr = (unsigned long)&hpc3c0->ethregs.tx_cbptr; + hpc3.ethtx.ctrl = hpc3c0->ethregs.tx_ctrl; /* HPC3_ETXCTRL_ACTIVE ? */ + hpc3.ethtx.cbp = hpc3c0->ethregs.tx_cbptr; + hpc3.ethtx.ndptr = hpc3c0->ethregs.tx_ndptr; + + for (i = 0; i < 8; ++i) { + /* HPC3_PDMACTRL_ISACT ? */ + hpc3.pbdma[i].addr = (unsigned long)&hpc3c0->pbdma[i]; + hpc3.pbdma[i].ctrl = hpc3c0->pbdma[i].pbdma_ctrl; + hpc3.pbdma[i].cbp = hpc3c0->pbdma[i].pbdma_bptr; + hpc3.pbdma[i].ndptr = hpc3c0->pbdma[i].pbdma_dptr; + } + i = 0; + if (gio_err_stat & CPU_ERRMASK) + i = gio_err_addr; + if (cpu_err_stat & CPU_ERRMASK) + i = cpu_err_addr; + save_cache_tags(i); + + sgimc->cstat = sgimc->gstat = 0; +} + +static void print_cache_tags(void) +{ + u32 scb, scw; + int i; + + printk(KERN_ERR "Cache tags @ %08x:\n", (unsigned)cache_tags.err_addr); + + /* PA[31:12] shifted to PTag0 (PA[35:12]) format */ + scw = (cache_tags.err_addr >> 4) & 0x0fffff00; + + scb = cache_tags.err_addr & ((1 << 12) - 1) & ~((1 << 5) - 1); + for (i = 0; i < 4; ++i) { /* for each possible VA[13:12] value */ + if ((cache_tags.tagd[i][0].lo & 0x0fffff00) != scw && + (cache_tags.tagd[i][1].lo & 0x0fffff00) != scw) + continue; + printk(KERN_ERR + "D: 0: %08x %08x, 1: %08x %08x (VA[13:5] %04x)\n", + cache_tags.tagd[i][0].hi, cache_tags.tagd[i][0].lo, + cache_tags.tagd[i][1].hi, cache_tags.tagd[i][1].lo, + scb | (1 << 12)*i); + } + scb = cache_tags.err_addr & ((1 << 12) - 1) & ~((1 << 6) - 1); + for (i = 0; i < 4; ++i) { /* for each possible VA[13:12] value */ + if ((cache_tags.tagi[i][0].lo & 0x0fffff00) != scw && + (cache_tags.tagi[i][1].lo & 0x0fffff00) != scw) + continue; + printk(KERN_ERR + "I: 0: %08x %08x, 1: %08x %08x (VA[13:6] %04x)\n", + cache_tags.tagi[i][0].hi, cache_tags.tagi[i][0].lo, + cache_tags.tagi[i][1].hi, cache_tags.tagi[i][1].lo, + scb | (1 << 12)*i); + } + i = read_c0_config(); + scb = i & (1 << 13) ? 7:6; /* scblksize = 2^[7..6] */ + scw = ((i >> 16) & 7) + 19 - 1; /* scwaysize = 2^[24..19] / 2 */ + + i = ((1 << scw) - 1) & ~((1 << scb) - 1); + printk(KERN_ERR "S: 0: %08x %08x, 1: %08x %08x (PA[%u:%u] %05x)\n", + cache_tags.tags[0][0].hi, cache_tags.tags[0][0].lo, + cache_tags.tags[0][1].hi, cache_tags.tags[0][1].lo, + scw-1, scb, i & (unsigned)cache_tags.err_addr); +} + +static inline const char *cause_excode_text(int cause) +{ + static const char *txt[32] = + { "Interrupt", + "TLB modification", + "TLB (load or instruction fetch)", + "TLB (store)", + "Address error (load or instruction fetch)", + "Address error (store)", + "Bus error (instruction fetch)", + "Bus error (data: load or store)", + "Syscall", + "Breakpoint", + "Reserved instruction", + "Coprocessor unusable", + "Arithmetic Overflow", + "Trap", + "14", + "Floating-Point", + "16", "17", "18", "19", "20", "21", "22", + "Watch Hi/Lo", + "24", "25", "26", "27", "28", "29", "30", "31", + }; + return txt[(cause & 0x7c) >> 2]; +} + +static void print_buserr(const struct pt_regs *regs) +{ + const int field = 2 * sizeof(unsigned long); + int error = 0; + + if (extio_stat & EXTIO_MC_BUSERR) { + printk(KERN_ERR "MC Bus Error\n"); + error |= 1; + } + 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); + error |= 2; + } + if (extio_stat & EXTIO_EISA_BUSERR) { + printk(KERN_ERR "EISA Bus Error\n"); + error |= 4; + } + 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); + error |= 8; + } + 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); + error |= 16; + } + if (!error) + printk(KERN_ERR "MC: Hmm, didn't find any error condition.\n"); + else { + printk(KERN_ERR "CP0: config %08x, " + "MC: cpuctrl0/1: %08x/%05x, giopar: %04x\n" + "MC: cpu/gio_memacc: %08x/%05x, memcfg0/1: %08x/%08x\n", + read_c0_config(), + sgimc->cpuctrl0, sgimc->cpuctrl0, sgimc->giopar, + sgimc->cmacc, sgimc->gmacc, + sgimc->mconfig0, sgimc->mconfig1); + print_cache_tags(); + } + printk(KERN_ALERT "%s, epc == %0*lx, ra == %0*lx\n", + cause_excode_text(regs->cp0_cause), + field, regs->cp0_epc, field, regs->regs[31]); +} + +/* + * Check, whether MC's (virtual) DMA address caused the bus error. + * See "Virtual DMA Specification", Draft 1.5, Feb 13 1992, SGI + */ + +static int addr_is_ram(unsigned long addr, unsigned sz) +{ + int i; + + for (i = 0; i < boot_mem_map.nr_map; i++) { + unsigned long a = boot_mem_map.map[i].addr; + if (a <= addr && addr+sz <= a+boot_mem_map.map[i].size) + return 1; + } + return 0; +} + +static int check_microtlb(u32 hi, u32 lo, unsigned long vaddr) +{ + /* This is likely rather similar to correct code ;-) */ + + vaddr &= 0x7fffffff; /* Doc. states that top bit is ignored */ + + /* If tlb-entry is valid and VPN-high (bits [30:21] ?) matches... */ + if ((lo & 2) && (vaddr >> 21) == ((hi<<1) >> 22)) { + u32 ctl = sgimc->dma_ctrl; + if (ctl & 1) { + unsigned int pgsz = (ctl & 2) ? 14:12; /* 16k:4k */ + /* PTEIndex is VPN-low (bits [22:14]/[20:12] ?) */ + unsigned long pte = (lo >> 6) << 12; /* PTEBase */ + pte += 8*((vaddr >> pgsz) & 0x1ff); + if (addr_is_ram(pte, 8)) { + /* + * Note: Since DMA hardware does look up + * translation on its own, this PTE *must* + * match the TLB/EntryLo-register format ! + */ + unsigned long a = *(unsigned long *) + PHYS_TO_XKSEG_UNCACHED(pte); + a = (a & 0x3f) << 6; /* PFN */ + a += vaddr & ((1 << pgsz) - 1); + return cpu_err_addr == a; + } + } + } + return 0; +} + +static int check_vdma_memaddr(void) +{ + if (cpu_err_stat & CPU_ERRMASK) { + u32 a = sgimc->maddronly; + + if (!(sgimc->dma_ctrl & 0x100)) /* Xlate-bit clear ? */ + return cpu_err_addr == a; + + if (check_microtlb(sgimc->dtlb_hi0, sgimc->dtlb_lo0, a) || + check_microtlb(sgimc->dtlb_hi1, sgimc->dtlb_lo1, a) || + check_microtlb(sgimc->dtlb_hi2, sgimc->dtlb_lo2, a) || + check_microtlb(sgimc->dtlb_hi3, sgimc->dtlb_lo3, a)) + return 1; + } + return 0; +} + +static int check_vdma_gioaddr(void) +{ + if (gio_err_stat & GIO_ERRMASK) { + u32 a = sgimc->gio_dma_trans; + a = (sgimc->gmaddronly & ~a) | (sgimc->gio_dma_sbits & a); + return gio_err_addr == a; + } + return 0; +} + +/* + * 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. + */ + +static int ip28_be_interrupt(const struct pt_regs *regs) +{ + int i; + + save_and_clear_buserr(); + /* + * Try to find out, whether we got here by a mispredicted speculative + * load/store operation. If so, it's not fatal, we can go on. + */ + /* Any cause other than "Interrupt" (ExcCode 0) is fatal. */ + if (regs->cp0_cause & CAUSEF_EXCCODE) + goto mips_be_fatal; + + /* Any cause other than "Bus error interrupt" (IP6) is weird. */ + if ((regs->cp0_cause & CAUSEF_IP6) != CAUSEF_IP6) + goto mips_be_fatal; + + if (extio_stat & (EXTIO_HPC3_BUSERR | EXTIO_EISA_BUSERR)) + goto mips_be_fatal; + + /* Any state other than "Memory bus error" is fatal. */ + if (cpu_err_stat & CPU_ERRMASK & ~SGIMC_CSTAT_ADDR) + goto mips_be_fatal; + + /* GIO errors other than timeouts are fatal */ + if (gio_err_stat & GIO_ERRMASK & ~SGIMC_GSTAT_TIME) + goto mips_be_fatal; + + /* + * Now we have an asynchronous bus error, speculatively or DMA caused. + * Need to search all DMA descriptors for the error address. + */ + for (i = 0; i < sizeof(hpc3)/sizeof(struct hpc3_stat); ++i) { + struct hpc3_stat *hp = (struct hpc3_stat *)&hpc3 + i; + if ((cpu_err_stat & CPU_ERRMASK) && + (cpu_err_addr == hp->ndptr || cpu_err_addr == hp->cbp)) + break; + if ((gio_err_stat & GIO_ERRMASK) && + (gio_err_addr == hp->ndptr || gio_err_addr == hp->cbp)) + break; + } + if (i < sizeof(hpc3)/sizeof(struct hpc3_stat)) { + struct hpc3_stat *hp = (struct hpc3_stat *)&hpc3 + i; + printk(KERN_ERR "at DMA addresses: HPC3 @ %08lx:" + " ctl %08x, ndp %08x, cbp %08x\n", + CPHYSADDR(hp->addr), hp->ctrl, hp->ndptr, hp->cbp); + goto mips_be_fatal; + } + /* Check MC's virtual DMA stuff. */ + if (check_vdma_memaddr()) { + printk(KERN_ERR "at GIO DMA: mem address 0x%08x.\n", + sgimc->maddronly); + goto mips_be_fatal; + } + if (check_vdma_gioaddr()) { + printk(KERN_ERR "at GIO DMA: gio address 0x%08x.\n", + sgimc->gmaddronly); + goto mips_be_fatal; + } + /* A speculative bus error... */ + if (debug_be_interrupt) { + print_buserr(regs); + printk(KERN_ERR "discarded!\n"); + } + return MIPS_BE_DISCARD; + +mips_be_fatal: + print_buserr(regs); + return MIPS_BE_FATAL; +} + +void ip22_be_interrupt(int irq) +{ + struct pt_regs *regs = get_irq_regs(); + + count_be_interrupt++; + + if (ip28_be_interrupt(regs) != MIPS_BE_DISCARD) { + /* Assume it would be too dangerous to continue ... */ + die_if_kernel("Oops", regs); + force_sig(SIGBUS, current); + } else if (debug_be_interrupt) + show_regs((struct pt_regs *)regs); +} + +static int ip28_be_handler(struct pt_regs *regs, int is_fixup) +{ + /* + * We arrive here only in the unusual case of do_be() invocation, + * i.e. by a bus error exception without a bus error interrupt. + */ + if (is_fixup) { + count_be_is_fixup++; + save_and_clear_buserr(); + return MIPS_BE_FIXUP; + } + count_be_handler++; + return ip28_be_interrupt(regs); +} + +void __init ip22_be_init(void) +{ + board_be_handler = ip28_be_handler; +} + +int ip28_show_be_info(struct seq_file *m) +{ + seq_printf(m, "IP28 be fixups\t\t: %u\n", count_be_is_fixup); + seq_printf(m, "IP28 be interrupts\t: %u\n", count_be_interrupt); + seq_printf(m, "IP28 be handler\t\t: %u\n", count_be_handler); + + return 0; +} + +static int __init debug_be_setup(char *str) +{ + debug_be_interrupt++; + return 1; +} +__setup("ip28_debug_be", debug_be_setup); |