diff options
Diffstat (limited to '')
-rw-r--r-- | arch/arm/mach-rpc/Makefile | 9 | ||||
-rw-r--r-- | arch/arm/mach-rpc/Makefile.boot | 5 | ||||
-rw-r--r-- | arch/arm/mach-rpc/dma.c | 393 | ||||
-rw-r--r-- | arch/arm/mach-rpc/ecard-loader.S | 40 | ||||
-rw-r--r-- | arch/arm/mach-rpc/ecard.c | 1148 | ||||
-rw-r--r-- | arch/arm/mach-rpc/ecard.h | 66 | ||||
-rw-r--r-- | arch/arm/mach-rpc/fiq.S | 17 | ||||
-rw-r--r-- | arch/arm/mach-rpc/floppydma.S | 29 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/acornfb.h | 137 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/entry-macro.S | 13 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/hardware.h | 73 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/io.h | 28 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/irqs.h | 42 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/isa-dma.h | 26 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/memory.h | 32 | ||||
-rw-r--r-- | arch/arm/mach-rpc/include/mach/uncompress.h | 181 | ||||
-rw-r--r-- | arch/arm/mach-rpc/io-acorn.S | 28 | ||||
-rw-r--r-- | arch/arm/mach-rpc/irq.c | 131 | ||||
-rw-r--r-- | arch/arm/mach-rpc/riscpc.c | 224 | ||||
-rw-r--r-- | arch/arm/mach-rpc/time.c | 97 |
20 files changed, 2719 insertions, 0 deletions
diff --git a/arch/arm/mach-rpc/Makefile b/arch/arm/mach-rpc/Makefile new file mode 100644 index 000000000..90a645a18 --- /dev/null +++ b/arch/arm/mach-rpc/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the linux kernel. +# + +# Object file lists. + +obj-y :=dma.o ecard.o ecard-loader.o fiq.o floppydma.o io-acorn.o irq.o \ + riscpc.o time.o diff --git a/arch/arm/mach-rpc/Makefile.boot b/arch/arm/mach-rpc/Makefile.boot new file mode 100644 index 000000000..0ed8e8fbd --- /dev/null +++ b/arch/arm/mach-rpc/Makefile.boot @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + zreladdr-y += 0x10008000 +params_phys-y := 0x10000100 +initrd_phys-y := 0x18000000 + diff --git a/arch/arm/mach-rpc/dma.c b/arch/arm/mach-rpc/dma.c new file mode 100644 index 000000000..50e0f97af --- /dev/null +++ b/arch/arm/mach-rpc/dma.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/arch/arm/mach-rpc/dma.c + * + * Copyright (C) 1998 Russell King + * + * DMA functions specific to RiscPC architecture + */ +#include <linux/mman.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> + +#include <asm/page.h> +#include <asm/dma.h> +#include <asm/fiq.h> +#include <asm/irq.h> +#include <mach/hardware.h> +#include <linux/uaccess.h> + +#include <asm/mach/dma.h> +#include <asm/hardware/iomd.h> + +struct iomd_dma { + struct dma_struct dma; + void __iomem *base; /* Controller base address */ + int irq; /* Controller IRQ */ + unsigned int state; + dma_addr_t cur_addr; + unsigned int cur_len; + dma_addr_t dma_addr; + unsigned int dma_len; +}; + +#if 0 +typedef enum { + dma_size_8 = 1, + dma_size_16 = 2, + dma_size_32 = 4, + dma_size_128 = 16 +} dma_size_t; +#endif + +#define TRANSFER_SIZE 2 + +#define CURA (0) +#define ENDA (IOMD_IO0ENDA - IOMD_IO0CURA) +#define CURB (IOMD_IO0CURB - IOMD_IO0CURA) +#define ENDB (IOMD_IO0ENDB - IOMD_IO0CURA) +#define CR (IOMD_IO0CR - IOMD_IO0CURA) +#define ST (IOMD_IO0ST - IOMD_IO0CURA) + +static void iomd_get_next_sg(struct iomd_dma *idma) +{ + unsigned long end, offset, flags = 0; + + if (idma->dma.sg) { + idma->cur_addr = idma->dma_addr; + offset = idma->cur_addr & ~PAGE_MASK; + + end = offset + idma->dma_len; + + if (end > PAGE_SIZE) + end = PAGE_SIZE; + + if (offset + TRANSFER_SIZE >= end) + flags |= DMA_END_L; + + idma->cur_len = end - TRANSFER_SIZE; + + idma->dma_len -= end - offset; + idma->dma_addr += end - offset; + + if (idma->dma_len == 0) { + if (idma->dma.sgcount > 1) { + idma->dma.sg = sg_next(idma->dma.sg); + idma->dma_addr = idma->dma.sg->dma_address; + idma->dma_len = idma->dma.sg->length; + idma->dma.sgcount--; + } else { + idma->dma.sg = NULL; + flags |= DMA_END_S; + } + } + } else { + flags = DMA_END_S | DMA_END_L; + idma->cur_addr = 0; + idma->cur_len = 0; + } + + idma->cur_len |= flags; +} + +static irqreturn_t iomd_dma_handle(int irq, void *dev_id) +{ + struct iomd_dma *idma = dev_id; + void __iomem *base = idma->base; + unsigned int state = idma->state; + unsigned int status, cur, end; + + do { + status = readb(base + ST); + if (!(status & DMA_ST_INT)) + goto out; + + if ((state ^ status) & DMA_ST_AB) + iomd_get_next_sg(idma); + + // This efficiently implements state = OFL != AB ? AB : 0 + state = ((status >> 2) ^ status) & DMA_ST_AB; + if (state) { + cur = CURA; + end = ENDA; + } else { + cur = CURB; + end = ENDB; + } + writel(idma->cur_addr, base + cur); + writel(idma->cur_len, base + end); + + if (status & DMA_ST_OFL && + idma->cur_len == (DMA_END_S|DMA_END_L)) + break; + } while (1); + + state = ~DMA_ST_AB; + disable_irq_nosync(irq); +out: + idma->state = state; + return IRQ_HANDLED; +} + +static int iomd_request_dma(unsigned int chan, dma_t *dma) +{ + struct iomd_dma *idma = container_of(dma, struct iomd_dma, dma); + + return request_irq(idma->irq, iomd_dma_handle, + 0, idma->dma.device_id, idma); +} + +static void iomd_free_dma(unsigned int chan, dma_t *dma) +{ + struct iomd_dma *idma = container_of(dma, struct iomd_dma, dma); + + free_irq(idma->irq, idma); +} + +static struct device isa_dma_dev = { + .init_name = "fallback device", + .coherent_dma_mask = ~(dma_addr_t)0, + .dma_mask = &isa_dma_dev.coherent_dma_mask, +}; + +static void iomd_enable_dma(unsigned int chan, dma_t *dma) +{ + struct iomd_dma *idma = container_of(dma, struct iomd_dma, dma); + void __iomem *base = idma->base; + unsigned int ctrl = TRANSFER_SIZE | DMA_CR_E; + + if (idma->dma.invalid) { + idma->dma.invalid = 0; + + /* + * Cope with ISA-style drivers which expect cache + * coherence. + */ + if (!idma->dma.sg) { + idma->dma.sg = &idma->dma.buf; + idma->dma.sgcount = 1; + idma->dma.buf.length = idma->dma.count; + idma->dma.buf.dma_address = dma_map_single(&isa_dma_dev, + idma->dma.addr, idma->dma.count, + idma->dma.dma_mode == DMA_MODE_READ ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + } + + idma->dma_addr = idma->dma.sg->dma_address; + idma->dma_len = idma->dma.sg->length; + + writeb(DMA_CR_C, base + CR); + idma->state = DMA_ST_AB; + } + + if (idma->dma.dma_mode == DMA_MODE_READ) + ctrl |= DMA_CR_D; + + writeb(ctrl, base + CR); + enable_irq(idma->irq); +} + +static void iomd_disable_dma(unsigned int chan, dma_t *dma) +{ + struct iomd_dma *idma = container_of(dma, struct iomd_dma, dma); + void __iomem *base = idma->base; + unsigned long flags; + + local_irq_save(flags); + if (idma->state != ~DMA_ST_AB) + disable_irq(idma->irq); + writeb(0, base + CR); + local_irq_restore(flags); +} + +static int iomd_set_dma_speed(unsigned int chan, dma_t *dma, int cycle) +{ + int tcr, speed; + + if (cycle < 188) + speed = 3; + else if (cycle <= 250) + speed = 2; + else if (cycle < 438) + speed = 1; + else + speed = 0; + + tcr = iomd_readb(IOMD_DMATCR); + speed &= 3; + + switch (chan) { + case DMA_0: + tcr = (tcr & ~0x03) | speed; + break; + + case DMA_1: + tcr = (tcr & ~0x0c) | (speed << 2); + break; + + case DMA_2: + tcr = (tcr & ~0x30) | (speed << 4); + break; + + case DMA_3: + tcr = (tcr & ~0xc0) | (speed << 6); + break; + + default: + break; + } + + iomd_writeb(tcr, IOMD_DMATCR); + + return speed; +} + +static struct dma_ops iomd_dma_ops = { + .type = "IOMD", + .request = iomd_request_dma, + .free = iomd_free_dma, + .enable = iomd_enable_dma, + .disable = iomd_disable_dma, + .setspeed = iomd_set_dma_speed, +}; + +static struct fiq_handler fh = { + .name = "floppydma" +}; + +struct floppy_dma { + struct dma_struct dma; + unsigned int fiq; +}; + +static void floppy_enable_dma(unsigned int chan, dma_t *dma) +{ + struct floppy_dma *fdma = container_of(dma, struct floppy_dma, dma); + void *fiqhandler_start; + unsigned int fiqhandler_length; + struct pt_regs regs; + + if (fdma->dma.sg) + BUG(); + + if (fdma->dma.dma_mode == DMA_MODE_READ) { + extern unsigned char floppy_fiqin_start, floppy_fiqin_end; + fiqhandler_start = &floppy_fiqin_start; + fiqhandler_length = &floppy_fiqin_end - &floppy_fiqin_start; + } else { + extern unsigned char floppy_fiqout_start, floppy_fiqout_end; + fiqhandler_start = &floppy_fiqout_start; + fiqhandler_length = &floppy_fiqout_end - &floppy_fiqout_start; + } + + regs.ARM_r9 = fdma->dma.count; + regs.ARM_r10 = (unsigned long)fdma->dma.addr; + regs.ARM_fp = (unsigned long)FLOPPYDMA_BASE; + + if (claim_fiq(&fh)) { + printk("floppydma: couldn't claim FIQ.\n"); + return; + } + + set_fiq_handler(fiqhandler_start, fiqhandler_length); + set_fiq_regs(®s); + enable_fiq(fdma->fiq); +} + +static void floppy_disable_dma(unsigned int chan, dma_t *dma) +{ + struct floppy_dma *fdma = container_of(dma, struct floppy_dma, dma); + disable_fiq(fdma->fiq); + release_fiq(&fh); +} + +static int floppy_get_residue(unsigned int chan, dma_t *dma) +{ + struct pt_regs regs; + get_fiq_regs(®s); + return regs.ARM_r9; +} + +static struct dma_ops floppy_dma_ops = { + .type = "FIQDMA", + .enable = floppy_enable_dma, + .disable = floppy_disable_dma, + .residue = floppy_get_residue, +}; + +/* + * This is virtual DMA - we don't need anything here. + */ +static void sound_enable_disable_dma(unsigned int chan, dma_t *dma) +{ +} + +static struct dma_ops sound_dma_ops = { + .type = "VIRTUAL", + .enable = sound_enable_disable_dma, + .disable = sound_enable_disable_dma, +}; + +static struct iomd_dma iomd_dma[6]; + +static struct floppy_dma floppy_dma = { + .dma = { + .d_ops = &floppy_dma_ops, + }, + .fiq = FIQ_FLOPPYDATA, +}; + +static dma_t sound_dma = { + .d_ops = &sound_dma_ops, +}; + +static int __init rpc_dma_init(void) +{ + unsigned int i; + int ret; + + iomd_writeb(0, IOMD_IO0CR); + iomd_writeb(0, IOMD_IO1CR); + iomd_writeb(0, IOMD_IO2CR); + iomd_writeb(0, IOMD_IO3CR); + + iomd_writeb(0xa0, IOMD_DMATCR); + + /* + * Setup DMA channels 2,3 to be for podules + * and channels 0,1 for internal devices + */ + iomd_writeb(DMA_EXT_IO3|DMA_EXT_IO2, IOMD_DMAEXT); + + iomd_dma[DMA_0].base = IOMD_BASE + IOMD_IO0CURA; + iomd_dma[DMA_0].irq = IRQ_DMA0; + iomd_dma[DMA_1].base = IOMD_BASE + IOMD_IO1CURA; + iomd_dma[DMA_1].irq = IRQ_DMA1; + iomd_dma[DMA_2].base = IOMD_BASE + IOMD_IO2CURA; + iomd_dma[DMA_2].irq = IRQ_DMA2; + iomd_dma[DMA_3].base = IOMD_BASE + IOMD_IO3CURA; + iomd_dma[DMA_3].irq = IRQ_DMA3; + iomd_dma[DMA_S0].base = IOMD_BASE + IOMD_SD0CURA; + iomd_dma[DMA_S0].irq = IRQ_DMAS0; + iomd_dma[DMA_S1].base = IOMD_BASE + IOMD_SD1CURA; + iomd_dma[DMA_S1].irq = IRQ_DMAS1; + + for (i = DMA_0; i <= DMA_S1; i++) { + iomd_dma[i].dma.d_ops = &iomd_dma_ops; + + ret = isa_dma_add(i, &iomd_dma[i].dma); + if (ret) + printk("IOMDDMA%u: unable to register: %d\n", i, ret); + } + + ret = isa_dma_add(DMA_VIRTUAL_FLOPPY, &floppy_dma.dma); + if (ret) + printk("IOMDFLOPPY: unable to register: %d\n", ret); + ret = isa_dma_add(DMA_VIRTUAL_SOUND, &sound_dma); + if (ret) + printk("IOMDSOUND: unable to register: %d\n", ret); + return 0; +} +core_initcall(rpc_dma_init); diff --git a/arch/arm/mach-rpc/ecard-loader.S b/arch/arm/mach-rpc/ecard-loader.S new file mode 100644 index 000000000..eb8ac0412 --- /dev/null +++ b/arch/arm/mach-rpc/ecard-loader.S @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * linux/arch/arm/lib/ecard.S + * + * Copyright (C) 1995, 1996 Russell King + * + * 27/03/03 Ian Molton Clean up CONFIG_CPU + */ +#include <linux/linkage.h> +#include <asm/assembler.h> + +#define CPSR2SPSR(rt) \ + mrs rt, cpsr; \ + msr spsr_cxsf, rt + +@ Purpose: call an expansion card loader to read bytes. +@ Proto : char read_loader(int offset, char *card_base, char *loader); +@ Returns: byte read + +ENTRY(ecard_loader_read) + stmfd sp!, {r4 - r12, lr} + mov r11, r1 + mov r1, r0 + CPSR2SPSR(r0) + mov lr, pc + mov pc, r2 + ldmfd sp!, {r4 - r12, pc} + +@ Purpose: call an expansion card loader to reset the card +@ Proto : void read_loader(int card_base, char *loader); +@ Returns: byte read + +ENTRY(ecard_loader_reset) + stmfd sp!, {r4 - r12, lr} + mov r11, r0 + CPSR2SPSR(r0) + mov lr, pc + add pc, r1, #8 + ldmfd sp!, {r4 - r12, pc} + diff --git a/arch/arm/mach-rpc/ecard.c b/arch/arm/mach-rpc/ecard.c new file mode 100644 index 000000000..827b50f1c --- /dev/null +++ b/arch/arm/mach-rpc/ecard.c @@ -0,0 +1,1148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/arch/arm/kernel/ecard.c + * + * Copyright 1995-2001 Russell King + * + * Find all installed expansion cards, and handle interrupts from them. + * + * Created from information from Acorns RiscOS3 PRMs + * + * 08-Dec-1996 RMK Added code for the 9'th expansion card - the ether + * podule slot. + * 06-May-1997 RMK Added blacklist for cards whose loader doesn't work. + * 12-Sep-1997 RMK Created new handling of interrupt enables/disables + * - cards can now register their own routine to control + * interrupts (recommended). + * 29-Sep-1997 RMK Expansion card interrupt hardware not being re-enabled + * on reset from Linux. (Caused cards not to respond + * under RiscOS without hard reset). + * 15-Feb-1998 RMK Added DMA support + * 12-Sep-1998 RMK Added EASI support + * 10-Jan-1999 RMK Run loaders in a simulated RISC OS environment. + * 17-Apr-1999 RMK Support for EASI Type C cycles. + */ +#define ECARD_C + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/sched/mm.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/reboot.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/kthread.h> +#include <linux/irq.h> +#include <linux/io.h> + +#include <asm/dma.h> +#include <asm/ecard.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/mmu_context.h> +#include <asm/mach/irq.h> +#include <asm/tlbflush.h> + +#include "ecard.h" + +struct ecard_request { + void (*fn)(struct ecard_request *); + ecard_t *ec; + unsigned int address; + unsigned int length; + unsigned int use_loader; + void *buffer; + struct completion *complete; +}; + +struct expcard_quirklist { + unsigned short manufacturer; + unsigned short product; + const char *type; + void (*init)(ecard_t *ec); +}; + +static ecard_t *cards; +static ecard_t *slot_to_expcard[MAX_ECARDS]; +static unsigned int ectcr; + +static void atomwide_3p_quirk(ecard_t *ec); + +/* List of descriptions of cards which don't have an extended + * identification, or chunk directories containing a description. + */ +static struct expcard_quirklist quirklist[] __initdata = { + { MANU_ACORN, PROD_ACORN_ETHER1, "Acorn Ether1" }, + { MANU_ATOMWIDE, PROD_ATOMWIDE_3PSERIAL, NULL, atomwide_3p_quirk }, +}; + +asmlinkage extern int +ecard_loader_reset(unsigned long base, loader_t loader); +asmlinkage extern int +ecard_loader_read(int off, unsigned long base, loader_t loader); + +static inline unsigned short ecard_getu16(unsigned char *v) +{ + return v[0] | v[1] << 8; +} + +static inline signed long ecard_gets24(unsigned char *v) +{ + return v[0] | v[1] << 8 | v[2] << 16 | ((v[2] & 0x80) ? 0xff000000 : 0); +} + +static inline ecard_t *slot_to_ecard(unsigned int slot) +{ + return slot < MAX_ECARDS ? slot_to_expcard[slot] : NULL; +} + +/* ===================== Expansion card daemon ======================== */ +/* + * Since the loader programs on the expansion cards need to be run + * in a specific environment, create a separate task with this + * environment up, and pass requests to this task as and when we + * need to. + * + * This should allow 99% of loaders to be called from Linux. + * + * From a security standpoint, we trust the card vendors. This + * may be a misplaced trust. + */ +static void ecard_task_reset(struct ecard_request *req) +{ + struct expansion_card *ec = req->ec; + struct resource *res; + + res = ec->slot_no == 8 + ? &ec->resource[ECARD_RES_MEMC] + : ec->easi + ? &ec->resource[ECARD_RES_EASI] + : &ec->resource[ECARD_RES_IOCSYNC]; + + ecard_loader_reset(res->start, ec->loader); +} + +static void ecard_task_readbytes(struct ecard_request *req) +{ + struct expansion_card *ec = req->ec; + unsigned char *buf = req->buffer; + unsigned int len = req->length; + unsigned int off = req->address; + + if (ec->slot_no == 8) { + void __iomem *base = (void __iomem *) + ec->resource[ECARD_RES_MEMC].start; + + /* + * The card maintains an index which increments the address + * into a 4096-byte page on each access. We need to keep + * track of the counter. + */ + static unsigned int index; + unsigned int page; + + page = (off >> 12) * 4; + if (page > 256 * 4) + return; + + off &= 4095; + + /* + * If we are reading offset 0, or our current index is + * greater than the offset, reset the hardware index counter. + */ + if (off == 0 || index > off) { + writeb(0, base); + index = 0; + } + + /* + * Increment the hardware index counter until we get to the + * required offset. The read bytes are discarded. + */ + while (index < off) { + readb(base + page); + index += 1; + } + + while (len--) { + *buf++ = readb(base + page); + index += 1; + } + } else { + unsigned long base = (ec->easi + ? &ec->resource[ECARD_RES_EASI] + : &ec->resource[ECARD_RES_IOCSYNC])->start; + void __iomem *pbase = (void __iomem *)base; + + if (!req->use_loader || !ec->loader) { + off *= 4; + while (len--) { + *buf++ = readb(pbase + off); + off += 4; + } + } else { + while(len--) { + /* + * The following is required by some + * expansion card loader programs. + */ + *(unsigned long *)0x108 = 0; + *buf++ = ecard_loader_read(off++, base, + ec->loader); + } + } + } + +} + +static DECLARE_WAIT_QUEUE_HEAD(ecard_wait); +static struct ecard_request *ecard_req; +static DEFINE_MUTEX(ecard_mutex); + +/* + * Set up the expansion card daemon's page tables. + */ +static void ecard_init_pgtables(struct mm_struct *mm) +{ + struct vm_area_struct vma = TLB_FLUSH_VMA(mm, VM_EXEC); + + /* We want to set up the page tables for the following mapping: + * Virtual Physical + * 0x03000000 0x03000000 + * 0x03010000 unmapped + * 0x03210000 0x03210000 + * 0x03400000 unmapped + * 0x08000000 0x08000000 + * 0x10000000 unmapped + * + * FIXME: we don't follow this 100% yet. + */ + pgd_t *src_pgd, *dst_pgd; + + src_pgd = pgd_offset(mm, (unsigned long)IO_BASE); + dst_pgd = pgd_offset(mm, IO_START); + + memcpy(dst_pgd, src_pgd, sizeof(pgd_t) * (IO_SIZE / PGDIR_SIZE)); + + src_pgd = pgd_offset(mm, (unsigned long)EASI_BASE); + dst_pgd = pgd_offset(mm, EASI_START); + + memcpy(dst_pgd, src_pgd, sizeof(pgd_t) * (EASI_SIZE / PGDIR_SIZE)); + + flush_tlb_range(&vma, IO_START, IO_START + IO_SIZE); + flush_tlb_range(&vma, EASI_START, EASI_START + EASI_SIZE); +} + +static int ecard_init_mm(void) +{ + struct mm_struct * mm = mm_alloc(); + struct mm_struct *active_mm = current->active_mm; + + if (!mm) + return -ENOMEM; + + current->mm = mm; + current->active_mm = mm; + activate_mm(active_mm, mm); + mmdrop(active_mm); + ecard_init_pgtables(mm); + return 0; +} + +static int +ecard_task(void * unused) +{ + /* + * Allocate a mm. We're not a lazy-TLB kernel task since we need + * to set page table entries where the user space would be. Note + * that this also creates the page tables. Failure is not an + * option here. + */ + if (ecard_init_mm()) + panic("kecardd: unable to alloc mm\n"); + + while (1) { + struct ecard_request *req; + + wait_event_interruptible(ecard_wait, ecard_req != NULL); + + req = xchg(&ecard_req, NULL); + if (req != NULL) { + req->fn(req); + complete(req->complete); + } + } +} + +/* + * Wake the expansion card daemon to action our request. + * + * FIXME: The test here is not sufficient to detect if the + * kcardd is running. + */ +static void ecard_call(struct ecard_request *req) +{ + DECLARE_COMPLETION_ONSTACK(completion); + + req->complete = &completion; + + mutex_lock(&ecard_mutex); + ecard_req = req; + wake_up(&ecard_wait); + + /* + * Now wait for kecardd to run. + */ + wait_for_completion(&completion); + mutex_unlock(&ecard_mutex); +} + +/* ======================= Mid-level card control ===================== */ + +static void +ecard_readbytes(void *addr, ecard_t *ec, int off, int len, int useld) +{ + struct ecard_request req; + + req.fn = ecard_task_readbytes; + req.ec = ec; + req.address = off; + req.length = len; + req.use_loader = useld; + req.buffer = addr; + + ecard_call(&req); +} + +int ecard_readchunk(struct in_chunk_dir *cd, ecard_t *ec, int id, int num) +{ + struct ex_chunk_dir excd; + int index = 16; + int useld = 0; + + if (!ec->cid.cd) + return 0; + + while(1) { + ecard_readbytes(&excd, ec, index, 8, useld); + index += 8; + if (c_id(&excd) == 0) { + if (!useld && ec->loader) { + useld = 1; + index = 0; + continue; + } + return 0; + } + if (c_id(&excd) == 0xf0) { /* link */ + index = c_start(&excd); + continue; + } + if (c_id(&excd) == 0x80) { /* loader */ + if (!ec->loader) { + ec->loader = kmalloc(c_len(&excd), + GFP_KERNEL); + if (ec->loader) + ecard_readbytes(ec->loader, ec, + (int)c_start(&excd), + c_len(&excd), useld); + else + return 0; + } + continue; + } + if (c_id(&excd) == id && num-- == 0) + break; + } + + if (c_id(&excd) & 0x80) { + switch (c_id(&excd) & 0x70) { + case 0x70: + ecard_readbytes((unsigned char *)excd.d.string, ec, + (int)c_start(&excd), c_len(&excd), + useld); + break; + case 0x00: + break; + } + } + cd->start_offset = c_start(&excd); + memcpy(cd->d.string, excd.d.string, 256); + return 1; +} + +/* ======================= Interrupt control ============================ */ + +static void ecard_def_irq_enable(ecard_t *ec, int irqnr) +{ +} + +static void ecard_def_irq_disable(ecard_t *ec, int irqnr) +{ +} + +static int ecard_def_irq_pending(ecard_t *ec) +{ + return !ec->irqmask || readb(ec->irqaddr) & ec->irqmask; +} + +static void ecard_def_fiq_enable(ecard_t *ec, int fiqnr) +{ + panic("ecard_def_fiq_enable called - impossible"); +} + +static void ecard_def_fiq_disable(ecard_t *ec, int fiqnr) +{ + panic("ecard_def_fiq_disable called - impossible"); +} + +static int ecard_def_fiq_pending(ecard_t *ec) +{ + return !ec->fiqmask || readb(ec->fiqaddr) & ec->fiqmask; +} + +static expansioncard_ops_t ecard_default_ops = { + ecard_def_irq_enable, + ecard_def_irq_disable, + ecard_def_irq_pending, + ecard_def_fiq_enable, + ecard_def_fiq_disable, + ecard_def_fiq_pending +}; + +/* + * Enable and disable interrupts from expansion cards. + * (interrupts are disabled for these functions). + * + * They are not meant to be called directly, but via enable/disable_irq. + */ +static void ecard_irq_unmask(struct irq_data *d) +{ + ecard_t *ec = irq_data_get_irq_chip_data(d); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->claimed && ec->ops->irqenable) + ec->ops->irqenable(ec, d->irq); + else + printk(KERN_ERR "ecard: rejecting request to " + "enable IRQs for %d\n", d->irq); + } +} + +static void ecard_irq_mask(struct irq_data *d) +{ + ecard_t *ec = irq_data_get_irq_chip_data(d); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->ops && ec->ops->irqdisable) + ec->ops->irqdisable(ec, d->irq); + } +} + +static struct irq_chip ecard_chip = { + .name = "ECARD", + .irq_ack = ecard_irq_mask, + .irq_mask = ecard_irq_mask, + .irq_unmask = ecard_irq_unmask, +}; + +void ecard_enablefiq(unsigned int fiqnr) +{ + ecard_t *ec = slot_to_ecard(fiqnr); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->claimed && ec->ops->fiqenable) + ec->ops->fiqenable(ec, fiqnr); + else + printk(KERN_ERR "ecard: rejecting request to " + "enable FIQs for %d\n", fiqnr); + } +} + +void ecard_disablefiq(unsigned int fiqnr) +{ + ecard_t *ec = slot_to_ecard(fiqnr); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->ops->fiqdisable) + ec->ops->fiqdisable(ec, fiqnr); + } +} + +static void ecard_dump_irq_state(void) +{ + ecard_t *ec; + + printk("Expansion card IRQ state:\n"); + + for (ec = cards; ec; ec = ec->next) { + const char *claimed; + + if (ec->slot_no == 8) + continue; + + claimed = ec->claimed ? "" : "not "; + + if (ec->ops && ec->ops->irqpending && + ec->ops != &ecard_default_ops) + printk(" %d: %sclaimed irq %spending\n", + ec->slot_no, claimed, + ec->ops->irqpending(ec) ? "" : "not "); + else + printk(" %d: %sclaimed irqaddr %p, mask = %02X, status = %02X\n", + ec->slot_no, claimed, + ec->irqaddr, ec->irqmask, readb(ec->irqaddr)); + } +} + +static void ecard_check_lockup(struct irq_desc *desc) +{ + static unsigned long last; + static int lockup; + + /* + * If the timer interrupt has not run since the last million + * unrecognised expansion card interrupts, then there is + * something seriously wrong. Disable the expansion card + * interrupts so at least we can continue. + * + * Maybe we ought to start a timer to re-enable them some time + * later? + */ + if (last == jiffies) { + lockup += 1; + if (lockup > 1000000) { + printk(KERN_ERR "\nInterrupt lockup detected - " + "disabling all expansion card interrupts\n"); + + desc->irq_data.chip->irq_mask(&desc->irq_data); + ecard_dump_irq_state(); + } + } else + lockup = 0; + + /* + * If we did not recognise the source of this interrupt, + * warn the user, but don't flood the user with these messages. + */ + if (!last || time_after(jiffies, last + 5*HZ)) { + last = jiffies; + printk(KERN_WARNING "Unrecognised interrupt from backplane\n"); + ecard_dump_irq_state(); + } +} + +static void ecard_irq_handler(struct irq_desc *desc) +{ + ecard_t *ec; + int called = 0; + + desc->irq_data.chip->irq_mask(&desc->irq_data); + for (ec = cards; ec; ec = ec->next) { + int pending; + + if (!ec->claimed || !ec->irq || ec->slot_no == 8) + continue; + + if (ec->ops && ec->ops->irqpending) + pending = ec->ops->irqpending(ec); + else + pending = ecard_default_ops.irqpending(ec); + + if (pending) { + generic_handle_irq(ec->irq); + called ++; + } + } + desc->irq_data.chip->irq_unmask(&desc->irq_data); + + if (called == 0) + ecard_check_lockup(desc); +} + +static void __iomem *__ecard_address(ecard_t *ec, card_type_t type, card_speed_t speed) +{ + void __iomem *address = NULL; + int slot = ec->slot_no; + + if (ec->slot_no == 8) + return ECARD_MEMC8_BASE; + + ectcr &= ~(1 << slot); + + switch (type) { + case ECARD_MEMC: + if (slot < 4) + address = ECARD_MEMC_BASE + (slot << 14); + break; + + case ECARD_IOC: + if (slot < 4) + address = ECARD_IOC_BASE + (slot << 14); + else + address = ECARD_IOC4_BASE + ((slot - 4) << 14); + if (address) + address += speed << 19; + break; + + case ECARD_EASI: + address = ECARD_EASI_BASE + (slot << 24); + if (speed == ECARD_FAST) + ectcr |= 1 << slot; + break; + + default: + break; + } + +#ifdef IOMD_ECTCR + iomd_writeb(ectcr, IOMD_ECTCR); +#endif + return address; +} + +static int ecard_prints(struct seq_file *m, ecard_t *ec) +{ + seq_printf(m, " %d: %s ", ec->slot_no, ec->easi ? "EASI" : " "); + + if (ec->cid.id == 0) { + struct in_chunk_dir incd; + + seq_printf(m, "[%04X:%04X] ", + ec->cid.manufacturer, ec->cid.product); + + if (!ec->card_desc && ec->cid.cd && + ecard_readchunk(&incd, ec, 0xf5, 0)) { + ec->card_desc = kmalloc(strlen(incd.d.string)+1, GFP_KERNEL); + + if (ec->card_desc) + strcpy((char *)ec->card_desc, incd.d.string); + } + + seq_printf(m, "%s\n", ec->card_desc ? ec->card_desc : "*unknown*"); + } else + seq_printf(m, "Simple card %d\n", ec->cid.id); + + return 0; +} + +static int ecard_devices_proc_show(struct seq_file *m, void *v) +{ + ecard_t *ec = cards; + + while (ec) { + ecard_prints(m, ec); + ec = ec->next; + } + return 0; +} + +static struct proc_dir_entry *proc_bus_ecard_dir = NULL; + +static void ecard_proc_init(void) +{ + proc_bus_ecard_dir = proc_mkdir("bus/ecard", NULL); + proc_create_single("devices", 0, proc_bus_ecard_dir, + ecard_devices_proc_show); +} + +#define ec_set_resource(ec,nr,st,sz) \ + do { \ + (ec)->resource[nr].name = dev_name(&ec->dev); \ + (ec)->resource[nr].start = st; \ + (ec)->resource[nr].end = (st) + (sz) - 1; \ + (ec)->resource[nr].flags = IORESOURCE_MEM; \ + } while (0) + +static void __init ecard_free_card(struct expansion_card *ec) +{ + int i; + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) + if (ec->resource[i].flags) + release_resource(&ec->resource[i]); + + kfree(ec); +} + +static struct expansion_card *__init ecard_alloc_card(int type, int slot) +{ + struct expansion_card *ec; + unsigned long base; + int i; + + ec = kzalloc(sizeof(ecard_t), GFP_KERNEL); + if (!ec) { + ec = ERR_PTR(-ENOMEM); + goto nomem; + } + + ec->slot_no = slot; + ec->easi = type == ECARD_EASI; + ec->irq = 0; + ec->fiq = 0; + ec->dma = NO_DMA; + ec->ops = &ecard_default_ops; + + dev_set_name(&ec->dev, "ecard%d", slot); + ec->dev.parent = NULL; + ec->dev.bus = &ecard_bus_type; + ec->dev.dma_mask = &ec->dma_mask; + ec->dma_mask = (u64)0xffffffff; + ec->dev.coherent_dma_mask = ec->dma_mask; + + if (slot < 4) { + ec_set_resource(ec, ECARD_RES_MEMC, + PODSLOT_MEMC_BASE + (slot << 14), + PODSLOT_MEMC_SIZE); + base = PODSLOT_IOC0_BASE + (slot << 14); + } else + base = PODSLOT_IOC4_BASE + ((slot - 4) << 14); + +#ifdef CONFIG_ARCH_RPC + if (slot < 8) { + ec_set_resource(ec, ECARD_RES_EASI, + PODSLOT_EASI_BASE + (slot << 24), + PODSLOT_EASI_SIZE); + } + + if (slot == 8) { + ec_set_resource(ec, ECARD_RES_MEMC, NETSLOT_BASE, NETSLOT_SIZE); + } else +#endif + + for (i = 0; i <= ECARD_RES_IOCSYNC - ECARD_RES_IOCSLOW; i++) + ec_set_resource(ec, i + ECARD_RES_IOCSLOW, + base + (i << 19), PODSLOT_IOC_SIZE); + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) { + if (ec->resource[i].flags && + request_resource(&iomem_resource, &ec->resource[i])) { + dev_err(&ec->dev, "resource(s) not available\n"); + ec->resource[i].end -= ec->resource[i].start; + ec->resource[i].start = 0; + ec->resource[i].flags = 0; + } + } + + nomem: + return ec; +} + +static ssize_t irq_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->irq); +} +static DEVICE_ATTR_RO(irq); + +static ssize_t dma_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->dma); +} +static DEVICE_ATTR_RO(dma); + +static ssize_t resource_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + char *str = buf; + int i; + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) + str += sprintf(str, "%08x %08x %08lx\n", + ec->resource[i].start, + ec->resource[i].end, + ec->resource[i].flags); + + return str - buf; +} +static DEVICE_ATTR_RO(resource); + +static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->cid.manufacturer); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t device_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->cid.product); +} +static DEVICE_ATTR_RO(device); + +static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%s\n", ec->easi ? "EASI" : "IOC"); +} +static DEVICE_ATTR_RO(type); + +static struct attribute *ecard_dev_attrs[] = { + &dev_attr_device.attr, + &dev_attr_dma.attr, + &dev_attr_irq.attr, + &dev_attr_resource.attr, + &dev_attr_type.attr, + &dev_attr_vendor.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ecard_dev); + +int ecard_request_resources(struct expansion_card *ec) +{ + int i, err = 0; + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) { + if (ecard_resource_end(ec, i) && + !request_mem_region(ecard_resource_start(ec, i), + ecard_resource_len(ec, i), + ec->dev.driver->name)) { + err = -EBUSY; + break; + } + } + + if (err) { + while (i--) + if (ecard_resource_end(ec, i)) + release_mem_region(ecard_resource_start(ec, i), + ecard_resource_len(ec, i)); + } + return err; +} +EXPORT_SYMBOL(ecard_request_resources); + +void ecard_release_resources(struct expansion_card *ec) +{ + int i; + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) + if (ecard_resource_end(ec, i)) + release_mem_region(ecard_resource_start(ec, i), + ecard_resource_len(ec, i)); +} +EXPORT_SYMBOL(ecard_release_resources); + +void ecard_setirq(struct expansion_card *ec, const struct expansion_card_ops *ops, void *irq_data) +{ + ec->irq_data = irq_data; + barrier(); + ec->ops = ops; +} +EXPORT_SYMBOL(ecard_setirq); + +void __iomem *ecardm_iomap(struct expansion_card *ec, unsigned int res, + unsigned long offset, unsigned long maxsize) +{ + unsigned long start = ecard_resource_start(ec, res); + unsigned long end = ecard_resource_end(ec, res); + + if (offset > (end - start)) + return NULL; + + start += offset; + if (maxsize && end - start > maxsize) + end = start + maxsize; + + return devm_ioremap(&ec->dev, start, end - start); +} +EXPORT_SYMBOL(ecardm_iomap); + +static void atomwide_3p_quirk(ecard_t *ec) +{ + void __iomem *addr = __ecard_address(ec, ECARD_IOC, ECARD_SYNC); + unsigned int i; + + /* Disable interrupts on each port */ + for (i = 0x2000; i <= 0x2800; i += 0x0400) + writeb(0, addr + i + 4); +} + +/* + * Probe for an expansion card. + * + * If bit 1 of the first byte of the card is set, then the + * card does not exist. + */ +static int __init ecard_probe(int slot, unsigned irq, card_type_t type) +{ + ecard_t **ecp; + ecard_t *ec; + struct ex_ecid cid; + void __iomem *addr; + int i, rc; + + ec = ecard_alloc_card(type, slot); + if (IS_ERR(ec)) { + rc = PTR_ERR(ec); + goto nomem; + } + + rc = -ENODEV; + if ((addr = __ecard_address(ec, type, ECARD_SYNC)) == NULL) + goto nodev; + + cid.r_zero = 1; + ecard_readbytes(&cid, ec, 0, 16, 0); + if (cid.r_zero) + goto nodev; + + ec->cid.id = cid.r_id; + ec->cid.cd = cid.r_cd; + ec->cid.is = cid.r_is; + ec->cid.w = cid.r_w; + ec->cid.manufacturer = ecard_getu16(cid.r_manu); + ec->cid.product = ecard_getu16(cid.r_prod); + ec->cid.country = cid.r_country; + ec->cid.irqmask = cid.r_irqmask; + ec->cid.irqoff = ecard_gets24(cid.r_irqoff); + ec->cid.fiqmask = cid.r_fiqmask; + ec->cid.fiqoff = ecard_gets24(cid.r_fiqoff); + ec->fiqaddr = + ec->irqaddr = addr; + + if (ec->cid.is) { + ec->irqmask = ec->cid.irqmask; + ec->irqaddr += ec->cid.irqoff; + ec->fiqmask = ec->cid.fiqmask; + ec->fiqaddr += ec->cid.fiqoff; + } else { + ec->irqmask = 1; + ec->fiqmask = 4; + } + + for (i = 0; i < ARRAY_SIZE(quirklist); i++) + if (quirklist[i].manufacturer == ec->cid.manufacturer && + quirklist[i].product == ec->cid.product) { + if (quirklist[i].type) + ec->card_desc = quirklist[i].type; + if (quirklist[i].init) + quirklist[i].init(ec); + break; + } + + ec->irq = irq; + + /* + * hook the interrupt handlers + */ + if (slot < 8) { + irq_set_chip_and_handler(ec->irq, &ecard_chip, + handle_level_irq); + irq_set_chip_data(ec->irq, ec); + irq_clear_status_flags(ec->irq, IRQ_NOREQUEST); + } + +#ifdef CONFIG_ARCH_RPC + /* On RiscPC, only first two slots have DMA capability */ + if (slot < 2) + ec->dma = 2 + slot; +#endif + + for (ecp = &cards; *ecp; ecp = &(*ecp)->next); + + *ecp = ec; + slot_to_expcard[slot] = ec; + + rc = device_register(&ec->dev); + if (rc) + goto nodev; + + return 0; + + nodev: + ecard_free_card(ec); + nomem: + return rc; +} + +/* + * Initialise the expansion card system. + * Locate all hardware - interrupt management and + * actual cards. + */ +static int __init ecard_init(void) +{ + struct task_struct *task; + int slot, irqbase; + + irqbase = irq_alloc_descs(-1, 0, 8, -1); + if (irqbase < 0) + return irqbase; + + task = kthread_run(ecard_task, NULL, "kecardd"); + if (IS_ERR(task)) { + printk(KERN_ERR "Ecard: unable to create kernel thread: %ld\n", + PTR_ERR(task)); + irq_free_descs(irqbase, 8); + return PTR_ERR(task); + } + + printk("Probing expansion cards\n"); + + for (slot = 0; slot < 8; slot ++) { + if (ecard_probe(slot, irqbase + slot, ECARD_EASI) == -ENODEV) + ecard_probe(slot, irqbase + slot, ECARD_IOC); + } + + ecard_probe(8, 11, ECARD_IOC); + + irq_set_chained_handler(IRQ_EXPANSIONCARD, ecard_irq_handler); + + ecard_proc_init(); + + return 0; +} + +subsys_initcall(ecard_init); + +/* + * ECARD "bus" + */ +static const struct ecard_id * +ecard_match_device(const struct ecard_id *ids, struct expansion_card *ec) +{ + int i; + + for (i = 0; ids[i].manufacturer != 65535; i++) + if (ec->cid.manufacturer == ids[i].manufacturer && + ec->cid.product == ids[i].product) + return ids + i; + + return NULL; +} + +static int ecard_drv_probe(struct device *dev) +{ + struct expansion_card *ec = ECARD_DEV(dev); + struct ecard_driver *drv = ECARD_DRV(dev->driver); + const struct ecard_id *id; + int ret; + + id = ecard_match_device(drv->id_table, ec); + + ec->claimed = 1; + ret = drv->probe(ec, id); + if (ret) + ec->claimed = 0; + return ret; +} + +static int ecard_drv_remove(struct device *dev) +{ + struct expansion_card *ec = ECARD_DEV(dev); + struct ecard_driver *drv = ECARD_DRV(dev->driver); + + drv->remove(ec); + ec->claimed = 0; + + /* + * Restore the default operations. We ensure that the + * ops are set before we change the data. + */ + ec->ops = &ecard_default_ops; + barrier(); + ec->irq_data = NULL; + + return 0; +} + +/* + * Before rebooting, we must make sure that the expansion card is in a + * sensible state, so it can be re-detected. This means that the first + * page of the ROM must be visible. We call the expansion cards reset + * handler, if any. + */ +static void ecard_drv_shutdown(struct device *dev) +{ + struct expansion_card *ec = ECARD_DEV(dev); + struct ecard_driver *drv = ECARD_DRV(dev->driver); + struct ecard_request req; + + if (dev->driver) { + if (drv->shutdown) + drv->shutdown(ec); + ec->claimed = 0; + } + + /* + * If this card has a loader, call the reset handler. + */ + if (ec->loader) { + req.fn = ecard_task_reset; + req.ec = ec; + ecard_call(&req); + } +} + +int ecard_register_driver(struct ecard_driver *drv) +{ + drv->drv.bus = &ecard_bus_type; + + return driver_register(&drv->drv); +} + +void ecard_remove_driver(struct ecard_driver *drv) +{ + driver_unregister(&drv->drv); +} + +static int ecard_match(struct device *_dev, struct device_driver *_drv) +{ + struct expansion_card *ec = ECARD_DEV(_dev); + struct ecard_driver *drv = ECARD_DRV(_drv); + int ret; + + if (drv->id_table) { + ret = ecard_match_device(drv->id_table, ec) != NULL; + } else { + ret = ec->cid.id == drv->id; + } + + return ret; +} + +struct bus_type ecard_bus_type = { + .name = "ecard", + .dev_groups = ecard_dev_groups, + .match = ecard_match, + .probe = ecard_drv_probe, + .remove = ecard_drv_remove, + .shutdown = ecard_drv_shutdown, +}; + +static int ecard_bus_init(void) +{ + return bus_register(&ecard_bus_type); +} + +postcore_initcall(ecard_bus_init); + +EXPORT_SYMBOL(ecard_readchunk); +EXPORT_SYMBOL(ecard_register_driver); +EXPORT_SYMBOL(ecard_remove_driver); +EXPORT_SYMBOL(ecard_bus_type); diff --git a/arch/arm/mach-rpc/ecard.h b/arch/arm/mach-rpc/ecard.h new file mode 100644 index 000000000..873dd3d9f --- /dev/null +++ b/arch/arm/mach-rpc/ecard.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ecard.h + * + * Copyright 2007 Russell King + */ + +/* Definitions internal to ecard.c - for it's use only!! + * + * External expansion card header as read from the card + */ +struct ex_ecid { + unsigned char r_irq:1; + unsigned char r_zero:1; + unsigned char r_fiq:1; + unsigned char r_id:4; + unsigned char r_a:1; + + unsigned char r_cd:1; + unsigned char r_is:1; + unsigned char r_w:2; + unsigned char r_r1:4; + + unsigned char r_r2:8; + + unsigned char r_prod[2]; + + unsigned char r_manu[2]; + + unsigned char r_country; + + unsigned char r_fiqmask; + unsigned char r_fiqoff[3]; + + unsigned char r_irqmask; + unsigned char r_irqoff[3]; +}; + +/* + * Chunk directory entry as read from the card + */ +struct ex_chunk_dir { + unsigned char r_id; + unsigned char r_len[3]; + unsigned long r_start; + union { + char string[256]; + char data[1]; + } d; +#define c_id(x) ((x)->r_id) +#define c_len(x) ((x)->r_len[0]|((x)->r_len[1]<<8)|((x)->r_len[2]<<16)) +#define c_start(x) ((x)->r_start) +}; + +typedef enum ecard_type { /* Cards address space */ + ECARD_IOC, + ECARD_MEMC, + ECARD_EASI +} card_type_t; + +typedef enum { /* Speed for ECARD_IOC space */ + ECARD_SLOW = 0, + ECARD_MEDIUM = 1, + ECARD_FAST = 2, + ECARD_SYNC = 3 +} card_speed_t; diff --git a/arch/arm/mach-rpc/fiq.S b/arch/arm/mach-rpc/fiq.S new file mode 100644 index 000000000..0de83e9b0 --- /dev/null +++ b/arch/arm/mach-rpc/fiq.S @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <mach/hardware.h> +#include <mach/entry-macro.S> + + .text + + .global rpc_default_fiq_end +ENTRY(rpc_default_fiq_start) + mov r12, #ioc_base_high + .if ioc_base_low + orr r12, r12, #ioc_base_low + .endif + strb r12, [r12, #0x38] @ Disable FIQ register + subs pc, lr, #4 +rpc_default_fiq_end: diff --git a/arch/arm/mach-rpc/floppydma.S b/arch/arm/mach-rpc/floppydma.S new file mode 100644 index 000000000..6698b8305 --- /dev/null +++ b/arch/arm/mach-rpc/floppydma.S @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * linux/arch/arm/lib/floppydma.S + * + * Copyright (C) 1995, 1996 Russell King + */ +#include <linux/linkage.h> +#include <asm/assembler.h> + .text + + .global floppy_fiqin_end +ENTRY(floppy_fiqin_start) + subs r9, r9, #1 + ldrbgt r12, [r11, #-4] + ldrble r12, [r11], #0 + strb r12, [r10], #1 + subs pc, lr, #4 +floppy_fiqin_end: + + .global floppy_fiqout_end +ENTRY(floppy_fiqout_start) + subs r9, r9, #1 + ldrbge r12, [r10], #1 + movlt r12, #0 + strble r12, [r11], #0 + subsle pc, lr, #4 + strb r12, [r11, #-4] + subs pc, lr, #4 +floppy_fiqout_end: diff --git a/arch/arm/mach-rpc/include/mach/acornfb.h b/arch/arm/mach-rpc/include/mach/acornfb.h new file mode 100644 index 000000000..2bf18ab3d --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/acornfb.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/acornfb.h + * + * Copyright (C) 1999 Russell King + * + * AcornFB architecture specific code + */ + +#define acornfb_bandwidth(var) ((var)->pixclock * 8 / (var)->bits_per_pixel) + +static inline int +acornfb_valid_pixrate(struct fb_var_screeninfo *var) +{ + u_long limit; + + if (!var->pixclock) + return 0; + + /* + * Limits below are taken from RISC OS bandwidthlimit file + */ + if (current_par.using_vram) { + if (current_par.vram_half_sam == 2048) + limit = 6578; + else + limit = 13157; + } else { + limit = 26315; + } + + return acornfb_bandwidth(var) >= limit; +} + +/* + * Try to find the best PLL parameters for the pixel clock. + * This algorithm seems to give best predictable results, + * and produces the same values as detailed in the VIDC20 + * data sheet. + */ +static inline u_int +acornfb_vidc20_find_pll(u_int pixclk) +{ + u_int r, best_r = 2, best_v = 2; + int best_d = 0x7fffffff; + + for (r = 2; r <= 32; r++) { + u_int rr, v, p; + int d; + + rr = 41667 * r; + + v = (rr + pixclk / 2) / pixclk; + + if (v > 32 || v < 2) + continue; + + p = (rr + v / 2) / v; + + d = pixclk - p; + + if (d < 0) + d = -d; + + if (d < best_d) { + best_d = d; + best_v = v - 1; + best_r = r - 1; + } + + if (d == 0) + break; + } + + return best_v << 8 | best_r; +} + +static inline void +acornfb_vidc20_find_rates(struct vidc_timing *vidc, + struct fb_var_screeninfo *var) +{ + u_int div; + + /* Select pixel-clock divisor to keep PLL in range */ + div = var->pixclock / 9090; /*9921*/ + + /* Limit divisor */ + if (div == 0) + div = 1; + if (div > 8) + div = 8; + + /* Encode divisor to VIDC20 setting */ + switch (div) { + case 1: vidc->control |= VIDC20_CTRL_PIX_CK; break; + case 2: vidc->control |= VIDC20_CTRL_PIX_CK2; break; + case 3: vidc->control |= VIDC20_CTRL_PIX_CK3; break; + case 4: vidc->control |= VIDC20_CTRL_PIX_CK4; break; + case 5: vidc->control |= VIDC20_CTRL_PIX_CK5; break; + case 6: vidc->control |= VIDC20_CTRL_PIX_CK6; break; + case 7: vidc->control |= VIDC20_CTRL_PIX_CK7; break; + case 8: vidc->control |= VIDC20_CTRL_PIX_CK8; break; + } + + /* + * With VRAM, the FIFO can be set to the highest possible setting + * because there are no latency considerations for other memory + * accesses. However, in 64 bit bus mode the FIFO preload value + * must not be set to VIDC20_CTRL_FIFO_28 because this will let + * the FIFO overflow. See VIDC20 manual page 33 (6.0 Setting the + * FIFO preload value). + */ + if (current_par.using_vram) { + if (current_par.vram_half_sam == 2048) + vidc->control |= VIDC20_CTRL_FIFO_24; + else + vidc->control |= VIDC20_CTRL_FIFO_28; + } else { + unsigned long bandwidth = acornfb_bandwidth(var); + + /* Encode bandwidth as VIDC20 setting */ + if (bandwidth > 33334) /* < 30.0MB/s */ + vidc->control |= VIDC20_CTRL_FIFO_16; + else if (bandwidth > 26666) /* < 37.5MB/s */ + vidc->control |= VIDC20_CTRL_FIFO_20; + else if (bandwidth > 22222) /* < 45.0MB/s */ + vidc->control |= VIDC20_CTRL_FIFO_24; + else /* > 45.0MB/s */ + vidc->control |= VIDC20_CTRL_FIFO_28; + } + + /* Find the PLL values */ + vidc->pll_ctl = acornfb_vidc20_find_pll(var->pixclock / div); +} + +#define acornfb_default_control() (VIDC20_CTRL_PIX_VCLK) +#define acornfb_default_econtrol() (VIDC20_ECTL_DAC | VIDC20_ECTL_REG(3)) diff --git a/arch/arm/mach-rpc/include/mach/entry-macro.S b/arch/arm/mach-rpc/include/mach/entry-macro.S new file mode 100644 index 000000000..a6d1a9f4b --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/entry-macro.S @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <mach/hardware.h> +#include <asm/hardware/entry-macro-iomd.S> + + .equ ioc_base_high, IOC_BASE & 0xff000000 + .equ ioc_base_low, IOC_BASE & 0x00ff0000 + + .macro get_irqnr_preamble, base, tmp + mov \base, #ioc_base_high @ point at IOC + .if ioc_base_low + orr \base, \base, #ioc_base_low + .endif + .endm diff --git a/arch/arm/mach-rpc/include/mach/hardware.h b/arch/arm/mach-rpc/include/mach/hardware.h new file mode 100644 index 000000000..6f197706f --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/hardware.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/hardware.h + * + * Copyright (C) 1996-1999 Russell King. + * + * This file contains the hardware definitions of the RiscPC series machines. + */ +#ifndef __ASM_ARCH_HARDWARE_H +#define __ASM_ARCH_HARDWARE_H + +#include <mach/memory.h> + +/* + * What hardware must be present + */ +#define HAS_IOMD +#define HAS_VIDC20 + +/* Hardware addresses of major areas. + * *_START is the physical address + * *_SIZE is the size of the region + * *_BASE is the virtual address + */ +#define RPC_RAM_SIZE 0x10000000 +#define RPC_RAM_START 0x10000000 + +#define EASI_SIZE 0x08000000 /* EASI I/O */ +#define EASI_START 0x08000000 +#define EASI_BASE IOMEM(0xe5000000) + +#define IO_START 0x03000000 /* I/O */ +#define IO_SIZE 0x01000000 +#define IO_BASE IOMEM(0xe0000000) + +#define SCREEN_START 0x02000000 /* VRAM */ +#define SCREEN_END 0xdfc00000 +#define SCREEN_BASE 0xdf800000 + +#define UNCACHEABLE_ADDR (FLUSH_BASE + 0x10000) + +/* + * IO Addresses + */ +#define ECARD_EASI_BASE (EASI_BASE) +#define VIDC_BASE (IO_BASE + 0x00400000) +#define EXPMASK_BASE (IO_BASE + 0x00360000) +#define ECARD_IOC4_BASE (IO_BASE + 0x00270000) +#define ECARD_IOC_BASE (IO_BASE + 0x00240000) +#define IOMD_BASE (IO_BASE + 0x00200000) +#define IOC_BASE (IO_BASE + 0x00200000) +#define ECARD_MEMC8_BASE (IO_BASE + 0x0002b000) +#define FLOPPYDMA_BASE (IO_BASE + 0x0002a000) +#define PCIO_BASE (IO_BASE + 0x00010000) +#define ECARD_MEMC_BASE (IO_BASE + 0x00000000) + +#define vidc_writel(val) __raw_writel(val, VIDC_BASE) + +#define NETSLOT_BASE 0x0302b000 +#define NETSLOT_SIZE 0x00001000 + +#define PODSLOT_IOC0_BASE 0x03240000 +#define PODSLOT_IOC4_BASE 0x03270000 +#define PODSLOT_IOC_SIZE (1 << 14) +#define PODSLOT_MEMC_BASE 0x03000000 +#define PODSLOT_MEMC_SIZE (1 << 14) +#define PODSLOT_EASI_BASE 0x08000000 +#define PODSLOT_EASI_SIZE (1 << 24) + +#define EXPMASK_STATUS (EXPMASK_BASE + 0x00) +#define EXPMASK_ENABLE (EXPMASK_BASE + 0x04) + +#endif diff --git a/arch/arm/mach-rpc/include/mach/io.h b/arch/arm/mach-rpc/include/mach/io.h new file mode 100644 index 000000000..8a8f28406 --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/io.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/io.h + * + * Copyright (C) 1997 Russell King + * + * Modifications: + * 06-Dec-1997 RMK Created. + */ +#ifndef __ASM_ARM_ARCH_IO_H +#define __ASM_ARM_ARCH_IO_H + +#include <mach/hardware.h> + +#define IO_SPACE_LIMIT 0xffff + +/* + * We need PC style IO addressing for: + * - floppy (at 0x3f2,0x3f4,0x3f5,0x3f7) + * - parport (at 0x278-0x27a, 0x27b-0x27f, 0x778-0x77a) + * - 8250 serial (only for compile) + * + * These peripherals are found in an area of MMIO which looks very much + * like an ISA bus, but with registers at the low byte of each word. + */ +#define __io(a) (PCIO_BASE + ((a) << 2)) + +#endif diff --git a/arch/arm/mach-rpc/include/mach/irqs.h b/arch/arm/mach-rpc/include/mach/irqs.h new file mode 100644 index 000000000..0c3428fd9 --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/irqs.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/irqs.h + * + * Copyright (C) 1996 Russell King + */ + +#define IRQ_PRINTER 0 +#define IRQ_BATLOW 1 +#define IRQ_FLOPPYINDEX 2 +#define IRQ_VSYNCPULSE 3 +#define IRQ_POWERON 4 +#define IRQ_TIMER0 5 +#define IRQ_TIMER1 6 +#define IRQ_IMMEDIATE 7 +#define IRQ_EXPCARDFIQ 8 +#define IRQ_HARDDISK 9 +#define IRQ_SERIALPORT 10 +#define IRQ_FLOPPYDISK 12 +#define IRQ_EXPANSIONCARD 13 +#define IRQ_KEYBOARDTX 14 +#define IRQ_KEYBOARDRX 15 + +#define IRQ_DMA0 16 +#define IRQ_DMA1 17 +#define IRQ_DMA2 18 +#define IRQ_DMA3 19 +#define IRQ_DMAS0 20 +#define IRQ_DMAS1 21 + +#define FIQ_FLOPPYDATA 0 +#define FIQ_ECONET 2 +#define FIQ_SERIALPORT 4 +#define FIQ_EXPANSIONCARD 6 +#define FIQ_FORCE 7 + +/* + * This is the offset of the FIQ "IRQ" numbers + */ +#define FIQ_START 64 + +#define NR_IRQS 128 diff --git a/arch/arm/mach-rpc/include/mach/isa-dma.h b/arch/arm/mach-rpc/include/mach/isa-dma.h new file mode 100644 index 000000000..d9c3af1ef --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/isa-dma.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/isa-dma.h + * + * Copyright (C) 1997 Russell King + */ +#ifndef __ASM_ARCH_DMA_H +#define __ASM_ARCH_DMA_H + +#define MAX_DMA_CHANNELS 8 + +#define DMA_0 0 +#define DMA_1 1 +#define DMA_2 2 +#define DMA_3 3 +#define DMA_S0 4 +#define DMA_S1 5 +#define DMA_VIRTUAL_FLOPPY 6 +#define DMA_VIRTUAL_SOUND 7 + +#define DMA_FLOPPY DMA_VIRTUAL_FLOPPY + +#define IOMD_DMA_BOUNDARY (PAGE_SIZE - 1) + +#endif /* _ASM_ARCH_DMA_H */ + diff --git a/arch/arm/mach-rpc/include/mach/memory.h b/arch/arm/mach-rpc/include/mach/memory.h new file mode 100644 index 000000000..a586eb31b --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/memory.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/memory.h + * + * Copyright (C) 1996,1997,1998 Russell King. + * + * Changelog: + * 20-Oct-1996 RMK Created + * 31-Dec-1997 RMK Fixed definitions to reduce warnings + * 11-Jan-1998 RMK Uninlined to reduce hits on cache + * 08-Feb-1998 RMK Added __virt_to_bus and __bus_to_virt + * 21-Mar-1999 RMK Renamed to memory.h + * RMK Added TASK_SIZE and PAGE_OFFSET + */ +#ifndef __ASM_ARCH_MEMORY_H +#define __ASM_ARCH_MEMORY_H + +/* + * Cache flushing area - ROM + */ +#define FLUSH_BASE_PHYS 0x00000000 +#define FLUSH_BASE 0xdf000000 + +/* + * Sparsemem support. Each section is a maximum of 64MB. The sections + * are offset by 128MB and can cover 128MB, so that gives us a maximum + * of 29 physmem bits. + */ +#define MAX_PHYSMEM_BITS 29 +#define SECTION_SIZE_BITS 26 + +#endif diff --git a/arch/arm/mach-rpc/include/mach/uncompress.h b/arch/arm/mach-rpc/include/mach/uncompress.h new file mode 100644 index 000000000..1fbe7eb95 --- /dev/null +++ b/arch/arm/mach-rpc/include/mach/uncompress.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * arch/arm/mach-rpc/include/mach/uncompress.h + * + * Copyright (C) 1996 Russell King + */ +#define VIDMEM ((char *)SCREEN_START) + +#include <linux/io.h> +#include <mach/hardware.h> +#include <asm/setup.h> +#include <asm/page.h> + +int video_size_row; +unsigned char bytes_per_char_h; +extern unsigned long con_charconvtable[256]; + +struct param_struct { + unsigned long page_size; + unsigned long nr_pages; + unsigned long ramdisk_size; + unsigned long mountrootrdonly; + unsigned long rootdev; + unsigned long video_num_cols; + unsigned long video_num_rows; + unsigned long video_x; + unsigned long video_y; + unsigned long memc_control_reg; + unsigned char sounddefault; + unsigned char adfsdrives; + unsigned char bytes_per_char_h; + unsigned char bytes_per_char_v; + unsigned long unused[256/4-11]; +}; + +static const unsigned long palette_4[16] = { + 0x00000000, + 0x000000cc, + 0x0000cc00, /* Green */ + 0x0000cccc, /* Yellow */ + 0x00cc0000, /* Blue */ + 0x00cc00cc, /* Magenta */ + 0x00cccc00, /* Cyan */ + 0x00cccccc, /* White */ + 0x00000000, + 0x000000ff, + 0x0000ff00, + 0x0000ffff, + 0x00ff0000, + 0x00ff00ff, + 0x00ffff00, + 0x00ffffff +}; + +#define palette_setpixel(p) *(unsigned long *)(IO_START+0x00400000) = 0x10000000|((p) & 255) +#define palette_write(v) *(unsigned long *)(IO_START+0x00400000) = 0x00000000|((v) & 0x00ffffff) + +/* + * params_phys is a linker defined symbol - see + * arch/arm/boot/compressed/Makefile + */ +extern __attribute__((pure)) struct param_struct *params(void); +#define params (params()) + +#ifndef STANDALONE_DEBUG +unsigned long video_num_cols; +unsigned long video_num_rows; +unsigned long video_x; +unsigned long video_y; +unsigned char bytes_per_char_v; +int white; + +/* + * This does not append a newline + */ +static inline void putc(int c) +{ + extern void ll_write_char(char *, char c, char white); + int x,y; + char *ptr; + + x = video_x; + y = video_y; + + if (c == '\n') { + if (++y >= video_num_rows) + y--; + } else if (c == '\r') { + x = 0; + } else { + ptr = VIDMEM + ((y*video_num_cols*bytes_per_char_v+x)*bytes_per_char_h); + ll_write_char(ptr, c, white); + if (++x >= video_num_cols) { + x = 0; + if ( ++y >= video_num_rows ) { + y--; + } + } + } + + video_x = x; + video_y = y; +} + +static inline void flush(void) +{ +} + +/* + * Setup for decompression + */ +static void arch_decomp_setup(void) +{ + int i; + struct tag *t = (struct tag *)params; + unsigned int nr_pages = 0, page_size = PAGE_SIZE; + + if (t->hdr.tag == ATAG_CORE) { + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_VIDEOTEXT) { + video_num_rows = t->u.videotext.video_lines; + video_num_cols = t->u.videotext.video_cols; + video_x = t->u.videotext.x; + video_y = t->u.videotext.y; + } else if (t->hdr.tag == ATAG_VIDEOLFB) { + bytes_per_char_h = t->u.videolfb.lfb_depth; + bytes_per_char_v = 8; + } else if (t->hdr.tag == ATAG_MEM) { + page_size = PAGE_SIZE; + nr_pages += (t->u.mem.size / PAGE_SIZE); + } + } + } else { + nr_pages = params->nr_pages; + page_size = params->page_size; + video_num_rows = params->video_num_rows; + video_num_cols = params->video_num_cols; + video_x = params->video_x; + video_y = params->video_y; + bytes_per_char_h = params->bytes_per_char_h; + bytes_per_char_v = params->bytes_per_char_v; + } + + video_size_row = video_num_cols * bytes_per_char_h; + + if (bytes_per_char_h == 4) + for (i = 0; i < 256; i++) + con_charconvtable[i] = + (i & 128 ? 1 << 0 : 0) | + (i & 64 ? 1 << 4 : 0) | + (i & 32 ? 1 << 8 : 0) | + (i & 16 ? 1 << 12 : 0) | + (i & 8 ? 1 << 16 : 0) | + (i & 4 ? 1 << 20 : 0) | + (i & 2 ? 1 << 24 : 0) | + (i & 1 ? 1 << 28 : 0); + else + for (i = 0; i < 16; i++) + con_charconvtable[i] = + (i & 8 ? 1 << 0 : 0) | + (i & 4 ? 1 << 8 : 0) | + (i & 2 ? 1 << 16 : 0) | + (i & 1 ? 1 << 24 : 0); + + + palette_setpixel(0); + if (bytes_per_char_h == 1) { + palette_write (0); + palette_write (0x00ffffff); + for (i = 2; i < 256; i++) + palette_write (0); + white = 1; + } else { + for (i = 0; i < 256; i++) + palette_write (i < 16 ? palette_4[i] : 0); + white = 7; + } + + if (nr_pages * page_size < 4096*1024) error("<4M of mem\n"); +} +#endif diff --git a/arch/arm/mach-rpc/io-acorn.S b/arch/arm/mach-rpc/io-acorn.S new file mode 100644 index 000000000..b9082a2a2 --- /dev/null +++ b/arch/arm/mach-rpc/io-acorn.S @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * linux/arch/arm/lib/io-acorn.S + * + * Copyright (C) 1995, 1996 Russell King + * + * 27/03/03 Ian Molton Clean up CONFIG_CPU + */ +#include <linux/linkage.h> +#include <linux/kern_levels.h> +#include <asm/assembler.h> + + .text + .align + +.Liosl_warning: + .ascii KERN_WARNING "insl/outsl not implemented, called from %08lX\0" + .align + +/* + * These make no sense on Acorn machines. + * Print a warning message. + */ +ENTRY(insl) +ENTRY(outsl) + adr r0, .Liosl_warning + mov r1, lr + b printk diff --git a/arch/arm/mach-rpc/irq.c b/arch/arm/mach-rpc/irq.c new file mode 100644 index 000000000..803aeb126 --- /dev/null +++ b/arch/arm/mach-rpc/irq.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/init.h> +#include <linux/list.h> +#include <linux/io.h> + +#include <asm/mach/irq.h> +#include <asm/hardware/iomd.h> +#include <asm/irq.h> +#include <asm/fiq.h> + +// These are offsets from the stat register for each IRQ bank +#define STAT 0x00 +#define REQ 0x04 +#define CLR 0x04 +#define MASK 0x08 + +static void __iomem *iomd_get_base(struct irq_data *d) +{ + void *cd = irq_data_get_irq_chip_data(d); + + return (void __iomem *)(unsigned long)cd; +} + +static void iomd_set_base_mask(unsigned int irq, void __iomem *base, u32 mask) +{ + struct irq_data *d = irq_get_irq_data(irq); + + d->mask = mask; + irq_set_chip_data(irq, (void *)(unsigned long)base); +} + +static void iomd_irq_mask_ack(struct irq_data *d) +{ + void __iomem *base = iomd_get_base(d); + unsigned int val, mask = d->mask; + + val = readb(base + MASK); + writeb(val & ~mask, base + MASK); + writeb(mask, base + CLR); +} + +static void iomd_irq_mask(struct irq_data *d) +{ + void __iomem *base = iomd_get_base(d); + unsigned int val, mask = d->mask; + + val = readb(base + MASK); + writeb(val & ~mask, base + MASK); +} + +static void iomd_irq_unmask(struct irq_data *d) +{ + void __iomem *base = iomd_get_base(d); + unsigned int val, mask = d->mask; + + val = readb(base + MASK); + writeb(val | mask, base + MASK); +} + +static struct irq_chip iomd_chip_clr = { + .irq_mask_ack = iomd_irq_mask_ack, + .irq_mask = iomd_irq_mask, + .irq_unmask = iomd_irq_unmask, +}; + +static struct irq_chip iomd_chip_noclr = { + .irq_mask = iomd_irq_mask, + .irq_unmask = iomd_irq_unmask, +}; + +extern unsigned char rpc_default_fiq_start, rpc_default_fiq_end; + +void __init rpc_init_irq(void) +{ + unsigned int irq, clr, set; + + iomd_writeb(0, IOMD_IRQMASKA); + iomd_writeb(0, IOMD_IRQMASKB); + iomd_writeb(0, IOMD_FIQMASK); + iomd_writeb(0, IOMD_DMAMASK); + + set_fiq_handler(&rpc_default_fiq_start, + &rpc_default_fiq_end - &rpc_default_fiq_start); + + for (irq = 0; irq < NR_IRQS; irq++) { + clr = IRQ_NOREQUEST; + set = 0; + + if (irq <= 6 || (irq >= 9 && irq <= 15)) + clr |= IRQ_NOPROBE; + + if (irq == 21 || (irq >= 16 && irq <= 19) || + irq == IRQ_KEYBOARDTX) + set |= IRQ_NOAUTOEN; + + switch (irq) { + case 0 ... 7: + irq_set_chip_and_handler(irq, &iomd_chip_clr, + handle_level_irq); + irq_modify_status(irq, clr, set); + iomd_set_base_mask(irq, IOMD_BASE + IOMD_IRQSTATA, + BIT(irq)); + break; + + case 8 ... 15: + irq_set_chip_and_handler(irq, &iomd_chip_noclr, + handle_level_irq); + irq_modify_status(irq, clr, set); + iomd_set_base_mask(irq, IOMD_BASE + IOMD_IRQSTATB, + BIT(irq - 8)); + break; + + case 16 ... 21: + irq_set_chip_and_handler(irq, &iomd_chip_noclr, + handle_level_irq); + irq_modify_status(irq, clr, set); + iomd_set_base_mask(irq, IOMD_BASE + IOMD_DMASTAT, + BIT(irq - 16)); + break; + + case 64 ... 71: + irq_set_chip(irq, &iomd_chip_noclr); + irq_modify_status(irq, clr, set); + iomd_set_base_mask(irq, IOMD_BASE + IOMD_FIQSTAT, + BIT(irq - 64)); + break; + } + } + + init_FIQ(FIQ_START); +} diff --git a/arch/arm/mach-rpc/riscpc.c b/arch/arm/mach-rpc/riscpc.c new file mode 100644 index 000000000..d23970bd6 --- /dev/null +++ b/arch/arm/mach-rpc/riscpc.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/arch/arm/mach-rpc/riscpc.c + * + * Copyright (C) 1998-2001 Russell King + * + * Architecture specific fixups. + */ +#include <linux/kernel.h> +#include <linux/tty.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/serial_8250.h> +#include <linux/ata_platform.h> +#include <linux/io.h> +#include <linux/i2c.h> +#include <linux/reboot.h> + +#include <asm/elf.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <asm/hardware/iomd.h> +#include <asm/page.h> +#include <asm/domain.h> +#include <asm/setup.h> +#include <asm/system_misc.h> + +#include <asm/mach/map.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> + +extern void rpc_init_irq(void); + +unsigned int vram_size; +unsigned int memc_ctrl_reg; +unsigned int number_mfm_drives; + +static int __init parse_tag_acorn(const struct tag *tag) +{ + memc_ctrl_reg = tag->u.acorn.memc_control_reg; + number_mfm_drives = tag->u.acorn.adfsdrives; + + switch (tag->u.acorn.vram_pages) { + case 512: + vram_size += PAGE_SIZE * 256; + fallthrough; /* ??? */ + case 256: + vram_size += PAGE_SIZE * 256; + default: + break; + } +#if 0 + if (vram_size) { + desc->video_start = 0x02000000; + desc->video_end = 0x02000000 + vram_size; + } +#endif + return 0; +} + +__tagtable(ATAG_ACORN, parse_tag_acorn); + +static struct map_desc rpc_io_desc[] __initdata = { + { /* VRAM */ + .virtual = SCREEN_BASE, + .pfn = __phys_to_pfn(SCREEN_START), + .length = 2*1048576, + .type = MT_DEVICE + }, { /* IO space */ + .virtual = (u32)IO_BASE, + .pfn = __phys_to_pfn(IO_START), + .length = IO_SIZE , + .type = MT_DEVICE + }, { /* EASI space */ + .virtual = (unsigned long)EASI_BASE, + .pfn = __phys_to_pfn(EASI_START), + .length = EASI_SIZE, + .type = MT_DEVICE + } +}; + +static void __init rpc_map_io(void) +{ + iotable_init(rpc_io_desc, ARRAY_SIZE(rpc_io_desc)); + + /* + * Turn off floppy. + */ + writeb(0xc, PCIO_BASE + (0x3f2 << 2)); + + /* + * RiscPC can't handle half-word loads and stores + */ + elf_hwcap &= ~HWCAP_HALF; +} + +static struct resource acornfb_resources[] = { + /* VIDC */ + DEFINE_RES_MEM(0x03400000, 0x00200000), + DEFINE_RES_IRQ(IRQ_VSYNCPULSE), +}; + +static struct platform_device acornfb_device = { + .name = "acornfb", + .id = -1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, + .num_resources = ARRAY_SIZE(acornfb_resources), + .resource = acornfb_resources, +}; + +static struct resource iomd_resources[] = { + DEFINE_RES_MEM(0x03200000, 0x10000), +}; + +static struct platform_device iomd_device = { + .name = "iomd", + .id = -1, + .num_resources = ARRAY_SIZE(iomd_resources), + .resource = iomd_resources, +}; + +static struct resource iomd_kart_resources[] = { + DEFINE_RES_IRQ(IRQ_KEYBOARDRX), + DEFINE_RES_IRQ(IRQ_KEYBOARDTX), +}; + +static struct platform_device kbd_device = { + .name = "kart", + .id = -1, + .dev = { + .parent = &iomd_device.dev, + }, + .num_resources = ARRAY_SIZE(iomd_kart_resources), + .resource = iomd_kart_resources, +}; + +static struct plat_serial8250_port serial_platform_data[] = { + { + .mapbase = 0x03010fe0, + .irq = IRQ_SERIALPORT, + .uartclk = 1843200, + .regshift = 2, + .iotype = UPIO_MEM, + .flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP | UPF_SKIP_TEST, + }, + { }, +}; + +static struct platform_device serial_device = { + .name = "serial8250", + .id = PLAT8250_DEV_PLATFORM, + .dev = { + .platform_data = serial_platform_data, + }, +}; + +static struct pata_platform_info pata_platform_data = { + .ioport_shift = 2, +}; + +static struct resource pata_resources[] = { + DEFINE_RES_MEM(0x030107c0, 0x20), + DEFINE_RES_MEM(0x03010fd8, 0x04), + DEFINE_RES_IRQ(IRQ_HARDDISK), +}; + +static struct platform_device pata_device = { + .name = "pata_platform", + .id = -1, + .num_resources = ARRAY_SIZE(pata_resources), + .resource = pata_resources, + .dev = { + .platform_data = &pata_platform_data, + .coherent_dma_mask = ~0, /* grumble */ + }, +}; + +static struct platform_device *devs[] __initdata = { + &iomd_device, + &kbd_device, + &serial_device, + &acornfb_device, + &pata_device, +}; + +static struct i2c_board_info i2c_rtc = { + I2C_BOARD_INFO("pcf8583", 0x50) +}; + +static int __init rpc_init(void) +{ + i2c_register_board_info(0, &i2c_rtc, 1); + return platform_add_devices(devs, ARRAY_SIZE(devs)); +} + +arch_initcall(rpc_init); + +static void rpc_restart(enum reboot_mode mode, const char *cmd) +{ + iomd_writeb(0, IOMD_ROMCR0); + + /* + * Jump into the ROM + */ + soft_restart(0); +} + +void ioc_timer_init(void); + +MACHINE_START(RISCPC, "Acorn-RiscPC") + /* Maintainer: Russell King */ + .atag_offset = 0x100, + .reserve_lp0 = 1, + .reserve_lp1 = 1, + .map_io = rpc_map_io, + .init_irq = rpc_init_irq, + .init_time = ioc_timer_init, + .restart = rpc_restart, +MACHINE_END diff --git a/arch/arm/mach-rpc/time.c b/arch/arm/mach-rpc/time.c new file mode 100644 index 000000000..da85cac76 --- /dev/null +++ b/arch/arm/mach-rpc/time.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/arch/arm/common/time-acorn.c + * + * Copyright (c) 1996-2000 Russell King. + * + * Changelog: + * 24-Sep-1996 RMK Created + * 10-Oct-1996 RMK Brought up to date with arch-sa110eval + * 04-Dec-1997 RMK Updated for new arch/arm/time.c + * 13=Jun-2004 DS Moved to arch/arm/common b/c shared w/CLPS7500 + */ +#include <linux/clocksource.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <asm/hardware/ioc.h> + +#include <asm/mach/time.h> + +#define RPC_CLOCK_FREQ 2000000 +#define RPC_LATCH DIV_ROUND_CLOSEST(RPC_CLOCK_FREQ, HZ) + +static u32 ioc_time; + +static u64 ioc_timer_read(struct clocksource *cs) +{ + unsigned int count1, count2, status; + unsigned long flags; + u32 ticks; + + local_irq_save(flags); + ioc_writeb (0, IOC_T0LATCH); + barrier (); + count1 = ioc_readb(IOC_T0CNTL) | (ioc_readb(IOC_T0CNTH) << 8); + barrier (); + status = ioc_readb(IOC_IRQREQA); + barrier (); + ioc_writeb (0, IOC_T0LATCH); + barrier (); + count2 = ioc_readb(IOC_T0CNTL) | (ioc_readb(IOC_T0CNTH) << 8); + ticks = ioc_time + RPC_LATCH - count2; + local_irq_restore(flags); + + if (count2 < count1) { + /* + * The timer has not reloaded between reading count1 and + * count2, check whether an interrupt was actually pending. + */ + if (status & (1 << 5)) + ticks += RPC_LATCH; + } else if (count2 > count1) { + /* + * The timer has reloaded, so count2 indicates the new + * count since the wrap. The interrupt would not have + * been processed, so add the missed ticks. + */ + ticks += RPC_LATCH; + } + + return ticks; +} + +static struct clocksource ioctime_clocksource = { + .read = ioc_timer_read, + .mask = CLOCKSOURCE_MASK(32), + .rating = 100, +}; + +void __init ioctime_init(void) +{ + ioc_writeb(RPC_LATCH & 255, IOC_T0LTCHL); + ioc_writeb(RPC_LATCH >> 8, IOC_T0LTCHH); + ioc_writeb(0, IOC_T0GO); +} + +static irqreturn_t +ioc_timer_interrupt(int irq, void *dev_id) +{ + ioc_time += RPC_LATCH; + timer_tick(); + return IRQ_HANDLED; +} + +/* + * Set up timer interrupt. + */ +void __init ioc_timer_init(void) +{ + WARN_ON(clocksource_register_hz(&ioctime_clocksource, RPC_CLOCK_FREQ)); + ioctime_init(); + if (request_irq(IRQ_TIMER0, ioc_timer_interrupt, 0, "timer", NULL)) + pr_err("Failed to request irq %d (timer)\n", IRQ_TIMER0); +} |