OSDN Git Service

m68k: Add X68000 support
authorYoshinori Sato <ysato@users.sourceforge.jp>
Sat, 30 Jan 2016 07:55:47 +0000 (16:55 +0900)
committerYoshinori Sato <yo-satoh@sios.com>
Thu, 23 Jan 2020 03:38:44 +0000 (12:38 +0900)
Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
15 files changed:
arch/m68k/Kconfig.machine
arch/m68k/Makefile
arch/m68k/include/asm/irq.h
arch/m68k/include/asm/machdep.h
arch/m68k/kernel/Makefile
arch/m68k/kernel/setup_no.c
arch/m68k/kernel/signal.c
arch/m68k/kernel/vmlinux-nommu.lds
arch/m68k/x68k/Makefile [new file with mode: 0644]
arch/m68k/x68k/entry.S [new file with mode: 0644]
arch/m68k/x68k/head.S [new file with mode: 0644]
arch/m68k/x68k/ints.c [new file with mode: 0644]
arch/m68k/x68k/mfp-timer.c [new file with mode: 0644]
arch/m68k/x68k/x68k.c [new file with mode: 0644]
arch/m68k/x68k/zs_earlyprintk.c [new file with mode: 0644]

index c01e103..c954a63 100644 (file)
@@ -135,6 +135,14 @@ config SUN3
 
          If you don't want to compile a kernel exclusively for a Sun 3, say N.
 
+config X68000
+       bool "SHARP X68000 support"
+       depends on !MMU
+       select M68000
+       select GENERIC_CLOCKEVENTS
+       help
+         This option enbales support for SHARP X68000 series (exclude X68030).
+
 endif # M68KCLASSIC
 
 config PILOT
index 5d92883..12a9ab4 100644 (file)
@@ -96,6 +96,7 @@ head-y                                := arch/m68k/kernel/head.o
 head-$(CONFIG_SUN3)            := arch/m68k/kernel/sun3-head.o
 head-$(CONFIG_M68000)          := arch/m68k/68000/head.o
 head-$(CONFIG_COLDFIRE)                := arch/m68k/coldfire/head.o
+head-$(CONFIG_X68000)          := arch/m68k/x68k/head.o
 
 core-y                         += arch/m68k/kernel/    arch/m68k/mm/
 libs-y                         += arch/m68k/lib/
@@ -115,9 +116,11 @@ core-$(CONFIG_NATFEAT)             += arch/m68k/emu/
 core-$(CONFIG_M68040)          += arch/m68k/fpsp040/
 core-$(CONFIG_M68060)          += arch/m68k/ifpsp060/
 core-$(CONFIG_M68KFPU_EMU)     += arch/m68k/math-emu/
+ifndef CONFIG_X68000
 core-$(CONFIG_M68000)          += arch/m68k/68000/
+endif
 core-$(CONFIG_COLDFIRE)                += arch/m68k/coldfire/
-
+core-$(CONFIG_X68000)          += arch/m68k/x68k/
 
 all:   zImage
 
@@ -162,3 +165,6 @@ archheaders:
 
 install:
        sh $(srctree)/arch/m68k/install.sh $(KERNELRELEASE) vmlinux.gz System.map "$(INSTALL_PATH)"
+
+vmlinux.bin vmlinux.bin.gz: vmlinux
+       $(Q)$(MAKE) $(build)=arch/m68k/x68k arch/m68k/x68k/$@
index 91dd493..a724deb 100644 (file)
@@ -10,7 +10,7 @@
  * With EtherNAT add-on card on Atari, the highest interrupt
  * number is 140 so NR_IRQS needs to be 141.
  */
-#if defined(CONFIG_COLDFIRE)
+#if defined(CONFIG_COLDFIRE) || defined(CONFIG_X68000)
 #define NR_IRQS 256
 #elif defined(CONFIG_VME) || defined(CONFIG_SUN3) || defined(CONFIG_SUN3X)
 #define NR_IRQS 200
index 49bd326..97e4e22 100644 (file)
@@ -37,5 +37,5 @@ extern void hw_timer_init(irq_handler_t handler);
 extern unsigned long hw_timer_offset(void);
 
 extern void config_BSP(char *command, int len);
-
+extern void zs_console_register(void);
 #endif /* _M68K_MACHDEP_H */
index dbac7f8..8fa04e1 100644 (file)
@@ -29,3 +29,4 @@ obj-$(CONFIG_UBOOT)           += uboot.o
 
 obj-$(CONFIG_EARLY_PRINTK)     += early_printk.o
 
+CFLAGS_signal.o = -O0
index 3c5def1..1bab22c 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/initrd.h>
 #include <linux/root_dev.h>
 #include <linux/rtc.h>
+#include <linux/platform_device.h>
 
 #include <asm/setup.h>
 #include <asm/bootinfo.h>
@@ -149,7 +150,10 @@ void __init setup_arch(char **cmdline_p)
 #if defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_DUMMY_CONSOLE)
        conswitchp = &dummy_con;
 #endif
-
+       parse_early_param();
+#if defined(CONFIG_X68000)
+       zs_console_register();
+#endif
        /*
         * Give all the memory to the bootmap allocator, tell it to put the
         * boot mem_map at the start of memory.
@@ -166,6 +170,7 @@ void __init setup_arch(char **cmdline_p)
        /*
         * Get kmalloc into gear.
         */
+       early_platform_driver_probe("earlyprintk",1,0);
        paging_init();
 }
 
index 05610e6..43b60a1 100644 (file)
@@ -679,7 +679,7 @@ static int mangle_kernel_stack(struct pt_regs *regs, int formatvec,
                         /* copy to the gap we'd made */
                         "2: movel %4@+,%/a0@+\n\t"
                         "   dbra %1,2b\n\t"
-                        "   bral ret_from_signal\n"
+                        "   jmp ret_from_signal\n"
 #endif
                         : /* no outputs, it doesn't ever return */
                         : "a" (sw), "d" (fsize), "d" (frame_offset/4-1),
index cf6edda..817e708 100644 (file)
@@ -33,6 +33,7 @@ SECTIONS {
        .romvec : {
                __rom_start = .;
                _romvec = .;
+               vectors = .;
                *(.romvec)
                *(.data..initvect)
        }
diff --git a/arch/m68k/x68k/Makefile b/arch/m68k/x68k/Makefile
new file mode 100644 (file)
index 0000000..530c4d5
--- /dev/null
@@ -0,0 +1,20 @@
+##################################################
+#
+# Makefile for X68000
+#
+# Copyright 2016 Yoshinori Sato
+
+extra-y                        := head.o
+obj-y                  += entry.o ints.o mfp-timer.o x68k.o zs_earlyprintk.o
+
+targets := vmlinux.bin vmlinux.bin.gz
+
+OBJCOPYFLAGS_vmlinux.bin  := -Obinary
+
+$(obj)/vmlinux.bin:  vmlinux FORCE
+       $(call if_changed,objcopy)
+
+$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE
+       $(call if_changed,gzip)
+
+CLEAN_FILES += arch/$(ARCH)/x68k/vmlinux.bin arch/$(ARCH)/x68k/vmlinux.bin.gz
diff --git a/arch/m68k/x68k/entry.S b/arch/m68k/x68k/entry.S
new file mode 100644 (file)
index 0000000..da5f208
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ *  entry.S -- non-mmu 68000 interrupt and exception entry points
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file README.legal in the main directory of this archive
+ * for more details.
+ *
+ * Linux/m68k support by Hamish Macdonald
+ */
+
+#include <linux/linkage.h>
+#include <asm/thread_info.h>
+#include <asm/unistd.h>
+#include <asm/errno.h>
+#include <asm/setup.h>
+#include <asm/segment.h>
+#include <asm/traps.h>
+#include <asm/asm-offsets.h>
+#include <asm/entry.h>
+
+.text
+
+.globl system_call
+.globl resume
+.globl ret_from_exception
+.globl ret_from_signal
+.globl sys_call_table
+.globl bad_interrupt
+.globl inthandler
+.globl inttable        
+
+badsys:
+       movel   #-ENOSYS,%sp@(PT_OFF_D0)
+       jra     ret_from_exception
+
+do_trace:
+       movel   #-ENOSYS,%sp@(PT_OFF_D0) /* needed for strace*/
+       subql   #4,%sp
+       SAVE_SWITCH_STACK
+       jbsr    syscall_trace_enter
+       RESTORE_SWITCH_STACK
+       addql   #4,%sp
+       movel   %sp@(PT_OFF_ORIG_D0),%d1
+       movel   #-ENOSYS,%d0
+       cmpl    #NR_syscalls,%d1
+       jcc     1f
+       lsl     #2,%d1
+       lea     sys_call_table, %a0
+       jbsr    %a0@(%d1)
+
+1:     movel   %d0,%sp@(PT_OFF_D0)     /* save the return value */
+       subql   #4,%sp                  /* dummy return address */
+       SAVE_SWITCH_STACK
+       jbsr    syscall_trace_leave
+
+ret_from_signal:
+       RESTORE_SWITCH_STACK
+       addql   #4,%sp
+       jra     ret_from_exception
+
+ENTRY(system_call)
+       subq    #2,%sp
+       movel   %sp@(2),%sp@
+       movew   %sp@(6),%sp@(4)
+       movel   %sp@(8),%sp@(6)
+       movew   #4<<12,%sp@(8)
+       SAVE_ALL_SYS
+       /* save top of frame*/
+       pea     %sp@
+       jbsr    set_esp0
+       addql   #4,%sp
+       movel   %sp@(PT_OFF_ORIG_D0),%d0
+
+       movel   %sp,%d1                 /* get thread_info pointer */
+       andl    #-THREAD_SIZE,%d1
+       movel   %d1,%a2
+       btst    #(TIF_SYSCALL_TRACE%8),%a2@(TINFO_FLAGS+(31-TIF_SYSCALL_TRACE)/8)
+       jne     do_trace
+       cmpl    #NR_syscalls,%d0
+       jcc     badsys
+       lsl     #2,%d0
+       lea     sys_call_table,%a0
+       movel   %a0@(%d0), %a0
+       jbsr    %a0@
+       movel   %d0,%sp@(PT_OFF_D0)     /* save the return value*/
+
+ret_from_exception:
+       btst    #5,%sp@(PT_OFF_SR)      /* check if returning to kernel*/
+       jeq     Luser_return            /* if so, skip resched, signals*/
+
+Lkernel_return:
+       movel   %sp@(PT_OFF_PC), %d0
+       btst    #0,%d0
+       bne     1f
+       moveml  %sp@+,%a0-%a2/%d1-%d5
+       movel   %sp@+,%d0
+       addql   #4,%sp                  /* orig d0 */
+       addql   #4,%sp
+       movel   %sp@(2),%sp@(4)
+       movew   %sp@,%sp@(2)
+       addq    #2,%sp
+       rte
+1:     stop    #0x2700
+Luser_return:
+       /* only allow interrupts when we are really the last one on the*/
+       /* kernel stack, otherwise stack overflow can occur during*/
+       /* heavy interrupt load*/
+       andw    #ALLOWINT,%sr
+
+       movel   %sp,%d1                 /* get thread_info pointer */
+       andl    #-THREAD_SIZE,%d1
+       movel   %d1,%a2
+1:
+       movel   %a2@(TINFO_FLAGS),%d1   /* thread_info->flags */
+       beq     Lkernel_return
+
+Lwork_to_do:
+       movel   %a2@(TINFO_FLAGS),%d1   /* thread_info->flags */
+       btst    #TIF_NEED_RESCHED,%d1
+       jne     reschedule
+
+Lsignal_return:
+       subql   #4,%sp                  /* dummy return address*/
+       SAVE_SWITCH_STACK
+       pea     %sp@(SWITCH_STACK_SIZE)
+       bsrw    do_notify_resume
+       addql   #4,%sp
+       RESTORE_SWITCH_STACK
+       addql   #4,%sp
+       jra     1b
+
+/*
+ * This is the main interrupt handler, responsible for calling process_int()
+ */
+
+inttable:
+       .rept   256-64
+       bsrw    inthandler
+       .endr
+
+inthandler:
+       subq    #2,%sp
+       movel   %sp@(2),%sp@
+       movew   %sp@(6),%sp@(4)
+       movel   %sp@(8),%sp@(6)
+       pea     -1
+       movel   %d0,%sp@-
+       movel   %sp@(8),%d0
+       subl    #inttable+4,%d0
+       moveml  %d1-%d5/%a0-%a2,%sp@-
+       movel   %d0,%d1
+       orw     #4<<12,%d1
+       movew   %d1, %sp@(PT_OFF_FORMATVEC)
+       lsr     #2,%d0
+       add     #64, %d0
+       and     #0x3ff, %d0
+
+       movel   %sp,%sp@-
+       movel   %d0,%sp@-               /*  put vector # on stack*/
+       jbsr    do_IRQ                  /*  process the IRQ*/
+3:             addql   #8,%sp                  /*  pop parameters off stack*/
+       bra     ret_from_exception
+
+/*
+ * Handler for uninitialized and spurious interrupts.
+ */
+ENTRY(bad_inthandler)
+       addql   #1,irq_err_count
+       rte
+
+/*
+ * Beware - when entering resume, prev (the current task) is
+ * in a0, next (the new task) is in a1, so don't change these
+ * registers until their contents are no longer needed.
+ */
+ENTRY(resume)
+       movel   %a0,%d1                         /* save prev thread in d1 */
+       movew   %sr,%a0@(TASK_THREAD+THREAD_SR) /* save sr */
+       SAVE_SWITCH_STACK
+       movel   %sp,%a0@(TASK_THREAD+THREAD_KSP) /* save kernel stack */
+       movel   %usp,%a3                        /* save usp */
+       movel   %a3,%a0@(TASK_THREAD+THREAD_USP)
+
+       movel   %a1@(TASK_THREAD+THREAD_USP),%a3 /* restore user stack */
+       movel   %a3,%usp
+       movel   %a1@(TASK_THREAD+THREAD_KSP),%sp /* restore new thread stack */
+       RESTORE_SWITCH_STACK
+       movew   %a1@(TASK_THREAD+THREAD_SR),%sr /* restore thread status reg */
+       rts
+
diff --git a/arch/m68k/x68k/head.S b/arch/m68k/x68k/head.S
new file mode 100644 (file)
index 0000000..3771cf3
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * head.S - Common startup code for 68000 core based CPU's
+ *
+ * 2012.10.21, Luis Alves <ljalvs@gmail.com>, Single head.S file for all
+ *             68000 core based CPU's. Based on the sources from:
+ *             Coldfire by Greg Ungerer <gerg@snapgear.com>
+ *             68328 by D. Jeff Dionne <jeff@ryeham.ee.ryerson.ca>,
+ *                      Kenneth Albanowski <kjahds@kjahds.com>,
+ *                      The Silver Hammer Group, Ltd.
+ *
+ */
+
+#include <linux/linkage.h>
+#include <linux/init.h>
+#include <asm/asm-offsets.h>
+#include <asm/thread_info.h>
+
+
+.global _start
+.global _rambase
+.global _ramvec
+.global _ramstart
+.global _ramend
+
+.data
+
+/*****************************************************************************
+ * RAM setup pointers. Used by the kernel to determine RAM location and size.
+ *****************************************************************************/
+
+_rambase:
+       .long   0
+_ramvec:
+       .long   0
+_ramstart:
+       .long   0
+_ramend:
+       .long   0
+
+__HEAD
+
+/*****************************************************************************
+ * Entry point, where all begins!
+ *****************************************************************************/
+
+_start:
+       movew   #0x2700, %sr                    /* disable all interrupts */
+
+       moveal  #_etext, %a0
+       moveal  #_sdata, %a1
+       moveal  #__bss_start, %a2
+/*****************************************************************************
+ * Setup basic memory information for kernel
+ *****************************************************************************/
+       movel   #CONFIG_RAMBASE,_rambase        /* set the base of RAM */
+       movel   #0x400000, _ramend              /* set end ram addr */
+       lea     __bss_stop,%a1
+       movel   %a1,_ramstart
+
+       lea     __bss_start, %a0                /* get start of bss */
+       lea     __bss_stop, %a1                 /* get end of bss */
+_clear_bss:
+       movel   #0, (%a0)+                      /* clear each word */
+       cmpl    %a0, %a1                        /* check if at end */
+       bne     _clear_bss
+
+/*****************************************************************************
+ * Load the current task pointer and stack.
+ *****************************************************************************/
+       lea     init_thread_union,%a0
+       lea     THREAD_SIZE(%a0),%sp
+       jsr     start_kernel                    /* start Linux kernel */
+_exit:
+       jmp     _exit                           /* should never get here */
diff --git a/arch/m68k/x68k/ints.c b/arch/m68k/x68k/ints.c
new file mode 100644 (file)
index 0000000..56ca087
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * int_sr.c
+ *
+ * Copyright 2016 Yoshinori Sato
+ *
+ * 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.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <asm/traps.h>
+#include <asm/io.h>
+#include <asm/machdep.h>
+
+/* assembler routines */
+asmlinkage void system_call(void);
+asmlinkage void buserr(void);
+asmlinkage void trap(void);
+asmlinkage void inthandler(void);
+
+extern e_vector *inttable;
+
+/* Bitmap of IRQ masked */
+#define IMASK_PRIORITY 7
+
+static DECLARE_BITMAP(imask_mask, IMASK_PRIORITY);
+static int interrupt_priority;
+
+const static char priority[] = {
+       6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+       5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+       1,1,1,1,3,3,3,3,3,3,3,3,0,0,0,0,
+       0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+};
+
+static inline void set_interrupt_registers(int ip)
+{
+       unsigned short sr;
+
+       asm volatile("movew     %%sr, %0\n\t"
+                    "andw      #0x0700, %0\n\t"
+                    "cmpw      #0x0700, %0\n\t"
+                    "beq       1f\n\t"
+                    "movew     %%sr,%0\n\t"
+                    "andw      #~0x0700, %0\n\t"
+                    "orw       %1, %0\n\t"
+                    "movew     %0, %%sr\n"
+                    "1:"
+                    : "=&r" (sr)
+                    : "g" (ip));
+}
+
+static void sr_mask(struct irq_data *data)
+{
+       unsigned int irq = data->irq;
+
+       clear_bit(priority[irq - 0x40], imask_mask);
+       if (interrupt_priority < priority[irq - 0x40])
+               interrupt_priority = priority[irq - 0x40];
+       set_interrupt_registers(interrupt_priority << 8);
+}
+
+static void sr_unmask(struct irq_data *data)
+{
+       unsigned int irq = data->irq;
+
+       set_bit(priority[irq - 0x40], imask_mask);
+       interrupt_priority = find_first_zero_bit(imask_mask, IMASK_PRIORITY);
+       set_interrupt_registers(interrupt_priority << 8);
+}
+
+static struct irq_chip intc_irq_chip = {
+       .name           = "M68K-SR-INT",
+       .irq_mask       = sr_mask,
+       .irq_unmask     = sr_unmask,
+};
+
+/*
+ * This function should be called during kernel startup to initialize
+ * the machine vector table.
+ */
+void __init trap_init(void)
+{
+       int i;
+       unsigned long v = (unsigned long)&inttable;
+
+       for (i = 0; i < VEC_USER; i++)
+               vectors[i] = trap;
+       for (i = VEC_SPUR; i <= VEC_INT7; i++)
+               vectors[i] = bad_inthandler;
+
+       for (i = VEC_USER; i < 256; i++)
+               vectors[i] = v + (i - VEC_USER)*4;
+       vectors[VEC_BUSERR] = buserr;
+       vectors[VEC_SYS] = system_call;
+
+}
+
+void __init init_IRQ(void)
+{
+       int i;
+
+       for (i = 0; i < NR_IRQS; i++) {
+               irq_set_chip(i, &intc_irq_chip);
+               irq_set_handler(i, handle_level_irq);
+       }
+}
+
diff --git a/arch/m68k/x68k/mfp-timer.c b/arch/m68k/x68k/mfp-timer.c
new file mode 100644 (file)
index 0000000..afc46e4
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * X68000 MFP Timer
+ * Copyright 2016 Yoshinori Sato
+ */
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/clockchips.h>
+
+#define INPUT_FREQ (4000000 / 200)
+#define MFP_IRQ 0x44
+#define TDDR (void *)0xe88025
+#define TCDCR (void *)0xe8801d
+#define IERB (void *)0xe88009
+#define IMRB (void *)0xe88015
+
+static irqreturn_t interrupt(int irq, void *dev_id)
+{
+       struct clock_event_device *ced = (struct clock_event_device *)dev_id;
+
+       ced->event_handler(ced);
+       return IRQ_HANDLED;
+}
+
+static int periodic(struct clock_event_device *ced)
+{
+       __raw_writeb((INPUT_FREQ + HZ/2) / HZ, TDDR);
+       __raw_writeb(0x07, TCDCR);
+       __raw_writeb(__raw_readb(IERB) | 0x10, IERB);
+       __raw_writeb(__raw_readb(IMRB) | 0x10, IMRB);
+       return 0;
+}
+
+static struct clock_event_device mfp_ced = {
+       .name = "timer",
+       .features = CLOCK_EVT_FEAT_PERIODIC,
+       .rating = 200,
+       .set_state_periodic = periodic,
+};
+
+void __init hw_timer_init(irq_handler_t handler)
+{
+       if (request_irq(MFP_IRQ, interrupt, IRQF_TIMER, "timer", &mfp_ced) < 0) {
+               pr_err("%s: Failed to request_irq\n", __func__);
+               return;
+       }
+       clockevents_config_and_register(&mfp_ced, INPUT_FREQ, 2, 0xff);
+}
diff --git a/arch/m68k/x68k/x68k.c b/arch/m68k/x68k/x68k.c
new file mode 100644 (file)
index 0000000..a6caf42
--- /dev/null
@@ -0,0 +1,55 @@
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <asm/machdep.h>
+
+/***************************************************************************/
+
+void scc_puts(char *);
+
+void __init config_BSP(char *command, int len)
+{
+       mach_sched_init = hw_timer_init;
+       mach_hwclk = NULL;
+       mach_reset = NULL;
+       memcpy(command, (void *)0x7e00, len);
+}
+
+static struct resource scc_a_rsrcs[] = {
+       {
+               .flags = IORESOURCE_MEM,
+               .start = 0xe98005,
+               .end = 0xe98007,
+       },
+       
+       {
+               .flags = IORESOURCE_IRQ,
+               .start = 0x50,
+       },
+};
+
+struct platform_device scc_a_pdev = {
+       .name           = "scc",
+       .id             = 0,
+       .num_resources  = ARRAY_SIZE(scc_a_rsrcs),
+       .resource       = scc_a_rsrcs,
+};
+EXPORT_SYMBOL(scc_a_pdev);
+
+struct platform_device scc_b_pdev = {
+       .name           = "scc",
+       .id             = 1,
+       .num_resources  = 0,
+};
+EXPORT_SYMBOL(scc_b_pdev);
+
+static int __init x68k_platform_init(void)
+{
+       __raw_writeb(0, (void *)0xe86001);
+       platform_device_register(&scc_a_pdev);
+
+       return 0;
+}
+
+arch_initcall(x68k_platform_init);
diff --git a/arch/m68k/x68k/zs_earlyprintk.c b/arch/m68k/x68k/zs_earlyprintk.c
new file mode 100644 (file)
index 0000000..73614eb
--- /dev/null
@@ -0,0 +1,77 @@
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+#define CMD ((void *)0xe98005)
+#define DATA ((void *)0xe98007)
+
+static void zs_write(struct console *co, const char *ptr,
+                                unsigned len)
+{
+       for(;len>0;len--) {
+               while(! (__raw_readb(CMD) & 4));
+               if (*ptr == '\n') {
+                       __raw_writeb('\r', DATA);
+                       while(! (__raw_readb(CMD) & 4));
+               }
+               __raw_writeb(*ptr++, DATA);
+       }
+}
+
+static struct console zs_console = {
+       .name           = "zs_console",
+       .write          = zs_write,
+       .setup          = NULL,
+       .flags          = CON_PRINTBUFFER,
+       .index          = -1,
+};
+
+static char zs_console_buf[32];
+
+static int zs_probe(struct platform_device *pdev)
+{
+       if (zs_console.data)
+               return -EEXIST;
+
+       if (!strstr(zs_console_buf, "keep"))
+               zs_console.flags |= CON_BOOT;
+
+       __raw_writeb(9, CMD);
+       __raw_writeb(0, CMD);
+       register_console(&zs_console);
+       return 0;
+}
+
+static int zs_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static struct platform_driver zs_driver = {
+       .probe          = zs_probe,
+       .remove         = zs_remove,
+       .driver         = {
+               .name   = "X68000-ZS",
+               .owner  = THIS_MODULE,
+       },
+};
+
+early_platform_init_buffer("earlyprintk", &zs_driver,
+                          zs_console_buf, ARRAY_SIZE(zs_console_buf));
+
+static struct platform_device zs_console_device = {
+       .name           = "X68000-ZS",
+       .id             = 0,
+};
+
+static struct platform_device *devices[] __initdata = {
+       &zs_console_device,
+};
+
+void __init zs_console_register(void)
+{
+       early_platform_add_devices(devices,
+                                  ARRAY_SIZE(devices));
+}