Merge tag 'v3.19.8' of http://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable into linux-3.19-stable

This is the 3.19.8 stable release
diff --git a/Makefile b/Makefile
index 110782b..085adaa 100644
--- a/Makefile
+++ b/Makefile
@@ -297,8 +297,8 @@
 
 HOSTCC       = gcc
 HOSTCXX      = g++
-HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
-HOSTCXXFLAGS = -O2
+HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O3 -fomit-frame-pointer -std=gnu89
+HOSTCXXFLAGS = -O3
 
 ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1)
 HOSTCFLAGS  += -Wno-unused-value -Wno-unused-parameter \
@@ -616,7 +616,7 @@
 ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
 KBUILD_CFLAGS	+= -Os $(call cc-disable-warning,maybe-uninitialized,)
 else
-KBUILD_CFLAGS	+= -O2
+KBUILD_CFLAGS	+= -O3
 endif
 
 # Tell gcc to never replace conditional load with a non-conditional one
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 843713c..e01e4f6 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -332,7 +332,7 @@
 
 config MACH_LOONGSON
 	bool "Loongson family of machines"
-	select SYS_SUPPORTS_ZBOOT
+	select SYS_SUPPORTS_ZBOOT_UART16550
 	help
 	  This enables the support of Loongson family of machines.
 
@@ -1600,6 +1600,15 @@
 	bool
 	select CPU_SUPPORTS_32BIT_KERNEL
 	select CPU_SUPPORTS_64BIT_KERNEL
+	select CPU_SUPPORTS_HIGHMEM if ! EMBEDDED
+	select ARCH_WANT_OPTIONAL_GPIOLIB
+
+config CPU_LOONGSON1
+	bool
+	select CPU_MIPS32
+	select CPU_MIPSR2
+	select CPU_HAS_PREFETCH
+	select CPU_SUPPORTS_32BIT_KERNEL
 	select CPU_SUPPORTS_HIGHMEM
 	select CPU_SUPPORTS_HUGEPAGES
 
@@ -2248,7 +2257,7 @@
 
 config ARCH_FLATMEM_ENABLE
 	def_bool y
-	depends on !NUMA && !CPU_LOONGSON2
+	depends on !NUMA && !(CPU_LOONGSON2 && HIBERNATION)
 
 config ARCH_DISCONTIGMEM_ENABLE
 	bool
diff --git a/arch/mips/boot/compressed/Makefile b/arch/mips/boot/compressed/Makefile
index 61af6b6..8598044 100644
--- a/arch/mips/boot/compressed/Makefile
+++ b/arch/mips/boot/compressed/Makefile
@@ -30,9 +30,10 @@
 targets := head.o decompress.o string.o dbg.o uart-16550.o uart-alchemy.o
 
 # decompressor objects (linked with vmlinuz)
-vmlinuzobjs-y := $(obj)/head.o $(obj)/decompress.o $(obj)/string.o $(obj)/dbg.o
+vmlinuzobjs-y := $(obj)/head.o $(obj)/decompress.o $(obj)/string.o
 
 ifdef CONFIG_DEBUG_ZBOOT
+vmlinuzobjs-y += $(obj)/dbg.o
 vmlinuzobjs-$(CONFIG_SYS_SUPPORTS_ZBOOT_UART16550) += $(obj)/uart-16550.o
 vmlinuzobjs-$(CONFIG_MIPS_ALCHEMY)		   += $(obj)/uart-alchemy.o
 endif
@@ -79,9 +80,18 @@
       cmd_zld = $(LD) $(LDFLAGS) -Ttext $(VMLINUZ_LOAD_ADDRESS) -T $< $(vmlinuzobjs-y) -o $@
 quiet_cmd_strip = STRIP	  $@
       cmd_strip = $(STRIP) -s $@
+ifdef CONFIG_EMBEDDED
+quiet_cmd_sstrip = SSTRIP  $@
+      cmd_sstrip = $(srctree)/scripts/sstrip.sh $@
+endif
 vmlinuz: $(src)/ld.script $(vmlinuzobjs-y) $(obj)/calc_vmlinuz_load_addr
 	$(call cmd,zld)
 	$(call cmd,strip)
+	$(call cmd,sstrip)
+
+vmlinuz.unsstrip: $(src)/ld.script $(vmlinuzobjs-y) $(obj)/calc_vmlinuz_load_addr
+	$(call cmd,zld)
+	$(call cmd,strip)
 
 #
 # Some DECstations need all possible sections of an ECOFF executable
@@ -94,14 +104,14 @@
 hostprogs-y += ../elf2ecoff
 
 ifdef CONFIG_32BIT
-	VMLINUZ = vmlinuz
+	VMLINUZ = vmlinuz.unsstrip
 else
 	VMLINUZ = vmlinuz.32
 endif
 
 quiet_cmd_32 = OBJCOPY $@
       cmd_32 = $(OBJCOPY) -O $(32bit-bfd) $(OBJCOPYFLAGS) $< $@
-vmlinuz.32: vmlinuz
+vmlinuz.32: vmlinuz.unsstrip
 	$(call cmd,32)
 
 quiet_cmd_ecoff = ECOFF	  $@
@@ -110,11 +120,11 @@
 	$(call cmd,ecoff)
 
 OBJCOPYFLAGS_vmlinuz.bin := $(OBJCOPYFLAGS) -O binary
-vmlinuz.bin: vmlinuz
+vmlinuz.bin: vmlinuz.unsstrip
 	$(call cmd,objcopy)
 
 OBJCOPYFLAGS_vmlinuz.srec := $(OBJCOPYFLAGS) -S -O srec
-vmlinuz.srec: vmlinuz
+vmlinuz.srec: vmlinuz.unsstrip
 	$(call cmd,objcopy)
 
-clean-files := $(objtree)/vmlinuz $(objtree)/vmlinuz.{32,ecoff,bin,srec}
+clean-files := $(objtree)/vmlinuz $(objtree)/vmlinuz.{32,ecoff,bin,srec,unsstrip}
diff --git a/arch/mips/boot/compressed/decompress.c b/arch/mips/boot/compressed/decompress.c
index 31903cf..14da73c 100644
--- a/arch/mips/boot/compressed/decompress.c
+++ b/arch/mips/boot/compressed/decompress.c
@@ -28,8 +28,13 @@
 extern unsigned char __image_begin, __image_end;
 
 /* debug interfaces  */
+#ifdef CONFIG_DEBUG_ZBOOT
 extern void puts(const char *s);
 extern void puthex(unsigned long long val);
+#else
+#define puts(s)
+#define puthex(val)
+#endif
 
 void error(char *x)
 {
diff --git a/arch/mips/boot/compressed/ld.script b/arch/mips/boot/compressed/ld.script
index 5a33409..de04ac9 100644
--- a/arch/mips/boot/compressed/ld.script
+++ b/arch/mips/boot/compressed/ld.script
@@ -49,5 +49,6 @@
 		*(.reginfo)
 		*(.comment)
 		*(.note)
+		*(.gnu.attributes)
 	}
 }
diff --git a/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h b/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
index a0ee0cb..4e18add 100644
--- a/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
+++ b/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
@@ -301,5 +301,40 @@
 /* GPIO : I/O SPACE; REG : 32BITS */
 #define GPIOL_OUT_VAL		0x00
 #define GPIOL_OUT_EN		0x04
+#define GPIOL_OUT_AUX1_SEL	0x10
+/* SMB : I/O SPACE, REG : 8BITS WIDTH */
+#define	SMB_SDA			0x00
+#define	SMB_STS			0x01
+#define	SMB_STS_SLVSTP		(1 << 7)
+#define	SMB_STS_SDAST		(1 << 6)
+#define	SMB_STS_BER		(1 << 5)
+#define	SMB_STS_NEGACK		(1 << 4)
+#define	SMB_STS_STASTR		(1 << 3)
+#define	SMB_STS_NMATCH		(1 << 2)
+#define	SMB_STS_MASTER		(1 << 1)
+#define	SMB_STS_XMIT		(1 << 0)
+#define	SMB_CTRL_STS		0x02
+#define	SMB_CSTS_TGSTL		(1 << 5)
+#define	SMB_CSTS_TSDA		(1 << 4)
+#define	SMB_CSTS_GCMTCH		(1 << 3)
+#define	SMB_CSTS_MATCH		(1 << 2)
+#define	SMB_CSTS_BB		(1 << 1)
+#define	SMB_CSTS_BUSY		(1 << 0)
+#define	SMB_CTRL1		0x03
+#define	SMB_CTRL1_STASTRE	(1 << 7)
+#define	SMB_CTRL1_NMINTE	(1 << 6)
+#define	SMB_CTRL1_GCMEN		(1 << 5)
+#define	SMB_CTRL1_ACK		(1 << 4)
+#define	SMB_CTRL1_RSVD		(1 << 3)
+#define	SMB_CTRL1_INTEN		(1 << 2)
+#define	SMB_CTRL1_STOP		(1 << 1)
+#define	SMB_CTRL1_START		(1 << 0)
+#define	SMB_ADDR		0x04
+#define	SMB_ADDR_SAEN		(1 << 7)
+#define	SMB_CONTROLLER_ADDR	(0xef << 0)
+#define	SMB_CTRL2		0x05
+#define	SMB_FREQ		(0x20 << 1)
+#define	SMB_ENABLE		(0x01 << 0)
+#define	SMB_CTRL3		0x06
 
 #endif				/* _CS5536_H */
diff --git a/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h b/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
index 021d017..50aafca 100644
--- a/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
+++ b/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
@@ -28,8 +28,19 @@
 #define COMPARE	 ((MFGPT_TICK_RATE + HZ/2) / HZ)
 
 #define MFGPT_BASE	mfgpt_base
+#define MFGPT0_CMP1	(MFGPT_BASE + 0)
 #define MFGPT0_CMP2	(MFGPT_BASE + 2)
 #define MFGPT0_CNT	(MFGPT_BASE + 4)
 #define MFGPT0_SETUP	(MFGPT_BASE + 6)
 
+#define MFGPT1_CMP1	(MFGPT_BASE + 0x08)
+#define MFGPT1_CMP2	(MFGPT_BASE + 0x0A)
+#define MFGPT1_CNT	(MFGPT_BASE + 0x0C)
+#define MFGPT1_SETUP	(MFGPT_BASE + 0x0E)
+
+#define MFGPT2_CMP1	(MFGPT_BASE + 0x10)
+#define MFGPT2_CMP2	(MFGPT_BASE + 0x12)
+#define MFGPT2_CNT	(MFGPT_BASE + 0x14)
+#define MFGPT2_SETUP	(MFGPT_BASE + 0x16)
+
 #endif /*!_CS5536_MFGPT_H */
diff --git a/arch/mips/include/asm/mach-loongson/loongson.h b/arch/mips/include/asm/mach-loongson/loongson.h
index 5459ac0..14e82d9 100644
--- a/arch/mips/include/asm/mach-loongson/loongson.h
+++ b/arch/mips/include/asm/mach-loongson/loongson.h
@@ -46,6 +46,12 @@
 #endif
 }
 
+/*
+ * Copy kernel command line from arcs_cmdline
+ */
+#include <asm/setup.h>
+extern char loongson_cmdline[COMMAND_LINE_SIZE];
+
 /* irq operation functions */
 extern void bonito_irqdispatch(void);
 extern void __init bonito_irq_init(void);
diff --git a/arch/mips/include/asm/mach-loongson/machine.h b/arch/mips/include/asm/mach-loongson/machine.h
index cb2b602..78fcd38 100644
--- a/arch/mips/include/asm/mach-loongson/machine.h
+++ b/arch/mips/include/asm/mach-loongson/machine.h
@@ -24,6 +24,12 @@
 
 #endif
 
+#ifdef CONFIG_DEXXON_GDIUM
+
+#define LOONGSON_MACHTYPE MACH_DEXXON_GDIUM2F10
+
+#endif
+
 #ifdef CONFIG_LOONGSON_MACH3X
 
 #define LOONGSON_MACHTYPE MACH_LOONGSON_GENERIC
diff --git a/arch/mips/include/asm/mach-loongson1/clock.h b/arch/mips/include/asm/mach-loongson1/clock.h
new file mode 100644
index 0000000..dd1afdb
--- /dev/null
+++ b/arch/mips/include/asm/mach-loongson1/clock.h
@@ -0,0 +1,53 @@
+#ifndef __ASM_MACH_LOONGSON1_CLOCK_H
+#define __ASM_MACH_LOONGSON1_CLOCK_H
+
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/seq_file.h>
+#include <linux/clk.h>
+
+extern void (*cpu_wait) (void);
+
+struct clk;
+
+struct clk_ops {
+	void (*init) (struct clk *clk);
+	void (*enable) (struct clk *clk);
+	void (*disable) (struct clk *clk);
+	void (*recalc) (struct clk *clk);
+	int (*set_rate) (struct clk *clk, unsigned long rate, int algo_id);
+	long (*round_rate) (struct clk *clk, unsigned long rate);
+};
+
+struct clk {
+	struct list_head node;
+	const char *name;
+	int id;
+	struct module *owner;
+
+	struct clk *parent;
+	struct clk_ops *ops;
+
+	struct kref kref;
+
+	unsigned long rate;
+	unsigned long flags;
+};
+
+#define CLK_ALWAYS_ENABLED	(1 << 0)
+#define CLK_RATE_PROPAGATES	(1 << 1)
+
+/* Should be defined by processor-specific code */
+void arch_init_clk_ops(struct clk_ops **, int type);
+
+int clk_init(void);
+
+int __clk_enable(struct clk *);
+void __clk_disable(struct clk *);
+
+void clk_recalc_rate(struct clk *);
+
+int clk_register(struct clk *);
+void clk_unregister(struct clk *);
+
+#endif				/* __ASM_MIPS_CLOCK_H */
diff --git a/arch/mips/include/asm/mach-loongson1/regs-intc.h b/arch/mips/include/asm/mach-loongson1/regs-intc.h
new file mode 100644
index 0000000..6d5db23
--- /dev/null
+++ b/arch/mips/include/asm/mach-loongson1/regs-intc.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com>
+ *
+ * Loongson1 Interrupt register definitions.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef __ASM_MACH_LOONGSON1_REGS_INTC_H
+#define __ASM_MACH_LOONGSON1_REGS_INTC_H
+
+#define LS1X_INTC_REG(n, x) \
+		(ioremap(LS1X_INTC_BASE + (n * 0x18) + (x), 4))
+
+#define LS1X_INTC_INTISR(n)		LS1X_INTC_REG(n, 0x0)
+#define LS1X_INTC_INTIEN(n)		LS1X_INTC_REG(n, 0x4)
+#define LS1X_INTC_INTSET(n)		LS1X_INTC_REG(n, 0x8)
+#define LS1X_INTC_INTCLR(n)		LS1X_INTC_REG(n, 0xc)
+#define LS1X_INTC_INTPOL(n)		LS1X_INTC_REG(n, 0x10)
+#define LS1X_INTC_INTEDGE(n)		LS1X_INTC_REG(n, 0x14)
+
+#endif /* __ASM_MACH_LOONGSON1_REGS_INTC_H */
diff --git a/arch/mips/include/asm/sparsemem.h b/arch/mips/include/asm/sparsemem.h
index b1071c1..8b8e551 100644
--- a/arch/mips/include/asm/sparsemem.h
+++ b/arch/mips/include/asm/sparsemem.h
@@ -11,7 +11,11 @@
 #else
 # define SECTION_SIZE_BITS	28
 #endif
-#define MAX_PHYSMEM_BITS	48
+#if !defined(CONFIG_MACH_LOONGSON) || !defined(CONFIG_CPU_LOONGSON2)	/* Commit c461731836 broke Loongson2.  */
+# define MAX_PHYSMEM_BITS	48
+#else
+# define MAX_PHYSMEM_BITS	35
+#endif
 
 #endif /* CONFIG_SPARSEMEM */
 #endif /* _MIPS_SPARSEMEM_H */
diff --git a/arch/mips/include/asm/timex.h b/arch/mips/include/asm/timex.h
index b05bb70..44c9a69 100644
--- a/arch/mips/include/asm/timex.h
+++ b/arch/mips/include/asm/timex.h
@@ -11,6 +11,10 @@
 
 #ifdef __KERNEL__
 
+#ifdef CONFIG_CSRC_R4K
+#define ARCH_HAS_PREPARED_LPJ
+#endif
+
 #include <linux/compiler.h>
 
 #include <asm/cpu.h>
diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h
index 89c2243..6257b9f 100644
--- a/arch/mips/include/uapi/asm/inst.h
+++ b/arch/mips/include/uapi/asm/inst.h
@@ -65,6 +65,8 @@
 enum spec2_op {
 	madd_op, maddu_op, mul_op, spec2_3_unused_op,
 	msub_op, msubu_op, /* more unused ops */
+	loongson_madd_op = 0x18, loongson_msub_op,
+	loongson_nmadd_op, loongson_nmsub_op,
 	clz_op = 0x20, clo_op,
 	dclz_op = 0x24, dclo_op,
 	sdbpp_op = 0x3f
@@ -147,7 +149,7 @@
  */
 enum cop1_fmt {
 	s_fmt, d_fmt, e_fmt, q_fmt,
-	w_fmt, l_fmt
+	w_fmt, l_fmt, ps_fmt
 };
 
 /*
@@ -176,7 +178,8 @@
 enum cop1x_func {
 	lwxc1_op     =	0x00, ldxc1_op	   =  0x01,
 	swxc1_op     =  0x08, sdxc1_op	   =  0x09,
-	pfetch_op    =	0x0f, madd_s_op	   =  0x20,
+	pfetch_op    =	0x0f,
+	prefx_op     =  0x17, madd_s_op    =  0x20,
 	madd_d_op    =	0x21, madd_e_op	   =  0x22,
 	msub_s_op    =	0x28, msub_d_op	   =  0x29,
 	msub_e_op    =	0x2a, nmadd_s_op   =  0x30,
diff --git a/arch/mips/kernel/scall64-o32.S b/arch/mips/kernel/scall64-o32.S
index d07b210..69996f2 100644
--- a/arch/mips/kernel/scall64-o32.S
+++ b/arch/mips/kernel/scall64-o32.S
@@ -26,6 +26,18 @@
 
 	.align	5
 NESTED(handle_sys, PT_SIZE, sp)
+#ifdef CONFIG_MIPS_USER_RDTSC
+	MFC0	k0, CP0_EPC
+	lw	k1, 0(k0)
+	sltiu	k1, k1, 0x1c
+	bne	k1, zero, 1f		# Normal syscall code: 0x0c < 0x1c
+	 nop
+	mfc0	v0, CP0_COUNT 		# Get TSC
+	PTR_ADDIU	k0, 4		# ret from syscall
+	MTC0	k0, CP0_EPC
+	eret
+1:
+#endif /* CONFIG_MIPS_USER_RDTSC */
 	.set	noat
 	SAVE_SOME
 	TRACE_IRQS_ON_RELOAD
diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c
index 8d01709..9cd25da 100644
--- a/arch/mips/kernel/time.c
+++ b/arch/mips/kernel/time.c
@@ -119,6 +119,11 @@
 
 void __init time_init(void)
 {
+#ifdef CONFIG_HR_SCHED_CLOCK
+	if (!mips_clockevent_init() || !cpu_has_mfc0_count_bug())
+		write_c0_count(0);
+#endif
+
 	plat_time_init();
 
 	/*
diff --git a/arch/mips/lib/Makefile b/arch/mips/lib/Makefile
index eeddc58..d7bec00 100644
--- a/arch/mips/lib/Makefile
+++ b/arch/mips/lib/Makefile
@@ -2,10 +2,14 @@
 # Makefile for MIPS-specific library files..
 #
 
-lib-y	+= bitops.o csum_partial.o delay.o memcpy.o memset.o \
+lib-y	+= bitops.o csum_partial.o memcpy.o memset.o \
 	   mips-atomic.o strlen_user.o strncpy_user.o \
 	   strnlen_user.o uncached.o
 
+ifndef CONFIG_CSRC_R4K
+lib-y	+= delay.o
+endif
+
 obj-y			+= iomap.o
 obj-$(CONFIG_PCI)	+= iomap-pci.o
 
diff --git a/arch/mips/loongson/Kconfig b/arch/mips/loongson/Kconfig
index 156de85..0a433e6 100644
--- a/arch/mips/loongson/Kconfig
+++ b/arch/mips/loongson/Kconfig
@@ -32,16 +32,43 @@
 
 config LEMOTE_MACH2F
 	bool "Lemote Loongson 2F family machines"
+	select ARCH_SPARSEMEM_ENABLE if HIBERNATION
+	select BOARD_SCACHE
+	select BOOT_ELF32
+	select CEVT_R4K if ! MIPS_EXTERNAL_TIMER
+	select CPU_HAS_WB
+	select CS5536 if PCI
+	select CSRC_R4K if ! MIPS_EXTERNAL_TIMER
+	select DMA_NONCOHERENT
+	select GENERIC_ISA_DMA_SUPPORT_BROKEN
+	select HAVE_CLK
+	select HW_HAS_PCI
+	select I8259
+	select IRQ_CPU
+	select SYS_HAS_CPU_LOONGSON2F
+	select SYS_HAS_EARLY_PRINTK
+	select SYS_SUPPORTS_32BIT_KERNEL
+	select SYS_SUPPORTS_64BIT_KERNEL
+	select SYS_SUPPORTS_HIGHMEM if ! EMBEDDED
+	select SYS_SUPPORTS_LITTLE_ENDIAN
+	select LOONGSON_MC146818 if RTC_DRV_CMOS
+	help
+	  Lemote Loongson 2F family machines utilize the 2F revision of
+	  Loongson processor and the AMD CS5536 south bridge.
+
+	  These family machines include fuloong2f mini PC, yeeloong2f notebook,
+	  LingLoong allinone PC and so forth.
+
+config DEXXON_GDIUM
+	bool "Dexxon Gdium Netbook"
 	select ARCH_SPARSEMEM_ENABLE
 	select BOARD_SCACHE
 	select BOOT_ELF32
 	select CEVT_R4K if ! MIPS_EXTERNAL_TIMER
 	select CPU_HAS_WB
-	select CS5536
 	select CSRC_R4K if ! MIPS_EXTERNAL_TIMER
 	select DMA_NONCOHERENT
 	select GENERIC_ISA_DMA_SUPPORT_BROKEN
-	select HAVE_CLK
 	select HW_HAS_PCI
 	select I8259
 	select IRQ_CPU
@@ -52,13 +79,10 @@
 	select SYS_SUPPORTS_64BIT_KERNEL
 	select SYS_SUPPORTS_HIGHMEM
 	select SYS_SUPPORTS_LITTLE_ENDIAN
-	select LOONGSON_MC146818
+	select ARCH_REQUIRE_GPIOLIB
+	select HAVE_PWM if MFD_SM501
 	help
-	  Lemote Loongson 2F family machines utilize the 2F revision of
-	  Loongson processor and the AMD CS5536 south bridge.
-
-	  These family machines include fuloong2f mini PC, yeeloong2f notebook,
-	  LingLoong allinone PC and so forth.
+	  Dexxon gdium netbook based on Loongson 2F and SM502.
 
 config LOONGSON_MACH3X
 	bool "Generic Loongson 3 family machines"
@@ -152,6 +176,24 @@
 	bool
 	default n
 
+config GDIUM_PWM_CLOCK
+	tristate "Gdium PWM Timer"
+	default n
+	depends on HAVE_PWM && EXPERIMENTAL && BROKEN
+	select MIPS_EXTERNAL_TIMER
+	help
+	  This options enables the experimental sm501-pwm based clock. With it,
+	  you may be possible to use the loongson2f cpufreq driver.
+
+config GDIUM_VERSION
+	int "Configure Gdium Version"
+	depends on DEXXON_GDIUM
+	default "3"
+	help
+	  I have no information about how to determine which version your board
+	  is, If the default config doesn't work for it, please change it to
+	  smaller ones.
+
 config LEFI_FIRMWARE_INTERFACE
 	bool
 
diff --git a/arch/mips/loongson/Makefile b/arch/mips/loongson/Makefile
index 7429994..63214c8 100644
--- a/arch/mips/loongson/Makefile
+++ b/arch/mips/loongson/Makefile
@@ -17,6 +17,12 @@
 obj-$(CONFIG_LEMOTE_MACH2F)  += lemote-2f/
 
 #
+# Dexxon gdium netbook, based on loongson 2F and SM502
+#
+
+obj-$(CONFIG_DEXXON_GDIUM)  += gdium/
+
+#
 # All Loongson-3 family machines
 #
 
diff --git a/arch/mips/loongson/Platform b/arch/mips/loongson/Platform
index 0ac20eb..cd957dd 100644
--- a/arch/mips/loongson/Platform
+++ b/arch/mips/loongson/Platform
@@ -30,4 +30,5 @@
 cflags-$(CONFIG_MACH_LOONGSON) += -I$(srctree)/arch/mips/include/asm/mach-loongson -mno-branch-likely
 load-$(CONFIG_LEMOTE_FULOONG2E) += 0xffffffff80100000
 load-$(CONFIG_LEMOTE_MACH2F) += 0xffffffff80200000
+load-$(CONFIG_DEXXON_GDIUM) += 0xffffffff80200000
 load-$(CONFIG_LOONGSON_MACH3X) += 0xffffffff80200000
diff --git a/arch/mips/loongson/common/cmdline.c b/arch/mips/loongson/common/cmdline.c
index 72fed00..96d5919 100644
--- a/arch/mips/loongson/common/cmdline.c
+++ b/arch/mips/loongson/common/cmdline.c
@@ -17,10 +17,15 @@
  * Free Software Foundation;  either version 2 of the  License, or (at your
  * option) any later version.
  */
+#include <linux/module.h>
 #include <asm/bootinfo.h>
 
 #include <loongson.h>
 
+/* the kernel command line copied from arcs_cmdline */
+char loongson_cmdline[COMMAND_LINE_SIZE];
+EXPORT_SYMBOL(loongson_cmdline);
+
 void __init prom_init_cmdline(void)
 {
 	int prom_argc;
@@ -45,4 +50,31 @@
 	}
 
 	prom_init_machtype();
+
+	/* append machine specific command line */
+	switch (mips_machtype) {
+	case MACH_LEMOTE_LL2F:
+		if ((strstr(arcs_cmdline, "video=")) == NULL)
+			strcat(arcs_cmdline, " video=sisfb:1360x768-16@60");
+		break;
+	case MACH_LEMOTE_FL2F:
+		if ((strstr(arcs_cmdline, "ide_core.ignore_cable=")) == NULL)
+			strcat(arcs_cmdline, " ide_core.ignore_cable=0");
+		break;
+	case MACH_LEMOTE_ML2F7:
+		/* Mengloong-2F has a 800x480 screen */
+		if ((strstr(arcs_cmdline, "vga=")) == NULL)
+			strcat(arcs_cmdline, " vga=0x313");
+		break;
+	case MACH_DEXXON_GDIUM2F10:
+		/* gdium has a 1024x600 screen */
+		if ((strstr(arcs_cmdline, "video=")) == NULL)
+			strcat(arcs_cmdline, " video=sm501fb:1024x600@60");
+		break;
+	default:
+		break;
+	}
+
+	/* copy arcs_cmdline into loongson_cmdline */
+	strncpy(loongson_cmdline, arcs_cmdline, COMMAND_LINE_SIZE);
 }
diff --git a/arch/mips/loongson/common/env.c b/arch/mips/loongson/common/env.c
index 045ea3d..1d1db80 100644
--- a/arch/mips/loongson/common/env.c
+++ b/arch/mips/loongson/common/env.c
@@ -29,6 +29,7 @@
 struct loongson_system_configuration loongson_sysconf;
 
 u64 loongson_chipcfg[MAX_PACKAGES] = {0xffffffffbfc00180};
+EXPORT_SYMBOL_GPL(loongson_chipcfg);
 u64 loongson_freqctrl[MAX_PACKAGES];
 
 unsigned long long smp_group[4];
diff --git a/arch/mips/loongson/gdium/Makefile b/arch/mips/loongson/gdium/Makefile
new file mode 100644
index 0000000..f3f4f51
--- /dev/null
+++ b/arch/mips/loongson/gdium/Makefile
@@ -0,0 +1,6 @@
+# Makefile for gdium
+
+obj-y += irq.o reset.o platform.o
+
+obj-$(CONFIG_MFD_SM501) += sm501-pwm.o
+obj-$(CONFIG_GDIUM_PWM_CLOCK) += gdium-clock.o
diff --git a/arch/mips/loongson/gdium/gdium-clock.c b/arch/mips/loongson/gdium/gdium-clock.c
new file mode 100644
index 0000000..fdbf42a
--- /dev/null
+++ b/arch/mips/loongson/gdium/gdium-clock.c
@@ -0,0 +1,234 @@
+/*
+ * Doesn't work really well. When used, the clocksource is producing
+ * bad timings and the clockevent can't be used (don't have one shot feature
+ * thus can't switch on the fly and the pwm is initialised too late to be able
+ * to use it at boot time).
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/pwm.h>
+#include <linux/clocksource.h>
+#include <linux/debugfs.h>
+#include <asm/irq_cpu.h>
+#include <asm/mipsregs.h>
+#include <asm/mips-boards/bonito64.h>
+#include <asm/time.h>
+
+#include <loongson.h>
+
+#define CLOCK_PWM		1
+#define CLOCK_PWM_FREQ		1500000				/* Freq in Hz */
+#define CLOCK_LATCH		((CLOCK_PWM_FREQ + HZ/2) / HZ)
+#define CLOCK_PWM_PERIOD	(1000000000/CLOCK_PWM_FREQ)	/* period ns  */
+#define CLOCK_PWM_DUTY		50
+#define CLOCK_PWM_IRQ		(MIPS_CPU_IRQ_BASE + 4)
+
+static const char drv_name[] = "gdium-clock";
+
+static struct pwm_device *clock_pwm;
+
+static DEFINE_SPINLOCK(clock_pwm_lock);
+static uint64_t clock_tick;
+
+static irqreturn_t gdium_pwm_clock_interrupt(int irq, void *dev_id)
+{
+	struct clock_event_device *cd = dev_id;
+	unsigned long flag;
+
+	spin_lock_irqsave(&clock_pwm_lock, flag);
+	clock_tick++;
+	/* wait intn2 to finish */
+	do {
+		LOONGSON_INTENCLR = (1 << 13);
+	} while (LOONGSON_INTISR & (1 << 13));
+	spin_unlock_irqrestore(&clock_pwm_lock, flag);
+
+	if (cd && cd->event_handler)
+		cd->event_handler(cd);
+
+	return IRQ_HANDLED;
+}
+
+static cycle_t gdium_pwm_clock_read(struct clocksource *cs)
+{
+	unsigned long flag;
+	uint32_t jifs;
+	uint64_t ticks;
+
+	spin_lock_irqsave(&clock_pwm_lock, flag);
+	jifs = jiffies;
+	ticks = clock_tick;
+	spin_unlock_irqrestore(&clock_pwm_lock, flag);
+	/* return (cycle_t)ticks; */
+	return (cycle_t)(CLOCK_LATCH * jifs);
+}
+
+static struct clocksource gdium_pwm_clock_clocksource = {
+	.name   = "gdium_csrc",
+	.read   = gdium_pwm_clock_read,
+	.mask   = CLOCKSOURCE_MASK(64),
+	.flags	= CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_MUST_VERIFY,
+	.shift	= 20,
+};
+
+/* Debug fs */
+static int gdium_pwm_clock_show(struct seq_file *s, void *p)
+{
+	unsigned long flag;
+	uint64_t ticks;
+
+	spin_lock_irqsave(&clock_pwm_lock, flag);
+	ticks = clock_tick;
+	spin_unlock_irqrestore(&clock_pwm_lock, flag);
+	seq_printf(s, "%lld\n", ticks);
+	return 0;
+}
+
+static int gdium_pwm_clock_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, gdium_pwm_clock_show, inode->i_private);
+}
+
+static const struct file_operations gdium_pwm_clock_fops = {
+	.open		= gdium_pwm_clock_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+static struct dentry   *debugfs_file;
+
+static void gdium_pwm_clock_set_mode(enum clock_event_mode mode,
+		struct clock_event_device *evt)
+{
+	/* Nothing to do ...  */
+}
+
+static struct clock_event_device gdium_pwm_clock_cevt = {
+	.name           = "gdium_cevt",
+	.features       = CLOCK_EVT_FEAT_PERIODIC,
+	/* .mult, .shift, .max_delta_ns and .min_delta_ns left uninitialized */
+	.rating         = 299,
+	.irq            = CLOCK_PWM_IRQ,
+	.set_mode       = gdium_pwm_clock_set_mode,
+};
+
+static struct platform_device_id platform_device_ids[] = {
+	{
+		.name = "gdium-pwmclk",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(platform, platform_device_ids);
+
+static struct platform_driver gdium_pwm_clock_driver = {
+	.driver		= {
+		.name   = drv_name,
+		.owner  = THIS_MODULE,
+	},
+	.id_table = platform_device_ids,
+};
+
+static int gdium_pwm_clock_drvinit(void)
+{
+	int ret;
+	struct clocksource *cs = &gdium_pwm_clock_clocksource;
+	struct clock_event_device *cd = &gdium_pwm_clock_cevt;
+	unsigned int cpu = smp_processor_id();
+
+	clock_tick = 0;
+
+	clock_pwm = pwm_request(CLOCK_PWM, drv_name);
+	if (clock_pwm == NULL) {
+		pr_err("unable to request PWM for Gdium clock\n");
+		return -EBUSY;
+	}
+	ret = pwm_config(clock_pwm, CLOCK_PWM_DUTY, CLOCK_PWM_PERIOD);
+	if (ret) {
+		pr_err("unable to configure PWM for Gdium clock\n");
+		goto err_pwm_request;
+	}
+	ret = pwm_enable(clock_pwm);
+	if (ret) {
+		pr_err("unable to enable PWM for Gdium clock\n");
+		goto err_pwm_request;
+	}
+
+	cd->cpumask = cpumask_of(cpu);
+
+	cd->shift = 22;
+	cd->mult  = div_sc(CLOCK_PWM_FREQ, NSEC_PER_SEC, cd->shift);
+	cd->max_delta_ns = clockevent_delta2ns(0x7FFF, cd);
+	cd->min_delta_ns = clockevent_delta2ns(0xF, cd);
+	clockevents_register_device(&gdium_pwm_clock_cevt);
+
+	/* SM501 PWM1 connected to intn2 <->ip4 */
+	LOONGSON_INTPOL = (1 << 13);
+	LOONGSON_INTEDGE &= ~(1 << 13);
+	ret = request_irq(CLOCK_PWM_IRQ, gdium_pwm_clock_interrupt, IRQF_DISABLED, drv_name, &gdium_pwm_clock_cevt);
+	if (ret) {
+		pr_err("Can't claim irq\n");
+		goto err_pwm_disable;
+	}
+
+	cs->rating = 200;
+	cs->mult = clocksource_hz2mult(CLOCK_PWM_FREQ, cs->shift);
+	ret = clocksource_register(&gdium_pwm_clock_clocksource);
+	if (ret) {
+		pr_err("Can't register clocksource\n");
+		goto err_irq;
+	}
+	pr_info("Clocksource registered with shift %d and mult %d\n",
+			cs->shift, cs->mult);
+
+	debugfs_file = debugfs_create_file(drv_name, S_IFREG | S_IRUGO,
+			NULL, NULL, &gdium_pwm_clock_fops);
+
+	return 0;
+
+err_irq:
+	free_irq(CLOCK_PWM_IRQ, &gdium_pwm_clock_cevt);
+err_pwm_disable:
+	pwm_disable(clock_pwm);
+err_pwm_request:
+	pwm_free(clock_pwm);
+	return ret;
+}
+
+static void gdium_pwm_clock_drvexit(void)
+{
+	free_irq(CLOCK_PWM_IRQ, &gdium_pwm_clock_cevt);
+	pwm_disable(clock_pwm);
+	pwm_free(clock_pwm);
+}
+
+
+static int __devinit gdium_pwm_clock_init(void)
+{
+	int ret = gdium_pwm_clock_drvinit();
+
+	if (ret) {
+		pr_err("Fail to register gdium clock driver\n");
+		return ret;
+	}
+
+	return platform_driver_register(&gdium_pwm_clock_driver);
+}
+
+static void __exit gdium_pwm_clock_cleanup(void)
+{
+	gdium_pwm_clock_drvexit();
+	platform_driver_unregister(&gdium_pwm_clock_driver);
+}
+
+module_init(gdium_pwm_clock_init);
+module_exit(gdium_pwm_clock_cleanup);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Gdium PWM clock driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gdium-pwmclk");
diff --git a/arch/mips/loongson/gdium/irq.c b/arch/mips/loongson/gdium/irq.c
new file mode 100644
index 0000000..2415d20
--- /dev/null
+++ b/arch/mips/loongson/gdium/irq.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 Lemote Inc.
+ * Author: Fuxin Zhang, zhangfx@lemote.com
+ *
+ * Copyright (c) 2010 yajin <yajin@vm-kernel.org>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#include <loongson.h>
+#include <machine.h>
+
+#define LOONGSON_TIMER_IRQ      (MIPS_CPU_IRQ_BASE + 7) /* cpu timer */
+#define LOONGSON_NORTH_BRIDGE_IRQ       (MIPS_CPU_IRQ_BASE + 6) /* bonito */
+#define LOONGSON_UART_IRQ       (MIPS_CPU_IRQ_BASE + 3) /* cpu serial port */
+
+void mach_irq_dispatch(unsigned int pending)
+{
+	if (pending & CAUSEF_IP7)
+		do_IRQ(LOONGSON_TIMER_IRQ);
+	else if (pending & CAUSEF_IP6) {        /* North Bridge, Perf counter */
+		do_perfcnt_IRQ();
+		bonito_irqdispatch();
+	} else if (pending & CAUSEF_IP3)        /* CPU UART */
+		do_IRQ(LOONGSON_UART_IRQ);
+#if defined(CONFIG_GDIUM_PWM_CLOCK) || defined(CONFIG_GDIUM_PWM_CLOCK_MODULE)
+	else if (pending & CAUSEF_IP4)		/* SM501 PWM clock */
+		do_IRQ(MIPS_CPU_IRQ_BASE + 4);
+#endif
+	else
+		spurious_interrupt();
+}
+
+static irqreturn_t ip6_action(int cpl, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
+struct irqaction ip6_irqaction = {
+	.handler = ip6_action,
+	.name = "cascade",
+	.flags = IRQF_SHARED,
+};
+
+void __init mach_init_irq(void)
+{
+	/* setup north bridge irq (bonito) */
+	setup_irq(LOONGSON_NORTH_BRIDGE_IRQ, &ip6_irqaction);
+}
diff --git a/arch/mips/loongson/gdium/platform.c b/arch/mips/loongson/gdium/platform.c
new file mode 100644
index 0000000..ffafba4
--- /dev/null
+++ b/arch/mips/loongson/gdium/platform.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2009 Philippe Vachon <philippe@cowpig.ca>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/pwm_backlight.h>
+#include <linux/i2c.h>
+#include <linux/i2c-gpio.h>
+
+#define GDIUM_GPIO_BASE 224
+
+static struct i2c_board_info __initdata sm502dev_i2c_devices[] = {
+	{
+		I2C_BOARD_INFO("lm75", 0x48),
+	},
+	{
+		I2C_BOARD_INFO("m41t83", 0x68),
+	},
+	{
+		I2C_BOARD_INFO("gdium-laptop", 0x40),
+	},
+};
+
+static int sm502dev_backlight_init(struct device *dev)
+{
+	/* Add gpio request stuff here */
+	return 0;
+}
+
+static void sm502dev_backlight_exit(struct device *dev)
+{
+	/* Add gpio free stuff here */
+}
+
+static struct platform_pwm_backlight_data backlight_data = {
+	.pwm_id		= 0,
+	.max_brightness	= 15,
+	.dft_brightness	= 8,
+	.pwm_period_ns	= 50000, /* 20 kHz */
+	.init		= sm502dev_backlight_init,
+	.exit		= sm502dev_backlight_exit,
+};
+
+static struct platform_device backlight = {
+	.name = "pwm-backlight",
+	.dev  = {
+		.platform_data = &backlight_data,
+	},
+	.id   = -1,
+};
+
+/*
+ * Warning this stunt is very dangerous
+ * as the sm501 gpio have dynamic numbers...
+ */
+/* bus 0 is the one for the ST7, DS75 etc... */
+static struct i2c_gpio_platform_data i2c_gpio0_data = {
+#if CONFIG_GDIUM_VERSION > 2
+	.sda_pin	= GDIUM_GPIO_BASE + 13,
+	.scl_pin	= GDIUM_GPIO_BASE + 6,
+#else
+	.sda_pin        = 192+15,
+	.scl_pin        = 192+14,
+#endif
+	.udelay		= 5,
+	.timeout	= HZ / 10,
+	.sda_is_open_drain = 0,
+	.scl_is_open_drain = 0,
+};
+
+static struct platform_device i2c_gpio0_device = {
+	.name	= "i2c-gpio",
+	.id	= 0,
+	.dev	= { .platform_data  = &i2c_gpio0_data, },
+};
+
+/* bus 1 is for the CRT/VGA external screen */
+static struct i2c_gpio_platform_data i2c_gpio1_data = {
+	.sda_pin	= GDIUM_GPIO_BASE + 10,
+	.scl_pin	= GDIUM_GPIO_BASE + 9,
+	.udelay		= 5,
+	.timeout	= HZ / 10,
+	.sda_is_open_drain = 0,
+	.scl_is_open_drain = 0,
+};
+
+static struct platform_device i2c_gpio1_device = {
+	.name	= "i2c-gpio",
+	.id	= 1,
+	.dev	= { .platform_data  = &i2c_gpio1_data, },
+};
+
+static struct platform_device gdium_clock = {
+	.name		= "gdium-pwmclk",
+	.id		= -1,
+};
+
+static struct platform_device *devices[] __initdata = {
+	&i2c_gpio0_device,
+	&i2c_gpio1_device,
+	&backlight,
+	&gdium_clock,
+};
+
+static int __init gdium_platform_devices_setup(void)
+{
+	int ret;
+
+	pr_info("Registering gdium platform devices\n");
+
+	ret = i2c_register_board_info(0, sm502dev_i2c_devices,
+		ARRAY_SIZE(sm502dev_i2c_devices));
+
+	if (ret != 0) {
+		pr_info("Error while registering platform devices: %d\n", ret);
+		return ret;
+	}
+
+	platform_add_devices(devices, ARRAY_SIZE(devices));
+
+	return 0;
+}
+
+/*
+ * some devices are on the pwm stuff which is behind the mfd which is
+ * behind the pci bus so arch_initcall can't work because too early
+ */
+late_initcall(gdium_platform_devices_setup);
diff --git a/arch/mips/loongson/gdium/reset.c b/arch/mips/loongson/gdium/reset.c
new file mode 100644
index 0000000..8289f95
--- /dev/null
+++ b/arch/mips/loongson/gdium/reset.c
@@ -0,0 +1,22 @@
+/* Board-specific reboot/shutdown routines
+ *
+ * Copyright (C) 2010 yajin <yajin@vm-kernel.org>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <loongson.h>
+
+void mach_prepare_shutdown(void)
+{
+	LOONGSON_GPIOIE &= ~(1<<1);
+	LOONGSON_GPIODATA |= (1<<1);
+}
+
+void mach_prepare_reboot(void)
+{
+	LOONGSON_GPIOIE &= ~(1<<2);
+	LOONGSON_GPIODATA &= ~(1<<2);
+}
diff --git a/arch/mips/loongson/gdium/sm501-pwm.c b/arch/mips/loongson/gdium/sm501-pwm.c
new file mode 100644
index 0000000..5af3b23
--- /dev/null
+++ b/arch/mips/loongson/gdium/sm501-pwm.c
@@ -0,0 +1,465 @@
+/*
+ * SM501 PWM clock
+ * Copyright (C) 2009-2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/pwm.h>
+#include <linux/sm501.h>
+#include <linux/sm501-regs.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+static const char drv_name[] = "sm501-pwm";
+
+#define INPUT_CLOCK		96 /* MHz */
+#define PWM_COUNT		3
+
+#define SM501PWM_HIGH_COUNTER	(1<<20)
+#define SM501PWM_LOW_COUNTER	(1<<8)
+#define SM501PWM_CLOCK_DIVIDE	(1>>4)
+#define SM501PWM_IP		(1<<3)
+#define SM501PWM_I		(1<<2)
+#define SM501PWM_E		(1<<0)
+
+struct pwm_device {
+	struct list_head	node;
+	struct device		*dev;
+	void __iomem		*regs;
+	int			duty_ns;
+	int			period_ns;
+	char			enabled;
+	void			(*handler)(struct pwm_device *pwm);
+
+	const char		*label;
+	unsigned int		use_count;
+	unsigned int		pwm_id;
+};
+
+struct sm501pwm_info {
+	void __iomem	*regs;
+	int		irq;
+	struct resource *res;
+	struct device	*dev;
+	struct dentry	*debugfs;
+
+	struct pwm_device pwm[3];
+};
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+	unsigned int high, low, divider;
+	int divider1, divider2;
+	unsigned long long delay;
+
+	if (!pwm || !pwm->regs || period_ns == 0 || duty_ns > period_ns)
+		return -EINVAL;
+
+	/* Get delay
+	 * We're loosing some precision but multiplying then dividing
+	 * will overflow
+	 */
+	if (period_ns > 1000) {
+		delay = period_ns / 1000;
+		delay *= INPUT_CLOCK;
+	} else {
+		delay = period_ns * 96;
+		delay /= 1000;
+	}
+
+	/* Get the number of clock low and high */
+	high  = delay * duty_ns / period_ns;
+	low = delay - high;
+
+	/* Get divider to make 'low' and 'high' fit into 12 bits */
+	/* No need to say that the divider must be >= 0 */
+	divider1 = fls(low)-12;
+	divider2 = fls(high)-12;
+
+	if (divider1 < 0)
+		divider1 = 0;
+	if (divider2 < 0)
+		divider2 = 0;
+
+	divider = max(divider1, divider2);
+
+	low >>= divider;
+	high >>= divider;
+
+	pwm->duty_ns = duty_ns;
+	pwm->period_ns = period_ns;
+
+	writel((high<<20)|(low<<8)|(divider<<4), pwm->regs);
+	return 0;
+}
+EXPORT_SYMBOL(pwm_config);
+
+int pwm_enable(struct pwm_device *pwm)
+{
+	u32 reg;
+
+	if (!pwm)
+		return -EINVAL;
+
+	switch (pwm->pwm_id) {
+	case 0:
+		sm501_configure_gpio(pwm->dev->parent, 29, 1);
+		break;
+	case 1:
+		sm501_configure_gpio(pwm->dev->parent, 30, 1);
+		break;
+	case 2:
+		sm501_configure_gpio(pwm->dev->parent, 31, 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	reg = readl(pwm->regs);
+	reg |= (SM501PWM_IP | SM501PWM_E);
+	writel(reg, pwm->regs);
+	pwm->enabled = 1;
+
+	return 0;
+}
+EXPORT_SYMBOL(pwm_enable);
+
+void pwm_disable(struct pwm_device *pwm)
+{
+	u32 reg;
+
+	if (!pwm)
+		return;
+
+	reg = readl(pwm->regs);
+	reg &= ~(SM501PWM_IP | SM501PWM_E);
+	writel(reg, pwm->regs);
+
+	switch (pwm->pwm_id) {
+	case 0:
+		sm501_configure_gpio(pwm->dev->parent, 29, 0);
+		break;
+	case 1:
+		sm501_configure_gpio(pwm->dev->parent, 30, 0);
+		break;
+	case 2:
+		sm501_configure_gpio(pwm->dev->parent, 31, 0);
+		break;
+	default:
+		break;
+	}
+	pwm->enabled = 0;
+}
+EXPORT_SYMBOL(pwm_disable);
+
+static DEFINE_MUTEX(pwm_lock);
+static LIST_HEAD(pwm_list);
+
+struct pwm_device *pwm_request(int pwm_id, const char *label)
+{
+	struct pwm_device *pwm;
+	int found = 0;
+
+	mutex_lock(&pwm_lock);
+
+	list_for_each_entry(pwm, &pwm_list, node) {
+		if (pwm->pwm_id == pwm_id && pwm->use_count == 0) {
+			pwm->use_count++;
+			pwm->label = label;
+			found = 1;
+			break;
+		}
+	}
+
+	mutex_unlock(&pwm_lock);
+
+	return (found) ? pwm : NULL;
+}
+EXPORT_SYMBOL(pwm_request);
+
+void pwm_free(struct pwm_device *pwm)
+{
+	mutex_lock(&pwm_lock);
+
+	if (pwm->use_count) {
+		pwm->use_count--;
+		pwm->label = NULL;
+	} else
+		dev_warn(pwm->dev, "PWM device already freed\n");
+
+	mutex_unlock(&pwm_lock);
+}
+EXPORT_SYMBOL(pwm_free);
+
+int pwm_int_enable(struct pwm_device *pwm)
+{
+	unsigned long conf;
+
+	if (!pwm || !pwm->regs || !pwm->handler)
+		return -EINVAL;
+
+	conf = readl(pwm->regs);
+	conf |= SM501PWM_I;
+	writel(conf, pwm->regs);
+	return 0;
+}
+EXPORT_SYMBOL(pwm_int_enable);
+
+int pwm_int_disable(struct pwm_device *pwm)
+{
+	unsigned long conf;
+
+	if (!pwm || !pwm->regs || !pwm->handler)
+		return -EINVAL;
+
+	conf = readl(pwm->regs);
+	conf &= ~SM501PWM_I;
+	writel(conf, pwm->regs);
+	return 0;
+}
+EXPORT_SYMBOL(pwm_int_disable);
+
+int pwm_set_handler(struct pwm_device *pwm,
+		    void (*handler)(struct pwm_device *pwm))
+{
+	if (!pwm || !handler)
+		return -EINVAL;
+	pwm->handler = handler;
+	return 0;
+}
+EXPORT_SYMBOL(pwm_set_handler);
+
+static irqreturn_t sm501pwm_irq(int irq, void *dev_id)
+{
+	unsigned long value;
+	struct sm501pwm_info *info = (struct sm501pwm_info *)dev_id;
+	struct pwm_device *pwm;
+	int i;
+
+	value = sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 0);
+
+	/* Check is the interrupt is for us */
+	if (value & (1<<22)) {
+		for (i = 0 ; i < PWM_COUNT ; i++) {
+			/*
+			 * Find which pwm triggered the interrupt
+			 * and ack
+			 */
+			value = readl(info->regs + i*4);
+			if (value & SM501PWM_IP)
+				writel(value | SM501PWM_IP, info->regs + i*4);
+
+			pwm = &info->pwm[i];
+			if (pwm->handler)
+				pwm->handler(pwm);
+		}
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static void add_pwm(int id, struct sm501pwm_info *info)
+{
+	struct pwm_device *pwm = &info->pwm[id];
+
+	pwm->use_count	= 0;
+	pwm->pwm_id	= id;
+	pwm->dev	= info->dev;
+	pwm->regs	= info->regs + id * 4;
+
+	mutex_lock(&pwm_lock);
+	list_add_tail(&pwm->node, &pwm_list);
+	mutex_unlock(&pwm_lock);
+}
+
+static void del_pwm(int id, struct sm501pwm_info *info)
+{
+	struct pwm_device *pwm = &info->pwm[id];
+
+	pwm->use_count  = 0;
+	pwm->pwm_id     = -1;
+	mutex_lock(&pwm_lock);
+	list_del(&pwm->node);
+	mutex_unlock(&pwm_lock);
+}
+
+/* Debug fs */
+static int sm501pwm_show(struct seq_file *s, void *p)
+{
+	struct pwm_device *pwm;
+
+	mutex_lock(&pwm_lock);
+	list_for_each_entry(pwm, &pwm_list, node) {
+		if (pwm->use_count) {
+			seq_printf(s, "pwm-%d (%12s) %d %d %s\n",
+					pwm->pwm_id, pwm->label,
+					pwm->duty_ns, pwm->period_ns,
+					pwm->enabled ? "on" : "off");
+			seq_printf(s, "       %08x\n", readl(pwm->regs));
+		}
+	}
+	mutex_unlock(&pwm_lock);
+
+	return 0;
+}
+
+static int sm501pwm_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, sm501pwm_show, inode->i_private);
+}
+
+static const struct file_operations sm501pwm_fops = {
+	.open		= sm501pwm_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+static int __init sm501pwm_probe(struct platform_device *pdev)
+{
+	struct sm501pwm_info *info;
+	struct device   *dev = &pdev->dev;
+	struct resource *res;
+	int ret = 0;
+	int res_len;
+	int i;
+
+	info = kzalloc(sizeof(struct sm501pwm_info), GFP_KERNEL);
+	if (!info) {
+		dev_err(dev, "Allocation failure\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	info->dev = dev;
+	platform_set_drvdata(pdev, info);
+
+	/* Get irq number */
+	info->irq = platform_get_irq(pdev, 0);
+	if (!info->irq) {
+		dev_err(dev, "no irq found\n");
+		ret = -ENODEV;
+		goto err_alloc;
+	}
+
+	/* Get regs address */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(dev, "No memory resource found\n");
+		ret = -ENODEV;
+		goto err_alloc;
+	}
+	info->res = res;
+	res_len = (res->end - res->start)+1;
+
+	if (!request_mem_region(res->start, res_len, drv_name)) {
+		dev_err(dev, "Can't request iomem resource\n");
+		ret = -EBUSY;
+		goto err_alloc;
+	}
+
+	info->regs = ioremap(res->start, res_len);
+	if (!info->regs) {
+		dev_err(dev, "ioremap failed\n");
+		ret = -ENOMEM;
+		goto err_mem;
+	}
+
+	ret = request_irq(info->irq, sm501pwm_irq, IRQF_SHARED, drv_name, info);
+	if (ret != 0) {
+		dev_err(dev, "can't get irq\n");
+		goto err_map;
+	}
+
+
+	sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 1);
+
+	for (i = 0; i < 3; i++)
+		add_pwm(i, info);
+
+	dev_info(dev, "SM501 PWM Found at %lx irq %d\n",
+		 (unsigned long)info->res->start, info->irq);
+
+	info->debugfs = debugfs_create_file("pwm", S_IFREG | S_IRUGO,
+				NULL, info, &sm501pwm_fops);
+
+
+	return 0;
+
+err_map:
+	iounmap(info->regs);
+
+err_mem:
+	release_mem_region(res->start, res_len);
+
+err_alloc:
+	kfree(info);
+	platform_set_drvdata(pdev, NULL);
+err:
+	return ret;
+}
+
+static int sm501pwm_remove(struct platform_device *pdev)
+{
+	struct sm501pwm_info *info = platform_get_drvdata(pdev);
+	int i;
+
+	if (info->debugfs)
+		debugfs_remove(info->debugfs);
+
+	for (i = 0; i < 3; i++) {
+		pwm_disable(&info->pwm[i]);
+		del_pwm(i, info);
+	}
+
+	sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 0);
+	sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 1<<22);
+
+	free_irq(info->irq, info);
+	iounmap(info->regs);
+	release_mem_region(info->res->start,
+			   (info->res->end - info->res->start)+1);
+	kfree(info);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver sm501pwm_driver = {
+	.probe		= sm501pwm_probe,
+	.remove		= sm501pwm_remove,
+	.driver		= {
+		.name	= drv_name,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __devinit sm501pwm_init(void)
+{
+	return platform_driver_register(&sm501pwm_driver);
+}
+
+static void __exit sm501pwm_cleanup(void)
+{
+	platform_driver_unregister(&sm501pwm_driver);
+}
+
+module_init(sm501pwm_init);
+module_exit(sm501pwm_cleanup);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("SM501 PWM driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sm501-pwm");
diff --git a/arch/mips/loongson/lemote-2f/Makefile b/arch/mips/loongson/lemote-2f/Makefile
index 4f9eaa3..f945bd7 100644
--- a/arch/mips/loongson/lemote-2f/Makefile
+++ b/arch/mips/loongson/lemote-2f/Makefile
@@ -2,7 +2,7 @@
 # Makefile for lemote loongson2f family machines
 #
 
-obj-y += clock.o machtype.o irq.o reset.o ec_kb3310b.o
+obj-y += clock.o machtype.o irq.o reset.o ec_kb3310b.o platform.o
 
 #
 # Suspend Support
diff --git a/arch/mips/loongson/lemote-2f/platform.c b/arch/mips/loongson/lemote-2f/platform.c
new file mode 100644
index 0000000..5316360
--- /dev/null
+++ b/arch/mips/loongson/lemote-2f/platform.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: Wu Zhangjin, wuzhangjin@gmail.com
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/platform_device.h>
+
+#include <asm/bootinfo.h>
+
+static struct platform_device yeeloong_pdev = {
+	.name = "yeeloong_laptop",
+	.id = -1,
+};
+
+static struct platform_device lynloong_pdev = {
+	.name = "lynloong_pc",
+	.id = -1,
+};
+
+static int __init lemote2f_platform_init(void)
+{
+	struct platform_device *pdev = NULL;
+
+	switch (mips_machtype) {
+	case MACH_LEMOTE_YL2F89:
+		pdev = &yeeloong_pdev;
+		break;
+	case MACH_LEMOTE_LL2F:
+		pdev = &lynloong_pdev;
+		break;
+	default:
+		break;
+
+	}
+
+	if (pdev != NULL)
+		return platform_device_register(pdev);
+
+	return -ENODEV;
+}
+
+arch_initcall(lemote2f_platform_init);
diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c
index 9dfcd7f..8dc2f2c 100644
--- a/arch/mips/math-emu/cp1emu.c
+++ b/arch/mips/math-emu/cp1emu.c
@@ -7,6 +7,9 @@
  * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com
  * Copyright (C) 2000  MIPS Technologies, Inc.
  *
+ * Loongson instruction support
+ * Copyright (C) 2011  Mark H Weaver <mhw@netris.org>
+ *
  *  This program is free software; you can distribute it and/or modify it
  *  under the terms of the GNU General Public License (Version 2) as
  *  published by the Free Software Foundation.
@@ -59,6 +62,11 @@
 static int fpux_emu(struct pt_regs *,
 	struct mips_fpu_struct *, mips_instruction, void *__user *);
 
+#ifdef CONFIG_MACH_LOONGSON
+static int loongson_spec2_emu(struct pt_regs *,
+	struct mips_fpu_struct *, mips_instruction, void *__user *);
+#endif
+
 /* Control registers */
 
 #define FPCREG_RID	0	/* $0  = revision id */
@@ -695,6 +703,14 @@
 #define DPFROMREG(dp, x)	DIFROMREG((dp).bits, x)
 #define DPTOREG(dp, x)	DITOREG((dp).bits, x)
 
+/* Support for Loongson paired single floating-point format */
+#define PSIFROMREG(si1, si2, x) ({ u64 di; DIFROMREG(di, x);		\
+			(si1) = (u32)di; (si2) = (u32)(di >> 32); })
+#define PSITOREG(si1, si2, x) DITOREG((si1) | ((u64)(si2) << 32), x)
+
+#define PSPFROMREG(sp1, sp2, x) PSIFROMREG((sp1).bits, (sp2).bits, x)
+#define PSPTOREG(sp1, sp2, x)	PSITOREG((sp1).bits, (sp2).bits, x)
+
 /*
  * Emulate the single floating point instruction pointed at by EPC.
  * Two instructions if the instruction is in a branch delay slot.
@@ -1088,6 +1104,16 @@
 			xcp->regs[MIPSInst_RD(ir)] =
 				xcp->regs[MIPSInst_RS(ir)];
 		break;
+
+#ifdef CONFIG_MACH_LOONGSON
+	case spec2_op:{
+		int sig = loongson_spec2_emu(xcp, ctx, ir, fault_addr);
+		if (sig)
+			return sig;
+		break;
+	}
+#endif
+
 	default:
 sigill:
 		return SIGILL;
@@ -1165,6 +1191,172 @@
 DEF3OP(nmadd, dp, ieee754dp_mul, ieee754dp_add, ieee754dp_neg);
 DEF3OP(nmsub, dp, ieee754dp_mul, ieee754dp_sub, ieee754dp_neg);
 
+#ifdef CONFIG_MACH_LOONGSON
+static int loongson_spec2_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+	mips_instruction ir, void *__user *fault_addr)
+{
+	int rfmt;		/* resulting format */
+	unsigned rcsr = 0;	/* resulting csr */
+	union {
+		union ieee754dp d;
+		struct {
+			union ieee754sp s;
+			union ieee754sp s2;
+		};
+	} rv;			/* resulting value */
+
+	/* XXX maybe add a counter for loongson spec2 fp instructions? */
+	/* MIPS_FPU_EMU_INC_STATS(cp1xops); */
+
+	switch (rfmt = (MIPSInst_FFMT(ir) & 0xf)) {
+	case s_fmt:{
+		union ieee754sp(*handler) (union ieee754sp, union ieee754sp, union ieee754sp);
+		union ieee754sp fd, fs, ft;
+
+		switch (MIPSInst_FUNC(ir)) {
+		case loongson_madd_op:
+			handler = fpemu_sp_madd;
+			goto scoptop;
+		case loongson_msub_op:
+			handler = fpemu_sp_msub;
+			goto scoptop;
+		case loongson_nmadd_op:
+			handler = fpemu_sp_nmadd;
+			goto scoptop;
+		case loongson_nmsub_op:
+			handler = fpemu_sp_nmsub;
+			goto scoptop;
+
+		      scoptop:
+			SPFROMREG(fd, MIPSInst_FD(ir));
+			SPFROMREG(fs, MIPSInst_FS(ir));
+			SPFROMREG(ft, MIPSInst_FT(ir));
+			rv.s = (*handler) (fd, fs, ft);
+
+		      copcsr:
+			if (ieee754_cxtest(IEEE754_INEXACT))
+				rcsr |= FPU_CSR_INE_X | FPU_CSR_INE_S;
+			if (ieee754_cxtest(IEEE754_UNDERFLOW))
+				rcsr |= FPU_CSR_UDF_X | FPU_CSR_UDF_S;
+			if (ieee754_cxtest(IEEE754_OVERFLOW))
+				rcsr |= FPU_CSR_OVF_X | FPU_CSR_OVF_S;
+			if (ieee754_cxtest(IEEE754_INVALID_OPERATION))
+				rcsr |= FPU_CSR_INV_X | FPU_CSR_INV_S;
+
+			break;
+
+		default:
+			return SIGILL;
+		}
+		break;
+	}
+
+	case d_fmt:{
+		union ieee754dp(*handler) (union ieee754dp, union ieee754dp, union ieee754dp);
+		union ieee754dp fd, fs, ft;
+
+		switch (MIPSInst_FUNC(ir)) {
+		case loongson_madd_op:
+			handler = fpemu_dp_madd;
+			goto dcoptop;
+		case loongson_msub_op:
+			handler = fpemu_dp_msub;
+			goto dcoptop;
+		case loongson_nmadd_op:
+			handler = fpemu_dp_nmadd;
+			goto dcoptop;
+		case loongson_nmsub_op:
+			handler = fpemu_dp_nmsub;
+			goto dcoptop;
+
+		      dcoptop:
+			DPFROMREG(fd, MIPSInst_FD(ir));
+			DPFROMREG(fs, MIPSInst_FS(ir));
+			DPFROMREG(ft, MIPSInst_FT(ir));
+			rv.d = (*handler) (fd, fs, ft);
+			goto copcsr;
+
+		default:
+			return SIGILL;
+		}
+		break;
+	}
+
+	case ps_fmt:{
+		union ieee754sp(*handler) (union ieee754sp, union ieee754sp, union ieee754sp);
+		struct _ieee754_csr ieee754_csr_save;
+		union ieee754sp fd1, fs1, ft1;
+		union ieee754sp fd2, fs2, ft2;
+
+		switch (MIPSInst_FUNC(ir)) {
+		case loongson_madd_op:
+			handler = fpemu_sp_madd;
+			goto pscoptop;
+		case loongson_msub_op:
+			handler = fpemu_sp_msub;
+			goto pscoptop;
+		case loongson_nmadd_op:
+			handler = fpemu_sp_nmadd;
+			goto pscoptop;
+		case loongson_nmsub_op:
+			handler = fpemu_sp_nmsub;
+			goto pscoptop;
+
+		      pscoptop:
+			PSPFROMREG(fd1, fd2, MIPSInst_FD(ir));
+			PSPFROMREG(fs1, fs2, MIPSInst_FS(ir));
+			PSPFROMREG(ft1, ft2, MIPSInst_FT(ir));
+			rv.s = (*handler) (fd1, fs1, ft1);
+			ieee754_csr_save = ieee754_csr;
+			rv.s2 = (*handler) (fd2, fs2, ft2);
+			ieee754_csr.cx |= ieee754_csr_save.cx;
+			ieee754_csr.sx |= ieee754_csr_save.sx;
+			goto copcsr;
+
+		default:
+			return SIGILL;
+		}
+		break;
+	}
+
+	default:
+		return SIGILL;
+	}
+
+	/*
+	 * Update the fpu CSR register for this operation.
+	 * If an exception is required, generate a tidy SIGFPE exception,
+	 * without updating the result register.
+	 * Note: cause exception bits do not accumulate, they are rewritten
+	 * for each op; only the flag/sticky bits accumulate.
+	 */
+	ctx->fcr31 = (ctx->fcr31 & ~FPU_CSR_ALL_X) | rcsr;
+	if ((ctx->fcr31 >> 5) & ctx->fcr31 & FPU_CSR_ALL_E) {
+		/*printk ("SIGFPE: fpu csr = %08x\n",ctx->fcr31); */
+		return SIGFPE;
+	}
+
+	/*
+	 * Now we can safely write the result back to the register file.
+	 */
+	switch (rfmt) {
+	case d_fmt:
+		DPTOREG(rv.d, MIPSInst_FD(ir));
+		break;
+	case s_fmt:
+		SPTOREG(rv.s, MIPSInst_FD(ir));
+		break;
+	case ps_fmt:
+		PSPTOREG(rv.s, rv.s2, MIPSInst_FD(ir));
+		break;
+	default:
+		return SIGILL;
+	}
+
+	return 0;
+}
+#endif
+
 static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
 	mips_instruction ir, void *__user *fault_addr)
 {
@@ -1266,7 +1458,7 @@
 			break;
 
 		default:
-			return SIGILL;
+			goto SIGILL_unless_prefx_op;
 		}
 		break;
 	}
@@ -1336,7 +1528,7 @@
 			goto copcsr;
 
 		default:
-			return SIGILL;
+			goto SIGILL_unless_prefx_op;
 		}
 		break;
 	}
@@ -1349,6 +1541,11 @@
 		break;
 
 	default:
+	      SIGILL_unless_prefx_op:
+		if (MIPSInst_FUNC(ir) == prefx_op) {
+			/* ignore prefx operation */
+			break;
+		}
 		return SIGILL;
 	}
 
@@ -1370,7 +1567,12 @@
 	unsigned cond;
 	union {
 		union ieee754dp d;
-		union ieee754sp s;
+		struct {
+			union ieee754sp s;
+#ifdef CONFIG_MACH_LOONGSON
+			union ieee754sp s2; /* for Loongson paired singles */
+#endif
+		};
 		int w;
 		s64 l;
 	} rv;			/* resulting value */
@@ -1467,7 +1669,7 @@
 		case fmov_op:
 			/* an easy one */
 			SPFROMREG(rv.s, MIPSInst_FS(ir));
-			goto copcsr;
+			break;
 
 			/* binary op on handler */
 scopbop:
@@ -1664,7 +1866,7 @@
 		case fmov_op:
 			/* an easy one */
 			DPFROMREG(rv.d, MIPSInst_FS(ir));
-			goto copcsr;
+			break;
 
 			/* binary op on handler */
 dcopbop:
@@ -1781,6 +1983,83 @@
 		break;
 	}
 
+#ifdef CONFIG_MACH_LOONGSON
+	case ps_fmt:{		/* 6 */
+		/* Support for Loongson paired single fp instructions */
+		union {
+			union ieee754sp(*b) (union ieee754sp, union ieee754sp);
+			union ieee754sp(*u) (union ieee754sp);
+		} handler;
+
+		switch (MIPSInst_FUNC(ir)) {
+			/* binary ops */
+		case fadd_op:
+			handler.b = ieee754sp_add;
+			goto pscopbop;
+		case fsub_op:
+			handler.b = ieee754sp_sub;
+			goto pscopbop;
+		case fmul_op:
+			handler.b = ieee754sp_mul;
+			goto pscopbop;
+
+			/* unary  ops */
+		case fabs_op:
+			handler.u = ieee754sp_abs;
+			goto pscopuop;
+		case fneg_op:
+			handler.u = ieee754sp_neg;
+			goto pscopuop;
+		case fmov_op:
+			/* an easy one */
+			PSPFROMREG(rv.s, rv.s2, MIPSInst_FS(ir));
+			break;
+
+		      pscopbop: /* paired binary op handler */
+			{
+				struct _ieee754_csr ieee754_csr_save;
+				union ieee754sp fs1, ft1;
+				union ieee754sp fs2, ft2;
+
+				PSPFROMREG(fs1, fs2, MIPSInst_FS(ir));
+				PSPFROMREG(ft1, ft2, MIPSInst_FT(ir));
+				rv.s  = (*handler.b) (fs1, ft1);
+				ieee754_csr_save = ieee754_csr;
+				rv.s2 = (*handler.b) (fs2, ft2);
+				ieee754_csr.cx |= ieee754_csr_save.cx;
+				ieee754_csr.sx |= ieee754_csr_save.sx;
+				goto copcsr;
+			}
+		      pscopuop: /* paired unary op handler */
+			{
+				struct _ieee754_csr ieee754_csr_save;
+				union ieee754sp fs1;
+				union ieee754sp fs2;
+
+				PSPFROMREG(fs1, fs2, MIPSInst_FS(ir));
+				rv.s  = (*handler.u) (fs1);
+				ieee754_csr_save = ieee754_csr;
+				rv.s2 = (*handler.u) (fs2);
+				ieee754_csr.cx |= ieee754_csr_save.cx;
+				ieee754_csr.sx |= ieee754_csr_save.sx;
+				goto copcsr;
+			}
+			break;
+
+		default:
+			if (MIPSInst_FUNC(ir) >= fcmp_op) {
+				/* Loongson fp hardware handles all
+				   cases of fp compare insns, so we
+				   shouldn't have to */
+				printk ("Loongson paired-single fp compare"
+					" unimplemented in cp1emu.c\n");
+			}
+			return SIGILL;
+		}
+		break;
+	}
+#endif
+
 	case l_fmt:
 
 		if (!cpu_has_mips_3_4_5 && !cpu_has_mips64)
@@ -1852,6 +2131,11 @@
 
 		DITOREG(rv.l, MIPSInst_FD(ir));
 		break;
+#ifdef CONFIG_MACH_LOONGSON
+	case ps_fmt:
+		PSPTOREG(rv.s, rv.s2, MIPSInst_FD(ir));
+		break;
+#endif
 	default:
 		return SIGILL;
 	}
diff --git a/arch/mips/pci/Makefile b/arch/mips/pci/Makefile
index 300591c..ed272e2 100644
--- a/arch/mips/pci/Makefile
+++ b/arch/mips/pci/Makefile
@@ -30,6 +30,7 @@
 obj-$(CONFIG_MIPS_COBALT)	+= fixup-cobalt.o
 obj-$(CONFIG_LEMOTE_FULOONG2E)	+= fixup-fuloong2e.o ops-loongson2.o
 obj-$(CONFIG_LEMOTE_MACH2F)	+= fixup-lemote2f.o ops-loongson2.o
+obj-$(CONFIG_DEXXON_GDIUM)      += fixup-gdium.o ops-loongson2.o
 obj-$(CONFIG_LOONGSON_MACH3X)	+= fixup-loongson3.o ops-loongson3.o
 obj-$(CONFIG_MIPS_MALTA)	+= fixup-malta.o pci-malta.o
 obj-$(CONFIG_PMC_MSP7120_GW)	+= fixup-pmcmsp.o ops-pmcmsp.o
diff --git a/arch/mips/pci/fixup-gdium.c b/arch/mips/pci/fixup-gdium.c
new file mode 100644
index 0000000..b296220
--- /dev/null
+++ b/arch/mips/pci/fixup-gdium.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 yajin <yajin@vm-kernel.org>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+
+#include <loongson.h>
+/*
+ * http://www.pcidatabase.com
+ * GDIUM has different PCI mapping
+ *  slot 13 (0x1814/0x0301) -> RaLink rt2561 Wireless-G PCI
+ *  slog 14 (0x126f/0x0501) -> sm501
+ *  slot 15 (0x1033/0x0035) -> NEC Dual OHCI controllers
+ *                             plus Single EHCI controller
+ *  slot 16 (0x10ec/0x8139) -> Realtek 8139c
+ *  slot 17 (0x1033/0x00e0) -> NEC USB 2.0 Host Controller
+ */
+
+#undef INT_IRQA
+#undef INT_IRQB
+#undef INT_IRQC
+#undef INT_IRQD
+#define INT_IRQA 36
+#define INT_IRQB 37
+#define INT_IRQC 38
+#define INT_IRQD 39
+
+int __init pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+{
+	int irq = 0;
+
+	switch (slot) {
+	case 13:
+		irq = INT_IRQC + ((pin - 1) & 3);
+		break;
+	case 14:
+		irq = INT_IRQA;
+		break;
+	case 15:
+#if CONFIG_GDIUM_VERSION > 2
+		irq = INT_IRQB;
+#else
+		irq = INT_IRQA + ((pin - 1) & 3);
+#endif
+		break;
+	case 16:
+		irq = INT_IRQD;
+		break;
+#if CONFIG_GDIUM_VERSION > 2
+	case 17:
+		irq = INT_IRQC;
+		break;
+#endif
+	default:
+		pr_info(" strange pci slot number %d on gdium.\n", slot);
+		break;
+	}
+	return irq;
+}
+
+/* Do platform specific device initialization at pci_enable_device() time */
+int pcibios_plat_dev_init(struct pci_dev *dev)
+{
+	return 0;
+}
+
+/* Fixups for the USB host controllers */
+static void __init gdium_usb_host_fixup(struct pci_dev *dev)
+{
+	unsigned int val;
+	pci_read_config_dword(dev, 0xe0, &val);
+#if CONFIG_GDIUM_VERSION > 2
+	pci_write_config_dword(dev, 0xe0, (val & ~3) | 0x3);
+#else
+	pci_write_config_dword(dev, 0xe0, (val & ~7) | 0x5);
+	pci_write_config_dword(dev, 0xe4, 1<<5);
+#endif
+}
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_USB,
+				gdium_usb_host_fixup);
+#if CONFIG_GDIUM_VERSION > 2
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_CT_65550,
+				gdium_usb_host_fixup);
+#endif
diff --git a/arch/mips/power/hibernate.S b/arch/mips/power/hibernate.S
index e7567c8..498e42d 100644
--- a/arch/mips/power/hibernate.S
+++ b/arch/mips/power/hibernate.S
@@ -30,8 +30,10 @@
 END(swsusp_arch_suspend)
 
 LEAF(swsusp_arch_resume)
+#if !defined(CONFIG_MACH_LOONGSON) || !defined(CONFIG_CPU_LOONGSON2)	/* Commit 771004298d broke Loongson2.  */
 	/* Avoid TLB mismatch during and after kernel resume */
 	jal local_flush_tlb_all
+#endif
 	PTR_L t0, restore_pblist
 0:
 	PTR_L t1, PBE_ADDRESS(t0)   /* source */
diff --git a/drivers/cpufreq/loongson2_cpufreq.c b/drivers/cpufreq/loongson2_cpufreq.c
index fc897ba..ac60f6b 100644
--- a/drivers/cpufreq/loongson2_cpufreq.c
+++ b/drivers/cpufreq/loongson2_cpufreq.c
@@ -161,20 +161,32 @@
 	/* Register platform stuff */
 	ret = platform_driver_register(&platform_driver);
 	if (ret)
-		return ret;
+		goto err_return;
 
 	pr_info("cpufreq: Loongson-2F CPU frequency driver.\n");
 
-	cpufreq_register_notifier(&loongson2_cpufreq_notifier_block,
-				  CPUFREQ_TRANSITION_NOTIFIER);
+	ret = cpufreq_register_notifier(&loongson2_cpufreq_notifier_block,
+					CPUFREQ_TRANSITION_NOTIFIER);
+	if (ret)
+		goto err_platform_driver_unregister;
 
 	ret = cpufreq_register_driver(&loongson2_cpufreq_driver);
+	if (ret)
+		goto err_cpufreq_unregister_notifier;
 
-	if (!ret && !nowait) {
+	if (!nowait) {
 		saved_cpu_wait = cpu_wait;
 		cpu_wait = loongson2_cpu_wait;
 	}
 
+	return 0;
+
+ err_cpufreq_unregister_notifier:
+	cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block,
+				    CPUFREQ_TRANSITION_NOTIFIER);
+ err_platform_driver_unregister:
+	platform_driver_unregister(&platform_driver);
+ err_return:
 	return ret;
 }
 
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index dfdc269..fcf0aa4 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -861,6 +861,13 @@
 	---help---
 	Support for Zydacron remote control.
 
+config HID_GDIUM
+	bool "Gdium Fn keys support" if EMBEDDED
+	depends on USB_HID && DEXXON_GDIUM
+	default !EMBEDDED
+	---help---
+	Support for Functions keys available on Gdiums.
+
 config HID_SENSOR_HUB
 	tristate "HID Sensors framework support"
 	depends on HID && HAS_IOMEM
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index debd15b..03696f9 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -123,6 +123,7 @@
 wacom-objs			:= wacom_wac.o wacom_sys.o
 obj-$(CONFIG_HID_WACOM)		+= wacom.o
 obj-$(CONFIG_HID_WALTOP)	+= hid-waltop.o
+obj-$(CONFIG_HID_GDIUM)		+= hid-gdium.o
 obj-$(CONFIG_HID_WIIMOTE)	+= hid-wiimote.o
 obj-$(CONFIG_HID_SENSOR_HUB)	+= hid-sensor-hub.o
 
diff --git a/drivers/hid/hid-gdium.c b/drivers/hid/hid-gdium.c
new file mode 100644
index 0000000..67cc095
--- /dev/null
+++ b/drivers/hid/hid-gdium.c
@@ -0,0 +1,210 @@
+/*
+ * hid-gdium  --  Gdium laptop function keys
+ *
+ * Arnaud Patard <apatard@mandriva.com>
+ *
+ * Based on hid-apple.c
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+#define GDIUM_FN_ON	1
+
+static int fnmode = GDIUM_FN_ON;
+module_param(fnmode, int, 0644);
+MODULE_PARM_DESC(fnmode, "Mode of fn key on Gdium (0 = disabled, 1 = Enabled)");
+
+struct gdium_data {
+	unsigned int fn_on;
+};
+
+
+struct gdium_key_translation {
+	u16 from;
+	u16 to;
+};
+
+static struct gdium_key_translation gdium_fn_keys[] = {
+	{ KEY_F1,	KEY_CAMERA },
+	{ KEY_F2,	KEY_CONNECT },
+	{ KEY_F3,	KEY_MUTE },
+	{ KEY_F4,	KEY_VOLUMEUP},
+	{ KEY_F5,	KEY_VOLUMEDOWN },
+	{ KEY_F6,	KEY_SWITCHVIDEOMODE },
+	{ KEY_F7,	KEY_F19 }, /* F7+12. Have to use existant keycodes */
+	{ KEY_F8,	KEY_BRIGHTNESSUP },
+	{ KEY_F9,	KEY_BRIGHTNESSDOWN },
+	{ KEY_F10,	KEY_SLEEP },
+	{ KEY_F11,	KEY_PROG1 },
+	{ KEY_F12,	KEY_PROG2 },
+	{ KEY_UP,	KEY_PAGEUP },
+	{ KEY_DOWN,	KEY_PAGEDOWN },
+	{ KEY_INSERT,	KEY_NUMLOCK },
+	{ KEY_DELETE,	KEY_SCROLLLOCK },
+	{ KEY_T,	KEY_STOPCD },
+	{ KEY_F,	KEY_PREVIOUSSONG },
+	{ KEY_H,	KEY_NEXTSONG },
+	{ KEY_G,        KEY_PLAYPAUSE },
+	{ }
+};
+
+static struct gdium_key_translation *gdium_find_translation(
+		struct gdium_key_translation *table, u16 from)
+{
+	struct gdium_key_translation *trans;
+
+	/* Look for the translation */
+	for (trans = table; trans->from; trans++)
+		if (trans->from == from)
+			return trans;
+	return NULL;
+}
+
+static int hidinput_gdium_event(struct hid_device *hid, struct input_dev *input,
+		struct hid_usage *usage, __s32 value)
+{
+	struct gdium_data *data = hid_get_drvdata(hid);
+	struct gdium_key_translation *trans;
+	int do_translate;
+
+	if (usage->type != EV_KEY)
+		return 0;
+
+	if ((usage->code == KEY_FN)) {
+		data->fn_on = !!value;
+		input_event(input, usage->type, usage->code, value);
+		return 1;
+	}
+
+	if (fnmode) {
+		trans = gdium_find_translation(gdium_fn_keys, usage->code);
+		if (trans) {
+			do_translate = data->fn_on;
+			if (do_translate) {
+				input_event(input, usage->type, trans->to, value);
+				return 1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int gdium_input_event(struct hid_device *hdev, struct hid_field *field,
+			struct hid_usage *usage, __s32 value)
+{
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput || !usage->type)
+		return 0;
+
+	if (hidinput_gdium_event(hdev, field->hidinput->input, usage, value))
+		return 1;
+
+	return 0;
+}
+
+
+static void gdium_input_setup(struct input_dev *input)
+{
+	struct gdium_key_translation *trans;
+
+	set_bit(KEY_NUMLOCK, input->keybit);
+
+	/* Enable all needed keys */
+	for (trans = gdium_fn_keys; trans->from; trans++)
+		set_bit(trans->to, input->keybit);
+}
+
+static int gdium_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if (((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD)
+			&& ((usage->hid & HID_USAGE) == 0x82)) {
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_FN);
+		gdium_input_setup(hi->input);
+		return 1;
+	}
+	return 0;
+}
+
+static int gdium_input_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct gdium_data *data;
+	int ret;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&hdev->dev, "can't alloc gdium keyboard data\n");
+		return -ENOMEM;
+	}
+
+	hid_set_drvdata(hdev, data);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		dev_err(&hdev->dev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		dev_err(&hdev->dev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	kfree(data);
+	return ret;
+}
+static void gdium_input_remove(struct hid_device *hdev)
+{
+	hid_hw_stop(hdev);
+	kfree(hid_get_drvdata(hdev));
+}
+
+static const struct hid_device_id gdium_input_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GDIUM, USB_DEVICE_ID_GDIUM) },
+	{}
+};
+MODULE_DEVICE_TABLE(hid, gdium_input_devices);
+
+static struct hid_driver gdium_input_driver = {
+	.name = "gdium-fnkeys",
+	.id_table = gdium_input_devices,
+	.probe = gdium_input_probe,
+	.remove = gdium_input_remove,
+	.event = gdium_input_event,
+	.input_mapping = gdium_input_mapping,
+};
+
+static int gdium_input_init(void)
+{
+	int ret;
+
+	ret = hid_register_driver(&gdium_input_driver);
+	if (ret)
+		 pr_err("can't register gdium keyboard driver\n");
+
+	return ret;
+}
+static void gdium_input_exit(void)
+{
+	hid_unregister_driver(&gdium_input_driver);
+}
+
+module_init(gdium_input_init);
+module_exit(gdium_input_exit);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 9243359..8d860e3 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1007,6 +1007,9 @@
 #define USB_VENDOR_ID_ZYTRONIC		0x14c8
 #define USB_DEVICE_ID_ZYTRONIC_ZXY100	0x0005
 
+#define USB_VENDOR_ID_GDIUM		0x04B4
+#define USB_DEVICE_ID_GDIUM		0xe001
+
 #define USB_VENDOR_ID_PRIMAX	0x0461
 #define USB_DEVICE_ID_PRIMAX_KEYBOARD	0x4e05
 
diff --git a/drivers/ide/ide-iops.c b/drivers/ide/ide-iops.c
index 376f2dc..b576801 100644
--- a/drivers/ide/ide-iops.c
+++ b/drivers/ide/ide-iops.c
@@ -27,6 +27,10 @@
 #include <asm/uaccess.h>
 #include <asm/io.h>
 
+#ifdef CONFIG_LEMOTE_MACH2F
+#include <asm/bootinfo.h>
+#endif
+
 void SELECT_MASK(ide_drive_t *drive, int mask)
 {
 	const struct ide_port_ops *port_ops = drive->hwif->port_ops;
@@ -300,6 +304,11 @@
 {
 	const char **list, *m = (char *)&drive->id[ATA_ID_PROD];
 
+#ifdef CONFIG_LEMOTE_MACH2F
+	if (mips_machtype != MACH_LEMOTE_YL2F89)
+		return;
+#endif
+
 	for (list = nien_quirk_list; *list != NULL; list++)
 		if (strstr(m, *list) != NULL) {
 			drive->dev_flags |= IDE_DFLAG_NIEN_QUIRK;
diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c
index 91077ef..2cfd9ff 100644
--- a/drivers/mfd/sm501.c
+++ b/drivers/mfd/sm501.c
@@ -58,7 +58,7 @@
 struct sm501_gpio {
 	/* no gpio support, empty definition for sm501_devdata. */
 };
-#endif
+#endif	/* CONFIG_MFD_SM501_GPIO */
 
 struct sm501_devdata {
 	spinlock_t			 reg_lock;
@@ -1124,6 +1124,22 @@
 {
 	return sm->gpio.registered;
 }
+
+void sm501_configure_gpio(struct device *dev, unsigned int gpio, unsigned
+		char mode)
+{
+	unsigned long set, reg, offset = gpio;
+
+	if (offset >= 32) {
+		reg = SM501_GPIO63_32_CONTROL;
+		offset = gpio - 32;
+	} else
+		reg = SM501_GPIO31_0_CONTROL;
+
+	set = mode ? 1 << offset : 0;
+
+	sm501_modify_reg(dev, reg, set, 0);
+}
 #else
 static inline int sm501_register_gpio(struct sm501_devdata *sm)
 {
@@ -1143,7 +1159,13 @@
 {
 	return 0;
 }
-#endif
+
+void sm501_configure_gpio(struct device *dev, unsigned int gpio,
+			 unsigned char mode)
+{
+}
+#endif	/* CONFIG_MFD_SM501_GPIO */
+EXPORT_SYMBOL_GPL(sm501_configure_gpio);
 
 static int sm501_register_gpio_i2c_instance(struct sm501_devdata *sm,
 					    struct sm501_platdata_gpio_i2c *iic)
@@ -1198,6 +1220,20 @@
 	return 0;
 }
 
+/* register sm501 PWM device */
+static int sm501_register_pwm(struct sm501_devdata *sm)
+{
+	struct platform_device *pdev;
+
+	pdev = sm501_create_subdev(sm, "sm501-pwm", 2, 0);
+	if (!pdev)
+		return -ENOMEM;
+	sm501_create_subio(sm, &pdev->resource[0], 0x10020, 0xC);
+	sm501_create_irq(sm, &pdev->resource[1]);
+
+	return sm501_register_device(sm, pdev);
+}
+
 /* sm501_dbg_regs
  *
  * Debug attribute to attach to parent device to show core registers
@@ -1356,6 +1392,8 @@
 			sm501_register_uart(sm, idata->devices);
 		if (idata->devices & SM501_USE_GPIO)
 			sm501_register_gpio(sm);
+		if (idata->devices & SM501_USE_PWM)
+			sm501_register_pwm(sm);
 	}
 
 	if (pdata && pdata->gpio_i2c != NULL && pdata->gpio_i2c_nr > 0) {
@@ -1542,10 +1580,15 @@
 	.devices	= SM501_USE_ALL,
 
 	/* Errata AB-3 says that 72MHz is the fastest available
-	 * for 33MHZ PCI with proper bus-mastering operation */
-
+	 * for 33MHZ PCI with proper bus-mastering operation
+	 * For gdium, it works under 84&112M clock freq.*/
+#ifdef CONFIG_DEXXON_GDIUM
+	.mclk		= 84 * MHZ,
+	.m1xclk		= 112 * MHZ,
+#else
 	.mclk		= 72 * MHZ,
 	.m1xclk		= 144 * MHZ,
+#endif
 };
 
 static struct sm501_platdata_fbsub sm501_pdata_fbsub = {
diff --git a/drivers/net/titan_ge.c b/drivers/net/titan_ge.c
new file mode 100644
index 0000000..dc137bf8
--- /dev/null
+++ b/drivers/net/titan_ge.c
@@ -0,0 +1,2069 @@
+/*
+ * drivers/net/titan_ge.c - Driver for Titan ethernet ports
+ *
+ * Copyright (C) 2003 PMC-Sierra Inc.
+ * Author : Manish Lachwani (lachwani@pmc-sierra.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/*
+ * The MAC unit of the Titan consists of the following:
+ *
+ * -> XDMA Engine to move data to from the memory to the MAC packet FIFO
+ * -> FIFO is where the incoming and outgoing data is placed
+ * -> TRTG is the unit that pulls the data from the FIFO for Tx and pushes
+ *    the data into the FIFO for Rx
+ * -> TMAC is the outgoing MAC interface and RMAC is the incoming.
+ * -> AFX is the address filtering block
+ * -> GMII block to communicate with the PHY
+ *
+ * Rx will look like the following:
+ * GMII --> RMAC --> AFX --> TRTG --> Rx FIFO --> XDMA --> CPU memory
+ *
+ * Tx will look like the following:
+ * CPU memory --> XDMA --> Tx FIFO --> TRTG --> TMAC --> GMII
+ *
+ * The Titan driver has support for the following performance features:
+ * -> Rx side checksumming
+ * -> Jumbo Frames
+ * -> Interrupt Coalscing
+ * -> Rx NAPI
+ * -> SKB Recycling
+ * -> Transmit/Receive descriptors in SRAM
+ * -> Fast routing for IP forwarding
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/ip.h>
+#include <linux/init.h>
+#include <linux/in.h>
+#include <linux/platform_device.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/prefetch.h>
+
+/* For MII specifc registers, titan_mdio.h should be included */
+#include <net/ip.h>
+
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <asm/types.h>
+#include <asm/pgtable.h>
+#include <asm/system.h>
+#include <asm/titan_dep.h>
+
+#include "titan_ge.h"
+#include "titan_mdio.h"
+
+/* Static Function Declarations	 */
+static int titan_ge_eth_open(struct net_device *);
+static void titan_ge_eth_stop(struct net_device *);
+static struct net_device_stats *titan_ge_get_stats(struct net_device *);
+static int titan_ge_init_rx_desc_ring(titan_ge_port_info *, int, int,
+				      unsigned long, unsigned long,
+				      unsigned long);
+static int titan_ge_init_tx_desc_ring(titan_ge_port_info *, int,
+				      unsigned long, unsigned long);
+
+static int titan_ge_open(struct net_device *);
+static int titan_ge_start_xmit(struct sk_buff *, struct net_device *);
+static int titan_ge_stop(struct net_device *);
+
+static unsigned long titan_ge_tx_coal(unsigned long, int);
+
+static void titan_ge_port_reset(unsigned int);
+static int titan_ge_free_tx_queue(titan_ge_port_info *);
+static int titan_ge_rx_task(struct net_device *, titan_ge_port_info *);
+static int titan_ge_port_start(struct net_device *, titan_ge_port_info *);
+
+static int titan_ge_return_tx_desc(titan_ge_port_info *, int);
+
+/*
+ * Some configuration for the FIFO and the XDMA channel needs
+ * to be done only once for all the ports. This flag controls
+ * that
+ */
+static unsigned long config_done;
+
+/*
+ * One time out of memory flag
+ */
+static unsigned int oom_flag;
+
+static int titan_ge_poll(struct net_device *netdev, int *budget);
+
+static int titan_ge_receive_queue(struct net_device *, unsigned int);
+
+static struct platform_device *titan_ge_device[3];
+
+/* MAC Address */
+extern unsigned char titan_ge_mac_addr_base[6];
+
+unsigned long titan_ge_base;
+static unsigned long titan_ge_sram;
+
+static char titan_string[] = "titan";
+
+/*
+ * The Titan GE has two alignment requirements:
+ * -> skb->data to be cacheline aligned (32 byte)
+ * -> IP header alignment to 16 bytes
+ *
+ * The latter is not implemented. So, that results in an extra copy on
+ * the Rx. This is a big performance hog. For the former case, the
+ * dev_alloc_skb() has been replaced with titan_ge_alloc_skb(). The size
+ * requested is calculated:
+ *
+ * Ethernet Frame Size : 1518
+ * Ethernet Header     : 14
+ * Future Titan change for IP header alignment : 2
+ *
+ * Hence, we allocate (1518 + 14 + 2+ 64) = 1580 bytes.  For IP header
+ * alignment, we use skb_reserve().
+ */
+
+#define ALIGNED_RX_SKB_ADDR(addr) \
+	((((unsigned long)(addr) + (64UL - 1UL)) \
+	& ~(64UL - 1UL)) - (unsigned long)(addr))
+
+#define titan_ge_alloc_skb(__length, __gfp_flags) \
+({      struct sk_buff *__skb; \
+	__skb = alloc_skb((__length) + 64, (__gfp_flags)); \
+	if(__skb) { \
+		int __offset = (int) ALIGNED_RX_SKB_ADDR(__skb->data); \
+		if(__offset) \
+			skb_reserve(__skb, __offset); \
+	} \
+	__skb; \
+})
+
+/*
+ * Configure the GMII block of the Titan based on what the PHY tells us
+ */
+static void titan_ge_gmii_config(int port_num)
+{
+	unsigned int reg_data = 0, phy_reg;
+	int err;
+
+	err = titan_ge_mdio_read(port_num, TITAN_GE_MDIO_PHY_STATUS, &phy_reg);
+
+	if (err == TITAN_GE_MDIO_ERROR) {
+		printk(KERN_ERR
+		       "Could not read PHY control register 0x11 \n");
+		printk(KERN_ERR
+			"Setting speed to 1000 Mbps and Duplex to Full \n");
+
+		return;
+	}
+
+	err = titan_ge_mdio_write(port_num, TITAN_GE_MDIO_PHY_IE, 0);
+
+	if (phy_reg & 0x8000) {
+		if (phy_reg & 0x2000) {
+			/* Full Duplex and 1000 Mbps */
+			TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
+					(port_num << 12)), 0x201);
+		}  else {
+			/* Half Duplex and 1000 Mbps */
+			TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
+					(port_num << 12)), 0x2201);
+			}
+	}
+	if (phy_reg & 0x4000) {
+		if (phy_reg & 0x2000) {
+			/* Full Duplex and 100 Mbps */
+			TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
+					(port_num << 12)), 0x100);
+		} else {
+			/* Half Duplex and 100 Mbps */
+			TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
+					(port_num << 12)), 0x2100);
+		}
+	}
+	reg_data = TITAN_GE_READ(TITAN_GE_GMII_CONFIG_GENERAL +
+				(port_num << 12));
+	reg_data |= 0x3;
+	TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_GENERAL +
+			(port_num << 12)), reg_data);
+}
+
+/*
+ * Enable the TMAC if it is not
+ */
+static void titan_ge_enable_tx(unsigned int port_num)
+{
+	unsigned long reg_data;
+
+	reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 + (port_num << 12));
+	if (!(reg_data & 0x8000)) {
+		printk("TMAC disabled for port %d!! \n", port_num);
+
+		reg_data |= 0x0001;	/* Enable TMAC */
+		reg_data |= 0x4000;	/* CRC Check Enable */
+		reg_data |= 0x2000;	/* Padding enable */
+		reg_data |= 0x0800;	/* CRC Add enable */
+		reg_data |= 0x0080;	/* PAUSE frame */
+
+		TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 +
+				(port_num << 12)), reg_data);
+	}
+}
+
+/*
+ * Tx Timeout function
+ */
+static void titan_ge_tx_timeout(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+
+	printk(KERN_INFO "%s: TX timeout  ", netdev->name);
+	printk(KERN_INFO "Resetting card \n");
+
+	/* Do the reset outside of interrupt context */
+	schedule_work(&titan_ge_eth->tx_timeout_task);
+}
+
+/*
+ * Update the AFX tables for UC and MC for slice 0 only
+ */
+static void titan_ge_update_afx(titan_ge_port_info * titan_ge_eth)
+{
+	int port = titan_ge_eth->port_num;
+	unsigned int i;
+	volatile unsigned long reg_data = 0;
+	u8 p_addr[6];
+
+	memcpy(p_addr, titan_ge_eth->port_mac_addr, 6);
+
+	/* Set the MAC address here for TMAC and RMAC */
+	TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_HI + (port << 12)),
+		       ((p_addr[5] << 8) | p_addr[4]));
+	TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_MID + (port << 12)),
+		       ((p_addr[3] << 8) | p_addr[2]));
+	TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_LOW + (port << 12)),
+		       ((p_addr[1] << 8) | p_addr[0]));
+
+	TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_HI + (port << 12)),
+		       ((p_addr[5] << 8) | p_addr[4]));
+	TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_MID + (port << 12)),
+		       ((p_addr[3] << 8) | p_addr[2]));
+	TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_LOW + (port << 12)),
+		       ((p_addr[1] << 8) | p_addr[0]));
+
+	TITAN_GE_WRITE((0x112c | (port << 12)), 0x1);
+	/* Configure the eight address filters */
+	for (i = 0; i < 8; i++) {
+		/* Select each of the eight filters */
+		TITAN_GE_WRITE((TITAN_GE_AFX_ADDRS_FILTER_CTRL_2 +
+				(port << 12)), i);
+
+		/* Configure the match */
+		reg_data = 0x9;	/* Forward Enable Bit */
+		TITAN_GE_WRITE((TITAN_GE_AFX_ADDRS_FILTER_CTRL_0 +
+				(port << 12)), reg_data);
+
+		/* Finally, AFX Exact Match Address Registers */
+		TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_LOW + (port << 12)),
+			       ((p_addr[1] << 8) | p_addr[0]));
+		TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_MID + (port << 12)),
+			       ((p_addr[3] << 8) | p_addr[2]));
+		TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_HIGH + (port << 12)),
+			       ((p_addr[5] << 8) | p_addr[4]));
+
+		/* VLAN id set to 0 */
+		TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_VID +
+				(port << 12)), 0);
+	}
+}
+
+/*
+ * Actual Routine to reset the adapter when the timeout occurred
+ */
+static void titan_ge_tx_timeout_task(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	int port = titan_ge_eth->port_num;
+
+	printk("Titan GE: Transmit timed out. Resetting ... \n");
+
+	/* Dump debug info */
+	printk(KERN_ERR "TRTG cause : %x \n",
+			TITAN_GE_READ(0x100c + (port << 12)));
+
+	/* Fix this for the other ports */
+	printk(KERN_ERR "FIFO cause : %x \n", TITAN_GE_READ(0x482c));
+	printk(KERN_ERR "IE cause : %x \n", TITAN_GE_READ(0x0040));
+	printk(KERN_ERR "XDMA GDI ERROR : %x \n",
+			TITAN_GE_READ(0x5008 + (port << 8)));
+	printk(KERN_ERR "CHANNEL ERROR: %x \n",
+			TITAN_GE_READ(TITAN_GE_CHANNEL0_INTERRUPT
+						+ (port << 8)));
+
+	netif_device_detach(netdev);
+	titan_ge_port_reset(titan_ge_eth->port_num);
+	titan_ge_port_start(netdev, titan_ge_eth);
+	netif_device_attach(netdev);
+}
+
+/*
+ * Change the MTU of the Ethernet Device
+ */
+static int titan_ge_change_mtu(struct net_device *netdev, int new_mtu)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned long flags;
+
+	if ((new_mtu > 9500) || (new_mtu < 64))
+		return -EINVAL;
+
+	spin_lock_irqsave(&titan_ge_eth->lock, flags);
+
+	netdev->mtu = new_mtu;
+
+	/* Now we have to reopen the interface so that SKBs with the new
+	 * size will be allocated */
+
+	if (netif_running(netdev)) {
+		titan_ge_eth_stop(netdev);
+
+		if (titan_ge_eth_open(netdev) != TITAN_OK) {
+			printk(KERN_ERR
+			       "%s: Fatal error on opening device\n",
+			       netdev->name);
+			spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+			return -1;
+		}
+	}
+
+	spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+	return 0;
+}
+
+/*
+ * Titan Gbe Interrupt Handler. All the three ports send interrupt to one line
+ * only. Once an interrupt is triggered, figure out the port and then check
+ * the channel.
+ */
+static irqreturn_t titan_ge_int_handler(int irq, void *dev_id)
+{
+	struct net_device *netdev = (struct net_device *) dev_id;
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	unsigned int reg_data;
+	unsigned int eth_int_cause_error = 0, is;
+	unsigned long eth_int_cause1;
+	int err = 0;
+#ifdef CONFIG_SMP
+	unsigned long eth_int_cause2;
+#endif
+
+	/* Ack the CPU interrupt */
+	switch (port_num) {
+	case 0:
+		is = OCD_READ(RM9000x2_OCD_INTP0STATUS1);
+		OCD_WRITE(RM9000x2_OCD_INTP0CLEAR1, is);
+
+#ifdef CONFIG_SMP
+		is = OCD_READ(RM9000x2_OCD_INTP1STATUS1);
+		OCD_WRITE(RM9000x2_OCD_INTP1CLEAR1, is);
+#endif
+		break;
+
+	case 1:
+		is = OCD_READ(RM9000x2_OCD_INTP0STATUS0);
+		OCD_WRITE(RM9000x2_OCD_INTP0CLEAR0, is);
+
+#ifdef CONFIG_SMP
+		is = OCD_READ(RM9000x2_OCD_INTP1STATUS0);
+		OCD_WRITE(RM9000x2_OCD_INTP1CLEAR0, is);
+#endif
+		break;
+
+	case 2:
+		is = OCD_READ(RM9000x2_OCD_INTP0STATUS4);
+		OCD_WRITE(RM9000x2_OCD_INTP0CLEAR4, is);
+
+#ifdef CONFIG_SMP
+		is = OCD_READ(RM9000x2_OCD_INTP1STATUS4);
+		OCD_WRITE(RM9000x2_OCD_INTP1CLEAR4, is);
+#endif
+	}
+
+	eth_int_cause1 = TITAN_GE_READ(TITAN_GE_INTR_XDMA_CORE_A);
+#ifdef CONFIG_SMP
+	eth_int_cause2 = TITAN_GE_READ(TITAN_GE_INTR_XDMA_CORE_B);
+#endif
+
+	/* Spurious interrupt */
+#ifdef CONFIG_SMP
+	if ( (eth_int_cause1 == 0) && (eth_int_cause2 == 0)) {
+#else
+	if (eth_int_cause1 == 0) {
+#endif
+		eth_int_cause_error = TITAN_GE_READ(TITAN_GE_CHANNEL0_INTERRUPT +
+					(port_num << 8));
+
+		if (eth_int_cause_error == 0)
+			return IRQ_NONE;
+	}
+
+	/* Handle Tx first. No need to ack interrupts */
+#ifdef CONFIG_SMP
+	if ( (eth_int_cause1 & 0x20202) ||
+		(eth_int_cause2 & 0x20202) )
+#else
+	if (eth_int_cause1 & 0x20202)
+#endif
+		titan_ge_free_tx_queue(titan_ge_eth);
+
+	/* Handle the Rx next */
+#ifdef CONFIG_SMP
+	if ( (eth_int_cause1 & 0x10101) ||
+		(eth_int_cause2 & 0x10101)) {
+#else
+	if (eth_int_cause1 & 0x10101) {
+#endif
+		if (netif_rx_schedule_prep(netdev)) {
+			unsigned int ack;
+
+			ack = TITAN_GE_READ(TITAN_GE_INTR_XDMA_IE);
+			/* Disable Tx and Rx both */
+			if (port_num == 0)
+				ack &= ~(0x3);
+			if (port_num == 1)
+				ack &= ~(0x300);
+
+			if (port_num == 2)
+				ack &= ~(0x30000);
+
+			/* Interrupts have been disabled */
+			TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, ack);
+
+			__netif_rx_schedule(netdev);
+		}
+	}
+
+	/* Handle error interrupts */
+	if (eth_int_cause_error && (eth_int_cause_error != 0x2)) {
+		printk(KERN_ERR
+			"XDMA Channel Error : %x  on port %d\n",
+			eth_int_cause_error, port_num);
+
+		printk(KERN_ERR
+			"XDMA GDI Hardware error : %x  on port %d\n",
+			TITAN_GE_READ(0x5008 + (port_num << 8)), port_num);
+
+		printk(KERN_ERR
+			"XDMA currently has %d Rx descriptors \n",
+			TITAN_GE_READ(0x5048 + (port_num << 8)));
+
+		printk(KERN_ERR
+			"XDMA currently has prefetcted %d Rx descriptors \n",
+			TITAN_GE_READ(0x505c + (port_num << 8)));
+
+		TITAN_GE_WRITE((TITAN_GE_CHANNEL0_INTERRUPT +
+			       (port_num << 8)), eth_int_cause_error);
+	}
+
+	/*
+	 * PHY interrupt to inform abt the changes. Reading the
+	 * PHY Status register will clear the interrupt
+	 */
+	if ((!(eth_int_cause1 & 0x30303)) &&
+		(eth_int_cause_error == 0)) {
+		err =
+		    titan_ge_mdio_read(port_num,
+			       TITAN_GE_MDIO_PHY_IS, &reg_data);
+
+		if (reg_data & 0x0400) {
+			/* Link status change */
+			titan_ge_mdio_read(port_num,
+				   TITAN_GE_MDIO_PHY_STATUS, &reg_data);
+			if (!(reg_data & 0x0400)) {
+				/* Link is down */
+				netif_carrier_off(netdev);
+				netif_stop_queue(netdev);
+			} else {
+				/* Link is up */
+				netif_carrier_on(netdev);
+				netif_wake_queue(netdev);
+
+				/* Enable the queue */
+				titan_ge_enable_tx(port_num);
+			}
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Multicast and Promiscuous mode set. The
+ * set_multi entry point is called whenever the
+ * multicast address list or the network interface
+ * flags are updated.
+ */
+static void titan_ge_set_multi(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	unsigned long reg_data;
+
+	reg_data = TITAN_GE_READ(TITAN_GE_AFX_ADDRS_FILTER_CTRL_1 +
+				(port_num << 12));
+
+	if (netdev->flags & IFF_PROMISC) {
+		reg_data |= 0x2;
+	}
+	else if (netdev->flags & IFF_ALLMULTI) {
+		reg_data |= 0x01;
+		reg_data |= 0x400; /* Use the 64-bit Multicast Hash bin */
+	}
+	else {
+		reg_data = 0x2;
+	}
+
+	TITAN_GE_WRITE((TITAN_GE_AFX_ADDRS_FILTER_CTRL_1 +
+			(port_num << 12)), reg_data);
+	if (reg_data & 0x01) {
+		TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_LOW +
+				(port_num << 12)), 0xffff);
+		TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_MIDLOW +
+				(port_num << 12)), 0xffff);
+		TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_MIDHI +
+				(port_num << 12)), 0xffff);
+		TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_HI +
+				(port_num << 12)), 0xffff);
+	}
+}
+
+/*
+ * Open the network device
+ */
+static int titan_ge_open(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	unsigned int irq = TITAN_ETH_PORT_IRQ - port_num;
+	int retval;
+
+	retval = request_irq(irq, titan_ge_int_handler,
+		     SA_INTERRUPT | SA_SAMPLE_RANDOM , netdev->name, netdev);
+
+	if (retval != 0) {
+		printk(KERN_ERR "Cannot assign IRQ number to TITAN GE \n");
+		return -1;
+	}
+
+	netdev->irq = irq;
+	printk(KERN_INFO "Assigned IRQ %d to port %d\n", irq, port_num);
+
+	spin_lock_irq(&(titan_ge_eth->lock));
+
+	if (titan_ge_eth_open(netdev) != TITAN_OK) {
+		spin_unlock_irq(&(titan_ge_eth->lock));
+		printk("%s: Error opening interface \n", netdev->name);
+		free_irq(netdev->irq, netdev);
+		return -EBUSY;
+	}
+
+	spin_unlock_irq(&(titan_ge_eth->lock));
+
+	return 0;
+}
+
+/*
+ * Allocate the SKBs for the Rx ring. Also used
+ * for refilling the queue
+ */
+static int titan_ge_rx_task(struct net_device *netdev,
+				titan_ge_port_info *titan_ge_port)
+{
+	struct device *device = &titan_ge_device[titan_ge_port->port_num]->dev;
+	volatile titan_ge_rx_desc *rx_desc;
+	struct sk_buff *skb;
+	int rx_used_desc;
+	int count = 0;
+
+	while (titan_ge_port->rx_ring_skbs < titan_ge_port->rx_ring_size) {
+
+	/* First try to get the skb from the recycler */
+#ifdef TITAN_GE_JUMBO_FRAMES
+		skb = titan_ge_alloc_skb(TITAN_GE_JUMBO_BUFSIZE, GFP_ATOMIC);
+#else
+		skb = titan_ge_alloc_skb(TITAN_GE_STD_BUFSIZE, GFP_ATOMIC);
+#endif
+		if (unlikely(!skb)) {
+			/* OOM, set the flag */
+			printk("OOM \n");
+			oom_flag = 1;
+			break;
+		}
+		count++;
+		skb->dev = netdev;
+
+		titan_ge_port->rx_ring_skbs++;
+
+		rx_used_desc = titan_ge_port->rx_used_desc_q;
+		rx_desc = &(titan_ge_port->rx_desc_area[rx_used_desc]);
+
+#ifdef TITAN_GE_JUMBO_FRAMES
+		rx_desc->buffer_addr = dma_map_single(device, skb->data,
+				TITAN_GE_JUMBO_BUFSIZE - 2, DMA_FROM_DEVICE);
+#else
+		rx_desc->buffer_addr = dma_map_single(device, skb->data,
+				TITAN_GE_STD_BUFSIZE - 2, DMA_FROM_DEVICE);
+#endif
+
+		titan_ge_port->rx_skb[rx_used_desc] = skb;
+		rx_desc->cmd_sts = TITAN_GE_RX_BUFFER_OWNED;
+
+		titan_ge_port->rx_used_desc_q =
+			(rx_used_desc + 1) % TITAN_GE_RX_QUEUE;
+	}
+
+	return count;
+}
+
+/*
+ * Actual init of the Tital GE port. There is one register for
+ * the channel configuration
+ */
+static void titan_port_init(struct net_device *netdev,
+			    titan_ge_port_info * titan_ge_eth)
+{
+	unsigned long reg_data;
+
+	titan_ge_port_reset(titan_ge_eth->port_num);
+
+	/* First reset the TMAC */
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
+	reg_data |= 0x80000000;
+	TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
+
+	udelay(30);
+
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
+	reg_data &= ~(0xc0000000);
+	TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
+
+	/* Now reset the RMAC */
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
+	reg_data |= 0x00080000;
+	TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
+
+	udelay(30);
+
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
+	reg_data &= ~(0x000c0000);
+	TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
+}
+
+/*
+ * Start the port. All the hardware specific configuration
+ * for the XDMA, Tx FIFO, Rx FIFO, TMAC, RMAC, TRTG and AFX
+ * go here
+ */
+static int titan_ge_port_start(struct net_device *netdev,
+				titan_ge_port_info * titan_port)
+{
+	volatile unsigned long reg_data, reg_data1;
+	int port_num = titan_port->port_num;
+	int count = 0;
+	unsigned long reg_data_1;
+
+	if (config_done == 0) {
+		reg_data = TITAN_GE_READ(0x0004);
+		reg_data |= 0x100;
+		TITAN_GE_WRITE(0x0004, reg_data);
+
+		reg_data &= ~(0x100);
+		TITAN_GE_WRITE(0x0004, reg_data);
+
+		/* Turn on GMII/MII mode and turn off TBI mode */
+		reg_data = TITAN_GE_READ(TITAN_GE_TSB_CTRL_1);
+		reg_data |= 0x00000700;
+		reg_data &= ~(0x00800000); /* Fencing */
+
+		TITAN_GE_WRITE(0x000c, 0x00001100);
+
+		TITAN_GE_WRITE(TITAN_GE_TSB_CTRL_1, reg_data);
+
+		/* Set the CPU Resource Limit register */
+		TITAN_GE_WRITE(0x00f8, 0x8);
+
+		/* Be conservative when using the BIU buffers */
+		TITAN_GE_WRITE(0x0068, 0x4);
+	}
+
+	titan_port->tx_threshold = 0;
+	titan_port->rx_threshold = 0;
+
+	/* We need to write the descriptors for Tx and Rx */
+	TITAN_GE_WRITE((TITAN_GE_CHANNEL0_TX_DESC + (port_num << 8)),
+		       (unsigned long) titan_port->tx_dma);
+	TITAN_GE_WRITE((TITAN_GE_CHANNEL0_RX_DESC + (port_num << 8)),
+		       (unsigned long) titan_port->rx_dma);
+
+	if (config_done == 0) {
+		/* Step 1:  XDMA config	*/
+		reg_data = TITAN_GE_READ(TITAN_GE_XDMA_CONFIG);
+		reg_data &= ~(0x80000000);      /* clear reset */
+		reg_data |= 0x1 << 29;	/* sparse tx descriptor spacing */
+		reg_data |= 0x1 << 28;	/* sparse rx descriptor spacing */
+		reg_data |= (0x1 << 23) | (0x1 << 24);  /* Descriptor Coherency */
+		reg_data |= (0x1 << 21) | (0x1 << 22);  /* Data Coherency */
+		TITAN_GE_WRITE(TITAN_GE_XDMA_CONFIG, reg_data);
+	}
+
+	/* IR register for the XDMA */
+	reg_data = TITAN_GE_READ(TITAN_GE_GDI_INTERRUPT_ENABLE + (port_num << 8));
+	reg_data |= 0x80068000; /* No Rx_OOD */
+	TITAN_GE_WRITE((TITAN_GE_GDI_INTERRUPT_ENABLE + (port_num << 8)), reg_data);
+
+	/* Start the Tx and Rx XDMA controller */
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG + (port_num << 8));
+	reg_data &= 0x4fffffff;     /* Clear tx reset */
+	reg_data &= 0xfff4ffff;     /* Clear rx reset */
+
+#ifdef TITAN_GE_JUMBO_FRAMES
+	reg_data |= 0xa0 | 0x30030000;
+#else
+	reg_data |= 0x40 | 0x20030000;
+#endif
+
+#ifndef CONFIG_SMP
+	reg_data &= ~(0x10);
+	reg_data |= 0x0f; /* All of the packet */
+#endif
+
+	TITAN_GE_WRITE((TITAN_GE_CHANNEL0_CONFIG + (port_num << 8)), reg_data);
+
+	/* Rx desc count */
+	count = titan_ge_rx_task(netdev, titan_port);
+	TITAN_GE_WRITE((0x5048 + (port_num << 8)), count);
+	count = TITAN_GE_READ(0x5048 + (port_num << 8));
+
+	udelay(30);
+
+	/*
+	 * Step 2:  Configure the SDQPF, i.e. FIFO
+	 */
+	if (config_done == 0) {
+		reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_RXFIFO_CTL);
+		reg_data = 0x1;
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_CTL, reg_data);
+		reg_data &= ~(0x1);
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_CTL, reg_data);
+		reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_RXFIFO_CTL);
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_CTL, reg_data);
+
+		reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_TXFIFO_CTL);
+		reg_data = 0x1;
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_CTL, reg_data);
+		reg_data &= ~(0x1);
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_CTL, reg_data);
+		reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_TXFIFO_CTL);
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_CTL, reg_data);
+	}
+	/*
+	 * Enable RX FIFO 0, 4 and 8
+	 */
+	if (port_num == 0) {
+		reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_RXFIFO_0);
+
+		reg_data |= 0x100000;
+		reg_data |= (0xff << 10);
+
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_0, reg_data);
+		/*
+		 * BAV2,BAV and DAV settings for the Rx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x4844);
+		reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
+		TITAN_GE_WRITE(0x4844, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_0, reg_data);
+
+		reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_TXFIFO_0);
+		reg_data |= 0x100000;
+
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_0, reg_data);
+
+		reg_data |= (0xff << 10);
+
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_0, reg_data);
+
+		/*
+		 * BAV2, BAV and DAV settings for the Tx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x4944);
+		reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
+
+		TITAN_GE_WRITE(0x4944, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_0, reg_data);
+
+	}
+
+	if (port_num == 1) {
+		reg_data = TITAN_GE_READ(0x4870);
+
+		reg_data |= 0x100000;
+		reg_data |= (0xff << 10) | (0xff + 1);
+
+		TITAN_GE_WRITE(0x4870, reg_data);
+		/*
+		 * BAV2,BAV and DAV settings for the Rx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x4874);
+		reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
+		TITAN_GE_WRITE(0x4874, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(0x4870, reg_data);
+
+		reg_data = TITAN_GE_READ(0x494c);
+		reg_data |= 0x100000;
+
+		TITAN_GE_WRITE(0x494c, reg_data);
+		reg_data |= (0xff << 10) | (0xff + 1);
+		TITAN_GE_WRITE(0x494c, reg_data);
+
+		/*
+		 * BAV2, BAV and DAV settings for the Tx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x4950);
+		reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
+
+		TITAN_GE_WRITE(0x4950, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(0x494c, reg_data);
+	}
+
+	/*
+	 * Titan 1.2 revision does support port #2
+	 */
+	if (port_num == 2) {
+		/*
+		 * Put the descriptors in the SRAM
+		 */
+		reg_data = TITAN_GE_READ(0x48a0);
+
+		reg_data |= 0x100000;
+		reg_data |= (0xff << 10) | (2*(0xff + 1));
+
+		TITAN_GE_WRITE(0x48a0, reg_data);
+		/*
+		 * BAV2,BAV and DAV settings for the Rx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x48a4);
+		reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
+		TITAN_GE_WRITE(0x48a4, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(0x48a0, reg_data);
+
+		reg_data = TITAN_GE_READ(0x4958);
+		reg_data |= 0x100000;
+
+		TITAN_GE_WRITE(0x4958, reg_data);
+		reg_data |= (0xff << 10) | (2*(0xff + 1));
+		TITAN_GE_WRITE(0x4958, reg_data);
+
+		/*
+		 * BAV2, BAV and DAV settings for the Tx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x495c);
+		reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
+
+		TITAN_GE_WRITE(0x495c, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(0x4958, reg_data);
+	}
+
+	if (port_num == 2) {
+		reg_data = TITAN_GE_READ(0x48a0);
+
+		reg_data |= 0x100000;
+		reg_data |= (0xff << 10) | (2*(0xff + 1));
+
+		TITAN_GE_WRITE(0x48a0, reg_data);
+		/*
+		 * BAV2,BAV and DAV settings for the Rx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x48a4);
+		reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
+		TITAN_GE_WRITE(0x48a4, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(0x48a0, reg_data);
+
+		reg_data = TITAN_GE_READ(0x4958);
+		reg_data |= 0x100000;
+
+		TITAN_GE_WRITE(0x4958, reg_data);
+		reg_data |= (0xff << 10) | (2*(0xff + 1));
+		TITAN_GE_WRITE(0x4958, reg_data);
+
+		/*
+		 * BAV2, BAV and DAV settings for the Tx FIFO
+		 */
+		reg_data1 = TITAN_GE_READ(0x495c);
+		reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
+
+		TITAN_GE_WRITE(0x495c, reg_data1);
+
+		reg_data &= ~(0x00100000);
+		reg_data |= 0x200000;
+
+		TITAN_GE_WRITE(0x4958, reg_data);
+	}
+
+	/*
+	 * Step 3:  TRTG block enable
+	 */
+	reg_data = TITAN_GE_READ(TITAN_GE_TRTG_CONFIG + (port_num << 12));
+
+	/*
+	 * This is the 1.2 revision of the chip. It has fix for the
+	 * IP header alignment. Now, the IP header begins at an
+	 * aligned address and this wont need an extra copy in the
+	 * driver. This performance drawback existed in the previous
+	 * versions of the silicon
+	 */
+	reg_data_1 = TITAN_GE_READ(0x103c + (port_num << 12));
+	reg_data_1 |= 0x40000000;
+	TITAN_GE_WRITE((0x103c + (port_num << 12)), reg_data_1);
+
+	reg_data_1 |= 0x04000000;
+	TITAN_GE_WRITE((0x103c + (port_num << 12)), reg_data_1);
+
+	mdelay(5);
+
+	reg_data_1 &= ~(0x04000000);
+	TITAN_GE_WRITE((0x103c + (port_num << 12)), reg_data_1);
+
+	mdelay(5);
+
+	reg_data |= 0x0001;
+	TITAN_GE_WRITE((TITAN_GE_TRTG_CONFIG + (port_num << 12)), reg_data);
+
+	/*
+	 * Step 4:  Start the Tx activity
+	 */
+	TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_2 + (port_num << 12)), 0xe197);
+#ifdef TITAN_GE_JUMBO_FRAMES
+	TITAN_GE_WRITE((0x1258 + (port_num << 12)), 0x4000);
+#endif
+	reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 + (port_num << 12));
+	reg_data |= 0x0001;	/* Enable TMAC */
+	reg_data |= 0x6c70;	/* PAUSE also set */
+
+	TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 + (port_num << 12)), reg_data);
+
+	udelay(30);
+
+	/* Destination Address drop bit */
+	reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_2 + (port_num << 12));
+	reg_data |= 0x218;        /* DA_DROP bit and pause */
+	TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_2 + (port_num << 12)), reg_data);
+
+	TITAN_GE_WRITE((0x1218 + (port_num << 12)), 0x3);
+
+#ifdef TITAN_GE_JUMBO_FRAMES
+	TITAN_GE_WRITE((0x1208 + (port_num << 12)), 0x4000);
+#endif
+	/* Start the Rx activity */
+	reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 + (port_num << 12));
+	reg_data |= 0x0001;	/* RMAC Enable */
+	reg_data |= 0x0010;	/* CRC Check enable */
+	reg_data |= 0x0040;	/* Min Frame check enable */
+	reg_data |= 0x4400;	/* Max Frame check enable */
+
+	TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 + (port_num << 12)), reg_data);
+
+	udelay(30);
+
+	/*
+	 * Enable the Interrupts for Tx and Rx
+	 */
+	reg_data1 = TITAN_GE_READ(TITAN_GE_INTR_XDMA_IE);
+
+	if (port_num == 0) {
+		reg_data1 |= 0x3;
+#ifdef CONFIG_SMP
+		TITAN_GE_WRITE(0x0038, 0x003);
+#else
+		TITAN_GE_WRITE(0x0038, 0x303);
+#endif
+	}
+
+	if (port_num == 1) {
+		reg_data1 |= 0x300;
+	}
+
+	if (port_num == 2)
+		reg_data1 |= 0x30000;
+
+	TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, reg_data1);
+	TITAN_GE_WRITE(0x003c, 0x300);
+
+	if (config_done == 0) {
+		TITAN_GE_WRITE(0x0024, 0x04000024);	/* IRQ vector */
+		TITAN_GE_WRITE(0x0020, 0x000fb000);	/* INTMSG base */
+	}
+
+	/* Priority */
+	reg_data = TITAN_GE_READ(0x1038 + (port_num << 12));
+	reg_data &= ~(0x00f00000);
+	TITAN_GE_WRITE((0x1038 + (port_num << 12)), reg_data);
+
+	/* Step 5:  GMII config */
+	titan_ge_gmii_config(port_num);
+
+	if (config_done == 0) {
+		TITAN_GE_WRITE(0x1a80, 0);
+		config_done = 1;
+	}
+
+	return TITAN_OK;
+}
+
+/*
+ * Function to queue the packet for the Ethernet device
+ */
+static void titan_ge_tx_queue(titan_ge_port_info * titan_ge_eth,
+				struct sk_buff * skb)
+{
+	struct device *device = &titan_ge_device[titan_ge_eth->port_num]->dev;
+	unsigned int curr_desc = titan_ge_eth->tx_curr_desc_q;
+	volatile titan_ge_tx_desc *tx_curr;
+	int port_num = titan_ge_eth->port_num;
+
+	tx_curr = &(titan_ge_eth->tx_desc_area[curr_desc]);
+	tx_curr->buffer_addr =
+		dma_map_single(device, skb->data, skb_headlen(skb),
+			       DMA_TO_DEVICE);
+
+	titan_ge_eth->tx_skb[curr_desc] = (struct sk_buff *) skb;
+	tx_curr->buffer_len = skb_headlen(skb);
+
+	/* Last descriptor enables interrupt and changes ownership */
+	tx_curr->cmd_sts = 0x1 | (1 << 15) | (1 << 5);
+
+	/* Kick the XDMA to start the transfer from memory to the FIFO */
+	TITAN_GE_WRITE((0x5044 + (port_num << 8)), 0x1);
+
+	/* Current descriptor updated */
+	titan_ge_eth->tx_curr_desc_q = (curr_desc + 1) % TITAN_GE_TX_QUEUE;
+
+	/* Prefetch the next descriptor */
+	prefetch((const void *)
+		 &titan_ge_eth->tx_desc_area[titan_ge_eth->tx_curr_desc_q]);
+}
+
+/*
+ * Actually does the open of the Ethernet device
+ */
+static int titan_ge_eth_open(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	struct device *device = &titan_ge_device[port_num]->dev;
+	unsigned long reg_data;
+	unsigned int phy_reg;
+	int err = 0;
+
+	/* Stop the Rx activity */
+	reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 + (port_num << 12));
+	reg_data &= ~(0x00000001);
+	TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 + (port_num << 12)), reg_data);
+
+	/* Clear the port interrupts */
+	TITAN_GE_WRITE((TITAN_GE_CHANNEL0_INTERRUPT + (port_num << 8)), 0x0);
+
+	if (config_done == 0) {
+		TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0);
+		TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_B, 0);
+	}
+
+	/* Set the MAC Address */
+	memcpy(titan_ge_eth->port_mac_addr, netdev->dev_addr, 6);
+
+	if (config_done == 0)
+		titan_port_init(netdev, titan_ge_eth);
+
+	titan_ge_update_afx(titan_ge_eth);
+
+	/* Allocate the Tx ring now */
+	titan_ge_eth->tx_ring_skbs = 0;
+	titan_ge_eth->tx_ring_size = TITAN_GE_TX_QUEUE;
+
+	/* Allocate space in the SRAM for the descriptors */
+	titan_ge_eth->tx_desc_area = (titan_ge_tx_desc *)
+		(titan_ge_sram + TITAN_TX_RING_BYTES * port_num);
+	titan_ge_eth->tx_dma = TITAN_SRAM_BASE + TITAN_TX_RING_BYTES * port_num;
+
+	if (!titan_ge_eth->tx_desc_area) {
+		printk(KERN_ERR
+		       "%s: Cannot allocate Tx Ring (size %d bytes) for port %d\n",
+		       netdev->name, TITAN_TX_RING_BYTES, port_num);
+		return -ENOMEM;
+	}
+
+	memset(titan_ge_eth->tx_desc_area, 0, titan_ge_eth->tx_desc_area_size);
+
+	/* Now initialize the Tx descriptor ring */
+	titan_ge_init_tx_desc_ring(titan_ge_eth,
+				   titan_ge_eth->tx_ring_size,
+				   (unsigned long) titan_ge_eth->tx_desc_area,
+				   (unsigned long) titan_ge_eth->tx_dma);
+
+	/* Allocate the Rx ring now */
+	titan_ge_eth->rx_ring_size = TITAN_GE_RX_QUEUE;
+	titan_ge_eth->rx_ring_skbs = 0;
+
+	titan_ge_eth->rx_desc_area =
+		(titan_ge_rx_desc *)(titan_ge_sram + 0x1000 + TITAN_RX_RING_BYTES * port_num);
+
+	titan_ge_eth->rx_dma = TITAN_SRAM_BASE + 0x1000 + TITAN_RX_RING_BYTES * port_num;
+
+	if (!titan_ge_eth->rx_desc_area) {
+		printk(KERN_ERR "%s: Cannot allocate Rx Ring (size %d bytes)\n",
+		       netdev->name, TITAN_RX_RING_BYTES);
+
+		printk(KERN_ERR "%s: Freeing previously allocated TX queues...",
+		       netdev->name);
+
+		dma_free_coherent(device, titan_ge_eth->tx_desc_area_size,
+				    (void *) titan_ge_eth->tx_desc_area,
+				    titan_ge_eth->tx_dma);
+
+		return -ENOMEM;
+	}
+
+	memset(titan_ge_eth->rx_desc_area, 0, titan_ge_eth->rx_desc_area_size);
+
+	/* Now initialize the Rx ring */
+#ifdef TITAN_GE_JUMBO_FRAMES
+	if ((titan_ge_init_rx_desc_ring
+	    (titan_ge_eth, titan_ge_eth->rx_ring_size, TITAN_GE_JUMBO_BUFSIZE,
+	     (unsigned long) titan_ge_eth->rx_desc_area, 0,
+	      (unsigned long) titan_ge_eth->rx_dma)) == 0)
+#else
+	if ((titan_ge_init_rx_desc_ring
+	     (titan_ge_eth, titan_ge_eth->rx_ring_size, TITAN_GE_STD_BUFSIZE,
+	      (unsigned long) titan_ge_eth->rx_desc_area, 0,
+	      (unsigned long) titan_ge_eth->rx_dma)) == 0)
+#endif
+		panic("%s: Error initializing RX Ring\n", netdev->name);
+
+	/* Fill the Rx ring with the SKBs */
+	titan_ge_port_start(netdev, titan_ge_eth);
+
+	/*
+	 * Check if Interrupt Coalscing needs to be turned on. The
+	 * values specified in the register is multiplied by
+	 * (8 x 64 nanoseconds) to determine when an interrupt should
+	 * be sent to the CPU.
+	 */
+
+	if (TITAN_GE_TX_COAL) {
+		titan_ge_eth->tx_int_coal =
+		    titan_ge_tx_coal(TITAN_GE_TX_COAL, port_num);
+	}
+
+	err = titan_ge_mdio_read(port_num, TITAN_GE_MDIO_PHY_STATUS, &phy_reg);
+	if (err == TITAN_GE_MDIO_ERROR) {
+		printk(KERN_ERR
+		       "Could not read PHY control register 0x11 \n");
+		return TITAN_ERROR;
+	}
+	if (!(phy_reg & 0x0400)) {
+		netif_carrier_off(netdev);
+		netif_stop_queue(netdev);
+		return TITAN_ERROR;
+	} else {
+		netif_carrier_on(netdev);
+		netif_start_queue(netdev);
+	}
+
+	return TITAN_OK;
+}
+
+/*
+ * Queue the packet for Tx. Currently no support for zero copy,
+ * checksum offload and Scatter Gather. The chip does support
+ * Scatter Gather only. But, that wont help here since zero copy
+ * requires support for Tx checksumming also.
+ */
+int titan_ge_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned long flags;
+	struct net_device_stats *stats;
+//printk("titan_ge_start_xmit\n");
+
+	stats = &titan_ge_eth->stats;
+	spin_lock_irqsave(&titan_ge_eth->lock, flags);
+
+	if ((TITAN_GE_TX_QUEUE - titan_ge_eth->tx_ring_skbs) <=
+	    (skb_shinfo(skb)->nr_frags + 1)) {
+		netif_stop_queue(netdev);
+		spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+		printk(KERN_ERR "Tx OOD \n");
+		return 1;
+	}
+
+	titan_ge_tx_queue(titan_ge_eth, skb);
+	titan_ge_eth->tx_ring_skbs++;
+
+	if (TITAN_GE_TX_QUEUE <= (titan_ge_eth->tx_ring_skbs + 4)) {
+		spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+		titan_ge_free_tx_queue(titan_ge_eth);
+		spin_lock_irqsave(&titan_ge_eth->lock, flags);
+	}
+
+	stats->tx_bytes += skb->len;
+	stats->tx_packets++;
+
+	spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+
+	netdev->trans_start = jiffies;
+
+	return 0;
+}
+
+/*
+ * Actually does the Rx. Rx side checksumming supported.
+ */
+static int titan_ge_rx(struct net_device *netdev, int port_num,
+			titan_ge_port_info * titan_ge_port,
+		       titan_ge_packet * packet)
+{
+	int rx_curr_desc, rx_used_desc;
+	volatile titan_ge_rx_desc *rx_desc;
+
+	rx_curr_desc = titan_ge_port->rx_curr_desc_q;
+	rx_used_desc = titan_ge_port->rx_used_desc_q;
+
+	if (((rx_curr_desc + 1) % TITAN_GE_RX_QUEUE) == rx_used_desc)
+		return TITAN_ERROR;
+
+	rx_desc = &(titan_ge_port->rx_desc_area[rx_curr_desc]);
+
+	if (rx_desc->cmd_sts & TITAN_GE_RX_BUFFER_OWNED)
+		return TITAN_ERROR;
+
+	packet->skb = titan_ge_port->rx_skb[rx_curr_desc];
+	packet->len = (rx_desc->cmd_sts & 0x7fff);
+
+	/*
+	 * At this point, we dont know if the checksumming
+	 * actually helps relieve CPU. So, keep it for
+	 * port 0 only
+	 */
+	packet->checksum = ntohs((rx_desc->buffer & 0xffff0000) >> 16);
+	packet->cmd_sts = rx_desc->cmd_sts;
+
+	titan_ge_port->rx_curr_desc_q = (rx_curr_desc + 1) % TITAN_GE_RX_QUEUE;
+
+	/* Prefetch the next descriptor */
+	prefetch((const void *)
+	       &titan_ge_port->rx_desc_area[titan_ge_port->rx_curr_desc_q + 1]);
+
+	return TITAN_OK;
+}
+
+/*
+ * Free the Tx queue of the used SKBs
+ */
+static int titan_ge_free_tx_queue(titan_ge_port_info *titan_ge_eth)
+{
+	unsigned long flags;
+
+	/* Take the lock */
+	spin_lock_irqsave(&(titan_ge_eth->lock), flags);
+
+	while (titan_ge_return_tx_desc(titan_ge_eth, titan_ge_eth->port_num) == 0)
+		if (titan_ge_eth->tx_ring_skbs != 1)
+			titan_ge_eth->tx_ring_skbs--;
+
+	spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+
+	return TITAN_OK;
+}
+
+/*
+ * Threshold beyond which we do the cleaning of
+ * Tx queue and new allocation for the Rx
+ * queue
+ */
+#define	TX_THRESHOLD	4
+#define	RX_THRESHOLD	10
+
+/*
+ * Receive the packets and send it to the kernel.
+ */
+static int titan_ge_receive_queue(struct net_device *netdev, unsigned int max)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	titan_ge_packet packet;
+	struct net_device_stats *stats;
+	struct sk_buff *skb;
+	unsigned long received_packets = 0;
+	unsigned int ack;
+
+	stats = &titan_ge_eth->stats;
+
+	while ((--max)
+	       && (titan_ge_rx(netdev, port_num, titan_ge_eth, &packet) == TITAN_OK)) {
+		skb = (struct sk_buff *) packet.skb;
+
+		titan_ge_eth->rx_ring_skbs--;
+
+		if (--titan_ge_eth->rx_work_limit < 0)
+			break;
+		received_packets++;
+
+		stats->rx_packets++;
+		stats->rx_bytes += packet.len;
+
+		if ((packet.cmd_sts & TITAN_GE_RX_PERR) ||
+			(packet.cmd_sts & TITAN_GE_RX_OVERFLOW_ERROR) ||
+			(packet.cmd_sts & TITAN_GE_RX_TRUNC) ||
+			(packet.cmd_sts & TITAN_GE_RX_CRC_ERROR)) {
+				stats->rx_dropped++;
+				dev_kfree_skb_any(skb);
+
+				continue;
+		}
+		/*
+		 * Either support fast path or slow path. Decision
+		 * making can really slow down the performance. The
+		 * idea is to cut down the number of checks and improve
+		 * the fastpath.
+		 */
+
+		skb_put(skb, packet.len - 2);
+
+		/*
+		 * Increment data pointer by two since thats where
+		 * the MAC starts
+		 */
+		skb_reserve(skb, 2);
+		skb->protocol = eth_type_trans(skb, netdev);
+		netif_receive_skb(skb);
+
+		if (titan_ge_eth->rx_threshold > RX_THRESHOLD) {
+			ack = titan_ge_rx_task(netdev, titan_ge_eth);
+			TITAN_GE_WRITE((0x5048 + (port_num << 8)), ack);
+			titan_ge_eth->rx_threshold = 0;
+		} else
+			titan_ge_eth->rx_threshold++;
+
+		if (titan_ge_eth->tx_threshold > TX_THRESHOLD) {
+			titan_ge_eth->tx_threshold = 0;
+			titan_ge_free_tx_queue(titan_ge_eth);
+		}
+		else
+			titan_ge_eth->tx_threshold++;
+
+	}
+	return received_packets;
+}
+
+
+/*
+ * Enable the Rx side interrupts
+ */
+static void titan_ge_enable_int(unsigned int port_num,
+			titan_ge_port_info *titan_ge_eth,
+			struct net_device *netdev)
+{
+	unsigned long reg_data = TITAN_GE_READ(TITAN_GE_INTR_XDMA_IE);
+
+	if (port_num == 0)
+		reg_data |= 0x3;
+	if (port_num == 1)
+		reg_data |= 0x300;
+	if (port_num == 2)
+		reg_data |= 0x30000;
+
+	/* Re-enable interrupts */
+	TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, reg_data);
+}
+
+/*
+ * Main function to handle the polling for Rx side NAPI.
+ * Receive interrupts have been disabled at this point.
+ * The poll schedules the transmit followed by receive.
+ */
+static int titan_ge_poll(struct net_device *netdev, int *budget)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	int port_num = titan_ge_eth->port_num;
+	int work_done = 0;
+	unsigned long flags, status;
+
+	titan_ge_eth->rx_work_limit = *budget;
+	if (titan_ge_eth->rx_work_limit > netdev->quota)
+		titan_ge_eth->rx_work_limit = netdev->quota;
+
+	do {
+		/* Do the transmit cleaning work here */
+		titan_ge_free_tx_queue(titan_ge_eth);
+
+		/* Ack the Rx interrupts */
+		if (port_num == 0)
+			TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0x3);
+		if (port_num == 1)
+			TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0x300);
+		if (port_num == 2)
+			TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0x30000);
+
+		work_done += titan_ge_receive_queue(netdev, 0);
+
+		/* Out of quota and there is work to be done */
+		if (titan_ge_eth->rx_work_limit < 0)
+			goto not_done;
+
+		/* Receive alloc_skb could lead to OOM */
+		if (oom_flag == 1) {
+			oom_flag = 0;
+			goto oom;
+		}
+
+		status = TITAN_GE_READ(TITAN_GE_INTR_XDMA_CORE_A);
+	} while (status & 0x30300);
+
+	/* If we are here, then no more interrupts to process */
+	goto done;
+
+not_done:
+	*budget -= work_done;
+	netdev->quota -= work_done;
+	return 1;
+
+oom:
+	printk(KERN_ERR "OOM \n");
+	netif_rx_complete(netdev);
+	return 0;
+
+done:
+	/*
+	 * No more packets on the poll list. Turn the interrupts
+	 * back on and we should be able to catch the new
+	 * packets in the interrupt handler
+	 */
+	if (!work_done)
+		work_done = 1;
+
+	*budget -= work_done;
+	netdev->quota -= work_done;
+
+	spin_lock_irqsave(&titan_ge_eth->lock, flags);
+
+	/* Remove us from the poll list */
+	netif_rx_complete(netdev);
+
+	/* Re-enable interrupts */
+	titan_ge_enable_int(port_num, titan_ge_eth, netdev);
+
+	spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
+
+	return 0;
+}
+
+/*
+ * Close the network device
+ */
+int titan_ge_stop(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+
+	spin_lock_irq(&(titan_ge_eth->lock));
+	titan_ge_eth_stop(netdev);
+	free_irq(netdev->irq, netdev);
+	spin_unlock_irq(&titan_ge_eth->lock);
+
+	return TITAN_OK;
+}
+
+/*
+ * Free the Tx ring
+ */
+static void titan_ge_free_tx_rings(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	unsigned int curr;
+	unsigned long reg_data;
+
+	/* Stop the Tx DMA */
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG +
+				(port_num << 8));
+	reg_data |= 0xc0000000;
+	TITAN_GE_WRITE((TITAN_GE_CHANNEL0_CONFIG +
+			(port_num << 8)), reg_data);
+
+	/* Disable the TMAC */
+	reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 +
+				(port_num << 12));
+	reg_data &= ~(0x00000001);
+	TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 +
+			(port_num << 12)), reg_data);
+
+	for (curr = 0;
+	     (titan_ge_eth->tx_ring_skbs) && (curr < TITAN_GE_TX_QUEUE);
+	     curr++) {
+		if (titan_ge_eth->tx_skb[curr]) {
+			dev_kfree_skb(titan_ge_eth->tx_skb[curr]);
+			titan_ge_eth->tx_ring_skbs--;
+		}
+	}
+
+	if (titan_ge_eth->tx_ring_skbs != 0)
+		printk
+		    ("%s: Error on Tx descriptor free - could not free %d"
+		     " descriptors\n", netdev->name,
+		     titan_ge_eth->tx_ring_skbs);
+
+#ifndef TITAN_RX_RING_IN_SRAM
+	dma_free_coherent(&titan_ge_device[port_num]->dev,
+			  titan_ge_eth->tx_desc_area_size,
+			  (void *) titan_ge_eth->tx_desc_area,
+			  titan_ge_eth->tx_dma);
+#endif
+}
+
+/*
+ * Free the Rx ring
+ */
+static void titan_ge_free_rx_rings(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	unsigned int curr;
+	unsigned long reg_data;
+
+	/* Stop the Rx DMA */
+	reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG +
+				(port_num << 8));
+	reg_data |= 0x000c0000;
+	TITAN_GE_WRITE((TITAN_GE_CHANNEL0_CONFIG +
+			(port_num << 8)), reg_data);
+
+	/* Disable the RMAC */
+	reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 +
+				(port_num << 12));
+	reg_data &= ~(0x00000001);
+	TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 +
+			(port_num << 12)), reg_data);
+
+	for (curr = 0;
+	     titan_ge_eth->rx_ring_skbs && (curr < TITAN_GE_RX_QUEUE);
+	     curr++) {
+		if (titan_ge_eth->rx_skb[curr]) {
+			dev_kfree_skb(titan_ge_eth->rx_skb[curr]);
+			titan_ge_eth->rx_ring_skbs--;
+		}
+	}
+
+	if (titan_ge_eth->rx_ring_skbs != 0)
+		printk(KERN_ERR
+		       "%s: Error in freeing Rx Ring. %d skb's still"
+		       " stuck in RX Ring - ignoring them\n", netdev->name,
+		       titan_ge_eth->rx_ring_skbs);
+
+#ifndef TITAN_RX_RING_IN_SRAM
+	dma_free_coherent(&titan_ge_device[port_num]->dev,
+			  titan_ge_eth->rx_desc_area_size,
+			  (void *) titan_ge_eth->rx_desc_area,
+			  titan_ge_eth->rx_dma);
+#endif
+}
+
+/*
+ * Actually does the stop of the Ethernet device
+ */
+static void titan_ge_eth_stop(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+
+	netif_stop_queue(netdev);
+
+	titan_ge_port_reset(titan_ge_eth->port_num);
+
+	titan_ge_free_tx_rings(netdev);
+	titan_ge_free_rx_rings(netdev);
+
+	/* Disable the Tx and Rx Interrupts for all channels */
+	TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, 0x0);
+}
+
+/*
+ * Update the MAC address. Note that we have to write the
+ * address in three station registers, 16 bits each. And this
+ * has to be done for TMAC and RMAC
+ */
+static void titan_ge_update_mac_address(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+	unsigned int port_num = titan_ge_eth->port_num;
+	u8 p_addr[6];
+
+	memcpy(titan_ge_eth->port_mac_addr, netdev->dev_addr, 6);
+	memcpy(p_addr, netdev->dev_addr, 6);
+
+	/* Update the Address Filtering Match tables */
+	titan_ge_update_afx(titan_ge_eth);
+
+	printk("Station MAC : %d %d %d %d %d %d  \n",
+		p_addr[5], p_addr[4], p_addr[3],
+		p_addr[2], p_addr[1], p_addr[0]);
+
+	/* Set the MAC address here for TMAC and RMAC */
+	TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_HI + (port_num << 12)),
+		       ((p_addr[5] << 8) | p_addr[4]));
+	TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_MID + (port_num << 12)),
+		       ((p_addr[3] << 8) | p_addr[2]));
+	TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_LOW + (port_num << 12)),
+		       ((p_addr[1] << 8) | p_addr[0]));
+
+	TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_HI + (port_num << 12)),
+		       ((p_addr[5] << 8) | p_addr[4]));
+	TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_MID + (port_num << 12)),
+		       ((p_addr[3] << 8) | p_addr[2]));
+	TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_LOW + (port_num << 12)),
+		       ((p_addr[1] << 8) | p_addr[0]));
+}
+
+/*
+ * Set the MAC address of the Ethernet device
+ */
+static int titan_ge_set_mac_address(struct net_device *dev, void *addr)
+{
+	titan_ge_port_info *tp = netdev_priv(dev);
+	struct sockaddr *sa = addr;
+
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+
+	spin_lock_irq(&tp->lock);
+	titan_ge_update_mac_address(dev);
+	spin_unlock_irq(&tp->lock);
+
+	return 0;
+}
+
+/*
+ * Get the Ethernet device stats
+ */
+static struct net_device_stats *titan_ge_get_stats(struct net_device *netdev)
+{
+	titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
+
+	return &titan_ge_eth->stats;
+}
+
+/*
+ * Initialize the Rx descriptor ring for the Titan Ge
+ */
+static int titan_ge_init_rx_desc_ring(titan_ge_port_info * titan_eth_port,
+				      int rx_desc_num,
+				      int rx_buff_size,
+				      unsigned long rx_desc_base_addr,
+				      unsigned long rx_buff_base_addr,
+				      unsigned long rx_dma)
+{
+	volatile titan_ge_rx_desc *rx_desc;
+	unsigned long buffer_addr;
+	int index;
+	unsigned long titan_ge_rx_desc_bus = rx_dma;
+
+	buffer_addr = rx_buff_base_addr;
+	rx_desc = (titan_ge_rx_desc *) rx_desc_base_addr;
+
+	/* Check alignment */
+	if (rx_buff_base_addr & 0xF)
+		return 0;
+
+	/* Check Rx buffer size */
+	if ((rx_buff_size < 8) || (rx_buff_size > TITAN_GE_MAX_RX_BUFFER))
+		return 0;
+
+	/* 64-bit alignment
+	if ((rx_buff_base_addr + rx_buff_size) & 0x7)
+		return 0; */
+
+	/* Initialize the Rx desc ring */
+	for (index = 0; index < rx_desc_num; index++) {
+		titan_ge_rx_desc_bus += sizeof(titan_ge_rx_desc);
+		rx_desc[index].cmd_sts = 0;
+		rx_desc[index].buffer_addr = buffer_addr;
+		titan_eth_port->rx_skb[index] = NULL;
+		buffer_addr += rx_buff_size;
+	}
+
+	titan_eth_port->rx_curr_desc_q = 0;
+	titan_eth_port->rx_used_desc_q = 0;
+
+	titan_eth_port->rx_desc_area = (titan_ge_rx_desc *) rx_desc_base_addr;
+	titan_eth_port->rx_desc_area_size =
+	    rx_desc_num * sizeof(titan_ge_rx_desc);
+
+	titan_eth_port->rx_dma = rx_dma;
+
+	return TITAN_OK;
+}
+
+/*
+ * Initialize the Tx descriptor ring. Descriptors in the SRAM
+ */
+static int titan_ge_init_tx_desc_ring(titan_ge_port_info * titan_ge_port,
+				      int tx_desc_num,
+				      unsigned long tx_desc_base_addr,
+				      unsigned long tx_dma)
+{
+	titan_ge_tx_desc *tx_desc;
+	int index;
+	unsigned long titan_ge_tx_desc_bus = tx_dma;
+
+	if (tx_desc_base_addr & 0xF)
+		return 0;
+
+	tx_desc = (titan_ge_tx_desc *) tx_desc_base_addr;
+
+	for (index = 0; index < tx_desc_num; index++) {
+		titan_ge_port->tx_dma_array[index] =
+		    (dma_addr_t) titan_ge_tx_desc_bus;
+		titan_ge_tx_desc_bus += sizeof(titan_ge_tx_desc);
+		tx_desc[index].cmd_sts = 0x0000;
+		tx_desc[index].buffer_len = 0;
+		tx_desc[index].buffer_addr = 0x00000000;
+		titan_ge_port->tx_skb[index] = NULL;
+	}
+
+	titan_ge_port->tx_curr_desc_q = 0;
+	titan_ge_port->tx_used_desc_q = 0;
+
+	titan_ge_port->tx_desc_area = (titan_ge_tx_desc *) tx_desc_base_addr;
+	titan_ge_port->tx_desc_area_size =
+	    tx_desc_num * sizeof(titan_ge_tx_desc);
+
+	titan_ge_port->tx_dma = tx_dma;
+	return TITAN_OK;
+}
+
+/*
+ * Initialize the device as an Ethernet device
+ */
+static int __init titan_ge_probe(struct device *device)
+{
+	titan_ge_port_info *titan_ge_eth;
+	struct net_device *netdev;
+	int port = to_platform_device(device)->id;
+	int err;
+
+	netdev = alloc_etherdev(sizeof(titan_ge_port_info));
+	if (!netdev) {
+		err = -ENODEV;
+		goto out;
+	}
+
+	netdev->open = titan_ge_open;
+	netdev->stop = titan_ge_stop;
+	netdev->hard_start_xmit = titan_ge_start_xmit;
+	netdev->get_stats = titan_ge_get_stats;
+	netdev->set_multicast_list = titan_ge_set_multi;
+	netdev->set_mac_address = titan_ge_set_mac_address;
+
+	/* Tx timeout */
+	netdev->tx_timeout = titan_ge_tx_timeout;
+	netdev->watchdog_timeo = 2 * HZ;
+
+	/* Set these to very high values */
+	netdev->poll = titan_ge_poll;
+	netdev->weight = 64;
+
+	netdev->tx_queue_len = TITAN_GE_TX_QUEUE;
+	netif_carrier_off(netdev);
+	netdev->base_addr = 0;
+
+	netdev->change_mtu = titan_ge_change_mtu;
+
+	titan_ge_eth = netdev_priv(netdev);
+	/* Allocation of memory for the driver structures */
+
+	titan_ge_eth->port_num = port;
+
+	/* Configure the Tx timeout handler */
+	INIT_WORK(&titan_ge_eth->tx_timeout_task,
+		  (void (*)(void *)) titan_ge_tx_timeout_task, netdev);
+
+	spin_lock_init(&titan_ge_eth->lock);
+
+	/* set MAC addresses */
+	memcpy(netdev->dev_addr, titan_ge_mac_addr_base, 6);
+	netdev->dev_addr[5] += port;
+
+	err = register_netdev(netdev);
+
+	if (err)
+		goto out_free_netdev;
+
+	printk(KERN_NOTICE
+	       "%s: port %d with MAC address %02x:%02x:%02x:%02x:%02x:%02x\n",
+	       netdev->name, port, netdev->dev_addr[0],
+	       netdev->dev_addr[1], netdev->dev_addr[2],
+	       netdev->dev_addr[3], netdev->dev_addr[4],
+	       netdev->dev_addr[5]);
+
+	printk(KERN_NOTICE "Rx NAPI supported, Tx Coalescing ON \n");
+
+	return 0;
+
+out_free_netdev:
+	kfree(netdev);
+
+out:
+	return err;
+}
+
+static void __devexit titan_device_remove(struct device *device)
+{
+}
+
+/*
+ * Reset the Ethernet port
+ */
+static void titan_ge_port_reset(unsigned int port_num)
+{
+	unsigned int reg_data;
+
+	/* Stop the Tx port activity */
+	reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 +
+				(port_num << 12));
+	reg_data &= ~(0x0001);
+	TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 +
+			(port_num << 12)), reg_data);
+
+	/* Stop the Rx port activity */
+	reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 +
+				(port_num << 12));
+	reg_data &= ~(0x0001);
+	TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 +
+			(port_num << 12)), reg_data);
+
+	return;
+}
+
+/*
+ * Return the Tx desc after use by the XDMA
+ */
+static int titan_ge_return_tx_desc(titan_ge_port_info * titan_ge_eth, int port)
+{
+	int tx_desc_used;
+	struct sk_buff *skb;
+
+	tx_desc_used = titan_ge_eth->tx_used_desc_q;
+
+	/* return right away */
+	if (tx_desc_used == titan_ge_eth->tx_curr_desc_q)
+		return TITAN_ERROR;
+
+	/* Now the critical stuff */
+	skb = titan_ge_eth->tx_skb[tx_desc_used];
+
+	dev_kfree_skb_any(skb);
+
+	titan_ge_eth->tx_skb[tx_desc_used] = NULL;
+	titan_ge_eth->tx_used_desc_q =
+	    (tx_desc_used + 1) % TITAN_GE_TX_QUEUE;
+
+	return 0;
+}
+
+/*
+ * Coalescing for the Tx path
+ */
+static unsigned long titan_ge_tx_coal(unsigned long delay, int port)
+{
+	unsigned long rx_delay;
+
+	rx_delay = TITAN_GE_READ(TITAN_GE_INT_COALESCING);
+	delay = (delay << 16) | rx_delay;
+
+	TITAN_GE_WRITE(TITAN_GE_INT_COALESCING, delay);
+	TITAN_GE_WRITE(0x5038, delay);
+
+	return delay;
+}
+
+static struct device_driver titan_soc_driver = {
+	.name   = titan_string,
+	.bus    = &platform_bus_type,
+	.probe  = titan_ge_probe,
+	.remove = __devexit_p(titan_device_remove),
+};
+
+static void titan_platform_release (struct device *device)
+{
+	struct platform_device *pldev;
+
+	/* free device */
+	pldev = to_platform_device (device);
+	kfree (pldev);
+}
+
+/*
+ * Register the Titan GE with the kernel
+ */
+static int __init titan_ge_init_module(void)
+{
+	struct platform_device *pldev;
+	unsigned int version, device;
+	int i;
+
+	printk(KERN_NOTICE
+	       "PMC-Sierra TITAN 10/100/1000 Ethernet Driver \n");
+
+	titan_ge_base = (unsigned long) ioremap(TITAN_GE_BASE, TITAN_GE_SIZE);
+	if (!titan_ge_base) {
+		printk("Mapping Titan GE failed\n");
+		goto out;
+	}
+
+	device = TITAN_GE_READ(TITAN_GE_DEVICE_ID);
+	version = (device & 0x000f0000) >> 16;
+	device &= 0x0000ffff;
+
+	printk(KERN_NOTICE "Device Id : %x,  Version : %x \n", device, version);
+
+#ifdef TITAN_RX_RING_IN_SRAM
+	titan_ge_sram = (unsigned long) ioremap(TITAN_SRAM_BASE,
+						TITAN_SRAM_SIZE);
+	if (!titan_ge_sram) {
+		printk("Mapping Titan SRAM failed\n");
+		goto out_unmap_ge;
+	}
+#endif
+
+	if (driver_register(&titan_soc_driver)) {
+		printk(KERN_ERR "Driver registration failed\n");
+		goto out_unmap_sram;
+	}
+
+	for (i = 0; i < 3; i++) {
+		titan_ge_device[i] = NULL;
+
+		if (!(pldev = kmalloc (sizeof (*pldev), GFP_KERNEL)))
+			continue;
+
+		memset (pldev, 0, sizeof (*pldev));
+		pldev->name		= titan_string;
+		pldev->id		= i;
+		pldev->dev.release	= titan_platform_release;
+		titan_ge_device[i]	= pldev;
+
+		if (platform_device_register (pldev)) {
+			kfree (pldev);
+			titan_ge_device[i] = NULL;
+			continue;
+		}
+
+		if (!pldev->dev.driver) {
+			/*
+			 * The driver was not bound to this device, there was
+			 * no hardware at this address. Unregister it, as the
+			 * release fuction will take care of freeing the
+			 * allocated structure
+			 */
+			titan_ge_device[i] = NULL;
+			platform_device_unregister (pldev);
+		}
+	}
+
+	return 0;
+
+out_unmap_sram:
+	iounmap((void *)titan_ge_sram);
+
+out_unmap_ge:
+	iounmap((void *)titan_ge_base);
+
+out:
+	return -ENOMEM;
+}
+
+/*
+ * Unregister the Titan GE from the kernel
+ */
+static void __exit titan_ge_cleanup_module(void)
+{
+	int i;
+
+	driver_unregister(&titan_soc_driver);
+
+	for (i = 0; i < 3; i++) {
+		if (titan_ge_device[i]) {
+			platform_device_unregister (titan_ge_device[i]);
+			titan_ge_device[i] = NULL;
+		}
+	}
+
+	iounmap((void *)titan_ge_sram);
+	iounmap((void *)titan_ge_base);
+}
+
+MODULE_AUTHOR("Manish Lachwani <lachwani@pmc-sierra.com>");
+MODULE_DESCRIPTION("Titan GE Ethernet driver");
+MODULE_LICENSE("GPL");
+
+module_init(titan_ge_init_module);
+module_exit(titan_ge_cleanup_module);
diff --git a/drivers/net/titan_ge.h b/drivers/net/titan_ge.h
new file mode 100644
index 0000000..3719f78
--- /dev/null
+++ b/drivers/net/titan_ge.h
@@ -0,0 +1,415 @@
+#ifndef _TITAN_GE_H_
+#define _TITAN_GE_H_
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <asm/byteorder.h>
+
+/*
+ * These functions should be later moved to a more generic location since there
+ * will be others accessing it also
+ */
+
+/*
+ * This is the way it works: LKB5 Base is at 0x0128. TITAN_BASE is defined in
+ * include/asm/titan_dep.h. TITAN_GE_BASE is the value in the TITAN_GE_LKB5
+ * register.
+ */
+
+#define	TITAN_GE_BASE	0xfe000000UL
+#define	TITAN_GE_SIZE	0x10000UL
+
+extern unsigned long titan_ge_base;
+
+#define	TITAN_GE_WRITE(offset, data) \
+		*(volatile u32 *)(titan_ge_base + (offset)) = (data)
+
+#define TITAN_GE_READ(offset) *(volatile u32 *)(titan_ge_base + (offset))
+
+#ifndef msec_delay
+#define msec_delay(x)   do { if(in_interrupt()) { \
+				/* Don't mdelay in interrupt context! */ \
+				BUG(); \
+			} else { \
+				set_current_state(TASK_UNINTERRUPTIBLE); \
+				schedule_timeout((x * HZ)/1000); \
+			} } while(0)
+#endif
+
+#define TITAN_GE_PORT_0
+
+#define	TITAN_SRAM_BASE		((OCD_READ(RM9000x2_OCD_LKB13) & ~1) << 4)
+#define	TITAN_SRAM_SIZE		0x2000UL
+
+/*
+ * We may need these constants
+ */
+#define TITAN_BIT0    0x00000001
+#define TITAN_BIT1    0x00000002
+#define TITAN_BIT2    0x00000004
+#define TITAN_BIT3    0x00000008
+#define TITAN_BIT4    0x00000010
+#define TITAN_BIT5    0x00000020
+#define TITAN_BIT6    0x00000040
+#define TITAN_BIT7    0x00000080
+#define TITAN_BIT8    0x00000100
+#define TITAN_BIT9    0x00000200
+#define TITAN_BIT10   0x00000400
+#define TITAN_BIT11   0x00000800
+#define TITAN_BIT12   0x00001000
+#define TITAN_BIT13   0x00002000
+#define TITAN_BIT14   0x00004000
+#define TITAN_BIT15   0x00008000
+#define TITAN_BIT16   0x00010000
+#define TITAN_BIT17   0x00020000
+#define TITAN_BIT18   0x00040000
+#define TITAN_BIT19   0x00080000
+#define TITAN_BIT20   0x00100000
+#define TITAN_BIT21   0x00200000
+#define TITAN_BIT22   0x00400000
+#define TITAN_BIT23   0x00800000
+#define TITAN_BIT24   0x01000000
+#define TITAN_BIT25   0x02000000
+#define TITAN_BIT26   0x04000000
+#define TITAN_BIT27   0x08000000
+#define TITAN_BIT28   0x10000000
+#define TITAN_BIT29   0x20000000
+#define TITAN_BIT30   0x40000000
+#define TITAN_BIT31   0x80000000
+
+/* Flow Control */
+#define	TITAN_GE_FC_NONE	0x0
+#define	TITAN_GE_FC_FULL	0x1
+#define	TITAN_GE_FC_TX_PAUSE	0x2
+#define	TITAN_GE_FC_RX_PAUSE	0x3
+
+/* Duplex Settings */
+#define	TITAN_GE_FULL_DUPLEX	0x1
+#define	TITAN_GE_HALF_DUPLEX	0x2
+
+/* Speed settings */
+#define	TITAN_GE_SPEED_1000	0x1
+#define	TITAN_GE_SPEED_100	0x2
+#define	TITAN_GE_SPEED_10	0x3
+
+/* Debugging info only */
+#undef TITAN_DEBUG
+
+/* Keep the rings in the Titan's SSRAM */
+#define TITAN_RX_RING_IN_SRAM
+
+#ifdef CONFIG_64BIT
+#define	TITAN_GE_IE_MASK	0xfffffffffb001b64
+#define	TITAN_GE_IE_STATUS	0xfffffffffb001b60
+#else
+#define	TITAN_GE_IE_MASK	0xfb001b64
+#define	TITAN_GE_IE_STATUS	0xfb001b60
+#endif
+
+/* Support for Jumbo Frames */
+#undef TITAN_GE_JUMBO_FRAMES
+
+/* Rx buffer size */
+#ifdef TITAN_GE_JUMBO_FRAMES
+#define	TITAN_GE_JUMBO_BUFSIZE	9080
+#else
+#define	TITAN_GE_STD_BUFSIZE	1580
+#endif
+
+/*
+ * Tx and Rx Interrupt Coalescing parameter. These values are
+ * for 1 Ghz processor. Rx coalescing can be taken care of
+ * by NAPI. NAPI is adaptive and hence useful. Tx coalescing
+ * is not adaptive. Hence, these values need to be adjusted
+ * based on load, CPU speed etc.
+ */
+#define	TITAN_GE_RX_COAL	150
+#define	TITAN_GE_TX_COAL	300
+
+#if defined(__BIG_ENDIAN)
+
+/* Define the Rx descriptor */
+typedef struct eth_rx_desc {
+	u32     reserved;	/* Unused 		*/
+	u32     buffer_addr;	/* CPU buffer address 	*/
+	u32	cmd_sts;	/* Command and Status	*/
+	u32	buffer;		/* XDMA buffer address	*/
+} titan_ge_rx_desc;
+
+/* Define the Tx descriptor */
+typedef struct eth_tx_desc {
+	u16     cmd_sts;	/* Command, Status and Buffer count */
+	u16	buffer_len;	/* Length of the buffer	*/
+	u32     buffer_addr;	/* Physical address of the buffer */
+} titan_ge_tx_desc;
+
+#elif defined(__LITTLE_ENDIAN)
+
+/* Define the Rx descriptor */
+typedef struct eth_rx_desc {
+	u32	buffer_addr;	/* CPU buffer address   */
+	u32	reserved;	/* Unused               */
+	u32	buffer;		/* XDMA buffer address  */
+	u32	cmd_sts;	/* Command and Status   */
+} titan_ge_rx_desc;
+
+/* Define the Tx descriptor */
+typedef struct eth_tx_desc {
+	u32     buffer_addr;	/* Physical address of the buffer */
+	u16     buffer_len;     /* Length of the buffer */
+	u16     cmd_sts;        /* Command, Status and Buffer count */
+} titan_ge_tx_desc;
+#endif
+
+/* Default Tx Queue Size */
+#define	TITAN_GE_TX_QUEUE	128
+#define TITAN_TX_RING_BYTES	(TITAN_GE_TX_QUEUE * sizeof(struct eth_tx_desc))
+
+/* Default Rx Queue Size */
+#define	TITAN_GE_RX_QUEUE	64
+#define TITAN_RX_RING_BYTES	(TITAN_GE_RX_QUEUE * sizeof(struct eth_rx_desc))
+
+/* Packet Structure */
+typedef struct _pkt_info {
+	unsigned int           len;
+	unsigned int            cmd_sts;
+	unsigned int            buffer;
+	struct sk_buff          *skb;
+	unsigned int		checksum;
+} titan_ge_packet;
+
+
+#define	PHYS_CNT	3
+
+/* Titan Port specific data structure */
+typedef struct _eth_port_ctrl {
+	unsigned int		port_num;
+	u8			port_mac_addr[6];
+
+	/* Rx descriptor pointers */
+	int 			rx_curr_desc_q, rx_used_desc_q;
+
+	/* Tx descriptor pointers */
+	int 			tx_curr_desc_q, tx_used_desc_q;
+
+	/* Rx descriptor area */
+	volatile titan_ge_rx_desc	*rx_desc_area;
+	unsigned int			rx_desc_area_size;
+	struct sk_buff*			rx_skb[TITAN_GE_RX_QUEUE];
+
+	/* Tx Descriptor area */
+	volatile titan_ge_tx_desc	*tx_desc_area;
+	unsigned int                    tx_desc_area_size;
+	struct sk_buff*                 tx_skb[TITAN_GE_TX_QUEUE];
+
+	/* Timeout task */
+	struct work_struct		tx_timeout_task;
+
+	/* DMA structures and handles */
+	dma_addr_t			tx_dma;
+	dma_addr_t			rx_dma;
+	dma_addr_t			tx_dma_array[TITAN_GE_TX_QUEUE];
+
+	/* Device lock */
+	spinlock_t			lock;
+
+	unsigned int			tx_ring_skbs;
+	unsigned int			rx_ring_size;
+	unsigned int			tx_ring_size;
+	unsigned int			rx_ring_skbs;
+
+	struct net_device_stats		stats;
+
+	/* Tx and Rx coalescing */
+	unsigned long			rx_int_coal;
+	unsigned long			tx_int_coal;
+
+	/* Threshold for replenishing the Rx and Tx rings */
+	unsigned int			tx_threshold;
+	unsigned int			rx_threshold;
+
+	/* NAPI work limit */
+	unsigned int			rx_work_limit;
+} titan_ge_port_info;
+
+/* Titan specific constants */
+#define	TITAN_ETH_PORT_IRQ		3
+
+/* Max Rx buffer */
+#define	TITAN_GE_MAX_RX_BUFFER		65536
+
+/* Tx and Rx Error */
+#define	TITAN_GE_ERROR
+
+/* Rx Descriptor Command and Status */
+
+#define	TITAN_GE_RX_CRC_ERROR		TITAN_BIT27	/* crc error */
+#define	TITAN_GE_RX_OVERFLOW_ERROR	TITAN_BIT15	/* overflow */
+#define TITAN_GE_RX_BUFFER_OWNED	TITAN_BIT21	/* buffer ownership */
+#define	TITAN_GE_RX_STP			TITAN_BIT31	/* start of packet */
+#define	TITAN_GE_RX_BAM			TITAN_BIT30	/* broadcast address match */
+#define TITAN_GE_RX_PAM			TITAN_BIT28	/* physical address match */
+#define TITAN_GE_RX_LAFM		TITAN_BIT29	/* logical address filter match */
+#define TITAN_GE_RX_VLAN		TITAN_BIT26	/* virtual lans */
+#define TITAN_GE_RX_PERR		TITAN_BIT19	/* packet error */
+#define TITAN_GE_RX_TRUNC		TITAN_BIT20	/* packet size greater than 32 buffers */
+
+/* Tx Descriptor Command */
+#define	TITAN_GE_TX_BUFFER_OWNED	TITAN_BIT5	/* buffer ownership */
+#define	TITAN_GE_TX_ENABLE_INTERRUPT	TITAN_BIT15	/* Interrupt Enable */
+
+/* Return Status */
+#define	TITAN_OK	0x1	/* Good Status */
+#define	TITAN_ERROR	0x2	/* Error Status */
+
+/* MIB specific register offset */
+#define TITAN_GE_MSTATX_STATS_BASE_LOW       0x0800  /* MSTATX COUNTL[15:0] */
+#define TITAN_GE_MSTATX_STATS_BASE_MID       0x0804  /* MSTATX COUNTM[15:0] */
+#define TITAN_GE_MSTATX_STATS_BASE_HI        0x0808  /* MSTATX COUNTH[7:0] */
+#define TITAN_GE_MSTATX_CONTROL              0x0828  /* MSTATX Control */
+#define TITAN_GE_MSTATX_VARIABLE_SELECT      0x082C  /* MSTATX Variable Select */
+
+/* MIB counter offsets, add to the TITAN_GE_MSTATX_STATS_BASE_XXX */
+#define TITAN_GE_MSTATX_RXFRAMESOK                   0x0040
+#define TITAN_GE_MSTATX_RXOCTETSOK                   0x0050
+#define TITAN_GE_MSTATX_RXFRAMES                     0x0060
+#define TITAN_GE_MSTATX_RXOCTETS                     0x0070
+#define TITAN_GE_MSTATX_RXUNICASTFRAMESOK            0x0080
+#define TITAN_GE_MSTATX_RXBROADCASTFRAMESOK          0x0090
+#define TITAN_GE_MSTATX_RXMULTICASTFRAMESOK          0x00A0
+#define TITAN_GE_MSTATX_RXTAGGEDFRAMESOK             0x00B0
+#define TITAN_GE_MSTATX_RXMACPAUSECONTROLFRAMESOK    0x00C0
+#define TITAN_GE_MSTATX_RXMACCONTROLFRAMESOK         0x00D0
+#define TITAN_GE_MSTATX_RXFCSERROR                   0x00E0
+#define TITAN_GE_MSTATX_RXALIGNMENTERROR             0x00F0
+#define TITAN_GE_MSTATX_RXSYMBOLERROR                0x0100
+#define TITAN_GE_MSTATX_RXLAYER1ERROR                0x0110
+#define TITAN_GE_MSTATX_RXINRANGELENGTHERROR         0x0120
+#define TITAN_GE_MSTATX_RXLONGLENGTHERROR            0x0130
+#define TITAN_GE_MSTATX_RXLONGLENGTHCRCERROR         0x0140
+#define TITAN_GE_MSTATX_RXSHORTLENGTHERROR           0x0150
+#define TITAN_GE_MSTATX_RXSHORTLLENGTHCRCERROR       0x0160
+#define TITAN_GE_MSTATX_RXFRAMES64OCTETS             0x0170
+#define TITAN_GE_MSTATX_RXFRAMES65TO127OCTETS        0x0180
+#define TITAN_GE_MSTATX_RXFRAMES128TO255OCTETS       0x0190
+#define TITAN_GE_MSTATX_RXFRAMES256TO511OCTETS       0x01A0
+#define TITAN_GE_MSTATX_RXFRAMES512TO1023OCTETS      0x01B0
+#define TITAN_GE_MSTATX_RXFRAMES1024TO1518OCTETS     0x01C0
+#define TITAN_GE_MSTATX_RXFRAMES1519TOMAXSIZE        0x01D0
+#define TITAN_GE_MSTATX_RXSTATIONADDRESSFILTERED     0x01E0
+#define TITAN_GE_MSTATX_RXVARIABLE                   0x01F0
+#define TITAN_GE_MSTATX_GENERICADDRESSFILTERED       0x0200
+#define TITAN_GE_MSTATX_UNICASTFILTERED              0x0210
+#define TITAN_GE_MSTATX_MULTICASTFILTERED            0x0220
+#define TITAN_GE_MSTATX_BROADCASTFILTERED            0x0230
+#define TITAN_GE_MSTATX_HASHFILTERED                 0x0240
+#define TITAN_GE_MSTATX_TXFRAMESOK                   0x0250
+#define TITAN_GE_MSTATX_TXOCTETSOK                   0x0260
+#define TITAN_GE_MSTATX_TXOCTETS                     0x0270
+#define TITAN_GE_MSTATX_TXTAGGEDFRAMESOK             0x0280
+#define TITAN_GE_MSTATX_TXMACPAUSECONTROLFRAMESOK    0x0290
+#define TITAN_GE_MSTATX_TXFCSERROR                   0x02A0
+#define TITAN_GE_MSTATX_TXSHORTLENGTHERROR           0x02B0
+#define TITAN_GE_MSTATX_TXLONGLENGTHERROR            0x02C0
+#define TITAN_GE_MSTATX_TXSYSTEMERROR                0x02D0
+#define TITAN_GE_MSTATX_TXMACERROR                   0x02E0
+#define TITAN_GE_MSTATX_TXCARRIERSENSEERROR          0x02F0
+#define TITAN_GE_MSTATX_TXSQETESTERROR               0x0300
+#define TITAN_GE_MSTATX_TXUNICASTFRAMESOK            0x0310
+#define TITAN_GE_MSTATX_TXBROADCASTFRAMESOK          0x0320
+#define TITAN_GE_MSTATX_TXMULTICASTFRAMESOK          0x0330
+#define TITAN_GE_MSTATX_TXUNICASTFRAMESATTEMPTED     0x0340
+#define TITAN_GE_MSTATX_TXBROADCASTFRAMESATTEMPTED   0x0350
+#define TITAN_GE_MSTATX_TXMULTICASTFRAMESATTEMPTED   0x0360
+#define TITAN_GE_MSTATX_TXFRAMES64OCTETS             0x0370
+#define TITAN_GE_MSTATX_TXFRAMES65TO127OCTETS        0x0380
+#define TITAN_GE_MSTATX_TXFRAMES128TO255OCTETS       0x0390
+#define TITAN_GE_MSTATX_TXFRAMES256TO511OCTETS       0x03A0
+#define TITAN_GE_MSTATX_TXFRAMES512TO1023OCTETS      0x03B0
+#define TITAN_GE_MSTATX_TXFRAMES1024TO1518OCTETS     0x03C0
+#define TITAN_GE_MSTATX_TXFRAMES1519TOMAXSIZE        0x03D0
+#define TITAN_GE_MSTATX_TXVARIABLE                   0x03E0
+#define TITAN_GE_MSTATX_RXSYSTEMERROR                0x03F0
+#define TITAN_GE_MSTATX_SINGLECOLLISION              0x0400
+#define TITAN_GE_MSTATX_MULTIPLECOLLISION            0x0410
+#define TITAN_GE_MSTATX_DEFERREDXMISSIONS            0x0420
+#define TITAN_GE_MSTATX_LATECOLLISIONS               0x0430
+#define TITAN_GE_MSTATX_ABORTEDDUETOXSCOLLS          0x0440
+
+/* Interrupt specific defines */
+#define TITAN_GE_DEVICE_ID         0x0000  /* Device ID */
+#define TITAN_GE_RESET             0x0004  /* Reset reg */
+#define TITAN_GE_TSB_CTRL_0        0x000C  /* TSB Control reg 0 */
+#define TITAN_GE_TSB_CTRL_1        0x0010  /* TSB Control reg 1 */
+#define TITAN_GE_INTR_GRP0_STATUS  0x0040  /* General Interrupt Group 0 Status */
+#define TITAN_GE_INTR_XDMA_CORE_A  0x0048  /* XDMA Channel Interrupt Status, Core A*/
+#define TITAN_GE_INTR_XDMA_CORE_B  0x004C  /* XDMA Channel Interrupt Status, Core B*/
+#define	TITAN_GE_INTR_XDMA_IE	   0x0058  /* XDMA Channel Interrupt Enable */
+#define TITAN_GE_SDQPF_ECC_INTR    0x480C  /* SDQPF ECC Interrupt Status */
+#define TITAN_GE_SDQPF_RXFIFO_CTL  0x4828  /* SDQPF RxFifo Control and Interrupt Enb*/
+#define TITAN_GE_SDQPF_RXFIFO_INTR 0x482C  /* SDQPF RxFifo Interrupt Status */
+#define TITAN_GE_SDQPF_TXFIFO_CTL  0x4928  /* SDQPF TxFifo Control and Interrupt Enb*/
+#define TITAN_GE_SDQPF_TXFIFO_INTR 0x492C  /* SDQPF TxFifo Interrupt Status */
+#define	TITAN_GE_SDQPF_RXFIFO_0	   0x4840  /* SDQPF RxFIFO Enable */
+#define	TITAN_GE_SDQPF_TXFIFO_0	   0x4940  /* SDQPF TxFIFO Enable */
+#define TITAN_GE_XDMA_CONFIG       0x5000  /* XDMA Global Configuration */
+#define TITAN_GE_XDMA_INTR_SUMMARY 0x5010  /* XDMA Interrupt Summary */
+#define TITAN_GE_XDMA_BUFADDRPRE   0x5018  /* XDMA Buffer Address Prefix */
+#define TITAN_GE_XDMA_DESCADDRPRE  0x501C  /* XDMA Descriptor Address Prefix */
+#define TITAN_GE_XDMA_PORTWEIGHT   0x502C  /* XDMA Port Weight Configuration */
+
+/* Rx MAC defines */
+#define TITAN_GE_RMAC_CONFIG_1               0x1200  /* RMAC Configuration 1 */
+#define TITAN_GE_RMAC_CONFIG_2               0x1204  /* RMAC Configuration 2 */
+#define TITAN_GE_RMAC_MAX_FRAME_LEN          0x1208  /* RMAC Max Frame Length */
+#define TITAN_GE_RMAC_STATION_HI             0x120C  /* Rx Station Address High */
+#define TITAN_GE_RMAC_STATION_MID            0x1210  /* Rx Station Address Middle */
+#define TITAN_GE_RMAC_STATION_LOW            0x1214  /* Rx Station Address Low */
+#define TITAN_GE_RMAC_LINK_CONFIG            0x1218  /* RMAC Link Configuration */
+
+/* Tx MAC defines */
+#define TITAN_GE_TMAC_CONFIG_1               0x1240  /* TMAC Configuration 1 */
+#define TITAN_GE_TMAC_CONFIG_2               0x1244  /* TMAC Configuration 2 */
+#define TITAN_GE_TMAC_IPG                    0x1248  /* TMAC Inter-Packet Gap */
+#define TITAN_GE_TMAC_STATION_HI             0x124C  /* Tx Station Address High */
+#define TITAN_GE_TMAC_STATION_MID            0x1250  /* Tx Station Address Middle */
+#define TITAN_GE_TMAC_STATION_LOW            0x1254  /* Tx Station Address Low */
+#define TITAN_GE_TMAC_MAX_FRAME_LEN          0x1258  /* TMAC Max Frame Length */
+#define TITAN_GE_TMAC_MIN_FRAME_LEN          0x125C  /* TMAC Min Frame Length */
+#define TITAN_GE_TMAC_PAUSE_FRAME_TIME       0x1260  /* TMAC Pause Frame Time */
+#define TITAN_GE_TMAC_PAUSE_FRAME_INTERVAL   0x1264  /* TMAC Pause Frame Interval */
+
+/* GMII register */
+#define TITAN_GE_GMII_INTERRUPT_STATUS       0x1348  /* GMII Interrupt Status */
+#define TITAN_GE_GMII_CONFIG_GENERAL         0x134C  /* GMII Configuration General */
+#define TITAN_GE_GMII_CONFIG_MODE            0x1350  /* GMII Configuration Mode */
+
+/* Tx and Rx XDMA defines */
+#define	TITAN_GE_INT_COALESCING		     0x5030 /* Interrupt Coalescing */
+#define	TITAN_GE_CHANNEL0_CONFIG	     0x5040 /* Channel 0 XDMA config */
+#define	TITAN_GE_CHANNEL0_INTERRUPT	     0x504c /* Channel 0 Interrupt Status */
+#define	TITAN_GE_GDI_INTERRUPT_ENABLE        0x5050 /* IE for the GDI Errors */
+#define	TITAN_GE_CHANNEL0_PACKET	     0x5060 /* Channel 0 Packet count */
+#define	TITAN_GE_CHANNEL0_BYTE		     0x5064 /* Channel 0 Byte count */
+#define	TITAN_GE_CHANNEL0_TX_DESC	     0x5054 /* Channel 0 Tx first desc */
+#define	TITAN_GE_CHANNEL0_RX_DESC	     0x5058 /* Channel 0 Rx first desc */
+
+/* AFX (Address Filter Exact) register offsets for Slice 0 */
+#define TITAN_GE_AFX_EXACT_MATCH_LOW         0x1100  /* AFX Exact Match Address Low*/
+#define TITAN_GE_AFX_EXACT_MATCH_MID         0x1104  /* AFX Exact Match Address Mid*/
+#define TITAN_GE_AFX_EXACT_MATCH_HIGH        0x1108  /* AFX Exact Match Address Hi */
+#define TITAN_GE_AFX_EXACT_MATCH_VID         0x110C  /* AFX Exact Match VID */
+#define TITAN_GE_AFX_MULTICAST_HASH_LOW      0x1110  /* AFX Multicast HASH Low */
+#define TITAN_GE_AFX_MULTICAST_HASH_MIDLOW   0x1114  /* AFX Multicast HASH MidLow */
+#define TITAN_GE_AFX_MULTICAST_HASH_MIDHI    0x1118  /* AFX Multicast HASH MidHi */
+#define TITAN_GE_AFX_MULTICAST_HASH_HI       0x111C  /* AFX Multicast HASH Hi */
+#define TITAN_GE_AFX_ADDRS_FILTER_CTRL_0     0x1120  /* AFX Address Filter Ctrl 0 */
+#define TITAN_GE_AFX_ADDRS_FILTER_CTRL_1     0x1124  /* AFX Address Filter Ctrl 1 */
+#define TITAN_GE_AFX_ADDRS_FILTER_CTRL_2     0x1128  /* AFX Address Filter Ctrl 2 */
+
+/* Traffic Groomer block */
+#define        TITAN_GE_TRTG_CONFIG	     0x1000  /* TRTG Config */
+
+#endif 				/* _TITAN_GE_H_ */
+
diff --git a/drivers/net/titan_mdio.c b/drivers/net/titan_mdio.c
new file mode 100644
index 0000000..8a8785b
--- /dev/null
+++ b/drivers/net/titan_mdio.c
@@ -0,0 +1,217 @@
+/*
+ * drivers/net/titan_mdio.c - Driver for Titan ethernet ports
+ *
+ * Copyright (C) 2003 PMC-Sierra Inc.
+ * Author : Manish Lachwani (lachwani@pmc-sierra.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Management Data IO (MDIO) driver for the Titan GMII. Interacts with the Marvel PHY
+ * on the Titan. No support for the TBI as yet.
+ *
+ */
+
+#include	"titan_mdio.h"
+
+#define MDIO_DEBUG
+
+/*
+ * Local constants
+ */
+#define MAX_CLKA            1023
+#define MAX_PHY_DEV         31
+#define MAX_PHY_REG         31
+#define WRITEADDRS_OPCODE   0x0
+#define	READ_OPCODE	    0x2
+#define WRITE_OPCODE        0x1
+#define MAX_MDIO_POLL       100
+
+/*
+ * Titan MDIO and SCMB registers
+ */
+#define TITAN_GE_SCMB_CONTROL                0x01c0  /* SCMB Control */
+#define TITAN_GE_SCMB_CLKA	             0x01c4  /* SCMB Clock A */
+#define TITAN_GE_MDIO_COMMAND                0x01d0  /* MDIO Command */
+#define TITAN_GE_MDIO_DEVICE_PORT_ADDRESS    0x01d4  /* MDIO Device and Port addrs */
+#define TITAN_GE_MDIO_DATA                   0x01d8  /* MDIO Data */
+#define TITAN_GE_MDIO_INTERRUPTS             0x01dC  /* MDIO Interrupts */
+
+/*
+ * Function to poll the MDIO
+ */
+static int titan_ge_mdio_poll(void)
+{
+	int	i, val;
+
+	for (i = 0; i < MAX_MDIO_POLL; i++) {
+		val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
+
+		if (!(val & 0x8000))
+			return TITAN_GE_MDIO_GOOD;
+	}
+
+	return TITAN_GE_MDIO_ERROR;
+}
+
+
+/*
+ * Initialize and configure the MDIO
+ */
+int titan_ge_mdio_setup(titan_ge_mdio_config *titan_mdio)
+{
+	unsigned long	val;
+
+	/* Reset the SCMB and program into MDIO mode*/
+	TITAN_GE_MDIO_WRITE(TITAN_GE_SCMB_CONTROL, 0x9000);
+	TITAN_GE_MDIO_WRITE(TITAN_GE_SCMB_CONTROL, 0x1000);
+
+	/* CLK A */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_SCMB_CLKA);
+	val = ( (val & ~(0x03ff)) | (titan_mdio->clka & 0x03ff));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_SCMB_CLKA, val);
+
+	/* Preamble Suppresion */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
+	val = ( (val & ~(0x0001)) | (titan_mdio->mdio_spre & 0x0001));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
+
+	/* MDIO mode */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
+	val = ( (val & ~(0x4000)) | (titan_mdio->mdio_mode & 0x4000));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
+
+	return TITAN_GE_MDIO_GOOD;
+}
+
+/*
+ * Set the PHY address in indirect mode
+ */
+int titan_ge_mdio_inaddrs(int dev_addr, int reg_addr)
+{
+	volatile unsigned long	val;
+
+	/* Setup the PHY device */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
+	val = ( (val & ~(0x1f00)) | ( (dev_addr << 8) & 0x1f00));
+	val = ( (val & ~(0x001f)) | ( reg_addr & 0x001f));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
+
+	/* Write the new address */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
+	val = ( (val & ~(0x0300)) | ( (WRITEADDRS_OPCODE << 8) & 0x0300));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
+
+	return TITAN_GE_MDIO_GOOD;
+}
+
+/*
+ * Read the MDIO register. This is what the individual parametes mean:
+ *
+ * dev_addr : PHY ID
+ * reg_addr : register offset
+ *
+ * See the spec for the Titan MAC. We operate in the Direct Mode.
+ */
+
+#define MAX_RETRIES	2
+
+int titan_ge_mdio_read(int dev_addr, int reg_addr, unsigned int *pdata)
+{
+	volatile unsigned long	val;
+	int retries = 0;
+
+	/* Setup the PHY device */
+
+again:
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
+	val = ( (val & ~(0x1f00)) | ( (dev_addr << 8) & 0x1f00));
+	val = ( (val & ~(0x001f)) | ( reg_addr & 0x001f));
+	val |= 0x4000;
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
+
+	udelay(30);
+
+	/* Issue the read command */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
+	val = ( (val & ~(0x0300)) | ( (READ_OPCODE << 8) & 0x0300));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
+
+	udelay(30);
+
+	if (titan_ge_mdio_poll() != TITAN_GE_MDIO_GOOD)
+		return TITAN_GE_MDIO_ERROR;
+
+	*pdata = (unsigned int)TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DATA);
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_INTERRUPTS);
+
+	udelay(30);
+
+	if (val & 0x2) {
+		if (retries == MAX_RETRIES)
+			return TITAN_GE_MDIO_ERROR;
+		else {
+			retries++;
+			goto again;
+		}
+	}
+
+	return TITAN_GE_MDIO_GOOD;
+}
+
+/*
+ * Write to the MDIO register
+ *
+ * dev_addr : PHY ID
+ * reg_addr : register that needs to be written to
+ *
+ */
+int titan_ge_mdio_write(int dev_addr, int reg_addr, unsigned int data)
+{
+	volatile unsigned long	val;
+
+	if (titan_ge_mdio_poll() != TITAN_GE_MDIO_GOOD)
+		return TITAN_GE_MDIO_ERROR;
+
+	/* Setup the PHY device */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
+	val = ( (val & ~(0x1f00)) | ( (dev_addr << 8) & 0x1f00));
+	val = ( (val & ~(0x001f)) | ( reg_addr & 0x001f));
+	val |= 0x4000;
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
+
+	udelay(30);
+
+	/* Setup the data to write */
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DATA, data);
+
+	udelay(30);
+
+	/* Issue the write command */
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
+	val = ( (val & ~(0x0300)) | ( (WRITE_OPCODE << 8) & 0x0300));
+	TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
+
+	udelay(30);
+
+	if (titan_ge_mdio_poll() != TITAN_GE_MDIO_GOOD)
+		return TITAN_GE_MDIO_ERROR;
+
+	val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_INTERRUPTS);
+	if (val & 0x2)
+		return TITAN_GE_MDIO_ERROR;
+
+	return TITAN_GE_MDIO_GOOD;
+}
+
diff --git a/drivers/net/titan_mdio.h b/drivers/net/titan_mdio.h
new file mode 100644
index 0000000..5d23344
--- /dev/null
+++ b/drivers/net/titan_mdio.h
@@ -0,0 +1,56 @@
+/*
+ * MDIO used to interact with the PHY when using GMII/MII
+ */
+#ifndef _TITAN_MDIO_H
+#define _TITAN_MDIO_H
+
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include "titan_ge.h"
+
+
+#define	TITAN_GE_MDIO_ERROR	(-9000)
+#define	TITAN_GE_MDIO_GOOD	0
+
+#define	TITAN_GE_MDIO_BASE		titan_ge_base
+
+#define	TITAN_GE_MDIO_READ(offset)	\
+	*(volatile u32 *)(titan_ge_base + (offset))
+
+#define	TITAN_GE_MDIO_WRITE(offset, data)	\
+	*(volatile u32 *)(titan_ge_base + (offset)) = (data)
+
+
+/* GMII specific registers */
+#define	TITAN_GE_MARVEL_PHY_ID		0x00
+#define	TITAN_PHY_AUTONEG_ADV		0x04
+#define	TITAN_PHY_LP_ABILITY		0x05
+#define	TITAN_GE_MDIO_MII_CTRL		0x09
+#define	TITAN_GE_MDIO_MII_EXTENDED	0x0f
+#define	TITAN_GE_MDIO_PHY_CTRL		0x10
+#define	TITAN_GE_MDIO_PHY_STATUS	0x11
+#define	TITAN_GE_MDIO_PHY_IE		0x12
+#define	TITAN_GE_MDIO_PHY_IS		0x13
+#define	TITAN_GE_MDIO_PHY_LED		0x18
+#define	TITAN_GE_MDIO_PHY_LED_OVER	0x19
+#define	PHY_ANEG_TIME_WAIT		45	/* 45 seconds wait time */
+
+/*
+ * MDIO Config Structure
+ */
+typedef struct {
+	unsigned int		clka;
+	int			mdio_spre;
+	int			mdio_mode;
+} titan_ge_mdio_config;
+
+/*
+ * Function Prototypes
+ */
+int titan_ge_mdio_setup(titan_ge_mdio_config *);
+int titan_ge_mdio_inaddrs(int, int);
+int titan_ge_mdio_read(int, int, unsigned int *);
+int titan_ge_mdio_write(int, int, unsigned int);
+
+#endif /* _TITAN_MDIO_H */
diff --git a/drivers/net/wireless/rtl818x/rtl8187/rfkill.c b/drivers/net/wireless/rtl818x/rtl8187/rfkill.c
index 3411671..4d252c1 100644
--- a/drivers/net/wireless/rtl818x/rtl8187/rfkill.c
+++ b/drivers/net/wireless/rtl818x/rtl8187/rfkill.c
@@ -22,6 +22,10 @@
 
 static bool rtl8187_is_radio_enabled(struct rtl8187_priv *priv)
 {
+#ifdef CONFIG_LEMOTE_MACH2F
+	/* Allow users to activate rfkill through only the /sys interface */
+	return 1;
+#else
 	u8 gpio;
 
 	gpio = rtl818x_ioread8(priv, &priv->map->GPIO0);
@@ -29,6 +33,7 @@
 	gpio = rtl818x_ioread8(priv, &priv->map->GPIO1);
 
 	return gpio & priv->rfkill_mask;
+#endif
 }
 
 void rtl8187_rfkill_init(struct ieee80211_hw *hw)
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 09fde58..eacabd1 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -4,5 +4,8 @@
 if GOLDFISH
 source "drivers/platform/goldfish/Kconfig"
 endif
+if MIPS
+source "drivers/platform/mips/Kconfig"
+endif
 
 source "drivers/platform/chrome/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index 3656b7b..ca26925 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -3,6 +3,7 @@
 #
 
 obj-$(CONFIG_X86)		+= x86/
+obj-$(CONFIG_MIPS)		+= mips/
 obj-$(CONFIG_OLPC)		+= olpc/
 obj-$(CONFIG_GOLDFISH)		+= goldfish/
 obj-$(CONFIG_CHROME_PLATFORMS)	+= chrome/
diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
new file mode 100644
index 0000000..722d690
--- /dev/null
+++ b/drivers/platform/mips/Kconfig
@@ -0,0 +1,60 @@
+#
+# MIPS Platform Specific Drivers
+#
+
+menuconfig MIPS_PLATFORM_DEVICES
+	bool "MIPS Platform Specific Device Drivers"
+	default y
+	help
+	  Say Y here to get to see options for device drivers of various
+	  MIPS platforms, including vendor-specific netbook/laptop/pc extension
+	  drivers.  This option alone does not add any kernel code.
+
+	  If you say N, all options in this submenu will be skipped and disabled.
+
+if MIPS_PLATFORM_DEVICES
+
+config LEMOTE_YEELOONG2F
+	tristate "Lemote YeeLoong Laptop"
+	depends on LEMOTE_MACH2F
+	select BACKLIGHT_LCD_SUPPORT
+	select LCD_CLASS_DEVICE
+	select BACKLIGHT_CLASS_DEVICE
+	select POWER_SUPPLY
+	select HWMON
+	select VIDEO_OUTPUT_CONTROL
+	select INPUT_SPARSEKMAP
+	select INPUT_EVDEV
+	depends on INPUT
+	default m
+	help
+	  YeeLoong netbook is a mini laptop made by Lemote, which is basically
+	  compatible to FuLoong2F mini PC, but it has an extra Embedded
+	  Controller(kb3310b) for battery, hotkey, backlight, temperature and
+	  fan management.
+
+config LEMOTE_LYNLOONG2F
+	tristate "Lemote LynLoong PC"
+	depends on LEMOTE_MACH2F
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
+	select VIDEO_OUTPUT_CONTROL
+	default m
+	help
+	  LynLoong PC is an AllINONE machine made by Lemote, which is basically
+	  compatible to FuLoong2F Mini PC, the only difference is that it has a
+	  size-fixed screen: 1360x768 with sisfb video driver. and also, it has
+	  its own specific suspend support.
+
+config GDIUM_LAPTOP
+	tristate "GDIUM laptop extras"
+	depends on DEXXON_GDIUM
+	select POWER_SUPPLY
+	select I2C
+	select INPUT_POLLDEV
+	default m
+	help
+	  This mini-driver drives the ST7 chipset present in the Gdium laptops.
+	  This gives battery support, wlan rfkill.
+
+endif # MIPS_PLATFORM_DEVICES
diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile
new file mode 100644
index 0000000..f013e78
--- /dev/null
+++ b/drivers/platform/mips/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for MIPS Platform-Specific Drivers
+#
+
+obj-$(CONFIG_LEMOTE_YEELOONG2F)	+= yeeloong_laptop.o # yeeloong_ecrom.o
+CFLAGS_yeeloong_laptop.o = -I$(srctree)/arch/mips/loongson/lemote-2f
+
+obj-$(CONFIG_LEMOTE_LYNLOONG2F)	+= lynloong_pc.o
+obj-$(CONFIG_GDIUM_LAPTOP)	+= gdium_laptop.o
diff --git a/drivers/platform/mips/gdium_laptop.c b/drivers/platform/mips/gdium_laptop.c
new file mode 100644
index 0000000..41a65ad
--- /dev/null
+++ b/drivers/platform/mips/gdium_laptop.c
@@ -0,0 +1,927 @@
+/*
+ * gdium_laptop  --  Gdium laptop extras
+ *
+ * Arnaud Patard <apatard@mandriva.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/input-polldev.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <asm/gpio.h>
+
+/* For input device */
+#define SCAN_INTERVAL		150
+
+/* For battery status */
+#define BAT_SCAN_INTERVAL	500
+
+#define EC_FIRM_VERSION		0
+
+#if CONFIG_GDIUM_VERSION > 2
+#define EC_REG_BASE		1
+#else
+#define EC_REG_BASE		0
+#endif
+
+#define EC_STATUS		(EC_REG_BASE+0)
+#define EC_STATUS_LID		(1<<0)
+#define EC_STATUS_PWRBUT	(1<<1)
+#define EC_STATUS_BATID		(1<<2)		/* this bit has no real meaning on v2.         */
+						/* Same as EC_STATUS_ADAPT                     */
+						/* but on v3 it's BATID which mean bat present */
+#define EC_STATUS_SYS_POWER	(1<<3)
+#define EC_STATUS_WLAN		(1<<4)
+#define EC_STATUS_ADAPT		(1<<5)
+
+#define EC_CTRL			(EC_REG_BASE+1)
+#define EC_CTRL_DDR_CLK		(1<<0)
+#define EC_CTRL_CHARGE_LED	(1<<1)
+#define EC_CTRL_BEEP		(1<<2)
+#define EC_CTRL_SUSB		(1<<3)	/* memory power */
+#define EC_CTRL_TRICKLE		(1<<4)
+#define EC_CTRL_WLAN_EN		(1<<5)
+#define EC_CTRL_SUSC		(1<<6) /* main power */
+#define EC_CTRL_CHARGE_EN	(1<<7)
+
+#define EC_BAT_LOW		(EC_REG_BASE+2)
+#define EC_BAT_HIGH		(EC_REG_BASE+3)
+
+#define EC_SIGN			(EC_REG_BASE+4)
+#define EC_SIGN_OS		0xAE /* write 0xae to control pm stuff */
+#define EC_SIGN_EC		0x00 /* write 0x00 to let the st7 manage pm stuff */
+
+#if 0
+#define EC_TEST			(EC_REG_BASE+5) /* Depending on firmware version this register */
+						/* may be the programmation register so don't play */
+						/* with it */
+#endif
+
+#define BAT_VOLT_PRESENT	500000	/* Min voltage to consider battery present uV */
+#define BAT_MIN			7000000	/* Min battery voltage in uV */
+#define BAT_MIN_MV		7000	/* Min battery voltage in mV */
+#define BAT_TRICKLE_EN		8000000	/* Charging at 1.4A before  8.0V and then charging at 0.25A */
+#define BAT_MAX			7950000	/* Max battery voltage ~8V in V */
+#define BAT_MAX_MV		7950	/* Max battery voltage ~8V in V */
+#define BAT_READ_ERROR		300000	/* battery read error of 0.3V */
+#define BAT_READ_ERROR_MV	300	/* battery read error of 0.3V */
+
+#define SM502_WLAN_ON		(224+16)/* SM502 GPIO16 may be used on gdium v2 (v3?) as wlan_on */
+					/* when R422 is connected */
+
+static unsigned char verbose;
+static unsigned char gpio16;
+static unsigned char ec;
+module_param(verbose, byte, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(verbose, "Add some debugging messages");
+module_param(gpio16, byte, S_IRUGO);
+MODULE_PARM_DESC(gpio16, "Enable wlan_on signal on SM502");
+module_param(ec, byte, S_IRUGO);
+MODULE_PARM_DESC(ec, "Let the ST7 handle the battery (default OS)");
+
+struct gdium_laptop_data {
+	struct i2c_client		*client;
+	struct input_polled_dev		*input_polldev;
+	struct dentry			*debugfs;
+	struct mutex			mutex;
+	struct platform_device		*bat_pdev;
+	struct power_supply		gdium_ac;
+	struct power_supply		gdium_battery;
+	struct workqueue_struct		*workqueue;
+	struct delayed_work		work;
+	char				charge_cmd;
+	/* important registers value */
+	char				status;
+	char				ctrl;
+	/* mV */
+	int				battery_level;
+	char				version;
+};
+
+/**********************************************************************/
+/* Low level I2C functions                                            */
+/* All are supposed to be called with mutex held                      */
+/**********************************************************************/
+/*
+ * Return battery voltage in mV
+ * >= 0 battery voltage
+ * < 0 error
+ */
+static s32 ec_read_battery(struct i2c_client *client)
+{
+	unsigned char bat_low, bat_high;
+	s32 data;
+	unsigned int ret;
+
+	/*
+	 * a = battery high
+	 * b = battery low
+	 * bat = a << 2 | b & 0x03;
+	 * battery voltage = (bat / 1024) * 5 * 2
+	 */
+	data = i2c_smbus_read_byte_data(client, EC_BAT_LOW);
+	if (data < 0) {
+		dev_err(&client->dev, "ec_read_bat: read bat_low failed\n");
+		return data;
+	}
+	bat_low = data & 0xff;
+	if (verbose)
+		dev_info(&client->dev, "bat_low %x\n", bat_low);
+
+	data = i2c_smbus_read_byte_data(client, EC_BAT_HIGH);
+	if (data < 0) {
+		dev_err(&client->dev, "ec_read_bat: read bat_high failed\n");
+		return data;
+	}
+	bat_high = data & 0xff;
+	if (verbose)
+		dev_info(&client->dev, "bat_high %x\n", bat_high);
+
+	ret = (bat_high << 2) | (bat_low & 3);
+	/*
+	 * mV
+	 */
+	ret = (ret * 5 * 2) * 1000 / 1024;
+
+	return ret;
+}
+
+static s32 ec_read_version(struct i2c_client *client)
+{
+#if CONFIG_GDIUM_VERSION > 2
+	return i2c_smbus_read_byte_data(client, EC_FIRM_VERSION);
+#else
+	return 0;
+#endif
+}
+
+static s32 ec_read_status(struct i2c_client *client)
+{
+	return i2c_smbus_read_byte_data(client, EC_STATUS);
+}
+
+static s32 ec_read_ctrl(struct i2c_client *client)
+{
+	return i2c_smbus_read_byte_data(client, EC_CTRL);
+}
+
+static s32 ec_write_ctrl(struct i2c_client *client, unsigned char newvalue)
+{
+	return i2c_smbus_write_byte_data(client, EC_CTRL, newvalue);
+}
+
+static s32 ec_read_sign(struct i2c_client *client)
+{
+	return i2c_smbus_read_byte_data(client, EC_SIGN);
+}
+
+static s32 ec_write_sign(struct i2c_client *client, unsigned char sign)
+{
+	unsigned char value;
+	s32 ret;
+
+	ret = i2c_smbus_write_byte_data(client, EC_SIGN, sign);
+	if (ret < 0) {
+		dev_err(&client->dev, "ec_set_control: write failed\n");
+		return ret;
+	}
+
+	value = ec_read_sign(client);
+	if (value != sign) {
+		dev_err(&client->dev, "Failed to set control to %s\n",
+				sign == EC_SIGN_OS ? "OS" : "EC");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+#if 0
+static int ec_power_off(struct i2c_client *client)
+{
+	char value;
+	int ret;
+
+	value = ec_read_ctrl(client);
+	if (value < 0) {
+		dev_err(&client->dev, "ec_power_off: read failed\n");
+		return value;
+	}
+	value &= ~(EC_CTRL_SUSB | EC_CTRL_SUSC);
+	ret = ec_write_ctrl(client, value);
+	if (ret < 0) {
+		dev_err(&client->dev, "ec_power_off: write failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+#endif
+
+static s32 ec_wlan_status(struct i2c_client *client)
+{
+	s32 value;
+
+	value = ec_read_ctrl(client);
+	if (value < 0)
+		return value;
+
+	return (value & EC_CTRL_WLAN_EN) ? 1 : 0;
+}
+
+static s32 ec_wlan_en(struct i2c_client *client, int on)
+{
+	s32 value;
+
+	value = ec_read_ctrl(client);
+	if (value < 0)
+		return value;
+
+	value &= ~EC_CTRL_WLAN_EN;
+	if (on)
+		value |= EC_CTRL_WLAN_EN;
+
+	return ec_write_ctrl(client, value&0xff);
+}
+
+#if 0
+static s32 ec_led_status(struct i2c_client *client)
+{
+	s32 value;
+
+	value = ec_read_ctrl(client);
+	if (value < 0)
+		return value;
+
+	return (value & EC_CTRL_CHARGE_LED) ? 1 : 0;
+}
+#endif
+
+/* Changing the charging led status has never worked */
+static s32 ec_led_en(struct i2c_client *client, int on)
+{
+#if 0
+	s32 value;
+
+	value = ec_read_ctrl(client);
+	if (value < 0)
+		return value;
+
+	value &= ~EC_CTRL_CHARGE_LED;
+	if (on)
+		value |= EC_CTRL_CHARGE_LED;
+	return ec_write_ctrl(client, value&0xff);
+#else
+	return 0;
+#endif
+}
+
+static s32 ec_charge_en(struct i2c_client *client, int on, int trickle)
+{
+	s32 value;
+	s32 set = 0;
+
+	value = ec_read_ctrl(client);
+	if (value < 0)
+		return value;
+
+	if (on)
+		set |= EC_CTRL_CHARGE_EN;
+	if (trickle)
+		set |= EC_CTRL_TRICKLE;
+
+	/* Be clever : don't change values if you don't need to */
+	if ((value & (EC_CTRL_CHARGE_EN | EC_CTRL_TRICKLE)) == set)
+		return 0;
+
+	value &= ~(EC_CTRL_CHARGE_EN | EC_CTRL_TRICKLE);
+	value |= set;
+	ec_led_en(client, on);
+	return ec_write_ctrl(client, (unsigned char)(value&0xff));
+
+}
+
+/**********************************************************************/
+/* Input functions                                                    */
+/**********************************************************************/
+struct gdium_keys {
+	int last_state;
+	int key_code;
+	int mask;
+	int type;
+};
+
+static struct gdium_keys gkeys[] = {
+	{
+		.key_code	= KEY_WLAN,
+		.mask		= EC_STATUS_WLAN,
+		.type		= EV_KEY,
+	},
+	{
+		.key_code	= KEY_POWER,
+		.mask		= EC_STATUS_PWRBUT,
+		.type		= EV_KEY, /*EV_PWR,*/
+	},
+	{
+		.key_code	= SW_LID,
+		.mask		= EC_STATUS_LID,
+		.type		= EV_SW,
+	},
+};
+
+static void gdium_laptop_keys_poll(struct input_polled_dev *dev)
+{
+	int state, i;
+	struct gdium_laptop_data *data = dev->private;
+	struct i2c_client *client = data->client;
+	struct input_dev *input = dev->input;
+	s32 status;
+
+	mutex_lock(&data->mutex);
+	status = ec_read_status(client);
+	mutex_unlock(&data->mutex);
+
+	if (status < 0) {
+		/*
+		 * Don't know exactly  which version of the firmware
+		 * has this bug but when the power button is pressed
+		 * there are i2c read errors :(
+		 */
+		if ((data->version >= 0x13) && !gkeys[1].last_state) {
+			input_event(input, EV_KEY, KEY_POWER, 1);
+			input_sync(input);
+			gkeys[1].last_state = 1;
+		}
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(gkeys); i++) {
+		state = status & gkeys[i].mask;
+		if (state != gkeys[i].last_state) {
+			gkeys[i].last_state = state;
+			/* for power key, we want power & key press/release event */
+			if (gkeys[i].type == EV_PWR) {
+				input_event(input, EV_KEY, gkeys[i].key_code, !!state);
+				input_sync(input);
+			}
+			/* Disable wifi on key press but not key release */
+			/*
+			 * On firmware >= 0x13 the EC_STATUS_WLAN has it's
+			 * original meaning of Wifi status and no more the
+			 * wifi button status so we have to ignore the event
+			 * on theses versions
+			 */
+			if (state && (gkeys[i].key_code == KEY_WLAN) && (data->version < 0x13)) {
+				mutex_lock(&data->mutex);
+				ec_wlan_en(client, !ec_wlan_status(client));
+				if (gpio16)
+					gpio_set_value(SM502_WLAN_ON, !ec_wlan_status(client));
+				mutex_unlock(&data->mutex);
+			}
+
+			input_event(input, gkeys[i].type, gkeys[i].key_code, !!state);
+			input_sync(input);
+		}
+	}
+}
+
+static int gdium_laptop_input_init(struct gdium_laptop_data *data)
+{
+	struct i2c_client *client = data->client;
+	struct input_dev *input;
+	int ret, i;
+
+	data->input_polldev = input_allocate_polled_device();
+	if (!data->input_polldev) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	input = data->input_polldev->input;
+	input->evbit[0] = BIT(EV_KEY) | BIT_MASK(EV_PWR) | BIT_MASK(EV_SW);
+	data->input_polldev->poll = gdium_laptop_keys_poll;
+	data->input_polldev->poll_interval = SCAN_INTERVAL;
+	data->input_polldev->private = data;
+	input->name = "gdium-keys";
+	input->dev.parent = &client->dev;
+
+	input->id.bustype = BUS_HOST;
+	input->id.vendor = 0x0001;
+	input->id.product = 0x0001;
+	input->id.version = 0x0100;
+
+	for (i = 0; i < ARRAY_SIZE(gkeys); i++)
+		input_set_capability(input, gkeys[i].type, gkeys[i].key_code);
+
+	ret = input_register_polled_device(data->input_polldev);
+	if (ret) {
+		dev_err(&client->dev, "Unable to register button device\n");
+		goto err_poll_dev;
+	}
+
+	return 0;
+
+err_poll_dev:
+	input_free_polled_device(data->input_polldev);
+err:
+	return ret;
+}
+
+static void gdium_laptop_input_exit(struct gdium_laptop_data *data)
+{
+	input_unregister_polled_device(data->input_polldev);
+	input_free_polled_device(data->input_polldev);
+}
+
+/**********************************************************************/
+/* Battery management                                                 */
+/**********************************************************************/
+static int gdium_ac_get_props(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	char status;
+	struct gdium_laptop_data *data = container_of(psy, struct gdium_laptop_data, gdium_ac);
+	int ret = 0;
+
+	if (!data) {
+		pr_err("gdium-ac: gdium_laptop_data not found\n");
+		return -EINVAL;
+	}
+
+	status = data->status;
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = !!(status & EC_STATUS_ADAPT);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+#undef RET
+#define RET (val->intval)
+
+static int gdium_battery_get_props(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	char status, ctrl;
+	struct gdium_laptop_data *data = container_of(psy, struct gdium_laptop_data, gdium_battery);
+	int percentage_capacity = 0, charge_now = 0, time_to_empty = 0;
+	int ret = 0, tmp;
+
+	if (!data) {
+		pr_err("gdium-battery: gdium_laptop_data not found\n");
+		return -EINVAL;
+	}
+
+	status = data->status;
+	ctrl   = data->ctrl;
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		/* uAh */
+		RET = 5000000;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+		/* This formula is gotten by gnuplot with the statistic data */
+		time_to_empty = (data->battery_level - BAT_MIN_MV + BAT_READ_ERROR_MV) * 113 - 29870;
+		if (psp == POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW) {
+			/* seconds */
+			RET = time_to_empty / 10;
+			break;
+		}
+		/* fall through */
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+	case POWER_SUPPLY_PROP_CAPACITY: {
+		tmp = data->battery_level * 1000;
+		/* > BAT_MIN to avoid negative values */
+		percentage_capacity = 0;
+		if ((status & EC_STATUS_BATID) && (tmp > BAT_MIN))
+			percentage_capacity = (tmp-BAT_MIN)*100/(BAT_MAX-BAT_MIN);
+
+		if (percentage_capacity > 100)
+			percentage_capacity = 100;
+
+		if (psp == POWER_SUPPLY_PROP_CAPACITY) {
+			RET = percentage_capacity;
+			break;
+		}
+		charge_now = 50000 * percentage_capacity;
+		if (psp == POWER_SUPPLY_PROP_CHARGE_NOW) {
+			/* uAh */
+			RET = charge_now;
+			break;
+		}
+	}	/* fall through */
+	case POWER_SUPPLY_PROP_STATUS: {
+		if (status & EC_STATUS_ADAPT)
+			if (ctrl & EC_CTRL_CHARGE_EN)
+				RET = POWER_SUPPLY_STATUS_CHARGING;
+			else
+				RET = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		else
+			RET = POWER_SUPPLY_STATUS_DISCHARGING;
+
+		if (psp == POWER_SUPPLY_PROP_STATUS)
+			break;
+		/* mAh -> µA */
+		switch (RET) {
+		case POWER_SUPPLY_STATUS_CHARGING:
+			RET = -(data->charge_cmd == 2) ? 1400000 : 250000;
+			break;
+		case POWER_SUPPLY_STATUS_DISCHARGING:
+			RET = charge_now / time_to_empty * 36000;
+			break;
+		case POWER_SUPPLY_STATUS_NOT_CHARGING:
+		default:
+			RET = 0;
+			break;
+		}
+	} break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+		RET = BAT_MAX+BAT_READ_ERROR;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+		RET = BAT_MIN-BAT_READ_ERROR;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		/* mV -> uV */
+		RET = data->battery_level * 1000;
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+#if CONFIG_GDIUM_VERSION > 2
+		RET = !!(status & EC_STATUS_BATID);
+#else
+		RET = !!(data->battery_level > BAT_VOLT_PRESENT);
+#endif
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+		tmp = data->battery_level * 1000;
+		RET = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+		if (status & EC_STATUS_BATID) {
+			if (tmp >= BAT_MAX) {
+				RET = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+				if (tmp >= BAT_MAX+BAT_READ_ERROR)
+					RET = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+			} else if (tmp <= BAT_MIN+BAT_READ_ERROR) {
+				RET = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+				if (tmp <= BAT_MIN)
+					RET = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+			} else
+				RET = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+		}
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		if (ctrl & EC_CTRL_TRICKLE)
+			RET = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+		else if (ctrl & EC_CTRL_CHARGE_EN)
+			RET = POWER_SUPPLY_CHARGE_TYPE_FAST;
+		else
+			RET = POWER_SUPPLY_CHARGE_TYPE_NONE;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		/* 1.4A ? */
+		RET = 1400000;
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+#undef RET
+
+static enum power_supply_property gdium_ac_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property gdium_battery_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static void gdium_laptop_battery_work(struct work_struct *work)
+{
+	struct gdium_laptop_data *data = container_of(work, struct gdium_laptop_data, work.work);
+	struct i2c_client *client;
+	int ret;
+	char old_status, old_charge_cmd;
+	char present;
+	s32 status;
+
+	mutex_lock(&data->mutex);
+	client	= data->client;
+	status	= ec_read_status(client);
+	ret	= ec_read_battery(client);
+
+	if ((status < 0) || (ret < 0))
+		goto i2c_read_error;
+
+	old_status = data->status;
+	old_charge_cmd = data->charge_cmd;
+	data->status = status;
+
+	/*
+	 * Charge only if :
+	 * - battery present
+	 * - ac adapter plugged in
+	 * - battery not fully charged
+	 */
+#if CONFIG_GDIUM_VERSION > 2
+	present = !!(data->status & EC_STATUS_BATID);
+#else
+	present = !!(ret > BAT_VOLT_PRESENT);
+#endif
+	data->battery_level = 0;
+	if (present) {
+		data->battery_level = (unsigned int)ret;
+		if (data->status & EC_STATUS_ADAPT)
+			data->battery_level -= BAT_READ_ERROR_MV;
+	}
+
+	data->charge_cmd = 0;
+	if ((data->status & EC_STATUS_ADAPT) && present && (data->battery_level <= BAT_MAX_MV))
+		data->charge_cmd = (ret < BAT_TRICKLE_EN) ? 2 : 3;
+
+	ec_charge_en(client, (data->charge_cmd >> 1) & 1, data->charge_cmd & 1);
+
+	/*
+	 * data->ctrl must be set _after_ calling ec_charge_en as this will change the
+	 * control register content
+	 */
+	data->ctrl = ec_read_ctrl(client);
+
+	if ((data->status & EC_STATUS_ADAPT) != (old_status & EC_STATUS_ADAPT)) {
+		power_supply_changed(&data->gdium_ac);
+		/* Send charging/discharging state change */
+		power_supply_changed(&data->gdium_battery);
+	} else if ((data->status & EC_STATUS_ADAPT) &&
+			((old_charge_cmd&2) != (data->charge_cmd&2)))
+		power_supply_changed(&data->gdium_battery);
+
+i2c_read_error:
+	mutex_unlock(&data->mutex);
+	queue_delayed_work(data->workqueue, &data->work, msecs_to_jiffies(BAT_SCAN_INTERVAL));
+}
+
+static int gdium_laptop_battery_init(struct gdium_laptop_data *data)
+{
+	int ret;
+
+	data->bat_pdev = platform_device_register_simple("gdium-battery", 0, NULL, 0);
+	if (IS_ERR(data->bat_pdev))
+		return PTR_ERR(data->bat_pdev);
+
+	data->gdium_battery.name		= data->bat_pdev->name;
+	data->gdium_battery.properties		= gdium_battery_props;
+	data->gdium_battery.num_properties	= ARRAY_SIZE(gdium_battery_props);
+	data->gdium_battery.get_property	= gdium_battery_get_props;
+	data->gdium_battery.use_for_apm		= 1;
+
+	ret = power_supply_register(&data->bat_pdev->dev, &data->gdium_battery);
+	if (ret)
+		goto err_platform;
+
+	data->gdium_ac.name			= "gdium-ac";
+	data->gdium_ac.type			= POWER_SUPPLY_TYPE_MAINS;
+	data->gdium_ac.properties		= gdium_ac_props;
+	data->gdium_ac.num_properties		= ARRAY_SIZE(gdium_ac_props);
+	data->gdium_ac.get_property		= gdium_ac_get_props;
+/*	data->gdium_ac.use_for_apm_ac		= 1,	*/
+
+	ret = power_supply_register(&data->bat_pdev->dev, &data->gdium_ac);
+	if (ret)
+		goto err_battery;
+
+	if (!ec) {
+		INIT_DELAYED_WORK(&data->work, gdium_laptop_battery_work);
+		data->workqueue = create_singlethread_workqueue("gdium-battery-work");
+		if (!data->workqueue) {
+			ret = -ESRCH;
+			goto err_work;
+		}
+		queue_delayed_work(data->workqueue, &data->work, msecs_to_jiffies(BAT_SCAN_INTERVAL));
+	}
+
+	return 0;
+
+err_work:
+err_battery:
+	power_supply_unregister(&data->gdium_battery);
+err_platform:
+	platform_device_unregister(data->bat_pdev);
+
+	return ret;
+}
+static void gdium_laptop_battery_exit(struct gdium_laptop_data *data)
+{
+	if (!ec) {
+		cancel_rearming_delayed_workqueue(data->workqueue, &data->work);
+		destroy_workqueue(data->workqueue);
+	}
+	power_supply_unregister(&data->gdium_battery);
+	power_supply_unregister(&data->gdium_ac);
+	platform_device_unregister(data->bat_pdev);
+}
+
+/* Debug fs */
+static int gdium_laptop_regs_show(struct seq_file *s, void *p)
+{
+	struct gdium_laptop_data *data = s->private;
+	struct i2c_client *client = data->client;
+
+	mutex_lock(&data->mutex);
+	seq_printf(s, "Version    : 0x%02x\n", (unsigned char)ec_read_version(client));
+	seq_printf(s, "Status     : 0x%02x\n", (unsigned char)ec_read_status(client));
+	seq_printf(s, "Ctrl       : 0x%02x\n", (unsigned char)ec_read_ctrl(client));
+	seq_printf(s, "Sign       : 0x%02x\n", (unsigned char)ec_read_sign(client));
+	seq_printf(s, "Bat Lo     : 0x%02x\n", (unsigned char)i2c_smbus_read_byte_data(client, EC_BAT_LOW));
+	seq_printf(s, "Bat Hi     : 0x%02x\n", (unsigned char)i2c_smbus_read_byte_data(client, EC_BAT_HIGH));
+	seq_printf(s, "Battery    : %d uV\n",  (unsigned int)ec_read_battery(client) * 1000);
+	seq_printf(s, "Charge cmd : %s %s\n", data->charge_cmd & 2 ? "C" : " ", data->charge_cmd & 1 ? "T" : " ");
+
+	mutex_unlock(&data->mutex);
+	return 0;
+}
+
+static int gdium_laptop_regs_open(struct inode *inode,
+					 struct file *file)
+{
+	return single_open(file, gdium_laptop_regs_show, inode->i_private);
+}
+
+static const struct file_operations gdium_laptop_regs_fops = {
+	.open		= gdium_laptop_regs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.owner		= THIS_MODULE,
+};
+
+
+static int gdium_laptop_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct gdium_laptop_data *data;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(&client->dev,
+				"%s: no smbus_byte support !\n", __func__);
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(struct gdium_laptop_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, data);
+	data->client = client;
+	mutex_init(&data->mutex);
+
+	ret = ec_read_version(client);
+	if (ret < 0)
+		goto err_alloc;
+
+	data->version = (unsigned char)ret;
+
+	ret = gdium_laptop_input_init(data);
+	if (ret)
+		goto err_alloc;
+
+	ret = gdium_laptop_battery_init(data);
+	if (ret)
+		goto err_input;
+
+
+	if (!ec) {
+		ret = ec_write_sign(client, EC_SIGN_OS);
+		if (ret)
+			goto err_sign;
+	}
+
+	if (gpio16) {
+		ret = gpio_request(SM502_WLAN_ON, "wlan-on");
+		if (ret < 0)
+			goto err_sign;
+		gpio_set_value(SM502_WLAN_ON, ec_wlan_status(client));
+		gpio_direction_output(SM502_WLAN_ON, 1);
+	}
+
+	dev_info(&client->dev, "Found firmware 0x%02x\n", data->version);
+	data->debugfs = debugfs_create_file("gdium_laptop", S_IFREG | S_IRUGO,
+				NULL, data, &gdium_laptop_regs_fops);
+
+	return 0;
+
+err_sign:
+	gdium_laptop_battery_exit(data);
+err_input:
+	gdium_laptop_input_exit(data);
+err_alloc:
+	kfree(data);
+	return ret;
+}
+
+static int gdium_laptop_remove(struct i2c_client *client)
+{
+	struct gdium_laptop_data *data = i2c_get_clientdata(client);
+
+	if (gpio16)
+		gpio_free(SM502_WLAN_ON);
+	ec_write_sign(client, EC_SIGN_EC);
+	if (data->debugfs)
+		debugfs_remove(data->debugfs);
+
+	gdium_laptop_battery_exit(data);
+	gdium_laptop_input_exit(data);
+
+	kfree(data);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int gdium_laptop_suspend(struct i2c_client *client, pm_message_t msg)
+{
+	struct gdium_laptop_data *data = i2c_get_clientdata(client);
+
+	if (!ec)
+		cancel_rearming_delayed_workqueue(data->workqueue, &data->work);
+	return 0;
+}
+
+static int gdium_laptop_resume(struct i2c_client *client)
+{
+	struct gdium_laptop_data *data = i2c_get_clientdata(client);
+
+	if (!ec)
+		queue_delayed_work(data->workqueue, &data->work, msecs_to_jiffies(BAT_SCAN_INTERVAL));
+	return 0;
+}
+#else
+#define gdium_laptop_suspend NULL
+#define gdium_laptop_resume NULL
+#endif
+static const struct i2c_device_id gdium_id[] = {
+	{ "gdium-laptop" },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, gdium_id);
+
+static struct i2c_driver gdium_laptop_driver = {
+	.driver = {
+		.name = "gdium-laptop",
+		.owner = THIS_MODULE,
+	},
+	.probe = gdium_laptop_probe,
+	.remove = gdium_laptop_remove,
+	.shutdown = gdium_laptop_remove,
+	.suspend = gdium_laptop_suspend,
+	.resume = gdium_laptop_resume,
+	.id_table = gdium_id,
+};
+
+static int __init gdium_laptop_init(void)
+{
+	return i2c_add_driver(&gdium_laptop_driver);
+}
+
+static void __exit gdium_laptop_exit(void)
+{
+	i2c_del_driver(&gdium_laptop_driver);
+}
+
+module_init(gdium_laptop_init);
+module_exit(gdium_laptop_exit);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Gdium laptop extras");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/mips/lynloong_pc.c b/drivers/platform/mips/lynloong_pc.c
new file mode 100644
index 0000000..68f29e4
--- /dev/null
+++ b/drivers/platform/mips/lynloong_pc.c
@@ -0,0 +1,515 @@
+/*
+ * Driver for LynLoong PC extras
+ *
+ *  Copyright (C) 2009 Lemote Inc.
+ *  Author: Wu Zhangjin <wuzhangjin@gmail.com>, Xiang Yu <xiangy@lemote.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>	/* for backlight subdriver */
+#include <linux/fb.h>
+#include <linux/video_output.h>	/* for video output subdriver */
+#include <linux/delay.h>	/* for suspend support */
+
+#include <cs5536/cs5536.h>
+#include <cs5536/cs5536_mfgpt.h>
+
+#include <loongson.h>
+
+static u32 gpio_base, mfgpt_base;
+
+static void set_gpio_reg_high(int gpio, int reg)
+{
+	u32 val;
+
+	val = inl(gpio_base + reg);
+	val |= (1 << gpio);
+	val &= ~(1 << (16 + gpio));
+	outl(val, gpio_base + reg);
+	mmiowb();
+}
+
+static void set_gpio_reg_low(int gpio, int reg)
+{
+	u32 val;
+
+	val = inl(gpio_base + reg);
+	val |= (1 << (16 + gpio));
+	val &= ~(1 << gpio);
+	outl(val, gpio_base + reg);
+	mmiowb();
+}
+
+static void set_gpio_output_low(int gpio)
+{
+	set_gpio_reg_high(gpio, GPIOL_OUT_EN);
+	set_gpio_reg_low(gpio, GPIOL_OUT_VAL);
+}
+
+static void set_gpio_output_high(int gpio)
+{
+	set_gpio_reg_high(gpio, GPIOL_OUT_EN);
+	set_gpio_reg_high(gpio, GPIOL_OUT_VAL);
+}
+
+/* backlight subdriver */
+
+#define MAX_BRIGHTNESS 100
+#define DEFAULT_BRIGHTNESS 50
+#define MIN_BRIGHTNESS 0
+static unsigned int level;
+
+DEFINE_SPINLOCK(backlight_lock);
+/* Tune the brightness */
+static void setup_mfgpt2(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&backlight_lock, flags);
+
+	/* Set MFGPT2 comparator 1,2 */
+	outw(MAX_BRIGHTNESS-level, MFGPT2_CMP1);
+	outw(MAX_BRIGHTNESS, MFGPT2_CMP2);
+	/* Clear MFGPT2 UP COUNTER */
+	outw(0, MFGPT2_CNT);
+	/* Enable counter, compare mode, 32k */
+	outw(0x8280, MFGPT2_SETUP);
+
+	spin_unlock_irqrestore(&backlight_lock, flags);
+}
+
+static int lynloong_set_brightness(struct backlight_device *bd)
+{
+	level = (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+		 bd->props.power == FB_BLANK_UNBLANK) ?
+	    bd->props.brightness : 0;
+
+	if (level > MAX_BRIGHTNESS)
+		level = MAX_BRIGHTNESS;
+	else if (level < MIN_BRIGHTNESS)
+		level = MIN_BRIGHTNESS;
+
+	setup_mfgpt2();
+
+	return 0;
+}
+
+static int lynloong_get_brightness(struct backlight_device *bd)
+{
+	return level;
+}
+
+static struct backlight_ops backlight_ops = {
+	.get_brightness = lynloong_get_brightness,
+	.update_status = lynloong_set_brightness,
+};
+
+static struct backlight_device *lynloong_backlight_dev;
+
+static int lynloong_backlight_init(void)
+{
+	int ret;
+	u32 hi;
+	struct backlight_properties props;
+
+	/* Get gpio_base */
+	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &gpio_base);
+	/* Get mfgpt_base */
+	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_MFGPT), &hi, &mfgpt_base);
+	/* Get gpio_base */
+	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &gpio_base);
+
+	/* Select for mfgpt */
+	set_gpio_reg_high(7, GPIOL_OUT_AUX1_SEL);
+	/* Enable brightness controlling */
+	set_gpio_output_high(7);
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.max_brightness = MAX_BRIGHTNESS;
+	props.type = BACKLIGHT_PLATFORM;
+	lynloong_backlight_dev = backlight_device_register("backlight0", NULL,
+			NULL, &backlight_ops, &props);
+
+	if (IS_ERR(lynloong_backlight_dev)) {
+		ret = PTR_ERR(lynloong_backlight_dev);
+		return ret;
+	}
+
+	lynloong_backlight_dev->props.brightness = DEFAULT_BRIGHTNESS;
+	backlight_update_status(lynloong_backlight_dev);
+
+	return 0;
+}
+
+static void lynloong_backlight_exit(void)
+{
+	if (lynloong_backlight_dev) {
+		backlight_device_unregister(lynloong_backlight_dev);
+		lynloong_backlight_dev = NULL;
+	}
+	/* Disable brightness controlling */
+	set_gpio_output_low(7);
+}
+
+/* video output driver */
+static int vo_status = 1;
+
+static int lcd_video_output_get(struct output_device *od)
+{
+	return vo_status;
+}
+
+static int lcd_video_output_set(struct output_device *od)
+{
+	int i;
+	unsigned long status;
+
+	status = !!od->request_state;
+
+	if (status == 0) {
+		/* Set the current status as off */
+		vo_status = 0;
+		/* Turn off the backlight */
+		set_gpio_output_low(11);
+		for (i = 0; i < 0x500; i++)
+			delay();
+		/* Turn off the LCD */
+		set_gpio_output_high(8);
+	} else {
+		/* Turn on the LCD */
+		set_gpio_output_low(8);
+		for (i = 0; i < 0x500; i++)
+			delay();
+		/* Turn on the backlight */
+		set_gpio_output_high(11);
+		/* Set the current status as on */
+		vo_status = 1;
+	}
+
+	return 0;
+}
+
+static struct output_properties lcd_output_properties = {
+	.set_state = lcd_video_output_set,
+	.get_status = lcd_video_output_get,
+};
+
+static struct output_device *lcd_output_dev;
+
+static void lynloong_lcd_vo_set(int status)
+{
+	lcd_output_dev->request_state = status;
+	lcd_video_output_set(lcd_output_dev);
+}
+
+static int lynloong_vo_init(void)
+{
+	int ret;
+
+	/* Register video output device: lcd */
+	lcd_output_dev = video_output_register("LCD", NULL, NULL,
+			&lcd_output_properties);
+
+	if (IS_ERR(lcd_output_dev)) {
+		ret = PTR_ERR(lcd_output_dev);
+		lcd_output_dev = NULL;
+		return ret;
+	}
+	/* Ensure LCD is on by default */
+	lynloong_lcd_vo_set(1);
+
+	return 0;
+}
+
+static void lynloong_vo_exit(void)
+{
+	if (lcd_output_dev) {
+		video_output_unregister(lcd_output_dev);
+		lcd_output_dev = NULL;
+	}
+}
+
+/* suspend support */
+
+#ifdef CONFIG_PM
+
+static u32 smb_base;
+
+/* I2C operations */
+
+static int i2c_wait(void)
+{
+	char c;
+	int i;
+
+	udelay(1000);
+	for (i = 0; i < 20; i++) {
+		c = inb(smb_base | SMB_STS);
+		if (c & (SMB_STS_BER | SMB_STS_NEGACK))
+			return -1;
+		if (c & SMB_STS_SDAST)
+			return 0;
+		udelay(100);
+	}
+	return -2;
+}
+
+static void i2c_read_single(int addr, int regNo, char *value)
+{
+	unsigned char c;
+
+	/* Start condition */
+	c = inb(smb_base | SMB_CTRL1);
+	outb(c | SMB_CTRL1_START, smb_base | SMB_CTRL1);
+	i2c_wait();
+
+	/* Send slave address */
+	outb(addr & 0xfe, smb_base | SMB_SDA);
+	i2c_wait();
+
+	/* Acknowledge smbus */
+	c = inb(smb_base | SMB_CTRL1);
+	outb(c | SMB_CTRL1_ACK, smb_base | SMB_CTRL1);
+
+	/* Send register index */
+	outb(regNo, smb_base | SMB_SDA);
+	i2c_wait();
+
+	/* Acknowledge smbus */
+	c = inb(smb_base | SMB_CTRL1);
+	outb(c | SMB_CTRL1_ACK, smb_base | SMB_CTRL1);
+
+	/* Start condition again */
+	c = inb(smb_base | SMB_CTRL1);
+	outb(c | SMB_CTRL1_START, smb_base | SMB_CTRL1);
+	i2c_wait();
+
+	/* Send salve address again */
+	outb(1 | addr, smb_base | SMB_SDA);
+	i2c_wait();
+
+	/* Acknowledge smbus */
+	c = inb(smb_base | SMB_CTRL1);
+	outb(c | SMB_CTRL1_ACK, smb_base | SMB_CTRL1);
+
+	/* Read data */
+	*value = inb(smb_base | SMB_SDA);
+
+	/* Stop condition */
+	outb(SMB_CTRL1_STOP, smb_base | SMB_CTRL1);
+	i2c_wait();
+}
+
+static void i2c_write_single(int addr, int regNo, char value)
+{
+	unsigned char c;
+
+	/* Start condition */
+	c = inb(smb_base | SMB_CTRL1);
+	outb(c | SMB_CTRL1_START, smb_base | SMB_CTRL1);
+	i2c_wait();
+	/* Send slave address */
+	outb(addr & 0xfe, smb_base | SMB_SDA);
+	i2c_wait();;
+
+	/* Send register index */
+	outb(regNo, smb_base | SMB_SDA);
+	i2c_wait();
+
+	/* Write data */
+	outb(value, smb_base | SMB_SDA);
+	i2c_wait();
+	/* Stop condition */
+	outb(SMB_CTRL1_STOP, smb_base | SMB_CTRL1);
+	i2c_wait();
+}
+
+static void stop_clock(int clk_reg, int clk_sel)
+{
+	u8 value;
+
+	i2c_read_single(0xd3, clk_reg, &value);
+	value &= ~(1 << clk_sel);
+	i2c_write_single(0xd2, clk_reg, value);
+}
+
+static void enable_clock(int clk_reg, int clk_sel)
+{
+	u8 value;
+
+	i2c_read_single(0xd3, clk_reg, &value);
+	value |= (1 << clk_sel);
+	i2c_write_single(0xd2, clk_reg, value);
+}
+
+static char cached_clk_freq;
+static char cached_pci_fixed_freq;
+
+static void decrease_clk_freq(void)
+{
+	char value;
+
+	i2c_read_single(0xd3, 1, &value);
+	cached_clk_freq = value;
+
+	/* Select frequency by software */
+	value |= (1 << 1);
+	/* CPU, 3V66, PCI : 100, 66, 33(1) */
+	value |= (1 << 2);
+	i2c_write_single(0xd2, 1, value);
+
+	/* Cache the pci frequency */
+	i2c_read_single(0xd3, 14, &value);
+	cached_pci_fixed_freq = value;
+
+	/* Enable PCI fix mode */
+	value |= (1 << 5);
+	/* 3V66, PCI : 64MHz, 32MHz */
+	value |= (1 << 3);
+	i2c_write_single(0xd2, 14, value);
+
+}
+
+static void resume_clk_freq(void)
+{
+	i2c_write_single(0xd2, 1, cached_clk_freq);
+	i2c_write_single(0xd2, 14, cached_pci_fixed_freq);
+}
+
+static void stop_clocks(void)
+{
+	/* CPU Clock Register */
+	stop_clock(2, 5);	/* not used */
+	stop_clock(2, 6);	/* not used */
+	stop_clock(2, 7);	/* not used */
+
+	/* PCI Clock Register */
+	stop_clock(3, 1);	/* 8100 */
+	stop_clock(3, 5);	/* SIS */
+	stop_clock(3, 0);	/* not used */
+	stop_clock(3, 6);	/* not used */
+
+	/* PCI 48M Clock Register */
+	stop_clock(4, 6);	/* USB grounding */
+	stop_clock(4, 5);	/* REF(5536_14M) */
+
+	/* 3V66 Control Register */
+	stop_clock(5, 0);	/* VCH_CLK..., grounding */
+}
+
+static void enable_clocks(void)
+{
+	enable_clock(3, 1);	/* 8100 */
+	enable_clock(3, 5);	/* SIS */
+
+	enable_clock(4, 6);
+	enable_clock(4, 5);	/* REF(5536_14M) */
+
+	enable_clock(5, 0);	/* VCH_CLOCK, grounding */
+}
+
+static int lynloong_suspend(struct device *dev)
+{
+	/* Disable AMP */
+	set_gpio_output_high(6);
+	/* Turn off LCD */
+	lynloong_lcd_vo_set(0);
+
+	/* Stop the clocks of some devices */
+	stop_clocks();
+
+	/* Decrease the external clock frequency */
+	decrease_clk_freq();
+
+	return 0;
+}
+
+static int lynloong_resume(struct device *dev)
+{
+	/* Turn on the LCD */
+	lynloong_lcd_vo_set(1);
+
+	/* Resume clock frequency, enable the relative clocks */
+	resume_clk_freq();
+	enable_clocks();
+
+	/* Enable AMP */
+	set_gpio_output_low(6);
+
+	return 0;
+}
+
+static const SIMPLE_DEV_PM_OPS(lynloong_pm_ops, lynloong_suspend,
+	lynloong_resume);
+#endif	/* !CONFIG_PM */
+
+static struct platform_device_id platform_device_ids[] = {
+	{
+		.name = "lynloong_pc",
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(platform, platform_device_ids);
+
+static struct platform_driver platform_driver = {
+	.driver = {
+		.name = "lynloong_pc",
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &lynloong_pm_ops,
+#endif
+	},
+	.id_table = platform_device_ids,
+};
+
+static int __init lynloong_init(void)
+{
+	int ret;
+
+	pr_info("LynLoong platform specific driver loaded.\n");
+
+	/* Register platform stuff */
+	ret = platform_driver_register(&platform_driver);
+	if (ret) {
+		pr_err("Failed to register LynLoong platform driver.\n");
+		return ret;
+	}
+
+	ret = lynloong_backlight_init();
+	if (ret) {
+		pr_err("Failed to register LynLoong backlight driver.\n");
+		return ret;
+	}
+
+	ret = lynloong_vo_init();
+	if (ret) {
+		pr_err("Failed to register LynLoong backlight driver.\n");
+		lynloong_vo_exit();
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit lynloong_exit(void)
+{
+	lynloong_vo_exit();
+	lynloong_backlight_exit();
+	platform_driver_unregister(&platform_driver);
+
+	pr_info("LynLoong platform specific driver unloaded.\n");
+}
+
+module_init(lynloong_init);
+module_exit(lynloong_exit);
+
+MODULE_AUTHOR("Wu Zhangjin <wuzhangjin@gmail.com>; Xiang Yu <xiangy@lemote.com>");
+MODULE_DESCRIPTION("LynLoong PC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/mips/yeeloong_ecrom.c b/drivers/platform/mips/yeeloong_ecrom.c
new file mode 100644
index 0000000..1bfe4cf
--- /dev/null
+++ b/drivers/platform/mips/yeeloong_ecrom.c
@@ -0,0 +1,944 @@
+/*
+ * Driver for flushing/dumping ROM of EC on YeeLoong laptop
+ *
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: liujl <liujl@lemote.com>
+ *
+ * NOTE :
+ * 	The EC resources accessing and programming are supported.
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <ec_kb3310b.h>
+
+#define	EC_MISC_DEV		"ec_misc"
+#define EC_IOC_MAGIC		'E'
+
+/* ec registers range */
+#define	EC_MAX_REGADDR	0xFFFF
+#define	EC_MIN_REGADDR	0xF000
+#define	EC_RAM_ADDR	0xF800
+
+/* version burned address */
+#define	VER_ADDR	0xf7a1
+#define	VER_MAX_SIZE	7
+#define	EC_ROM_MAX_SIZE	0x10000
+
+/* ec internal register */
+#define	REG_POWER_MODE		0xF710
+#define	FLAG_NORMAL_MODE	0x00
+#define	FLAG_IDLE_MODE		0x01
+#define	FLAG_RESET_MODE		0x02
+
+/* ec update program flag */
+#define	PROGRAM_FLAG_NONE	0x00
+#define	PROGRAM_FLAG_IE		0x01
+#define	PROGRAM_FLAG_ROM	0x02
+
+/* XBI relative registers */
+#define REG_XBISEG0     0xFEA0
+#define REG_XBISEG1     0xFEA1
+#define REG_XBIRSV2     0xFEA2
+#define REG_XBIRSV3     0xFEA3
+#define REG_XBIRSV4     0xFEA4
+#define REG_XBICFG      0xFEA5
+#define REG_XBICS       0xFEA6
+#define REG_XBIWE       0xFEA7
+#define REG_XBISPIA0    0xFEA8
+#define REG_XBISPIA1    0xFEA9
+#define REG_XBISPIA2    0xFEAA
+#define REG_XBISPIDAT   0xFEAB
+#define REG_XBISPICMD   0xFEAC
+#define REG_XBISPICFG   0xFEAD
+#define REG_XBISPIDATR  0xFEAE
+#define REG_XBISPICFG2  0xFEAF
+
+/* commands definition for REG_XBISPICMD */
+#define	SPICMD_WRITE_STATUS		0x01
+#define	SPICMD_BYTE_PROGRAM		0x02
+#define	SPICMD_READ_BYTE		0x03
+#define	SPICMD_WRITE_DISABLE	0x04
+#define	SPICMD_READ_STATUS		0x05
+#define	SPICMD_WRITE_ENABLE		0x06
+#define	SPICMD_HIGH_SPEED_READ	0x0B
+#define	SPICMD_POWER_DOWN		0xB9
+#define	SPICMD_SST_EWSR			0x50
+#define	SPICMD_SST_SEC_ERASE	0x20
+#define	SPICMD_SST_BLK_ERASE	0x52
+#define	SPICMD_SST_CHIP_ERASE	0x60
+#define	SPICMD_FRDO				0x3B
+#define	SPICMD_SEC_ERASE		0xD7
+#define	SPICMD_BLK_ERASE		0xD8
+#define SPICMD_CHIP_ERASE		0xC7
+
+/* bits definition for REG_XBISPICFG */
+#define	SPICFG_AUTO_CHECK		0x01
+#define	SPICFG_SPI_BUSY			0x02
+#define	SPICFG_DUMMY_READ		0x04
+#define	SPICFG_EN_SPICMD		0x08
+#define	SPICFG_LOW_SPICS		0x10
+#define	SPICFG_EN_SHORT_READ	0x20
+#define	SPICFG_EN_OFFSET_READ	0x40
+#define	SPICFG_EN_FAST_READ		0x80
+
+/* watchdog timer registers */
+#define	REG_WDTCFG				0xfe80
+#define	REG_WDTPF				0xfe81
+#define REG_WDT					0xfe82
+
+/* lpc configure register */
+#define	REG_LPCCFG				0xfe95
+
+/* 8051 reg */
+#define	REG_PXCFG				0xff14
+
+/* Fan register in KB3310 */
+#define	REG_ECFAN_SPEED_LEVEL	0xf4e4
+#define	REG_ECFAN_SWITCH		0xf4d2
+
+/* the ec flash rom id number */
+#define	EC_ROM_PRODUCT_ID_SPANSION	0x01
+#define	EC_ROM_PRODUCT_ID_MXIC		0xC2
+#define	EC_ROM_PRODUCT_ID_AMIC		0x37
+#define	EC_ROM_PRODUCT_ID_EONIC		0x1C
+
+/* misc ioctl operations */
+#define	IOCTL_RDREG		_IOR(EC_IOC_MAGIC, 1, int)
+#define	IOCTL_WRREG		_IOW(EC_IOC_MAGIC, 2, int)
+#define	IOCTL_READ_EC		_IOR(EC_IOC_MAGIC, 3, int)
+#define	IOCTL_PROGRAM_IE	_IOW(EC_IOC_MAGIC, 4, int)
+#define	IOCTL_PROGRAM_EC	_IOW(EC_IOC_MAGIC, 5, int)
+
+/* start address for programming of EC content or IE */
+/*  ec running code start address */
+#define	EC_START_ADDR	0x00000000
+/*  ec information element storing address */
+#define	IE_START_ADDR	0x00020000
+
+/* EC state */
+#define	EC_STATE_IDLE	0x00	/*  ec in idle state */
+#define	EC_STATE_BUSY	0x01	/*  ec in busy state */
+
+/* timeout value for programming */
+#define	EC_FLASH_TIMEOUT	0x1000	/*  ec program timeout */
+/* command checkout timeout including cmd to port or state flag check */
+#define	EC_CMD_TIMEOUT		0x1000
+#define	EC_SPICMD_STANDARD_TIMEOUT	(4 * 1000)	/*  unit : us */
+#define	EC_MAX_DELAY_UNIT	(10)	/*  every time for polling */
+#define	SPI_FINISH_WAIT_TIME	10
+/* EC content max size */
+#define	EC_CONTENT_MAX_SIZE	(64 * 1024)
+#define	IE_CONTENT_MAX_SIZE	(0x100000 - IE_START_ADDR)
+
+/* the register operation access struct */
+struct ec_reg {
+	u32 addr;		/* the address of kb3310 registers */
+	u8 val;			/* the register value */
+};
+
+struct ec_info {
+	u32 start_addr;
+	u32 size;
+	u8 *buf;
+};
+
+/* open for using rom protection action */
+#define	EC_ROM_PROTECTION
+
+/* enable the chip reset mode */
+static int ec_init_reset_mode(void)
+{
+	int timeout;
+	unsigned char status = 0;
+	int ret = 0;
+
+	/* make chip goto reset mode */
+	ret = ec_query_seq(CMD_INIT_RESET_MODE);
+	if (ret < 0) {
+		printk(KERN_ERR "ec init reset mode failed.\n");
+		goto out;
+	}
+
+	/* make the action take active */
+	timeout = EC_CMD_TIMEOUT;
+	status = ec_read(REG_POWER_MODE) & FLAG_RESET_MODE;
+	while (timeout--) {
+		if (status) {
+			udelay(EC_REG_DELAY);
+			break;
+		}
+		status = ec_read(REG_POWER_MODE) & FLAG_RESET_MODE;
+		udelay(EC_REG_DELAY);
+	}
+	if (timeout <= 0) {
+		printk(KERN_ERR "ec rom fixup : can't check reset status.\n");
+		ret = -EINVAL;
+	} else
+		printk(KERN_INFO "(%d/%d)reset 0xf710 :  0x%x\n", timeout,
+			   EC_CMD_TIMEOUT - timeout, status);
+
+	/* set MCU to reset mode */
+	udelay(EC_REG_DELAY);
+	status = ec_read(REG_PXCFG);
+	status |= (1 << 0);
+	ec_write(REG_PXCFG, status);
+	udelay(EC_REG_DELAY);
+
+	/* disable FWH/LPC */
+	udelay(EC_REG_DELAY);
+	status = ec_read(REG_LPCCFG);
+	status &= ~(1 << 7);
+	ec_write(REG_LPCCFG, status);
+	udelay(EC_REG_DELAY);
+
+	printk(KERN_INFO "entering reset mode ok..............\n");
+
+ out:
+	return ret;
+}
+
+/* make ec exit from reset mode */
+static void ec_exit_reset_mode(void)
+{
+	unsigned char regval;
+
+	udelay(EC_REG_DELAY);
+	regval = ec_read(REG_LPCCFG);
+	regval |= (1 << 7);
+	ec_write(REG_LPCCFG, regval);
+	regval = ec_read(REG_PXCFG);
+	regval &= ~(1 << 0);
+	ec_write(REG_PXCFG, regval);
+	printk(KERN_INFO "exit reset mode ok..................\n");
+
+	return;
+}
+
+/* make ec disable WDD */
+static void ec_disable_WDD(void)
+{
+	unsigned char status;
+
+	udelay(EC_REG_DELAY);
+	status = ec_read(REG_WDTCFG);
+	ec_write(REG_WDTPF, 0x03);
+	ec_write(REG_WDTCFG, (status & 0x80) | 0x48);
+	printk(KERN_INFO "Disable WDD ok..................\n");
+
+	return;
+}
+
+/* make ec enable WDD */
+static void ec_enable_WDD(void)
+{
+	unsigned char status;
+
+	udelay(EC_REG_DELAY);
+	status = ec_read(REG_WDTCFG);
+	ec_write(REG_WDT, 0x28);	/* set WDT 5sec(0x28) */
+	ec_write(REG_WDTCFG, (status & 0x80) | 0x03);
+	printk(KERN_INFO "Enable WDD ok..................\n");
+
+	return;
+}
+
+/* make ec goto idle mode */
+static int ec_init_idle_mode(void)
+{
+	int timeout;
+	unsigned char status = 0;
+	int ret = 0;
+
+	ec_query_seq(CMD_INIT_IDLE_MODE);
+
+	/* make the action take active */
+	timeout = EC_CMD_TIMEOUT;
+	status = ec_read(REG_POWER_MODE) & FLAG_IDLE_MODE;
+	while (timeout--) {
+		if (status) {
+			udelay(EC_REG_DELAY);
+			break;
+		}
+		status = ec_read(REG_POWER_MODE) & FLAG_IDLE_MODE;
+		udelay(EC_REG_DELAY);
+	}
+	if (timeout <= 0) {
+		printk(KERN_ERR "ec rom fixup : can't check out the status.\n");
+		ret = -EINVAL;
+	} else
+		printk(KERN_INFO "(%d/%d)0xf710 :  0x%x\n", timeout,
+			   EC_CMD_TIMEOUT - timeout, ec_read(REG_POWER_MODE));
+
+	printk(KERN_INFO "entering idle mode ok...................\n");
+
+	return ret;
+}
+
+/* make ec exit from idle mode */
+static int ec_exit_idle_mode(void)
+{
+
+	ec_query_seq(CMD_EXIT_IDLE_MODE);
+
+	printk(KERN_INFO "exit idle mode ok...................\n");
+
+	return 0;
+}
+
+static int ec_instruction_cycle(void)
+{
+	unsigned long timeout;
+	int ret = 0;
+
+	timeout = EC_FLASH_TIMEOUT;
+	while (timeout-- >= 0) {
+		if (!(ec_read(REG_XBISPICFG) & SPICFG_SPI_BUSY))
+			break;
+	}
+	if (timeout <= 0) {
+		printk(KERN_ERR
+		       "EC_INSTRUCTION_CYCLE : timeout for check flag.\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+ out:
+	return ret;
+}
+
+/* To see if the ec is in busy state or not. */
+static inline int ec_flash_busy(unsigned long timeout)
+{
+	/* assurance the first command be going to rom */
+	if (ec_instruction_cycle() < 0)
+		return EC_STATE_BUSY;
+#if 1
+	timeout = timeout / EC_MAX_DELAY_UNIT;
+	while (timeout-- > 0) {
+		/* check the rom's status of busy flag */
+		ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
+		if (ec_instruction_cycle() < 0)
+			return EC_STATE_BUSY;
+		if ((ec_read(REG_XBISPIDAT) & 0x01) == 0x00)
+			return EC_STATE_IDLE;
+		udelay(EC_MAX_DELAY_UNIT);
+	}
+	if (timeout <= 0) {
+		printk(KERN_ERR
+		       "EC_FLASH_BUSY : timeout for check rom flag.\n");
+		return EC_STATE_BUSY;
+	}
+#else
+	/* check the rom's status of busy flag */
+	ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
+	if (ec_instruction_cycle() < 0)
+		return EC_STATE_BUSY;
+
+	timeout = timeout / EC_MAX_DELAY_UNIT;
+	while (timeout-- > 0) {
+		if ((ec_read(REG_XBISPIDAT) & 0x01) == 0x00)
+			return EC_STATE_IDLE;
+		udelay(EC_MAX_DELAY_UNIT);
+	}
+	if (timeout <= 0) {
+		printk(KERN_ERR
+		       "EC_FLASH_BUSY : timeout for check rom flag.\n");
+		return EC_STATE_BUSY;
+	}
+#endif
+
+	return EC_STATE_IDLE;
+}
+
+static int rom_instruction_cycle(unsigned char cmd)
+{
+	unsigned long timeout = 0;
+
+	switch (cmd) {
+	case SPICMD_READ_STATUS:
+	case SPICMD_WRITE_ENABLE:
+	case SPICMD_WRITE_DISABLE:
+	case SPICMD_READ_BYTE:
+	case SPICMD_HIGH_SPEED_READ:
+		timeout = 0;
+		break;
+	case SPICMD_WRITE_STATUS:
+		timeout = 300 * 1000;
+		break;
+	case SPICMD_BYTE_PROGRAM:
+		timeout = 5 * 1000;
+		break;
+	case SPICMD_SST_SEC_ERASE:
+	case SPICMD_SEC_ERASE:
+		timeout = 1000 * 1000;
+		break;
+	case SPICMD_SST_BLK_ERASE:
+	case SPICMD_BLK_ERASE:
+		timeout = 3 * 1000 * 1000;
+		break;
+	case SPICMD_SST_CHIP_ERASE:
+	case SPICMD_CHIP_ERASE:
+		timeout = 20 * 1000 * 1000;
+		break;
+	default:
+		timeout = EC_SPICMD_STANDARD_TIMEOUT;
+	}
+	if (timeout == 0)
+		return ec_instruction_cycle();
+	if (timeout < EC_SPICMD_STANDARD_TIMEOUT)
+		timeout = EC_SPICMD_STANDARD_TIMEOUT;
+
+	return ec_flash_busy(timeout);
+}
+
+/* delay for start/stop action */
+static void delay_spi(int n)
+{
+	while (n--)
+		inb(EC_IO_PORT_HIGH);
+}
+
+/* start the action to spi rom function */
+static void ec_start_spi(void)
+{
+	unsigned char val;
+
+	delay_spi(SPI_FINISH_WAIT_TIME);
+	val = ec_read(REG_XBISPICFG) | SPICFG_EN_SPICMD | SPICFG_AUTO_CHECK;
+	ec_write(REG_XBISPICFG, val);
+	delay_spi(SPI_FINISH_WAIT_TIME);
+}
+
+/* stop the action to spi rom function */
+static void ec_stop_spi(void)
+{
+	unsigned char val;
+
+	delay_spi(SPI_FINISH_WAIT_TIME);
+	val =
+	    ec_read(REG_XBISPICFG) & (~(SPICFG_EN_SPICMD | SPICFG_AUTO_CHECK));
+	ec_write(REG_XBISPICFG, val);
+	delay_spi(SPI_FINISH_WAIT_TIME);
+}
+
+/* read one byte from xbi interface */
+static int ec_read_byte(unsigned int addr, unsigned char *byte)
+{
+	int ret = 0;
+
+	/* enable spicmd writing. */
+	ec_start_spi();
+
+	/* enable write spi flash */
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
+	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
+		printk(KERN_ERR "EC_READ_BYTE : SPICMD_WRITE_ENABLE failed.\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* write the address */
+	ec_write(REG_XBISPIA2, (addr & 0xff0000) >> 16);
+	ec_write(REG_XBISPIA1, (addr & 0x00ff00) >> 8);
+	ec_write(REG_XBISPIA0, (addr & 0x0000ff) >> 0);
+	/* start action */
+	ec_write(REG_XBISPICMD, SPICMD_HIGH_SPEED_READ);
+	if (rom_instruction_cycle(SPICMD_HIGH_SPEED_READ) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_READ_BYTE : SPICMD_HIGH_SPEED_READ failed.\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	*byte = ec_read(REG_XBISPIDAT);
+
+ out:
+	/* disable spicmd writing. */
+	ec_stop_spi();
+
+	return ret;
+}
+
+/* write one byte to ec rom */
+static int ec_write_byte(unsigned int addr, unsigned char byte)
+{
+	int ret = 0;
+
+	/* enable spicmd writing. */
+	ec_start_spi();
+
+	/* enable write spi flash */
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
+	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_WRITE_BYTE : SPICMD_WRITE_ENABLE failed.\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* write the address */
+	ec_write(REG_XBISPIA2, (addr & 0xff0000) >> 16);
+	ec_write(REG_XBISPIA1, (addr & 0x00ff00) >> 8);
+	ec_write(REG_XBISPIA0, (addr & 0x0000ff) >> 0);
+	ec_write(REG_XBISPIDAT, byte);
+	/* start action */
+	ec_write(REG_XBISPICMD, SPICMD_BYTE_PROGRAM);
+	if (rom_instruction_cycle(SPICMD_BYTE_PROGRAM) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_WRITE_BYTE : SPICMD_BYTE_PROGRAM failed.\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+ out:
+	/* disable spicmd writing. */
+	ec_stop_spi();
+
+	return ret;
+}
+
+/* unprotect SPI ROM */
+/* EC_ROM_unprotect function code */
+static int EC_ROM_unprotect(void)
+{
+	unsigned char status;
+
+	/* enable write spi flash */
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
+	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_UNIT_ERASE : SPICMD_WRITE_ENABLE failed.\n");
+		return 1;
+	}
+
+	/* unprotect the status register of rom */
+	ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
+	if (rom_instruction_cycle(SPICMD_READ_STATUS) == EC_STATE_BUSY) {
+		printk(KERN_ERR "EC_UNIT_ERASE : SPICMD_READ_STATUS failed.\n");
+		return 1;
+	}
+	status = ec_read(REG_XBISPIDAT);
+	ec_write(REG_XBISPIDAT, status & 0x02);
+	if (ec_instruction_cycle() < 0) {
+		printk(KERN_ERR "EC_UNIT_ERASE : write status value failed.\n");
+		return 1;
+	}
+
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_STATUS);
+	if (rom_instruction_cycle(SPICMD_WRITE_STATUS) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_UNIT_ERASE : SPICMD_WRITE_STATUS failed.\n");
+		return 1;
+	}
+
+	/* enable write spi flash */
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
+	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_UNIT_ERASE : SPICMD_WRITE_ENABLE failed.\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+/* erase one block or chip or sector as needed */
+static int ec_unit_erase(unsigned char erase_cmd, unsigned int addr)
+{
+	unsigned char status;
+	int ret = 0, i = 0;
+	int unprotect_count = 3;
+	int check_flag = 0;
+
+	/* enable spicmd writing. */
+	ec_start_spi();
+
+#ifdef EC_ROM_PROTECTION
+	/* added for re-check SPICMD_READ_STATUS */
+	while (unprotect_count-- > 0) {
+		if (EC_ROM_unprotect()) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* first time:500ms --> 5.5sec -->10.5sec */
+		for (i = 0; i < ((2 - unprotect_count) * 100 + 10); i++)
+			udelay(50000);
+		ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
+		if (rom_instruction_cycle(SPICMD_READ_STATUS)
+				== EC_STATE_BUSY) {
+			printk(KERN_ERR
+			       "EC_PROGRAM_ROM : SPICMD_READ_STATUS failed.\n");
+		} else {
+			status = ec_read(REG_XBISPIDAT);
+			printk(KERN_INFO "Read unprotect status : 0x%x\n",
+				   status);
+			if ((status & 0x1C) == 0x00) {
+				printk(KERN_INFO
+					   "Read unprotect status OK1 : 0x%x\n",
+					   status & 0x1C);
+				check_flag = 1;
+				break;
+			}
+		}
+	}
+
+	if (!check_flag) {
+		printk(KERN_INFO "SPI ROM unprotect fail.\n");
+		return 1;
+	}
+#endif
+
+	/* block address fill */
+	if (erase_cmd == SPICMD_BLK_ERASE) {
+		ec_write(REG_XBISPIA2, (addr & 0x00ff0000) >> 16);
+		ec_write(REG_XBISPIA1, (addr & 0x0000ff00) >> 8);
+		ec_write(REG_XBISPIA0, (addr & 0x000000ff) >> 0);
+	}
+
+	/* erase the whole chip first */
+	ec_write(REG_XBISPICMD, erase_cmd);
+	if (rom_instruction_cycle(erase_cmd) == EC_STATE_BUSY) {
+		printk(KERN_ERR "EC_UNIT_ERASE : erase failed.\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+ out:
+	/* disable spicmd writing. */
+	ec_stop_spi();
+
+	return ret;
+}
+
+/* update the whole rom content with H/W mode
+ * PLEASE USING ec_unit_erase() FIRSTLY
+ */
+static int ec_program_rom(struct ec_info *info, int flag)
+{
+	unsigned int addr = 0;
+	unsigned long size = 0;
+	unsigned char *ptr = NULL;
+	unsigned char data;
+	unsigned char val = 0;
+	int ret = 0;
+	int i, j;
+	unsigned char status;
+
+	/* modify for program serial No.
+	 * set IE_START_ADDR & use idle mode,
+	 * disable WDD
+	 */
+	if (flag == PROGRAM_FLAG_ROM) {
+		ret = ec_init_reset_mode();
+		addr = info->start_addr + EC_START_ADDR;
+		printk(KERN_INFO "PROGRAM_FLAG_ROM..............\n");
+	} else if (flag == PROGRAM_FLAG_IE) {
+		ret = ec_init_idle_mode();
+		ec_disable_WDD();
+		addr = info->start_addr + IE_START_ADDR;
+		printk(KERN_INFO "PROGRAM_FLAG_IE..............\n");
+	} else {
+		return 0;
+	}
+
+	if (ret < 0) {
+		if (flag == PROGRAM_FLAG_IE)
+			ec_enable_WDD();
+		return ret;
+	}
+
+	size = info->size;
+	ptr = info->buf;
+	printk(KERN_INFO "starting update ec ROM..............\n");
+
+	ret = ec_unit_erase(SPICMD_BLK_ERASE, addr);
+	if (ret) {
+		printk(KERN_ERR "program ec : erase block failed.\n");
+		goto out;
+	}
+	printk(KERN_ERR "program ec : erase block OK.\n");
+
+	i = 0;
+	while (i < size) {
+		data = *(ptr + i);
+		ec_write_byte(addr, data);
+		ec_read_byte(addr, &val);
+		if (val != data) {
+			ec_write_byte(addr, data);
+			ec_read_byte(addr, &val);
+			if (val != data) {
+				printk(KERN_INFO
+				"EC : Second flash program failed at:\t");
+				printk(KERN_INFO
+				"addr : 0x%x, source : 0x%x, dest: 0x%x\n",
+				     addr, data, val);
+				printk(KERN_INFO "This should not happen... STOP\n");
+				break;
+			}
+		}
+		i++;
+		addr++;
+	}
+
+#ifdef	EC_ROM_PROTECTION
+	/* we should start spi access firstly */
+	ec_start_spi();
+
+	/* enable write spi flash */
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
+	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_PROGRAM_ROM : SPICMD_WRITE_ENABLE failed.\n");
+		goto out1;
+	}
+
+	/* protect the status register of rom */
+	ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
+	if (rom_instruction_cycle(SPICMD_READ_STATUS) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_PROGRAM_ROM : SPICMD_READ_STATUS failed.\n");
+		goto out1;
+	}
+	status = ec_read(REG_XBISPIDAT);
+
+	ec_write(REG_XBISPIDAT, status | 0x1C);
+	if (ec_instruction_cycle() < 0) {
+		printk(KERN_ERR
+		       "EC_PROGRAM_ROM : write status value failed.\n");
+		goto out1;
+	}
+
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_STATUS);
+	if (rom_instruction_cycle(SPICMD_WRITE_STATUS) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_PROGRAM_ROM : SPICMD_WRITE_STATUS failed.\n");
+		goto out1;
+	}
+#endif
+
+	/* disable the write action to spi rom */
+	ec_write(REG_XBISPICMD, SPICMD_WRITE_DISABLE);
+	if (rom_instruction_cycle(SPICMD_WRITE_DISABLE) == EC_STATE_BUSY) {
+		printk(KERN_ERR
+		       "EC_PROGRAM_ROM : SPICMD_WRITE_DISABLE failed.\n");
+		goto out1;
+	}
+
+ out1:
+	/* we should stop spi access firstly */
+	ec_stop_spi();
+ out:
+	/* for security */
+	for (j = 0; j < 2000; j++)
+		udelay(1000);
+
+	/* modify for program serial No.
+	 * after program No exit idle mode
+	 * and enable WDD
+	 */
+	if (flag == PROGRAM_FLAG_ROM) {
+		/* exit from the reset mode */
+		ec_exit_reset_mode();
+	} else {
+		/* ec exit from idle mode */
+		ret = ec_exit_idle_mode();
+		ec_enable_WDD();
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* ioctl  */
+static int misc_ioctl(struct inode *inode, struct file *filp, u_int cmd,
+		      u_long arg)
+{
+	struct ec_info ecinfo;
+	void __user *ptr = (void __user *)arg;
+	struct ec_reg *ecreg = (struct ec_reg *)(filp->private_data);
+	int ret = 0;
+
+	switch (cmd) {
+	case IOCTL_RDREG:
+		ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg));
+		if (ret) {
+			printk(KERN_ERR "reg read : copy from user error.\n");
+			return -EFAULT;
+		}
+		if ((ecreg->addr > EC_MAX_REGADDR)
+		    || (ecreg->addr < EC_MIN_REGADDR)) {
+			printk(KERN_ERR
+			       "reg read : out of register address range.\n");
+			return -EINVAL;
+		}
+		ecreg->val = ec_read(ecreg->addr);
+		ret = copy_to_user(ptr, ecreg, sizeof(struct ec_reg));
+		if (ret) {
+			printk(KERN_ERR "reg read : copy to user error.\n");
+			return -EFAULT;
+		}
+		break;
+	case IOCTL_WRREG:
+		ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg));
+		if (ret) {
+			printk(KERN_ERR "reg write : copy from user error.\n");
+			return -EFAULT;
+		}
+		if ((ecreg->addr > EC_MAX_REGADDR)
+		    || (ecreg->addr < EC_MIN_REGADDR)) {
+			printk(KERN_ERR
+			       "reg write : out of register address range.\n");
+			return -EINVAL;
+		}
+		ec_write(ecreg->addr, ecreg->val);
+		break;
+	case IOCTL_READ_EC:
+		ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg));
+		if (ret) {
+			printk(KERN_ERR "spi read : copy from user error.\n");
+			return -EFAULT;
+		}
+		if ((ecreg->addr > EC_RAM_ADDR)
+		    && (ecreg->addr < EC_MAX_REGADDR)) {
+			printk(KERN_ERR
+			       "spi read : out of register address range.\n");
+			return -EINVAL;
+		}
+		ec_read_byte(ecreg->addr, &(ecreg->val));
+		ret = copy_to_user(ptr, ecreg, sizeof(struct ec_reg));
+		if (ret) {
+			printk(KERN_ERR "spi read : copy to user error.\n");
+			return -EFAULT;
+		}
+		break;
+	case IOCTL_PROGRAM_IE:
+		ecinfo.start_addr = EC_START_ADDR;
+		ecinfo.size = EC_CONTENT_MAX_SIZE;
+		ecinfo.buf = (u8 *) kmalloc(ecinfo.size, GFP_KERNEL);
+		if (ecinfo.buf == NULL) {
+			printk(KERN_ERR "program ie : kmalloc failed.\n");
+			return -ENOMEM;
+		}
+		ret = copy_from_user(ecinfo.buf, (u8 *) ptr, ecinfo.size);
+		if (ret) {
+			printk(KERN_ERR "program ie : copy from user error.\n");
+			kfree(ecinfo.buf);
+			ecinfo.buf = NULL;
+			return -EFAULT;
+		}
+
+		/* use ec_program_rom to write serial No */
+		ec_program_rom(&ecinfo, PROGRAM_FLAG_IE);
+
+		kfree(ecinfo.buf);
+		ecinfo.buf = NULL;
+		break;
+	case IOCTL_PROGRAM_EC:
+		ecinfo.start_addr = EC_START_ADDR;
+		if (get_user((ecinfo.size), (u32 *) ptr)) {
+			printk(KERN_ERR "program ec : get user error.\n");
+			return -EFAULT;
+		}
+		if ((ecinfo.size) > EC_CONTENT_MAX_SIZE) {
+			printk(KERN_ERR "program ec : size out of limited.\n");
+			return -EINVAL;
+		}
+		ecinfo.buf = (u8 *) kmalloc(ecinfo.size, GFP_KERNEL);
+		if (ecinfo.buf == NULL) {
+			printk(KERN_ERR "program ec : kmalloc failed.\n");
+			return -ENOMEM;
+		}
+		ret = copy_from_user(ecinfo.buf, ((u8 *) ptr + 4), ecinfo.size);
+		if (ret) {
+			printk(KERN_ERR "program ec : copy from user error.\n");
+			kfree(ecinfo.buf);
+			ecinfo.buf = NULL;
+			return -EFAULT;
+		}
+
+		ec_program_rom(&ecinfo, PROGRAM_FLAG_ROM);
+
+		kfree(ecinfo.buf);
+		ecinfo.buf = NULL;
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static long misc_compat_ioctl(struct file *file, unsigned int cmd,
+			      unsigned long arg)
+{
+	return misc_ioctl(file->f_dentry->d_inode, file, cmd, arg);
+}
+
+static int misc_open(struct inode *inode, struct file *filp)
+{
+	struct ec_reg *ecreg = NULL;
+	ecreg = kmalloc(sizeof(struct ec_reg), GFP_KERNEL);
+	if (ecreg)
+		filp->private_data = ecreg;
+
+	return ecreg ? 0 : -ENOMEM;
+}
+
+static int misc_release(struct inode *inode, struct file *filp)
+{
+	struct ec_reg *ecreg = (struct ec_reg *)(filp->private_data);
+
+	filp->private_data = NULL;
+	kfree(ecreg);
+
+	return 0;
+}
+
+static const struct file_operations ecmisc_fops = {
+	.open = misc_open,
+	.release = misc_release,
+	.read = NULL,
+	.write = NULL,
+#ifdef	CONFIG_64BIT
+	.compat_ioctl = misc_compat_ioctl,
+#else
+	.ioctl = misc_ioctl,
+#endif
+};
+
+static struct miscdevice ecmisc_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = EC_MISC_DEV,
+	.fops = &ecmisc_fops
+};
+
+static int __init ecmisc_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "EC misc device init.\n");
+	ret = misc_register(&ecmisc_device);
+
+	return ret;
+}
+
+static void __exit ecmisc_exit(void)
+{
+	printk(KERN_INFO "EC misc device exit.\n");
+	misc_deregister(&ecmisc_device);
+}
+
+module_init(ecmisc_init);
+module_exit(ecmisc_exit);
+
+MODULE_AUTHOR("liujl <liujl@lemote.com>");
+MODULE_DESCRIPTION("Driver for flushing/dumping ROM of EC on YeeLoong laptop");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/mips/yeeloong_laptop.c b/drivers/platform/mips/yeeloong_laptop.c
new file mode 100644
index 0000000..32a2bdb
--- /dev/null
+++ b/drivers/platform/mips/yeeloong_laptop.c
@@ -0,0 +1,1360 @@
+/*
+ * Driver for YeeLoong laptop extras
+ *
+ *  Copyright (C) 2009 Lemote Inc.
+ *  Author: Wu Zhangjin <wuzhangjin@gmail.com>, Liu Junliang <liujl@lemote.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>	/* for backlight subdriver */
+#include <linux/fb.h>
+#include <linux/hwmon.h>	/* for hwmon subdriver */
+#include <linux/hwmon-sysfs.h>
+#include <linux/video_output.h>	/* for video output subdriver */
+#include <linux/lcd.h>		/* for lcd output subdriver */
+#include <linux/input.h>	/* for hotkey subdriver */
+#include <linux/input/sparse-keymap.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>	/* for AC & Battery subdriver */
+#include <linux/reboot.h>	/* for register_reboot_notifier */
+#include <linux/suspend.h>	/* for register_pm_notifier */
+
+#include <cs5536/cs5536.h>
+
+#include <loongson.h>		/* for loongson_cmdline */
+#include <ec_kb3310b.h>
+
+#define ON	1
+#define OFF	0
+#define EVENT_START EVENT_LID
+
+/* common function */
+#define EC_VER_LEN 64
+
+static int ec_version_before(char *version)
+{
+	char *p, ec_ver[EC_VER_LEN];
+
+	p = strstr(loongson_cmdline, "EC_VER=");
+	if (!p)
+		memset(ec_ver, 0, EC_VER_LEN);
+	else {
+		strncpy(ec_ver, p, EC_VER_LEN);
+		p = strstr(ec_ver, " ");
+		if (p)
+			*p = '\0';
+	}
+
+	return (strncasecmp(ec_ver, version, 64) < 0);
+}
+
+/* backlight subdriver */
+#define MIN_BRIGHTNESS	1
+#define MAX_BRIGHTNESS	8
+
+static int yeeloong_set_brightness(struct backlight_device *bd)
+{
+	unsigned char level;
+	static unsigned char old_level;
+
+	level = (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+		 bd->props.power == FB_BLANK_UNBLANK) ?
+	    bd->props.brightness : 0;
+
+	level = clamp_val(level, MIN_BRIGHTNESS, MAX_BRIGHTNESS);
+
+	/* Avoid to modify the brightness when EC is tuning it */
+	if (old_level != level) {
+		if (ec_read(REG_DISPLAY_BRIGHTNESS) == old_level)
+			ec_write(REG_DISPLAY_BRIGHTNESS, level);
+		old_level = level;
+	}
+
+	return 0;
+}
+
+static int yeeloong_get_brightness(struct backlight_device *bd)
+{
+	return ec_read(REG_DISPLAY_BRIGHTNESS);
+}
+
+static struct backlight_ops backlight_ops = {
+	.get_brightness = yeeloong_get_brightness,
+	.update_status = yeeloong_set_brightness,
+};
+
+static struct backlight_device *yeeloong_backlight_dev;
+
+static int yeeloong_backlight_init(void)
+{
+	int ret;
+	struct backlight_properties props;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.max_brightness = MAX_BRIGHTNESS;
+	props.type = BACKLIGHT_PLATFORM;
+	yeeloong_backlight_dev = backlight_device_register("backlight0", NULL,
+			NULL, &backlight_ops, &props);
+
+	if (IS_ERR(yeeloong_backlight_dev)) {
+		ret = PTR_ERR(yeeloong_backlight_dev);
+		yeeloong_backlight_dev = NULL;
+		return ret;
+	}
+
+	yeeloong_backlight_dev->props.brightness =
+		yeeloong_get_brightness(yeeloong_backlight_dev);
+	backlight_update_status(yeeloong_backlight_dev);
+
+	return 0;
+}
+
+static void yeeloong_backlight_exit(void)
+{
+	if (yeeloong_backlight_dev) {
+		backlight_device_unregister(yeeloong_backlight_dev);
+		yeeloong_backlight_dev = NULL;
+	}
+}
+
+/* AC & Battery subdriver */
+
+static struct power_supply yeeloong_ac, yeeloong_bat;
+
+#define RET (val->intval)
+
+#define BAT_CAP_CRITICAL 5
+#define BAT_CAP_HIGH     95
+
+#define get_bat(type) \
+	ec_read(REG_BAT_##type)
+
+#define get_bat_l(type) \
+	((get_bat(type##_HIGH) << 8) | get_bat(type##_LOW))
+
+static int yeeloong_get_ac_props(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	if (psp == POWER_SUPPLY_PROP_ONLINE)
+		RET = !!(get_bat(POWER) & BIT_BAT_POWER_ACIN);
+
+	return 0;
+}
+
+static enum power_supply_property yeeloong_ac_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+};
+
+static struct power_supply yeeloong_ac = {
+	.name = "yeeloong-ac",
+	.type = POWER_SUPPLY_TYPE_MAINS,
+	.properties = yeeloong_ac_props,
+	.num_properties = ARRAY_SIZE(yeeloong_ac_props),
+	.get_property = yeeloong_get_ac_props,
+};
+
+static inline bool is_bat_in(void)
+{
+	return !!(get_bat(STATUS) & BIT_BAT_STATUS_IN);
+}
+
+static int get_bat_temp(void)
+{
+	return get_bat_l(TEMPERATURE) * 10;
+}
+
+static int get_bat_current(void)
+{
+	return -(s16)get_bat_l(CURRENT);
+}
+
+static int get_bat_voltage(void)
+{
+	return get_bat_l(VOLTAGE);
+}
+
+static char *get_manufacturer(void)
+{
+	return (get_bat(VENDOR) == FLAG_BAT_VENDOR_SANYO) ? "SANYO" : "SIMPLO";
+}
+
+static int get_relative_cap(void)
+{
+	/*
+	 * When the relative capacity becomes 2, the hardware is observed to
+	 * have been turned off forcely. so, we must tune it be suitable to
+	 * make the software do related actions.
+	 */
+	int tmp = get_bat_l(RELATIVE_CAP);
+
+	if (tmp <= (BAT_CAP_CRITICAL * 2))
+		tmp -= 3;
+
+	return tmp;
+}
+
+static int yeeloong_get_bat_props(struct power_supply *psy,
+				     enum power_supply_property psp,
+				     union power_supply_propval *val)
+{
+	switch (psp) {
+	/* Fixed information */
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+		/* mV -> µV */
+		RET = get_bat_l(DESIGN_VOL) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		/* mAh->µAh */
+		RET = get_bat_l(DESIGN_CAP) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL:
+		/* µAh */
+		RET = get_bat_l(FULLCHG_CAP) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = get_manufacturer();
+		break;
+	/* Dynamic information */
+	case POWER_SUPPLY_PROP_PRESENT:
+		RET = is_bat_in();
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		/* mA -> µA */
+		RET = is_bat_in() ? get_bat_current() * 1000 : 0;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		/* mV -> µV */
+		RET = is_bat_in() ? get_bat_voltage() * 1000 : 0;
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		/* Celcius */
+		RET = is_bat_in() ? get_bat_temp() : 0;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		RET = is_bat_in() ? get_relative_cap() : 0;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL: {
+		int status;
+
+		if (!is_bat_in()) {
+			RET = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+			break;
+		}
+
+		status = get_bat(STATUS);
+		RET = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+
+		if (unlikely(status & BIT_BAT_STATUS_DESTROY)) {
+			RET = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+			break;
+		}
+
+		if (status & BIT_BAT_STATUS_FULL)
+			RET = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+		else {
+			int curr_cap = get_relative_cap();
+
+			if (status & BIT_BAT_STATUS_LOW) {
+				RET = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+				if (curr_cap <= BAT_CAP_CRITICAL)
+					RET = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+			} else if (curr_cap >= BAT_CAP_HIGH)
+				RET = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+		}
+	} break;
+	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+		/* seconds */
+		RET = is_bat_in() ? (get_relative_cap() - 3) * 54 + 142 : 0;
+		break;
+	case POWER_SUPPLY_PROP_STATUS: {
+			int charge = get_bat(CHARGE);
+
+			RET = POWER_SUPPLY_STATUS_UNKNOWN;
+			if (charge & FLAG_BAT_CHARGE_DISCHARGE)
+				RET = POWER_SUPPLY_STATUS_DISCHARGING;
+			else if (charge & FLAG_BAT_CHARGE_CHARGE)
+				RET = POWER_SUPPLY_STATUS_CHARGING;
+	} break;
+	case POWER_SUPPLY_PROP_HEALTH: {
+			int status;
+
+			if (!is_bat_in()) {
+				RET = POWER_SUPPLY_HEALTH_UNKNOWN;
+				break;
+			}
+
+			status = get_bat(STATUS);
+			RET = POWER_SUPPLY_HEALTH_GOOD;
+
+			if (status & (BIT_BAT_STATUS_DESTROY |
+						BIT_BAT_STATUS_LOW))
+				RET = POWER_SUPPLY_HEALTH_DEAD;
+			if (get_bat(CHARGE_STATUS) &
+					BIT_BAT_CHARGE_STATUS_OVERTEMP)
+				RET = POWER_SUPPLY_HEALTH_OVERHEAT;
+	} break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:	/* 1/100(%)*1000 µAh */
+		RET = get_relative_cap() * get_bat_l(FULLCHG_CAP) * 10;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+#undef RET
+
+static enum power_supply_property yeeloong_bat_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static struct power_supply yeeloong_bat = {
+	.name = "yeeloong-bat",
+	.type = POWER_SUPPLY_TYPE_BATTERY,
+	.properties = yeeloong_bat_props,
+	.num_properties = ARRAY_SIZE(yeeloong_bat_props),
+	.get_property = yeeloong_get_bat_props,
+};
+
+static int ac_bat_initialized;
+
+static int yeeloong_bat_init(void)
+{
+	int ret;
+
+	ret = power_supply_register(NULL, &yeeloong_ac);
+	if (ret)
+		return ret;
+	ret = power_supply_register(NULL, &yeeloong_bat);
+	if (ret) {
+		power_supply_unregister(&yeeloong_ac);
+		return ret;
+	}
+	ac_bat_initialized = 1;
+
+	return 0;
+}
+
+static void yeeloong_bat_exit(void)
+{
+	ac_bat_initialized = 0;
+
+	power_supply_unregister(&yeeloong_ac);
+	power_supply_unregister(&yeeloong_bat);
+}
+/* hwmon subdriver */
+
+#define MIN_FAN_SPEED 0
+#define MAX_FAN_SPEED 3
+
+#define get_fan(type) \
+	ec_read(REG_FAN_##type)
+
+#define set_fan(type, val) \
+	ec_write(REG_FAN_##type, val)
+
+static inline int get_fan_speed_level(void)
+{
+	return get_fan(SPEED_LEVEL);
+}
+static inline void set_fan_speed_level(int speed)
+{
+	set_fan(SPEED_LEVEL, speed);
+}
+
+static inline int get_fan_mode(void)
+{
+	return get_fan(AUTO_MAN_SWITCH);
+}
+static inline void set_fan_mode(int mode)
+{
+	set_fan(AUTO_MAN_SWITCH, mode);
+}
+
+/*
+ * 3 different modes: Full speed(0); manual mode(1); auto mode(2)
+ */
+static int get_fan_pwm_enable(void)
+{
+	return (get_fan_mode() == BIT_FAN_AUTO) ? 2 :
+		(get_fan_speed_level() == MAX_FAN_SPEED) ? 0 : 1;
+}
+
+static void set_fan_pwm_enable(int mode)
+{
+	set_fan_mode((mode == 2) ? BIT_FAN_AUTO : BIT_FAN_MANUAL);
+	if (mode == 0)
+		set_fan_speed_level(MAX_FAN_SPEED);
+}
+
+static int get_fan_pwm(void)
+{
+	return get_fan_speed_level();
+}
+
+static void set_fan_pwm(int value)
+{
+	if (get_fan_mode() != BIT_FAN_MANUAL)
+		return;
+
+	value = clamp_val(value, MIN_FAN_SPEED, MAX_FAN_SPEED);
+
+	/* We must ensure the fan is on */
+	if (value > 0)
+		set_fan(CONTROL, ON);
+
+	set_fan_speed_level(value);
+}
+
+static inline int get_fan_speed(void)
+{
+	return ((get_fan(SPEED_HIGH) & 0x0f) << 8) | get_fan(SPEED_LOW);
+}
+
+static int get_fan_rpm(void)
+{
+	return FAN_SPEED_DIVIDER / get_fan_speed();
+}
+
+static int get_cpu_temp(void)
+{
+	return (s8)ec_read(REG_TEMPERATURE_VALUE) * 1000;
+}
+
+static int get_cpu_temp_max(void)
+{
+	return 60 * 1000;
+}
+
+static int get_bat_temp_alarm(void)
+{
+	return !!(get_bat(CHARGE_STATUS) & BIT_BAT_CHARGE_STATUS_OVERTEMP);
+}
+
+static ssize_t store_sys_hwmon(void (*set) (int), const char *buf, size_t count)
+{
+	int ret;
+	unsigned long value;
+
+	if (!count)
+		return 0;
+
+	ret = kstrtoul(buf, 10, &value);
+	if (ret)
+		return ret;
+
+	set(value);
+
+	return count;
+}
+
+static ssize_t show_sys_hwmon(int (*get) (void), char *buf)
+{
+	return sprintf(buf, "%d\n", get());
+}
+
+#define CREATE_SENSOR_ATTR(_name, _mode, _set, _get)		\
+	static ssize_t show_##_name(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		return show_sys_hwmon(_set, buf);			\
+	}								\
+	static ssize_t store_##_name(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		return store_sys_hwmon(_get, buf, count);		\
+	}								\
+	static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
+
+CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, get_fan_rpm, NULL);
+CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR, get_fan_pwm, set_fan_pwm);
+CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, get_fan_pwm_enable,
+		set_fan_pwm_enable);
+CREATE_SENSOR_ATTR(temp1_input, S_IRUGO, get_cpu_temp, NULL);
+CREATE_SENSOR_ATTR(temp1_max, S_IRUGO, get_cpu_temp_max, NULL);
+CREATE_SENSOR_ATTR(temp2_input, S_IRUGO, get_bat_temp, NULL);
+CREATE_SENSOR_ATTR(temp2_max_alarm, S_IRUGO, get_bat_temp_alarm, NULL);
+CREATE_SENSOR_ATTR(curr1_input, S_IRUGO, get_bat_current, NULL);
+CREATE_SENSOR_ATTR(in1_input, S_IRUGO, get_bat_voltage, NULL);
+
+static ssize_t
+show_name(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "yeeloong\n");
+}
+
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static struct attribute *hwmon_attributes[] = {
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_curr1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_name.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group hwmon_attribute_group = {
+	.attrs = hwmon_attributes
+};
+
+static struct device *yeeloong_hwmon_dev;
+
+static int yeeloong_hwmon_init(void)
+{
+	int ret;
+
+	yeeloong_hwmon_dev = hwmon_device_register(NULL);
+	if (IS_ERR(yeeloong_hwmon_dev)) {
+		yeeloong_hwmon_dev = NULL;
+		return PTR_ERR(yeeloong_hwmon_dev);
+	}
+	ret = sysfs_create_group(&yeeloong_hwmon_dev->kobj,
+				 &hwmon_attribute_group);
+	if (ret) {
+		hwmon_device_unregister(yeeloong_hwmon_dev);
+		yeeloong_hwmon_dev = NULL;
+		return ret;
+	}
+	/* ensure fan is set to auto mode */
+	set_fan_pwm_enable(2);
+
+	return 0;
+}
+
+static void yeeloong_hwmon_exit(void)
+{
+	if (yeeloong_hwmon_dev) {
+		sysfs_remove_group(&yeeloong_hwmon_dev->kobj,
+				   &hwmon_attribute_group);
+		hwmon_device_unregister(yeeloong_hwmon_dev);
+		yeeloong_hwmon_dev = NULL;
+	}
+}
+
+/* video output subdriver */
+
+#define LCD	0
+#define CRT	1
+#define VOD_NUM	2	/* The total number of video output device*/
+
+static struct output_device *vod[VOD_NUM];
+
+static int vor[] = {REG_DISPLAY_LCD, REG_CRT_DETECT};
+
+static int get_vo_dev(struct output_device *od)
+{
+	int i, dev;
+
+	dev = -1;
+	for (i = 0; i < VOD_NUM; i++)
+		if (od == vod[i])
+			dev = i;
+
+	return dev;
+}
+
+static int vo_get_status(int dev)
+{
+	return ec_read(vor[dev]);
+}
+
+static int yeeloong_vo_get_status(struct output_device *od)
+{
+	int vd;
+
+	vd = get_vo_dev(od);
+	if (vd != -1)
+		return vo_get_status(vd);
+
+	return -ENODEV;
+}
+
+static void vo_set_state(int dev, int state)
+{
+	int addr;
+	unsigned long value;
+
+	switch (dev) {
+	case LCD:
+		addr = 0x31;
+		break;
+	case CRT:
+		addr = 0x21;
+		break;
+	default:
+		/* return directly if the wrong video output device */
+		return;
+	}
+
+	outb(addr, 0x3c4);
+	value = inb(0x3c5);
+
+	switch (dev) {
+	case LCD:
+		value |= (state ? 0x03 : 0x02);
+		break;
+	case CRT:
+		if (state)
+			clear_bit(7, &value);
+		else
+			set_bit(7, &value);
+		break;
+	default:
+		break;
+	}
+
+	outb(addr, 0x3c4);
+	outb(value, 0x3c5);
+
+	if (dev == LCD)
+		ec_write(REG_BACKLIGHT_CTRL, state);
+}
+
+static int yeeloong_vo_set_state(struct output_device *od)
+{
+	int vd;
+
+	vd = get_vo_dev(od);
+	if (vd == -1)
+		return -ENODEV;
+
+	if (vd == CRT && !vo_get_status(vd))
+		return 0;
+
+	vo_set_state(vd, !!od->request_state);
+
+	return 0;
+}
+
+static struct output_properties vop = {
+	.set_state = yeeloong_vo_set_state,
+	.get_status = yeeloong_vo_get_status,
+};
+
+static int yeeloong_vo_init(void)
+{
+	int ret, i;
+	char dev_name[VOD_NUM][4] = {"LCD", "CRT"};
+
+	/* Register video output device: lcd, crt */
+	for (i = 0; i < VOD_NUM; i++) {
+		vod[i] = video_output_register(dev_name[i], NULL, NULL, &vop);
+		if (IS_ERR(vod[i])) {
+			if (i != 0)
+				video_output_unregister(vod[i-1]);
+			ret = PTR_ERR(vod[i]);
+			vod[i] = NULL;
+			return ret;
+		}
+	}
+	/* Ensure LCD is on by default */
+	vo_set_state(LCD, ON);
+
+	/*
+	 * Turn off CRT by default, and will be enabled when the CRT
+	 * connectting event reported by SCI
+	 */
+	vo_set_state(CRT, OFF);
+
+	return 0;
+}
+
+static void yeeloong_vo_exit(void)
+{
+	int i;
+
+	for (i = 0; i < VOD_NUM; i++) {
+		if (vod[i]) {
+			video_output_unregister(vod[i]);
+			vod[i] = NULL;
+		}
+	}
+}
+
+/* lcd subdriver */
+
+struct lcd_device *lcd[VOD_NUM];
+
+static int get_lcd_dev(struct lcd_device *ld)
+{
+	int i, dev;
+
+	dev = -1;
+	for (i = 0; i < VOD_NUM; i++)
+		if (ld == lcd[i])
+			dev = i;
+
+	return dev;
+}
+
+static int yeeloong_lcd_set_power(struct lcd_device *ld, int power)
+{
+	int dev = get_lcd_dev(ld);
+
+	if (power == FB_BLANK_UNBLANK)
+		vo_set_state(dev, ON);
+	if (power == FB_BLANK_POWERDOWN)
+		vo_set_state(dev, OFF);
+
+	return 0;
+}
+
+static int yeeloong_lcd_get_power(struct lcd_device *ld)
+{
+	return vo_get_status(get_lcd_dev(ld));
+}
+
+static struct lcd_ops lcd_ops = {
+	.set_power = yeeloong_lcd_set_power,
+	.get_power = yeeloong_lcd_get_power,
+};
+
+static int yeeloong_lcd_init(void)
+{
+	int ret, i;
+	char dev_name[VOD_NUM][4] = {"LCD", "CRT"};
+
+	/* Register video output device: lcd, crt */
+	for (i = 0; i < VOD_NUM; i++) {
+		lcd[i] = lcd_device_register(dev_name[i], NULL, NULL, &lcd_ops);
+		if (IS_ERR(lcd[i])) {
+			if (i != 0)
+				lcd_device_unregister(lcd[i-1]);
+			ret = PTR_ERR(lcd[i]);
+			lcd[i] = NULL;
+			return ret;
+		}
+	}
+#if 0
+	/* This has been done by the vide output driver */
+
+	/* Ensure LCD is on by default */
+	vo_set_state(LCD, ON);
+
+	/*
+	 * Turn off CRT by default, and will be enabled when the CRT
+	 * connectting event reported by SCI
+	 */
+	vo_set_state(CRT, OFF);
+#endif
+	return 0;
+}
+
+static void yeeloong_lcd_exit(void)
+{
+	int i;
+
+	for (i = 0; i < VOD_NUM; i++) {
+		if (lcd[i]) {
+			lcd_device_unregister(lcd[i]);
+			lcd[i] = NULL;
+		}
+	}
+}
+
+/* hotkey subdriver */
+
+static struct input_dev *yeeloong_hotkey_dev;
+
+static atomic_t reboot_flag, sleep_flag;
+#define in_sleep() (&sleep_flag)
+#define in_reboot() (&reboot_flag)
+
+static const struct key_entry yeeloong_keymap[] = {
+	{KE_SW, EVENT_LID, { SW_LID } },
+	{KE_KEY, EVENT_CAMERA, { KEY_CAMERA } }, /* Fn + ESC */
+	{KE_KEY, EVENT_SLEEP, { KEY_SLEEP } }, /* Fn + F1 */
+	{KE_KEY, EVENT_BLACK_SCREEN, { KEY_DISPLAYTOGGLE } }, /* Fn + F2 */
+	{KE_KEY, EVENT_DISPLAY_TOGGLE, { KEY_SWITCHVIDEOMODE } }, /* Fn + F3 */
+	{KE_KEY, EVENT_AUDIO_MUTE, { KEY_MUTE } }, /* Fn + F4 */
+	{KE_KEY, EVENT_WLAN, { KEY_WLAN } }, /* Fn + F5 */
+	{KE_KEY, EVENT_DISPLAY_BRIGHTNESS, { KEY_BRIGHTNESSUP } }, /* Fn + up */
+	{KE_KEY, EVENT_DISPLAY_BRIGHTNESS, { KEY_BRIGHTNESSDOWN } }, /* Fn + down */
+	{KE_KEY, EVENT_AUDIO_VOLUME, { KEY_VOLUMEUP } }, /* Fn + right */
+	{KE_KEY, EVENT_AUDIO_VOLUME, { KEY_VOLUMEDOWN } }, /* Fn + left */
+	{KE_END, 0}
+};
+
+static int is_fake_event(u16 keycode)
+{
+	switch (keycode) {
+	case KEY_SLEEP:
+	case SW_LID:
+		return atomic_read(in_sleep()) | atomic_read(in_reboot());
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static struct key_entry *get_event_key_entry(int event, int status)
+{
+	struct key_entry *ke;
+	static int old_brightness_status = -1;
+	static int old_volume_status = -1;
+
+	ke = sparse_keymap_entry_from_scancode(yeeloong_hotkey_dev, event);
+	if (!ke)
+		return NULL;
+
+	switch (event) {
+	case EVENT_DISPLAY_BRIGHTNESS:
+		/* current status > old one, means up */
+		if ((status < old_brightness_status) || (0 == status))
+			ke++;
+		old_brightness_status = status;
+		break;
+	case EVENT_AUDIO_VOLUME:
+		if ((status < old_volume_status) || (0 == status))
+			ke++;
+		old_volume_status = status;
+		break;
+	default:
+		break;
+	}
+
+	return ke;
+}
+
+static int report_lid_switch(int status)
+{
+	static int old_status;
+
+	/*
+	 * LID is a switch button, so, two continuous same status should be
+	 * ignored
+	 */
+	if (old_status != status) {
+		input_report_switch(yeeloong_hotkey_dev, SW_LID, !status);
+		input_sync(yeeloong_hotkey_dev);
+	}
+	old_status = status;
+
+	return status;
+}
+
+static int crt_detect_handler(int status)
+{
+	/*
+	 * When CRT is inserted, enable its output and disable the LCD output,
+	 * otherwise, do reversely.
+	 */
+	vo_set_state(CRT, status);
+	vo_set_state(LCD, !status);
+
+	return status;
+}
+
+static int displaytoggle_handler(int status)
+{
+	/* EC(>=PQ1D26) does this job for us, we can not do it again,
+	 * otherwise, the brightness will not resume to the normal level! */
+	if (ec_version_before("EC_VER=PQ1D26"))
+		vo_set_state(LCD, status);
+
+	return status;
+}
+
+static int mypow(int x, int y)
+{
+	int i, j = x;
+
+	for (i = 1; i < y; i++)
+		j *= j;
+
+	return j;
+}
+
+static int switchvideomode_handler(int status)
+{
+	/* Default status: CRT|LCD = 0|1 = 1 */
+	static int bin_state = 1;
+	int i;
+
+	/*
+	 * Only enable switch video output button
+	 * when CRT is connected
+	 */
+	if (!vo_get_status(CRT))
+		return 0;
+	/*
+	 * 2. no CRT connected: LCD on, CRT off
+	 * 3. BOTH on
+	 * 0. BOTH off
+	 * 1. LCD off, CRT on
+	 */
+
+	bin_state++;
+	if (bin_state > mypow(2, VOD_NUM) - 1)
+		bin_state = 0;
+	
+	for (i = 0; i < VOD_NUM; i++)
+		vo_set_state(i, bin_state & (1 << i));
+
+	return bin_state;
+}
+
+static int camera_handler(int status)
+{
+	int value;
+
+	value = ec_read(REG_CAMERA_CONTROL);
+	ec_write(REG_CAMERA_CONTROL, value | (1 << 1));
+
+	return status;
+}
+
+static int usb2_handler(int status)
+{
+	pr_emerg("USB2 Over Current occurred\n");
+
+	return status;
+}
+
+static int usb0_handler(int status)
+{
+	pr_emerg("USB0 Over Current occurred\n");
+
+	return status;
+}
+
+static int ac_bat_handler(int status)
+{
+	if (ac_bat_initialized) {
+		power_supply_changed(&yeeloong_ac);
+		power_supply_changed(&yeeloong_bat);
+	}
+
+	return status;
+}
+
+struct sci_event {
+	int reg;
+	sci_handler handler;
+};
+
+static const struct sci_event se[] = {
+	[EVENT_AC_BAT] = {0, ac_bat_handler},
+	[EVENT_AUDIO_MUTE] = {REG_AUDIO_MUTE, NULL},
+	[EVENT_AUDIO_VOLUME] = {REG_AUDIO_VOLUME, NULL},
+	[EVENT_CRT_DETECT] = {REG_CRT_DETECT, crt_detect_handler},
+	[EVENT_CAMERA] = {REG_CAMERA_STATUS, camera_handler},
+	[EVENT_BLACK_SCREEN] = {REG_DISPLAY_LCD, displaytoggle_handler},
+	[EVENT_DISPLAY_BRIGHTNESS] = {REG_DISPLAY_BRIGHTNESS, NULL},
+	[EVENT_LID] = {REG_LID_DETECT, NULL},
+	[EVENT_DISPLAY_TOGGLE] = {0, switchvideomode_handler},
+	[EVENT_USB_OC0] = {REG_USB2_FLAG, usb0_handler},
+	[EVENT_USB_OC2] = {REG_USB2_FLAG, usb2_handler},
+	[EVENT_WLAN] = {REG_WLAN, NULL},
+};
+
+static void do_event_action(int event)
+{
+	int status = -1;
+	struct key_entry *ke;
+	struct sci_event *sep;
+
+	sep = (struct sci_event *)&se[event];
+
+	if (sep->reg != 0)
+		status = ec_read(sep->reg);
+
+	if (status == -1) {
+		/* ec_read hasn't been called, status is invalid */
+		return;
+	}
+
+	if (sep->handler != NULL)
+		status = sep->handler(status);
+
+	pr_debug("%s: event: %d status: %d\n", __func__, event, status);
+
+	/* Report current key to user-space */
+	ke = get_event_key_entry(event, status);
+
+	/*
+	 * Ignore the LID and SLEEP event when we are already in sleep or
+	 * reboot state, this will avoid the recursive pm operations. but note:
+	 * the report_lid_switch() called in arch/mips/loongson/lemote-2f/pm.c
+	 * is necessary, because it is used to wake the system from sleep
+	 * state. In the future, perhaps SW_LID should works like SLEEP, no
+	 * need to function as a SWITCH, just report the state when the LID is
+	 * closed is enough, this event can tell the software to "SLEEP", no
+	 * need to tell the softwares when we are resuming from "SLEEP".
+	 */
+	if (ke && !is_fake_event(ke->keycode)) {
+		if (ke->keycode == SW_LID)
+			report_lid_switch(status);
+		else
+			sparse_keymap_report_entry(yeeloong_hotkey_dev, ke, 1,
+					true);
+	}
+}
+
+/*
+ * SCI(system control interrupt) main interrupt routine
+ *
+ * We will do the query and get event number together so the interrupt routine
+ * should be longer than 120us now at least 3ms elpase for it.
+ */
+static irqreturn_t sci_irq_handler(int irq, void *dev_id)
+{
+	int ret, event;
+
+	if (SCI_IRQ_NUM != irq)
+		return IRQ_NONE;
+
+	/* Query the event number */
+	ret = ec_query_event_num();
+	if (ret < 0)
+		return IRQ_NONE;
+
+	event = ec_get_event_num();
+	if (event < EVENT_START || event > EVENT_END)
+		return IRQ_NONE;
+
+	/* Execute corresponding actions */
+	do_event_action(event);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Config and init some msr and gpio register properly.
+ */
+static int sci_irq_init(void)
+{
+	u32 hi, lo;
+	u32 gpio_base;
+	unsigned long flags;
+	int ret;
+
+	/* Get gpio base */
+	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &lo);
+	gpio_base = lo & 0xff00;
+
+	/* Filter the former kb3310 interrupt for security */
+	ret = ec_query_event_num();
+	if (ret)
+		return ret;
+
+	/* For filtering next number interrupt */
+	udelay(10000);
+
+	/* Set gpio native registers and msrs for GPIO27 SCI EVENT PIN
+	 * gpio :
+	 *      input, pull-up, no-invert, event-count and value 0,
+	 *      no-filter, no edge mode
+	 *      gpio27 map to Virtual gpio0
+	 * msr :
+	 *      no primary and lpc
+	 *      Unrestricted Z input to IG10 from Virtual gpio 0.
+	 */
+	local_irq_save(flags);
+	_rdmsr(0x80000024, &hi, &lo);
+	lo &= ~(1 << 10);
+	_wrmsr(0x80000024, hi, lo);
+	_rdmsr(0x80000025, &hi, &lo);
+	lo &= ~(1 << 10);
+	_wrmsr(0x80000025, hi, lo);
+	_rdmsr(0x80000023, &hi, &lo);
+	lo |= (0x0a << 0);
+	_wrmsr(0x80000023, hi, lo);
+	local_irq_restore(flags);
+
+	/* Set gpio27 as sci interrupt
+	 *
+	 * input, pull-up, no-fliter, no-negedge, invert
+	 * the sci event is just about 120us
+	 */
+	asm(".set noreorder\n");
+	/*  input enable */
+	outl(0x00000800, (gpio_base | 0xA0));
+	/*  revert the input */
+	outl(0x00000800, (gpio_base | 0xA4));
+	/*  event-int enable */
+	outl(0x00000800, (gpio_base | 0xB8));
+	asm(".set reorder\n");
+
+	return 0;
+}
+
+static int notify_reboot(struct notifier_block *nb, unsigned long event, void *buf)
+{
+	switch (event) {
+	case SYS_RESTART:
+	case SYS_HALT:
+	case SYS_POWER_OFF:
+		atomic_set(in_reboot(), 1);
+		break;
+	default:
+		return NOTIFY_DONE;
+	}
+
+	return NOTIFY_OK;
+}
+
+static int notify_pm(struct notifier_block *nb, unsigned long event, void *buf)
+{
+	switch (event) {
+	case PM_HIBERNATION_PREPARE:
+	case PM_SUSPEND_PREPARE:
+		atomic_inc(in_sleep());
+		break;
+	case PM_POST_HIBERNATION:
+	case PM_POST_SUSPEND:
+	case PM_RESTORE_PREPARE:	/* do we need this ?? */
+		atomic_dec(in_sleep());
+		break;
+	default:
+		return NOTIFY_DONE;
+	}
+
+	pr_debug("%s: event = %lu, in_sleep() = %d\n", __func__, event,
+			atomic_read(in_sleep()));
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block reboot_notifier = {
+	.notifier_call = notify_reboot,
+};
+
+static struct notifier_block pm_notifier = {
+	.notifier_call = notify_pm,
+};
+
+static int yeeloong_hotkey_init(void)
+{
+	int ret = 0;
+
+	ret = register_reboot_notifier(&reboot_notifier);
+	if (ret) {
+		pr_err("Can't register reboot notifier\n");
+		goto end;
+	}
+
+	ret = register_pm_notifier(&pm_notifier);
+	if (ret) {
+		pr_err("Can't register pm notifier\n");
+		goto free_reboot_notifier;
+	}
+
+	ret = sci_irq_init();
+	if (ret) {
+		pr_err("Can't init SCI interrupt\n");
+		goto free_pm_notifier;
+	}
+
+	ret = request_threaded_irq(SCI_IRQ_NUM, NULL, &sci_irq_handler,
+			IRQF_ONESHOT, "sci", NULL);
+	if (ret) {
+		pr_err("Can't thread SCI interrupt handler\n");
+		goto free_pm_notifier;
+	}
+
+	yeeloong_hotkey_dev = input_allocate_device();
+
+	if (!yeeloong_hotkey_dev) {
+		ret = -ENOMEM;
+		goto free_irq;
+	}
+
+	yeeloong_hotkey_dev->name = "HotKeys";
+	yeeloong_hotkey_dev->phys = "button/input0";
+	yeeloong_hotkey_dev->id.bustype = BUS_HOST;
+	yeeloong_hotkey_dev->dev.parent = NULL;
+
+	ret = sparse_keymap_setup(yeeloong_hotkey_dev, yeeloong_keymap, NULL);
+	if (ret) {
+		pr_err("Failed to setup input device keymap\n");
+		goto free_dev;
+	}
+
+	ret = input_register_device(yeeloong_hotkey_dev);
+	if (ret)
+		goto free_keymap;
+
+	/* Update the current status of LID */
+	report_lid_switch(ON);
+
+#ifdef CONFIG_LOONGSON_SUSPEND
+	/* Install the real yeeloong_report_lid_status for pm.c */
+	yeeloong_report_lid_status = report_lid_switch;
+#endif
+	return 0;
+
+free_keymap:
+	sparse_keymap_free(yeeloong_hotkey_dev);
+free_dev:
+	input_free_device(yeeloong_hotkey_dev);
+free_irq:
+	free_irq(SCI_IRQ_NUM, NULL);
+free_pm_notifier:
+	unregister_pm_notifier(&pm_notifier);
+free_reboot_notifier:
+	unregister_reboot_notifier(&reboot_notifier);
+end:
+	return ret;
+}
+
+static void yeeloong_hotkey_exit(void)
+{
+	/* Free irq */
+	free_irq(SCI_IRQ_NUM, NULL);
+
+#ifdef CONFIG_LOONGSON_SUSPEND
+	/* Uninstall yeeloong_report_lid_status for pm.c */
+	if (yeeloong_report_lid_status == report_lid_switch)
+		yeeloong_report_lid_status = NULL;
+#endif
+
+	if (yeeloong_hotkey_dev) {
+		sparse_keymap_free(yeeloong_hotkey_dev);
+		input_unregister_device(yeeloong_hotkey_dev);
+		yeeloong_hotkey_dev = NULL;
+	}
+}
+
+#ifdef CONFIG_PM
+static void usb_ports_set(int status)
+{
+	status = !!status;
+
+	ec_write(REG_USB0_FLAG, status);
+	ec_write(REG_USB1_FLAG, status);
+	ec_write(REG_USB2_FLAG, status);
+}
+
+static int yeeloong_suspend(struct device *dev)
+
+{
+	if (ec_version_before("EC_VER=PQ1D27"))
+		vo_set_state(LCD, OFF);
+	vo_set_state(CRT, OFF);
+	usb_ports_set(OFF);
+
+	return 0;
+}
+
+static int yeeloong_resume(struct device *dev)
+{
+	int ret;
+
+	if (ec_version_before("EC_VER=PQ1D27"))
+		vo_set_state(LCD, ON);
+	vo_set_state(CRT, ON);
+	usb_ports_set(ON);
+
+	ret = sci_irq_init();
+	if (ret)
+		return -EFAULT;
+
+	return 0;
+}
+
+static const SIMPLE_DEV_PM_OPS(yeeloong_pm_ops, yeeloong_suspend,
+	yeeloong_resume);
+#endif
+
+static struct platform_device_id platform_device_ids[] = {
+	{
+		.name = "yeeloong_laptop",
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(platform, platform_device_ids);
+
+static struct platform_driver platform_driver = {
+	.driver = {
+		.name = "yeeloong_laptop",
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &yeeloong_pm_ops,
+#endif
+	},
+	.id_table = platform_device_ids,
+};
+
+static int __init yeeloong_init(void)
+{
+	int ret;
+
+	pr_info("YeeLoong Laptop platform specific driver loaded.\n");
+
+	/* Register platform stuff */
+	ret = platform_driver_register(&platform_driver);
+	if (ret) {
+		pr_err("Failed to register YeeLoong platform driver.\n");
+		return ret;
+	}
+
+#define yeeloong_init_drv(drv, alias) do {			\
+	pr_info("Registered YeeLoong " alias " driver.\n");	\
+	ret = yeeloong_ ## drv ## _init();			\
+	if (ret) {						\
+		pr_err("Failed to register YeeLoong " alias " driver.\n");	\
+		yeeloong_ ## drv ## _exit();			\
+		return ret;					\
+	}							\
+} while (0)
+
+	yeeloong_init_drv(backlight, "backlight");
+	yeeloong_init_drv(bat, "battery and AC");
+	yeeloong_init_drv(hwmon, "hardware monitor");
+	yeeloong_init_drv(vo, "video output");
+	yeeloong_init_drv(lcd, "lcd output");
+	yeeloong_init_drv(hotkey, "hotkey input");
+
+	return 0;
+}
+
+static void __exit yeeloong_exit(void)
+{
+	yeeloong_hotkey_exit();
+	yeeloong_lcd_exit();
+	yeeloong_vo_exit();
+	yeeloong_hwmon_exit();
+	yeeloong_bat_exit();
+	yeeloong_backlight_exit();
+	platform_driver_unregister(&platform_driver);
+
+	pr_info("YeeLoong platform specific driver unloaded.\n");
+}
+
+module_init(yeeloong_init);
+module_exit(yeeloong_exit);
+
+MODULE_AUTHOR("Wu Zhangjin <wuzhangjin@gmail.com>; Liu Junliang <liujl@lemote.com>");
+MODULE_DESCRIPTION("YeeLoong laptop driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index f15cddf..33503de 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -716,6 +716,7 @@
 config RTC_DRV_CMOS
 	tristate "PC-style 'CMOS'"
 	depends on X86 || ARM || M32R || PPC || MIPS || SPARC64
+	depends on !DEXXON_GDIUM
 	default y if X86
 	help
 	  Say "yes" here to get direct support for the real time clock
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
index ce63646..31aa83c 100644
--- a/drivers/usb/host/pci-quirks.c
+++ b/drivers/usb/host/pci-quirks.c
@@ -450,6 +450,26 @@
 }
 EXPORT_SYMBOL_GPL(usb_amd_dev_put);
 
+#if defined(CONFIG_USB_UHCI_HCD) || defined(CONFIG_USB_UHCI_HCD_MODULE) \
+ || defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) \
+ || defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE) \
+ || defined(CONFIG_USB_XHCI_HCD) || defined(CONFIG_USB_XHCI_HCD_MODULE)
+static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
+{
+	u16 cmd;
+	return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
+}
+
+#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
+#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
+
+static int mmio_resource_enabled(struct pci_dev *pdev, int idx)
+{
+	return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
+}
+#endif
+
+#if defined(CONFIG_USB_UHCI_HCD) || defined(CONFIG_USB_UHCI_HCD_MODULE)
 /*
  * Make sure the controller is completely inactive, unable to
  * generate interrupts or do DMA.
@@ -531,15 +551,6 @@
 }
 EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
 
-static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
-{
-	u16 cmd;
-	return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
-}
-
-#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
-#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
-
 static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
 {
 	unsigned long base = 0;
@@ -557,12 +568,11 @@
 	if (base)
 		uhci_check_and_reset_hc(pdev, base);
 }
+#else
+#define quirk_usb_handoff_uhci(x) do { } while (0)
+#endif /* CONFIG_USB_UHCI_HCD* */
 
-static int mmio_resource_enabled(struct pci_dev *pdev, int idx)
-{
-	return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
-}
-
+#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE)
 static void quirk_usb_handoff_ohci(struct pci_dev *pdev)
 {
 	void __iomem *base;
@@ -641,7 +651,11 @@
 	/* Now the controller is safely in SUSPEND and nothing can wake it up */
 	iounmap(base);
 }
+#else
+#define quirk_usb_handoff_ohci(x) do { } while(0)
+#endif /* CONFIG_USB_OHCI_HCD* */
 
+#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE)
 static const struct dmi_system_id ehci_dmi_nohandoff_table[] = {
 	{
 		/*  Pegatron Lucid (ExoPC) */
@@ -814,6 +828,9 @@
 
 	iounmap(base);
 }
+#else
+#define quirk_usb_disable_ehci(x) do { } while (0)
+#endif /* CONFIG_USB_EHCI_HCD* */
 
 /*
  * handshake - spin reading a register until handshake completes
@@ -953,6 +970,7 @@
 }
 EXPORT_SYMBOL_GPL(usb_disable_xhci_ports);
 
+#if defined(CONFIG_USB_XHCI_HCD) || defined(CONFIG_USB_XHCI_HCD_MODULE)
 /**
  * PCI Quirks for xHCI.
  *
@@ -1060,6 +1078,9 @@
 
 	iounmap(base);
 }
+#else
+#define quirk_usb_handoff_xhci(x) do { } while (0)
+#endif /* CONFIG_USB_UHCI_HCD* */
 
 static void quirk_usb_early_handoff(struct pci_dev *pdev)
 {
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index efdcee1..bdff3f2 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -79,6 +79,9 @@
 #define OPTION_PRODUCT_ETNA_KOI_MODEM		0x7100
 #define OPTION_PRODUCT_GTM380_MODEM		0x7201
 
+#define HUAWO_VENDOR_ID				0x21F5
+#define HUAWO_PRODUCT_E1621			0x2008
+
 #define HUAWEI_VENDOR_ID			0x12D1
 #define HUAWEI_PRODUCT_E173			0x140C
 #define HUAWEI_PRODUCT_E1750			0x1406
@@ -641,6 +644,7 @@
 	{ USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) },
 	{ USB_DEVICE(QUANTA_VENDOR_ID, 0xea42),
 		.driver_info = (kernel_ulong_t)&net_intf4_blacklist },
+	{ USB_DEVICE(HUAWO_VENDOR_ID, HUAWO_PRODUCT_E1621) },	/* QUANTA 6500 chips, Unicom extensive use of this card */
 	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c05, USB_CLASS_COMM, 0x02, 0xff) },
 	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c1f, USB_CLASS_COMM, 0x02, 0xff) },
 	{ USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c23, USB_CLASS_COMM, 0x02, 0xff) },
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 8bf495f..f6a15b6 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -36,6 +36,12 @@
        tristate
        default n
 
+config VIDEO_OUTPUT_CONTROL
+	tristate "Lowlevel video output switch controls"
+	help
+	  This framework adds support for low-level control of the video 
+	  output switch.
+
 config VIDEOMODE_HELPERS
 	bool
 
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 9ad3c17..3d869d9 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -7,6 +7,8 @@
 
 obj-y				  += fbdev/
 
+#video output switch sysfs driver
+obj-$(CONFIG_VIDEO_OUTPUT_CONTROL) += output.o
 obj-$(CONFIG_VIDEOMODE_HELPERS) += display_timing.o videomode.o
 ifeq ($(CONFIG_OF),y)
 obj-$(CONFIG_VIDEOMODE_HELPERS) += of_display_timing.o of_videomode.o
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index 4916c97..3e02ed4 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -2453,6 +2453,19 @@
 	  Configuration re: surface address, size, and format must be provided
 	  through device tree, or plain old platform data.
 
+config FB_SM712
+	tristate "Silicon Motion SM712 framebuffer support"
+	depends on FB && PCI
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	help
+	  Frame buffer driver for the Silicon Motion SM712 chip.
+
+	  This driver is also available as a module. The module will be
+	  called sm712fb. If you want to compile it as a module, say M
+	  here and read <file:Documentation/kbuild/modules.txt>.
+
 source "drivers/video/fbdev/omap/Kconfig"
 source "drivers/video/fbdev/omap2/Kconfig"
 source "drivers/video/fbdev/exynos/Kconfig"
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index 1979aff..9b694f9 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -114,6 +114,7 @@
 obj-$(CONFIG_FB_IBM_GXT4500)	  += gxt4500.o
 obj-$(CONFIG_FB_PS3)		  += ps3fb.o
 obj-$(CONFIG_FB_SM501)            += sm501fb.o
+obj-$(CONFIG_FB_SM712)		  += sm712fb/
 obj-$(CONFIG_FB_UDL)		  += udlfb.o
 obj-$(CONFIG_FB_SMSCUFX)	  += smscufx.o
 obj-$(CONFIG_FB_XILINX)           += xilinxfb.o
diff --git a/drivers/video/fbdev/sm712fb/Makefile b/drivers/video/fbdev/sm712fb/Makefile
new file mode 100644
index 0000000..9bf3519
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_FB_SM712) += sm712fb.o
+
+sm712fb-objs := sm712fb_drv.o sm712fb_accel.o
diff --git a/drivers/video/fbdev/sm712fb/TODO b/drivers/video/fbdev/sm712fb/TODO
new file mode 100644
index 0000000..dcfd4e7
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/TODO
@@ -0,0 +1,7 @@
+TODO:
+- Dual head support
+- refine the code, convert more registers magic numbers to macros
+- Does it really works on Big Endian machines?
+
+Please send any patches to Greg Kroah-Hartman <greg@kroah.com> and
+Tom Li <biergaizi@member.fsf.org>.
diff --git a/drivers/video/fbdev/sm712fb/sm712fb_accel.c b/drivers/video/fbdev/sm712fb/sm712fb_accel.c
new file mode 100644
index 0000000..12fce1f
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/sm712fb_accel.c
@@ -0,0 +1,246 @@
+/*
+ * Silicon Motion SM712 frame buffer device
+ *
+ * Copyright (C) 2006 Silicon Motion Technology Corp.
+ * Authors:  Ge Wang, gewang@siliconmotion.com
+ *	     Boyod boyod.yang@siliconmotion.com.cn
+ *
+ * Copyright (C) 2009 Lemote, Inc.
+ * Author:   Wu Zhangjin, wuzhangjin@gmail.com
+ *
+ * Copyright (C) 2011 Igalia, S.L.
+ * Author:   Javier M. Mellid <jmunhoz@igalia.com>
+ *
+ * Copyright (C) 2014 Tom Li.
+ * Author:   Tom Li (Yifeng Li) <biergaizi@member.fsf.org>
+ *
+ * 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.
+ *
+ * Framebuffer driver for Silicon Motion SM712 chip
+ */
+
+#include <linux/fb.h>
+#include <linux/screen_info.h>
+#include <linux/delay.h>
+
+#include "sm712fb_drv.h"
+#include "sm712fb_accel.h"
+
+static inline u32 bytes_to_dword(const u8 *bytes, int length)
+{
+	u32 dword = 0;
+
+	switch (length) {
+	case 4:
+#ifdef __BIG_ENDIAN
+		dword += bytes[3];
+#else
+		dword += bytes[3] << 24;
+#endif
+	case 3:
+#ifdef __BIG_ENDIAN
+		dword += bytes[2] << 8;
+#else
+		dword += bytes[2] << 16;
+#endif
+	case 2:
+#ifdef __BIG_ENDIAN
+		dword += bytes[1] << 16;
+#else
+		dword += bytes[1] << 8;
+#endif
+	case 1:
+#ifdef __BIG_ENDIAN
+		dword += bytes[0] << 24;
+#else
+		dword += bytes[0];
+#endif
+	}
+	return dword;
+}
+
+int sm712fb_init_accel(struct sm712fb_info *fb)
+{
+	u8 reg;
+
+	/* reset the 2D engine */
+	sm712_write_seq(fb, 0x21, sm712_read_seq(fb, 0x21) & 0xf8);
+	reg = sm712_read_seq(fb, 0x15);
+	sm712_write_seq(fb, 0x15, reg | 0x30);
+	sm712_write_seq(fb, 0x15, reg);
+
+	if (sm712fb_wait(fb) != 0)
+		return -1;
+
+	sm712_write_dpr(fb, DPR_CROP_TOPLEFT_COORDS, DPR_COORDS(0, 0));
+
+	/* same width for DPR_PITCH and DPR_SRC_WINDOW */
+	sm712_write_dpr(fb, DPR_PITCH,
+			DPR_COORDS(fb->fb.var.xres, fb->fb.var.xres));
+	sm712_write_dpr(fb, DPR_SRC_WINDOW,
+			DPR_COORDS(fb->fb.var.xres, fb->fb.var.xres));
+
+	sm712_write_dpr(fb, DPR_BYTE_BIT_MASK, 0xffffffff);
+	sm712_write_dpr(fb, DPR_COLOR_COMPARE_MASK, 0);
+	sm712_write_dpr(fb, DPR_COLOR_COMPARE, 0);
+	sm712_write_dpr(fb, DPR_SRC_BASE, 0);
+	sm712_write_dpr(fb, DPR_DST_BASE, 0);
+	sm712_read_dpr(fb, DPR_DST_BASE);
+
+	return 0;
+}
+
+int sm712fb_wait(struct sm712fb_info *fb)
+{
+	int i;
+	u8 reg;
+
+	for (i = 0; i < 10000; i++) {
+		reg = sm712_read_seq(fb, SCR_DE_STATUS);
+		if ((reg & SCR_DE_STATUS_MASK) == SCR_DE_ENGINE_IDLE)
+			return 0;
+		udelay(1);
+	}
+	return -EBUSY;
+}
+
+void sm712fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
+{
+	u32 width = rect->width, height = rect->height;
+	u32 dx = rect->dx, dy = rect->dy;
+	u32 color;
+
+	struct sm712fb_info *sfb = info->par;
+
+	if (unlikely(info->state != FBINFO_STATE_RUNNING))
+		return;
+	if ((rect->dx >= info->var.xres_virtual) ||
+	    (rect->dy >= info->var.yres_virtual))
+		return;
+
+	if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
+	    info->fix.visual == FB_VISUAL_DIRECTCOLOR)
+		color = ((u32 *) (info->pseudo_palette))[rect->color];
+	else
+		color = rect->color;
+
+	sm712_write_dpr(sfb, DPR_FG_COLOR, color);
+	sm712_write_dpr(sfb, DPR_DST_COORDS, DPR_COORDS(dx, dy));
+	sm712_write_dpr(sfb, DPR_SPAN_COORDS, DPR_COORDS(width, height));
+	sm712_write_dpr(sfb, DPR_DE_CTRL, DE_CTRL_START | DE_CTRL_ROP_ENABLE |
+			(DE_CTRL_COMMAND_SOLIDFILL << DE_CTRL_COMMAND_SHIFT) |
+			(DE_CTRL_ROP_SRC << DE_CTRL_ROP_SHIFT));
+	sm712_read_dpr(sfb, DPR_DE_CTRL);
+	sm712fb_wait(sfb);
+}
+
+void sm712fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
+{
+	u32 sx = area->sx, sy = area->sy;
+	u32 dx = area->dx, dy = area->dy;
+	u32 height = area->height, width = area->width;
+	u32 direction;
+
+	struct sm712fb_info *sfb = info->par;
+
+	if (unlikely(info->state != FBINFO_STATE_RUNNING))
+		return;
+	if ((sx >= info->var.xres_virtual) || (sy >= info->var.yres_virtual))
+		return;
+
+	if (sy < dy || (sy == dy && sx <= dx)) {
+		sx += width - 1;
+		dx += width - 1;
+		sy += height - 1;
+		dy += height - 1;
+		direction = DE_CTRL_RTOL;
+	} else
+		direction = 0;
+
+	sm712_write_dpr(sfb, DPR_SRC_COORDS, DPR_COORDS(sx, sy));
+	sm712_write_dpr(sfb, DPR_DST_COORDS, DPR_COORDS(dx, dy));
+	sm712_write_dpr(sfb, DPR_SPAN_COORDS, DPR_COORDS(width, height));
+	sm712_write_dpr(sfb, DPR_DE_CTRL,
+			DE_CTRL_START | DE_CTRL_ROP_ENABLE | direction |
+			(DE_CTRL_COMMAND_BITBLT << DE_CTRL_COMMAND_SHIFT) |
+			(DE_CTRL_ROP_SRC << DE_CTRL_ROP_SHIFT));
+	sm712_read_dpr(sfb, DPR_DE_CTRL);
+	sm712fb_wait(sfb);
+}
+
+void sm712fb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+	u32 dx = image->dx, dy = image->dy;
+	u32 width = image->width, height = image->height;
+	u32 fg_color, bg_color;
+
+	struct sm712fb_info *sfb = info->par;
+
+	u32 imgidx = 0;
+	u32 line = image->width >> 3;
+
+	int i, j;
+	u32 total_bytes, total_dwords, remain_bytes;
+
+	if (unlikely(info->state != FBINFO_STATE_RUNNING))
+		return;
+	if ((image->dx >= info->var.xres_virtual) ||
+	    (image->dy >= info->var.yres_virtual))
+		return;
+
+	if (unlikely(image->depth != 1)) {
+		/* unsupported depth, fallback to draw Tux */
+		cfb_imageblit(info, image);
+		return;
+	}
+
+	if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
+	    info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
+		fg_color = ((u32 *) (info->pseudo_palette))[image->fg_color];
+		bg_color = ((u32 *) (info->pseudo_palette))[image->bg_color];
+	} else {
+		fg_color = image->fg_color;
+		bg_color = image->bg_color;
+	}
+
+	/* total bytes we need to write */
+	total_bytes = (width + 7) / 8;
+
+	/* split the bytes into dwords and remainder bytes */
+	total_dwords = (total_bytes & ~3) / 4;
+	remain_bytes = total_bytes & 3;
+
+	sm712_write_dpr(sfb, DPR_SRC_COORDS, 0);
+	sm712_write_dpr(sfb, DPR_DST_COORDS, DPR_COORDS(dx, dy));
+	sm712_write_dpr(sfb, DPR_SPAN_COORDS, DPR_COORDS(width, height));
+	sm712_write_dpr(sfb, DPR_FG_COLOR, fg_color);
+	sm712_write_dpr(sfb, DPR_BG_COLOR, bg_color);
+
+	sm712_write_dpr(sfb, DPR_DE_CTRL, DE_CTRL_START | DE_CTRL_ROP_ENABLE |
+			(DE_CTRL_COMMAND_HOST_WRITE << DE_CTRL_COMMAND_SHIFT) |
+			(DE_CTRL_HOST_MONO << DE_CTRL_HOST_SHIFT) |
+			(DE_CTRL_ROP_SRC << DE_CTRL_ROP_SHIFT));
+
+	for (i = 0; i < height; i++) {
+		/* cast bytes data into dwords and write to the dataport */
+		for (j = 0; j < total_dwords; j++) {
+			sm712_write_dataport(sfb,
+					     bytes_to_dword(&image->
+							    data[imgidx] +
+							    j * 4, 4));
+		}
+
+		if (remain_bytes) {
+			sm712_write_dataport(sfb,
+					     bytes_to_dword(&image->
+							    data[imgidx] +
+							    (total_dwords * 4),
+							    remain_bytes));
+		}
+		imgidx += line;
+	}
+	sm712_read_dpr(sfb, DPR_DE_CTRL);
+	sm712fb_wait(sfb);
+}
diff --git a/drivers/video/fbdev/sm712fb/sm712fb_accel.h b/drivers/video/fbdev/sm712fb/sm712fb_accel.h
new file mode 100644
index 0000000..6f79177
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/sm712fb_accel.h
@@ -0,0 +1,33 @@
+/*
+ * Silicon Motion SM712 frame buffer device
+ *
+ * Copyright (C) 2006 Silicon Motion Technology Corp.
+ * Authors:  Ge Wang, gewang@siliconmotion.com
+ *	     Boyod boyod.yang@siliconmotion.com.cn
+ *
+ * Copyright (C) 2009 Lemote, Inc.
+ * Author:   Wu Zhangjin, wuzhangjin@gmail.com
+ *
+ * Copyright (C) 2011 Igalia, S.L.
+ * Author:   Javier M. Mellid <jmunhoz@igalia.com>
+ *
+ * Copyright (C) 2014 Tom Li.
+ * Author:   Tom Li (Yifeng Li) <biergaizi@member.fsf.org>
+ *
+ * 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.
+ *
+ * Framebuffer driver for Silicon Motion SM712 chip
+ */
+
+#ifndef _SM712FB_ACCEL_H
+#define _SM712FB_ACCEL_H
+
+int sm712fb_init_accel(struct sm712fb_info *fb);
+int sm712fb_wait(struct sm712fb_info *fb);
+void sm712fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect);
+void sm712fb_copyarea(struct fb_info *info, const struct fb_copyarea *area);
+void sm712fb_imageblit(struct fb_info *info, const struct fb_image *image);
+
+#endif
diff --git a/drivers/video/fbdev/sm712fb/sm712fb_drv.c b/drivers/video/fbdev/sm712fb/sm712fb_drv.c
new file mode 100644
index 0000000..7f7cd4f
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/sm712fb_drv.c
@@ -0,0 +1,1022 @@
+/*
+ * Silicon Motion SM712 frame buffer device
+ *
+ * Copyright (C) 2006 Silicon Motion Technology Corp.
+ * Authors:  Ge Wang, gewang@siliconmotion.com
+ *	     Boyod boyod.yang@siliconmotion.com.cn
+ *
+ * Copyright (C) 2009 Lemote, Inc.
+ * Author:   Wu Zhangjin, wuzhangjin@gmail.com
+ *
+ * Copyright (C) 2011 Igalia, S.L.
+ * Author:   Javier M. Mellid <jmunhoz@igalia.com>
+ *
+ * Copyright (C) 2014 Tom Li.
+ * Author:   Tom Li (Yifeng Li) <biergaizi@member.fsf.org>
+ *
+ * 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.
+ *
+ * Framebuffer driver for Silicon Motion SM712 chip
+ */
+
+#include <linux/io.h>
+#include <linux/fb.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/screen_info.h>
+
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+#endif
+
+#include "sm712fb_drv.h"
+#include "sm712fb_accel.h"
+#include "sm712fb_modedb.h"
+
+static struct fb_var_screeninfo sm712fb_var = {
+	.xres = 1024,
+	.yres = 600,
+	.xres_virtual = 1024,
+	.yres_virtual = 600,
+	.bits_per_pixel = 16,
+	.red = {16, 8, 0},
+	.green = {8, 8, 0},
+	.blue = {0, 8, 0},
+	.activate = FB_ACTIVATE_NOW,
+	.height = -1,
+	.width = -1,
+	.vmode = FB_VMODE_NONINTERLACED,
+	.nonstd = 0,
+	.accel_flags = FB_ACCELF_TEXT,
+};
+
+static struct fb_fix_screeninfo sm712fb_fix = {
+	.id = "smXXXfb",
+	.type = FB_TYPE_PACKED_PIXELS,
+	.visual = FB_VISUAL_TRUECOLOR,
+	.line_length = 800 * 3,
+	.accel = FB_ACCEL_SMI_LYNX,
+	.type_aux = 0,
+	.xpanstep = 0,
+	.ypanstep = 0,
+	.ywrapstep = 0,
+};
+
+struct vesa_mode {
+	char index[6];
+	u16 lfb_width;
+	u16 lfb_height;
+	u16 lfb_depth;
+};
+
+static bool accel = 1;
+
+static struct vesa_mode vesa_mode_table[] = {
+	{"0x301", 640, 480, 8},
+	{"0x303", 800, 600, 8},
+	{"0x305", 1024, 768, 8},
+	{"0x307", 1280, 1024, 8},
+
+	{"0x311", 640, 480, 16},
+	{"0x314", 800, 600, 16},
+	{"0x317", 1024, 768, 16},
+	{"0x31A", 1280, 1024, 16},
+
+	{"0x312", 640, 480, 24},
+	{"0x315", 800, 600, 24},
+	{"0x318", 1024, 768, 24},
+	{"0x31B", 1280, 1024, 24},
+};
+
+struct screen_info sm712_scr_info;
+
+static int sm712fb_setup(char *options)
+{
+	char *this_opt;
+
+	if (!options || !*options)
+		return 0;
+
+	while ((this_opt = strsep(&options, ",")) != NULL) {
+		if (!*this_opt)
+			continue;
+
+		if (!strcmp(this_opt, "accel:0"))
+			accel = false;
+		else if (!strcmp(this_opt, "accel:1"))
+			accel = true;
+	}
+	return 0;
+}
+
+/* process command line options, get vga parameter */
+static int __init sm712_vga_setup(char *options)
+{
+	int i;
+
+	if (!options || !*options)
+		return -EINVAL;
+
+	sm712_scr_info.lfb_width = 0;
+	sm712_scr_info.lfb_height = 0;
+	sm712_scr_info.lfb_depth = 0;
+
+	pr_debug("sm712_vga_setup = %s\n", options);
+
+	for (i = 0; i < ARRAY_SIZE(vesa_mode_table); i++) {
+		if (strstr(options, vesa_mode_table[i].index)) {
+			sm712_scr_info.lfb_width = vesa_mode_table[i].lfb_width;
+			sm712_scr_info.lfb_height =
+			    vesa_mode_table[i].lfb_height;
+			sm712_scr_info.lfb_depth = vesa_mode_table[i].lfb_depth;
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+__setup("vga=", sm712_vga_setup);
+
+static void sm712_setpalette(int regno, unsigned red, unsigned green,
+			     unsigned blue, struct fb_info *info)
+{
+	struct sm712fb_info *sfb = info->par;
+
+	/* set bit 5:4 = 01 (write LCD RAM only) */
+	sm712_write_seq(sfb, 0x66, (sm712_read_seq(sfb, 0x66) & 0xC3) | 0x10);
+
+	sm712_writeb(sfb->mmio, DAC_REG, regno);
+	sm712_writeb(sfb->mmio, DAC_VAL, red >> 10);
+	sm712_writeb(sfb->mmio, DAC_VAL, green >> 10);
+	sm712_writeb(sfb->mmio, DAC_VAL, blue >> 10);
+}
+
+/* chan_to_field
+ *
+ * convert a colour value into a field position
+ *
+ * from pxafb.c
+ */
+
+static inline unsigned int chan_to_field(unsigned int chan,
+					 struct fb_bitfield *bf)
+{
+	chan &= 0xffff;
+	chan >>= 16 - bf->length;
+	return chan << bf->offset;
+}
+
+static int sm712_blank(int blank_mode, struct fb_info *info)
+{
+	struct sm712fb_info *sfb = info->par;
+
+	/* clear DPMS setting */
+	switch (blank_mode) {
+	case FB_BLANK_UNBLANK:
+		/* Screen On: HSync: On, VSync : On */
+		sm712_write_seq(sfb, 0x01,
+				(sm712_read_seq(sfb, 0x01) & (~0x20)));
+		sm712_write_seq(sfb, 0x6a, 0x16);
+		sm712_write_seq(sfb, 0x6b, 0x02);
+		sm712_write_seq(sfb, 0x21, (sm712_read_seq(sfb, 0x21) & 0x77));
+		sm712_write_seq(sfb, 0x22,
+				(sm712_read_seq(sfb, 0x22) & (~0x30)));
+		sm712_write_seq(sfb, 0x23,
+				(sm712_read_seq(sfb, 0x23) & (~0xc0)));
+		sm712_write_seq(sfb, 0x24, (sm712_read_seq(sfb, 0x24) | 0x01));
+		sm712_write_seq(sfb, 0x31, (sm712_read_seq(sfb, 0x31) | 0x03));
+		break;
+	case FB_BLANK_NORMAL:
+		/* Screen Off: HSync: On, VSync : On   Soft blank */
+		sm712_write_seq(sfb, 0x01,
+				(sm712_read_seq(sfb, 0x01) & (~0x20)));
+		sm712_write_seq(sfb, 0x6a, 0x16);
+		sm712_write_seq(sfb, 0x6b, 0x02);
+		sm712_write_seq(sfb, 0x22,
+				(sm712_read_seq(sfb, 0x22) & (~0x30)));
+		sm712_write_seq(sfb, 0x23,
+				(sm712_read_seq(sfb, 0x23) & (~0xc0)));
+		sm712_write_seq(sfb, 0x24, (sm712_read_seq(sfb, 0x24) | 0x01));
+		sm712_write_seq(sfb, 0x31,
+				((sm712_read_seq(sfb, 0x31) & (~0x07)) | 0x00));
+		break;
+	case FB_BLANK_VSYNC_SUSPEND:
+		/* Screen On: HSync: On, VSync : Off */
+		sm712_write_seq(sfb, 0x01, (sm712_read_seq(sfb, 0x01) | 0x20));
+		sm712_write_seq(sfb, 0x20,
+				(sm712_read_seq(sfb, 0x20) & (~0xB0)));
+		sm712_write_seq(sfb, 0x6a, 0x0c);
+		sm712_write_seq(sfb, 0x6b, 0x02);
+		sm712_write_seq(sfb, 0x21, (sm712_read_seq(sfb, 0x21) | 0x88));
+		sm712_write_seq(sfb, 0x22,
+				((sm712_read_seq(sfb, 0x22) & (~0x30)) | 0x20));
+		sm712_write_seq(sfb, 0x23,
+				((sm712_read_seq(sfb, 0x23) & (~0xc0)) | 0x20));
+		sm712_write_seq(sfb, 0x24,
+				(sm712_read_seq(sfb, 0x24) & (~0x01)));
+		sm712_write_seq(sfb, 0x31,
+				((sm712_read_seq(sfb, 0x31) & (~0x07)) | 0x00));
+		sm712_write_seq(sfb, 0x34, (sm712_read_seq(sfb, 0x34) | 0x80));
+		break;
+	case FB_BLANK_HSYNC_SUSPEND:
+		/* Screen On: HSync: Off, VSync : On */
+		sm712_write_seq(sfb, 0x01, (sm712_read_seq(sfb, 0x01) | 0x20));
+		sm712_write_seq(sfb, 0x20,
+				(sm712_read_seq(sfb, 0x20) & (~0xB0)));
+		sm712_write_seq(sfb, 0x6a, 0x0c);
+		sm712_write_seq(sfb, 0x6b, 0x02);
+		sm712_write_seq(sfb, 0x21, (sm712_read_seq(sfb, 0x21) | 0x88));
+		sm712_write_seq(sfb, 0x22,
+				((sm712_read_seq(sfb, 0x22) & (~0x30)) | 0x10));
+		sm712_write_seq(sfb, 0x23,
+				((sm712_read_seq(sfb, 0x23) & (~0xc0)) | 0xD8));
+		sm712_write_seq(sfb, 0x24,
+				(sm712_read_seq(sfb, 0x24) & (~0x01)));
+		sm712_write_seq(sfb, 0x31,
+				((sm712_read_seq(sfb, 0x31) & (~0x07)) | 0x00));
+		sm712_write_seq(sfb, 0x34, (sm712_read_seq(sfb, 0x34) | 0x80));
+		break;
+	case FB_BLANK_POWERDOWN:
+		/* Screen On: HSync: Off, VSync : Off */
+		sm712_write_seq(sfb, 0x01, (sm712_read_seq(sfb, 0x01) | 0x20));
+		sm712_write_seq(sfb, 0x20,
+				(sm712_read_seq(sfb, 0x20) & (~0xB0)));
+		sm712_write_seq(sfb, 0x6a, 0x5a);
+		sm712_write_seq(sfb, 0x6b, 0x20);
+		sm712_write_seq(sfb, 0x21, (sm712_read_seq(sfb, 0x21) | 0x88));
+		sm712_write_seq(sfb, 0x22,
+				((sm712_read_seq(sfb, 0x22) & (~0x30)) | 0x30));
+		sm712_write_seq(sfb, 0x23,
+				((sm712_read_seq(sfb, 0x23) & (~0xc0)) | 0xD8));
+		sm712_write_seq(sfb, 0x24,
+				(sm712_read_seq(sfb, 0x24) & (~0x01)));
+		sm712_write_seq(sfb, 0x31,
+				((sm712_read_seq(sfb, 0x31) & (~0x07)) | 0x00));
+		sm712_write_seq(sfb, 0x34, (sm712_read_seq(sfb, 0x34) | 0x80));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sm712_setcolreg(unsigned regno, unsigned red, unsigned green,
+			   unsigned blue, unsigned trans, struct fb_info *info)
+{
+	struct sm712fb_info *sfb;
+	u32 val;
+
+	sfb = info->par;
+
+	if (regno > 255)
+		return 1;
+
+	switch (sfb->fb.fix.visual) {
+	case FB_VISUAL_DIRECTCOLOR:
+	case FB_VISUAL_TRUECOLOR:
+		/*
+		 * 16/32 bit true-colour, use pseudo-palette for 16 base color
+		 */
+		if (regno < 16) {
+			if (sfb->fb.var.bits_per_pixel == 16) {
+				u32 *pal = sfb->fb.pseudo_palette;
+
+				val = chan_to_field(red, &sfb->fb.var.red);
+				val |= chan_to_field(green, &sfb->fb.var.green);
+				val |= chan_to_field(blue, &sfb->fb.var.blue);
+#ifdef __BIG_ENDIAN
+				pal[regno] =
+				    ((red & 0xf800) >> 8) |
+				    ((green & 0xe000) >> 13) |
+				    ((green & 0x1c00) << 3) |
+				    ((blue & 0xf800) >> 3);
+#else
+				pal[regno] = val;
+#endif
+			} else {
+				u32 *pal = sfb->fb.pseudo_palette;
+
+				val = chan_to_field(red, &sfb->fb.var.red);
+				val |= chan_to_field(green, &sfb->fb.var.green);
+				val |= chan_to_field(blue, &sfb->fb.var.blue);
+#ifdef __BIG_ENDIAN
+				val =
+				    (val & 0xff00ff00 >> 8) |
+				    (val & 0x00ff00ff << 8);
+#endif
+				pal[regno] = val;
+			}
+		}
+		break;
+
+	case FB_VISUAL_PSEUDOCOLOR:
+		/* color depth 8 bit */
+		sm712_setpalette(regno, red, green, blue, info);
+		break;
+
+	default:
+		return 1;	/* unknown type */
+	}
+
+	return 0;
+
+}
+
+#ifdef __BIG_ENDIAN
+static ssize_t sm712fb_read(struct fb_info *info, char __user *buf,
+			    size_t count, loff_t *ppos)
+{
+	unsigned long p = *ppos;
+
+	u32 *buffer, *dst;
+	u32 __iomem *src;
+	int c, i, cnt = 0, err = 0;
+	unsigned long total_size;
+
+	if (!info || !info->screen_base)
+		return -ENODEV;
+
+	if (info->state != FBINFO_STATE_RUNNING)
+		return -EPERM;
+
+	total_size = info->screen_size;
+
+	if (total_size == 0)
+		total_size = info->fix.smem_len;
+
+	if (p >= total_size)
+		return 0;
+
+	if (count >= total_size)
+		count = total_size;
+
+	if (count + p > total_size)
+		count = total_size - p;
+
+	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	src = (u32 __iomem *) (info->screen_base + p);
+
+	if (info->fbops->fb_sync)
+		info->fbops->fb_sync(info);
+
+	while (count) {
+		c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
+		dst = buffer;
+		for (i = c >> 2; i--;) {
+			*dst = fb_readl(src++);
+			*dst =
+			    (*dst & 0xff00ff00 >> 8) | (*dst & 0x00ff00ff << 8);
+			dst++;
+		}
+		if (c & 3) {
+			u8 *dst8 = (u8 *) dst;
+			u8 __iomem *src8 = (u8 __iomem *) src;
+
+			for (i = c & 3; i--;) {
+				if (i & 1) {
+					*dst8++ = fb_readb(++src8);
+				} else {
+					*dst8++ = fb_readb(--src8);
+					src8 += 2;
+				}
+			}
+			src = (u32 __iomem *) src8;
+		}
+
+		if (copy_to_user(buf, buffer, c)) {
+			err = -EFAULT;
+			break;
+		}
+		*ppos += c;
+		buf += c;
+		cnt += c;
+		count -= c;
+	}
+
+	kfree(buffer);
+
+	return (err) ? err : cnt;
+}
+
+static ssize_t
+sm712fb_write(struct fb_info *info, const char __user *buf, size_t count,
+	      loff_t *ppos)
+{
+	unsigned long p = *ppos;
+
+	u32 *buffer, *src;
+	u32 __iomem *dst;
+	int c, i, cnt = 0, err = 0;
+	unsigned long total_size;
+
+	if (!info || !info->screen_base)
+		return -ENODEV;
+
+	if (info->state != FBINFO_STATE_RUNNING)
+		return -EPERM;
+
+	total_size = info->screen_size;
+
+	if (total_size == 0)
+		total_size = info->fix.smem_len;
+
+	if (p > total_size)
+		return -EFBIG;
+
+	if (count > total_size) {
+		err = -EFBIG;
+		count = total_size;
+	}
+
+	if (count + p > total_size) {
+		if (!err)
+			err = -ENOSPC;
+
+		count = total_size - p;
+	}
+
+	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	dst = (u32 __iomem *) (info->screen_base + p);
+
+	if (info->fbops->fb_sync)
+		info->fbops->fb_sync(info);
+
+	while (count) {
+		c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
+		src = buffer;
+
+		if (copy_from_user(src, buf, c)) {
+			err = -EFAULT;
+			break;
+		}
+
+		for (i = c >> 2; i--;) {
+			fb_writel((*src & 0xff00ff00 >> 8) |
+				  (*src & 0x00ff00ff << 8), dst++);
+			src++;
+		}
+		if (c & 3) {
+			u8 *src8 = (u8 *) src;
+			u8 __iomem *dst8 = (u8 __iomem *) dst;
+
+			for (i = c & 3; i--;) {
+				if (i & 1) {
+					fb_writeb(*src8++, ++dst8);
+				} else {
+					fb_writeb(*src8++, --dst8);
+					dst8 += 2;
+				}
+			}
+			dst = (u32 __iomem *) dst8;
+		}
+
+		*ppos += c;
+		buf += c;
+		cnt += c;
+		count -= c;
+	}
+
+	kfree(buffer);
+
+	return (cnt) ? cnt : err;
+}
+#endif /* ! __BIG_ENDIAN */
+
+static void sm712_set_timing(struct sm712fb_info *sfb)
+{
+	int i = 0, j = 0;
+	u32 m_nScreenStride;
+
+	dev_dbg(&sfb->pdev->dev,
+		"sfb->width=%d sfb->height=%d "
+		"sfb->fb.var.bits_per_pixel=%d sfb->hz=%d\n",
+		sfb->width, sfb->height, sfb->fb.var.bits_per_pixel, sfb->hz);
+
+	for (j = 0; j < numVGAModes; j++) {
+		if (VGAMode[j].mmSizeX != sfb->width ||
+		    VGAMode[j].mmSizeY != sfb->height ||
+		    VGAMode[j].bpp != sfb->fb.var.bits_per_pixel ||
+		    VGAMode[j].hz != sfb->hz) {
+			continue;
+		}
+
+		dev_dbg(&sfb->pdev->dev,
+			"VGAMode[j].mmSizeX=%d VGAMode[j].mmSizeY=%d "
+			"VGAMode[j].bpp=%d VGAMode[j].hz=%d\n",
+			VGAMode[j].mmSizeX, VGAMode[j].mmSizeY,
+			VGAMode[j].bpp, VGAMode[j].hz);
+
+		dev_dbg(&sfb->pdev->dev, "VGAMode index=%d\n", j);
+
+		sm712_writeb(sfb->mmio, 0x3c6, 0x0);
+
+		sm712_write_seq(sfb, 0, 0x1);
+
+		sm712_writeb(sfb->mmio, 0x3c2, VGAMode[j].Init_MISC);
+
+		/* init SEQ register SR00 - SR04 */
+		for (i = 0; i < SR00_SR04_SIZE; i++)
+			sm712_write_seq(sfb, i, VGAMode[j].Init_SR00_SR04[i]);
+
+		/* init SEQ register SR10 - SR24 */
+		for (i = 0; i < SR10_SR24_SIZE; i++)
+			sm712_write_seq(sfb, i + 0x10,
+					VGAMode[j].Init_SR10_SR24[i]);
+
+		/* init SEQ register SR30 - SR75 */
+		for (i = 0; i < SR30_SR75_SIZE; i++)
+			if ((i + 0x30) != 0x62 &&
+			    (i + 0x30) != 0x6a && (i + 0x30) != 0x6b)
+				sm712_write_seq(sfb, i + 0x30,
+						VGAMode[j].Init_SR30_SR75[i]);
+
+		/* init SEQ register SR80 - SR93 */
+		for (i = 0; i < SR80_SR93_SIZE; i++)
+			sm712_write_seq(sfb, i + 0x80,
+					VGAMode[j].Init_SR80_SR93[i]);
+
+		/* init SEQ register SRA0 - SRAF */
+		for (i = 0; i < SRA0_SRAF_SIZE; i++)
+			sm712_write_seq(sfb, i + 0xa0,
+					VGAMode[j].Init_SRA0_SRAF[i]);
+
+		/* init Graphic register GR00 - GR08 */
+		for (i = 0; i < GR00_GR08_SIZE; i++)
+			sm712_write_grph(sfb, i, VGAMode[j].Init_GR00_GR08[i]);
+
+		/* init Attribute register AR00 - AR14 */
+		for (i = 0; i < AR00_AR14_SIZE; i++)
+			sm712_write_attr(sfb, i, VGAMode[j].Init_AR00_AR14[i]);
+
+		/* init CRTC register CR00 - CR18 */
+		for (i = 0; i < CR00_CR18_SIZE; i++)
+			sm712_write_crtc(sfb, i, VGAMode[j].Init_CR00_CR18[i]);
+
+		/* init CRTC register CR30 - CR4D */
+		for (i = 0; i < CR30_CR4D_SIZE; i++)
+			sm712_write_crtc(sfb, i + 0x30,
+					 VGAMode[j].Init_CR30_CR4D[i]);
+
+		/* init CRTC register CR90 - CRA7 */
+		for (i = 0; i < CR90_CRA7_SIZE; i++)
+			sm712_write_crtc(sfb, i + 0x90,
+					 VGAMode[j].Init_CR90_CRA7[i]);
+	}
+	sm712_writeb(sfb->mmio, 0x3c2, 0x67);
+
+	/* set VPR registers */
+	sm712_writel(sfb->vpr, 0x0C, 0x0);
+	sm712_writel(sfb->vpr, 0x40, 0x0);
+
+	/* set data width */
+	m_nScreenStride = (sfb->width * sfb->fb.var.bits_per_pixel) / 64;
+	switch (sfb->fb.var.bits_per_pixel) {
+	case 8:
+		sm712_writel(sfb->vpr, 0x0, 0x0);
+		break;
+	case 16:
+		sm712_writel(sfb->vpr, 0x0, 0x00020000);
+		break;
+	case 24:
+		sm712_writel(sfb->vpr, 0x0, 0x00040000);
+		break;
+	case 32:
+		sm712_writel(sfb->vpr, 0x0, 0x00030000);
+		break;
+	}
+	sm712_writel(sfb->vpr, 0x10,
+		     (u32) (((m_nScreenStride + 2) << 16) | m_nScreenStride));
+}
+
+static void sm712fb_setmode(struct sm712fb_info *sfb)
+{
+	switch (sfb->fb.var.bits_per_pixel) {
+	case 32:
+		sfb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+		sfb->fb.fix.line_length = sfb->fb.var.xres * 4;
+		sfb->fb.var.red.length = 8;
+		sfb->fb.var.green.length = 8;
+		sfb->fb.var.blue.length = 8;
+		sfb->fb.var.red.offset = 16;
+		sfb->fb.var.green.offset = 8;
+		sfb->fb.var.blue.offset = 0;
+		break;
+	case 24:
+		sfb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+		sfb->fb.fix.line_length = sfb->fb.var.xres * 3;
+		sfb->fb.var.red.length = 8;
+		sfb->fb.var.green.length = 8;
+		sfb->fb.var.blue.length = 8;
+		sfb->fb.var.red.offset = 16;
+		sfb->fb.var.green.offset = 8;
+		sfb->fb.var.blue.offset = 0;
+		break;
+	case 8:
+		sfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
+		sfb->fb.fix.line_length = sfb->fb.var.xres;
+		sfb->fb.var.red.length = 3;
+		sfb->fb.var.green.length = 3;
+		sfb->fb.var.blue.length = 2;
+		sfb->fb.var.red.offset = 5;
+		sfb->fb.var.green.offset = 2;
+		sfb->fb.var.blue.offset = 0;
+		break;
+	case 16:
+	default:
+		sfb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+		sfb->fb.fix.line_length = sfb->fb.var.xres * 2;
+		sfb->fb.var.red.length = 5;
+		sfb->fb.var.green.length = 6;
+		sfb->fb.var.blue.length = 5;
+		sfb->fb.var.red.offset = 11;
+		sfb->fb.var.green.offset = 5;
+		sfb->fb.var.blue.offset = 0;
+		break;
+	}
+
+	sfb->width = sfb->fb.var.xres;
+	sfb->height = sfb->fb.var.yres;
+	sfb->hz = 60;
+	sm712_set_timing(sfb);
+}
+
+static int sm712_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	/* sanity checks */
+	if (var->xres_virtual < var->xres)
+		var->xres_virtual = var->xres;
+
+	if (var->yres_virtual < var->yres)
+		var->yres_virtual = var->yres;
+
+	/* set valid default bpp */
+	if ((var->bits_per_pixel != 8) && (var->bits_per_pixel != 16) &&
+	    (var->bits_per_pixel != 24) && (var->bits_per_pixel != 32))
+		var->bits_per_pixel = 16;
+
+	return 0;
+}
+
+static int sm712_set_par(struct fb_info *info)
+{
+	sm712fb_setmode(info->par);
+
+	return 0;
+}
+
+static struct fb_ops sm712fb_ops = {
+	.owner = THIS_MODULE,
+	.fb_check_var = sm712_check_var,
+	.fb_set_par = sm712_set_par,
+	.fb_setcolreg = sm712_setcolreg,
+	.fb_blank = sm712_blank,
+	.fb_fillrect = cfb_fillrect,
+	.fb_imageblit = cfb_imageblit,
+	.fb_copyarea = cfb_copyarea,
+#ifdef __BIG_ENDIAN
+	.fb_read = sm712fb_read,
+	.fb_write = sm712fb_write,
+#endif
+};
+
+/*
+ * alloc struct sm712fb_info and assign default values
+ */
+static struct sm712fb_info *sm712_fb_info_new(struct pci_dev *pdev)
+{
+	struct sm712fb_info *sfb;
+
+	sfb = kzalloc(sizeof(*sfb), GFP_KERNEL);
+
+	if (!sfb)
+		return NULL;
+
+	sfb->pdev = pdev;
+
+	sfb->fb.flags = FBINFO_FLAG_DEFAULT;
+	sfb->fb.fbops = &sm712fb_ops;
+	sfb->fb.fix = sm712fb_fix;
+	sfb->fb.var = sm712fb_var;
+	sfb->fb.pseudo_palette = sfb->colreg;
+	sfb->fb.par = sfb;
+	sfb->accel = accel;
+
+	return sfb;
+}
+
+/*
+ * free struct sm712fb_info
+ */
+static void sm712_fb_info_free(struct sm712fb_info *sfb)
+{
+	kfree(sfb);
+}
+
+/*
+ * Map in the screen memory
+ */
+
+static int sm712_map_smem(struct sm712fb_info *sfb,
+			  struct pci_dev *pdev, u_long smem_len)
+{
+
+	sfb->fb.fix.smem_start = pci_resource_start(pdev, 0);
+
+#ifdef __BIG_ENDIAN
+	if (sfb->fb.var.bits_per_pixel == 32)
+		sfb->fb.fix.smem_start += 0x800000;
+#endif
+
+	sfb->fb.fix.smem_len = smem_len;
+
+	sfb->fb.screen_base = sfb->lfb;
+
+	if (!sfb->fb.screen_base) {
+		dev_err(&pdev->dev,
+			"%s: unable to map screen memory\n", sfb->fb.fix.id);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * Unmap in the screen memory
+ *
+ */
+static void sm712_unmap_smem(struct sm712fb_info *sfb)
+{
+	if (sfb && sfb->fb.screen_base) {
+		iounmap(sfb->fb.screen_base);
+		sfb->fb.screen_base = NULL;
+		sfb->lfb = NULL;
+	}
+}
+
+static inline void sm712_init_hw(struct sm712fb_info *sfb)
+{
+	/* enable linear memory mode and packed pixel format */
+	outb_p(0x18, 0x3c4);
+	outb_p(0x11, 0x3c5);
+
+	/* set MCLK = 14.31818 *  (0x16 / 0x2) */
+	sm712_write_seq(sfb, 0x6a, 0x16);
+	sm712_write_seq(sfb, 0x6b, 0x02);
+	sm712_write_seq(sfb, 0x62, 0x3e);
+
+	/* enable PCI burst */
+	sm712_write_seq(sfb, 0x17, 0x20);
+
+#ifdef __BIG_ENDIAN
+	/* enable word swap */
+	if (sfb->fb.var.bits_per_pixel == 32)
+		sm712_write_seq(sfb, 0x17, 0x30);
+#endif
+
+	if (!sfb->accel) {
+		dev_info(&sfb->pdev->dev, "2d acceleration was disabled by user.\n");
+		sfb->fb.flags = FBINFO_FLAG_DEFAULT | FBINFO_HWACCEL_NONE;
+		return;
+	}
+
+	if (sm712fb_init_accel(sfb) < 0) {
+		dev_info(&sfb->pdev->dev, "failed to enable 2d accleration.\n");
+		sfb->fb.flags = FBINFO_FLAG_DEFAULT | FBINFO_HWACCEL_NONE;
+		return;
+	} else {
+		sm712fb_ops.fb_fillrect = sm712fb_fillrect;
+		sm712fb_ops.fb_copyarea = sm712fb_copyarea;
+		sm712fb_ops.fb_imageblit = sm712fb_imageblit;
+		sfb->fb.flags |= FBINFO_HWACCEL_COPYAREA |
+				 FBINFO_HWACCEL_FILLRECT |
+				 FBINFO_HWACCEL_IMAGEBLIT |
+				 FBINFO_READS_FAST;
+		dev_info(&sfb->pdev->dev, "sm712fb: enable 2d acceleration.\n");
+	}
+}
+
+static int sm712fb_pci_probe(struct pci_dev *pdev,
+			     const struct pci_device_id *ent)
+{
+	struct sm712fb_info *sfb;
+	int err;
+	unsigned long mmio_base;
+
+#ifndef MODULE
+	char *option = NULL;
+
+	if (!fb_get_options("sm712fb", &option))
+		sm712fb_setup(option);
+#endif
+
+	dev_info(&pdev->dev, "Silicon Motion display driver.");
+
+	err = pci_enable_device(pdev);	/* enable SMTC chip */
+	if (err)
+		return err;
+
+	sprintf(sm712fb_fix.id, "sm712fb");
+
+	sfb = sm712_fb_info_new(pdev);
+
+	if (!sfb) {
+		err = -ENOMEM;
+		goto free_fail;
+	}
+
+	sfb->chip_id = ent->device;
+
+	pci_set_drvdata(pdev, sfb);
+
+	/* get mode parameter from sm712_scr_info */
+	if (sm712_scr_info.lfb_width != 0) {
+		sfb->fb.var.xres = sm712_scr_info.lfb_width;
+		sfb->fb.var.yres = sm712_scr_info.lfb_height;
+		sfb->fb.var.bits_per_pixel = sm712_scr_info.lfb_depth;
+	} else {
+		/* default resolution 1024x600 16bit mode */
+		sfb->fb.var.xres = SM712_DEFAULT_XRES;
+		sfb->fb.var.yres = SM712_DEFAULT_YRES;
+		sfb->fb.var.bits_per_pixel = SM712_DEFAULT_BPP;
+	}
+
+#ifdef __BIG_ENDIAN
+	if (sfb->fb.var.bits_per_pixel == 24)
+		sfb->fb.var.bits_per_pixel = (sm712_scr_info.lfb_depth = 32);
+#endif
+
+	/* Map address and memory detection */
+	mmio_base = pci_resource_start(pdev, 0);
+	pci_read_config_byte(pdev, PCI_REVISION_ID, &sfb->chip_rev_id);
+
+	if (sfb->chip_id != 0x712) {
+		dev_err(&pdev->dev,
+			"No valid Silicon Motion display chip was detected!");
+
+		goto fb_fail;
+	}
+
+	sfb->fb.fix.mmio_start = mmio_base + SM712_REG_BASE;
+	sfb->fb.fix.mmio_len = SM712_REG_SIZE;
+#ifdef __BIG_ENDIAN
+	sfb->lfb = ioremap(mmio_base, 0x00c00000);
+#else
+	sfb->lfb = ioremap(mmio_base, 0x00800000);
+#endif
+	sfb->mmio = sfb->lfb + SM712_MMIO_BASE;
+	sfb->dpr = sfb->lfb + SM712_DPR_BASE;
+	sfb->vpr = sfb->lfb + SM712_VPR_BASE;
+	sfb->dataport = sfb->lfb + SM712_DATAPORT_BASE;
+#ifdef __BIG_ENDIAN
+	if (sfb->fb.var.bits_per_pixel == 32) {
+		sfb->lfb += 0x800000;
+		dev_info(&pdev->dev, "sfb->lfb=%p", sfb->lfb);
+	}
+#endif
+	if (!sfb->mmio) {
+		dev_err(&pdev->dev,
+			"%s: unable to map memory mapped IO!", sfb->fb.fix.id);
+		err = -ENOMEM;
+		goto fb_fail;
+	}
+
+	sm712_init_hw(sfb);
+
+	/* can support 32 bpp */
+	if (15 == sfb->fb.var.bits_per_pixel)
+		sfb->fb.var.bits_per_pixel = 16;
+
+	sfb->fb.var.xres_virtual = sfb->fb.var.xres;
+	sfb->fb.var.yres_virtual = sfb->fb.var.yres;
+	err = sm712_map_smem(sfb, pdev, SM712_VRAM_SIZE);
+	if (err)
+		goto fail;
+
+	sm712fb_setmode(sfb);
+
+	err = register_framebuffer(&sfb->fb);
+	if (err < 0)
+		goto fail;
+
+	dev_info(&pdev->dev,
+		 "Silicon Motion SM%X Rev%X primary display mode %dx%d-%d Init Complete.",
+		 sfb->chip_id, sfb->chip_rev_id, sfb->fb.var.xres,
+		 sfb->fb.var.yres, sfb->fb.var.bits_per_pixel);
+
+	return 0;
+
+fail:
+	dev_err(&pdev->dev, "Silicon Motion, Inc. primary display init fail.");
+
+	sm712_unmap_smem(sfb);
+fb_fail:
+	sm712_fb_info_free(sfb);
+free_fail:
+	pci_disable_device(pdev);
+
+	return err;
+}
+
+/*
+ * 0x712 (LynxEM+)
+ */
+static const struct pci_device_id sm712fb_pci_table[] = {
+	{PCI_DEVICE(0x126f, 0x712),},
+	{0,}
+};
+
+static void sm712fb_pci_remove(struct pci_dev *pdev)
+{
+	struct sm712fb_info *sfb;
+
+	sfb = pci_get_drvdata(pdev);
+	sm712_unmap_smem(sfb);
+	unregister_framebuffer(&sfb->fb);
+	sm712_fb_info_free(sfb);
+}
+
+#ifdef CONFIG_PM
+static int sm712fb_pci_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct sm712fb_info *sfb;
+
+	sfb = pci_get_drvdata(pdev);
+
+	/* set the hw in sleep mode use external clock and self memory refresh
+	 * so that we can turn off internal PLLs later on
+	 */
+	sm712_write_seq(sfb, 0x20, (sm712_read_seq(sfb, 0x20) | 0xc0));
+	sm712_write_seq(sfb, 0x69, (sm712_read_seq(sfb, 0x69) & 0xf7));
+
+	console_lock();
+	fb_set_suspend(&sfb->fb, 1);
+	console_unlock();
+
+	/* additionally turn off all function blocks including internal PLLs */
+	sm712_write_seq(sfb, 0x21, 0xff);
+
+	return 0;
+}
+
+static int sm712fb_pci_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct sm712fb_info *sfb;
+
+	sfb = pci_get_drvdata(pdev);
+
+	/* reinit hardware */
+	sm712_init_hw(sfb);
+
+	sm712_write_seq(sfb, 0x34, (sm712_read_seq(sfb, 0x34) | 0xc0));
+	sm712_write_seq(sfb, 0x33, ((sm712_read_seq(sfb, 0x33) | 0x08) & 0xfb));
+
+	sm712fb_setmode(sfb);
+
+	console_lock();
+	fb_set_suspend(&sfb->fb, 0);
+	console_unlock();
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sm712_pm_ops, sm712fb_pci_suspend, sm712fb_pci_resume);
+#define SM712_PM_OPS (&sm712_pm_ops)
+
+#else /* !CONFIG_PM */
+
+#define SM712_PM_OPS NULL
+
+#endif /* !CONFIG_PM */
+
+static struct pci_driver sm712fb_driver = {
+	.name = "sm712fb",
+	.id_table = sm712fb_pci_table,
+	.probe = sm712fb_pci_probe,
+	.remove = sm712fb_pci_remove,
+	.driver.pm = SM712_PM_OPS,
+};
+
+module_pci_driver(sm712fb_driver);
+
+module_param(accel, bool, S_IRUGO);
+MODULE_PARM_DESC(accel, "Enable or disable 2D Acceleration");
+
+MODULE_AUTHOR("Siliconmotion ");
+MODULE_DESCRIPTION("Framebuffer driver for Silicon Motion SM712 Graphic Cards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/sm712fb/sm712fb_drv.h b/drivers/video/fbdev/sm712fb/sm712fb_drv.h
new file mode 100644
index 0000000..bf81bff
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/sm712fb_drv.h
@@ -0,0 +1,130 @@
+/*
+ * Silicon Motion SM712 frame buffer device
+ *
+ * Copyright (C) 2006 Silicon Motion Technology Corp.
+ * Authors:  Ge Wang, gewang@siliconmotion.com
+ *	     Boyod boyod.yang@siliconmotion.com.cn
+ *
+ * Copyright (C) 2009 Lemote, Inc.
+ * Author:   Wu Zhangjin, wuzhangjin@gmail.com
+ *
+ * Copyright (C) 2011 Igalia, S.L.
+ * Author:   Javier M. Mellid <jmunhoz@igalia.com>
+ *
+ * Copyright (C) 2014 Tom Li.
+ * Author:   Tom Li (Yifeng Li) <biergaizi@member.fsf.org>
+ *
+ * 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.
+ *
+ * Framebuffer driver for Silicon Motion SM712 chip
+ */
+
+#ifndef _SM712FB_DRV_H
+#define _SM712FB_DRV_H
+
+/*
+* Private structure
+*/
+struct sm712fb_info {
+	struct pci_dev *pdev;
+	struct fb_info fb;
+	u16 chip_id;
+	u8  chip_rev_id;
+
+	void __iomem *lfb;	/* linear frame buffer, the base address */
+
+	void __iomem *dpr;	/* drawing processor control regs */
+	void __iomem *vpr;	/* video processor control regs */
+	void __iomem *cpr;	/* capture processor control regs */
+	void __iomem *mmio;	/* memory map IO port */
+	void __iomem *dataport; /* 2d drawing engine data port */
+
+	u_int width;
+	u_int height;
+	u_int hz;
+
+	u32 colreg[17];
+
+	bool accel;
+};
+
+/* constants for registers operations */
+
+#include "sm712fb_io.h"
+
+#define FB_ACCEL_SMI_LYNX		88
+
+#define SM712_DEFAULT_XRES		1024
+#define SM712_DEFAULT_YRES		600
+#define SM712_DEFAULT_BPP		16
+
+#define SM712_VRAM_SIZE			0x00400000
+
+#define	SM712_REG_BASE			0x00400000
+#define SM712_REG_SIZE                  0x00400000
+
+#define	SM712_MMIO_BASE			0x00700000
+
+#define	SM712_DPR_BASE			0x00408000
+#define SM712_DPR_SIZE                  (0x6C + 1)
+
+#define	DPR_COORDS(x, y)		(((x) << 16) | (y))
+
+#define	DPR_SRC_COORDS			0x00
+#define	DPR_DST_COORDS			0x04
+#define	DPR_SPAN_COORDS			0x08
+#define	DPR_DE_CTRL			0x0c
+#define	DPR_PITCH			0x10
+#define	DPR_FG_COLOR			0x14
+#define	DPR_BG_COLOR			0x18
+#define	DPR_STRETCH			0x1c
+#define	DPR_COLOR_COMPARE		0x20
+#define	DPR_COLOR_COMPARE_MASK		0x24
+#define	DPR_BYTE_BIT_MASK		0x28
+#define	DPR_CROP_TOPLEFT_COORDS		0x2c
+#define	DPR_CROP_BOTRIGHT_COORDS	0x30
+#define	DPR_SRC_WINDOW			0x3c
+#define	DPR_SRC_BASE			0x40
+#define	DPR_DST_BASE			0x44
+
+#define	DE_CTRL_START			0x80000000
+#define	DE_CTRL_RTOL			0x08000000
+#define	DE_CTRL_COMMAND_MASK		0x001f0000
+#define	DE_CTRL_COMMAND_SHIFT			16
+#define	DE_CTRL_COMMAND_BITBLT			0x00
+#define	DE_CTRL_COMMAND_SOLIDFILL		0x01
+#define DE_CTRL_COMMAND_HOST_WRITE              0x08
+#define	DE_CTRL_ROP_ENABLE		0x00008000
+#define	DE_CTRL_ROP_MASK		0x000000ff
+#define	DE_CTRL_ROP_SHIFT			0
+#define	DE_CTRL_ROP_SRC				0x0c
+
+#define DE_CTRL_HOST_SHIFT              22
+#define DE_CTRL_HOST_MONO               1
+
+#define SCR_DE_STATUS			0x16
+#define SCR_DE_STATUS_MASK		0x18
+#define SCR_DE_ENGINE_IDLE		0x10
+
+#define	SM712_VPR_BASE			0x0040c000
+#define SM712_VPR_SIZE                  (0x44 + 1)
+
+#define SM712_DATAPORT_BASE             0x00400000
+
+#define SR00_SR04_SIZE      (0x04 - 0x00 + 1)
+#define SR10_SR24_SIZE      (0x24 - 0x10 + 1)
+#define SR30_SR75_SIZE      (0x75 - 0x30 + 1)
+#define SR80_SR93_SIZE      (0x93 - 0x80 + 1)
+#define SRA0_SRAF_SIZE      (0xAF - 0xA0 + 1)
+#define GR00_GR08_SIZE      (0x08 - 0x00 + 1)
+#define AR00_AR14_SIZE      (0x14 - 0x00 + 1)
+#define CR00_CR18_SIZE      (0x18 - 0x00 + 1)
+#define CR30_CR4D_SIZE      (0x4D - 0x30 + 1)
+#define CR90_CRA7_SIZE      (0xA7 - 0x90 + 1)
+
+#define DAC_REG	(0x3c8)
+#define DAC_VAL	(0x3c9)
+
+#endif
diff --git a/drivers/video/fbdev/sm712fb/sm712fb_io.h b/drivers/video/fbdev/sm712fb/sm712fb_io.h
new file mode 100644
index 0000000..93346a0
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/sm712fb_io.h
@@ -0,0 +1,90 @@
+/*
+ * Silicon Motion SM712 frame buffer device
+ *
+ * Copyright (C) 2006 Silicon Motion Technology Corp.
+ * Authors:  Ge Wang, gewang@siliconmotion.com
+ *	     Boyod boyod.yang@siliconmotion.com.cn
+ *
+ * Copyright (C) 2009 Lemote, Inc.
+ * Author:   Wu Zhangjin, wuzhangjin@gmail.com
+ *
+ * Copyright (C) 2011 Igalia, S.L.
+ * Author:   Javier M. Mellid <jmunhoz@igalia.com>
+ *
+ * Copyright (C) 2014 Tom Li.
+ * Author:   Tom Li (Yifeng Li) <biergaizi@member.fsf.org>
+ *
+ * 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.
+ *
+ */
+
+
+#define sm712_writeb(base, reg, dat)	writeb(dat, base + reg)
+#define sm712_writew(base, reg, dat)	writew(dat, base + reg)
+#define sm712_writel(base, reg, dat)	writel(dat, base + reg)
+
+#define sm712_readb(base, reg)	readb(base + reg)
+#define sm712_readw(base, reg)	readw(base + reg)
+#define sm712_readl(base, reg)	readl(base + reg)
+
+
+static inline void sm712_write_crtc(struct sm712fb_info *fb, u8 reg, u8 val)
+{
+	sm712_writeb(fb->mmio, 0x3d4, reg);
+	sm712_writeb(fb->mmio, 0x3d5, val);
+}
+
+static inline u8 sm712_read_crtc(struct sm712fb_info *fb, u8 reg)
+{
+	sm712_writeb(fb->mmio, 0x3d4, reg);
+	return sm712_readb(fb->mmio, 0x3d5);
+}
+
+static inline void sm712_write_grph(struct sm712fb_info *fb, u8 reg, u8 val)
+{
+	sm712_writeb(fb->mmio, 0x3ce, reg);
+	sm712_writeb(fb->mmio, 0x3cf, val);
+}
+
+static inline u8 sm712_read_grph(struct sm712fb_info *fb, u8 reg)
+{
+	sm712_writeb(fb->mmio, 0x3ce, reg);
+	return sm712_readb(fb->mmio, 0x3cf);
+}
+
+static inline void sm712_write_attr(struct sm712fb_info *fb, u8 reg, u8 val)
+{
+	sm712_readb(fb->mmio, 0x3da);
+	sm712_writeb(fb->mmio, 0x3c0, reg);
+	sm712_readb(fb->mmio, 0x3c1);
+	sm712_writeb(fb->mmio, 0x3c0, val);
+}
+
+static inline void sm712_write_seq(struct sm712fb_info *fb, u8 reg, u8 val)
+{
+	sm712_writeb(fb->mmio, 0x3c4, reg);
+	sm712_writeb(fb->mmio, 0x3c5, val);
+}
+
+static inline u8 sm712_read_seq(struct sm712fb_info *fb, u8 reg)
+{
+	sm712_writeb(fb->mmio, 0x3c4, reg);
+	return sm712_readb(fb->mmio, 0x3c5);
+}
+
+static inline u32 sm712_read_dpr(struct sm712fb_info *fb, u8 reg)
+{
+	return sm712_readl(fb->dpr, reg);
+}
+
+static inline void sm712_write_dpr(struct sm712fb_info *fb, u8 reg, u32 val)
+{
+	sm712_writel(fb->dpr, reg, val);
+}
+
+static inline void sm712_write_dataport(struct sm712fb_info *fb, u32 val)
+{
+	sm712_writel(fb->dataport, 0, val);
+}
diff --git a/drivers/video/fbdev/sm712fb/sm712fb_modedb.h b/drivers/video/fbdev/sm712fb/sm712fb_modedb.h
new file mode 100644
index 0000000..16ee7e3
--- /dev/null
+++ b/drivers/video/fbdev/sm712fb/sm712fb_modedb.h
@@ -0,0 +1,682 @@
+/* The next structure holds all information relevant for a specific video mode.
+ */
+
+struct ModeInit {
+	int mmSizeX;
+	int mmSizeY;
+	int bpp;
+	int hz;
+	unsigned char Init_MISC;
+	unsigned char Init_SR00_SR04[SR00_SR04_SIZE];
+	unsigned char Init_SR10_SR24[SR10_SR24_SIZE];
+	unsigned char Init_SR30_SR75[SR30_SR75_SIZE];
+	unsigned char Init_SR80_SR93[SR80_SR93_SIZE];
+	unsigned char Init_SRA0_SRAF[SRA0_SRAF_SIZE];
+	unsigned char Init_GR00_GR08[GR00_GR08_SIZE];
+	unsigned char Init_AR00_AR14[AR00_AR14_SIZE];
+	unsigned char Init_CR00_CR18[CR00_CR18_SIZE];
+	unsigned char Init_CR30_CR4D[CR30_CR4D_SIZE];
+	unsigned char Init_CR90_CRA7[CR90_CRA7_SIZE];
+};
+
+/**********************************************************************
+			 SM712 Mode table.
+ **********************************************************************/
+struct ModeInit VGAMode[] = {
+	{
+	 /*  mode#0: 640 x 480  16Bpp  60Hz */
+	 640, 480, 16, 60,
+	 /*  Init_MISC */
+	 0xE3,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x00, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xFF, 0xBE, 0xEF, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x32, 0x03, 0xA0, 0x09, 0xC0, 0x32, 0x32, 0x32,
+	  0x32, 0x32, 0x32, 0x32, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x32, 0x32, 0x32,
+	  0x04, 0x24, 0x63, 0x4F, 0x52, 0x0B, 0xDF, 0xEA,
+	  0x04, 0x50, 0x19, 0x32, 0x32, 0x00, 0x00, 0x32,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x07, 0x82, 0x07, 0x04,
+	  0x00, 0x45, 0x30, 0x30, 0x40, 0x30,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x32,
+	  0xF7, 0x00, 0x00, 0x00, 0xEF, 0xFF, 0x32, 0x32,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
+	  0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xFF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0x5F, 0x4F, 0x4F, 0x00, 0x53, 0x1F, 0x0B, 0x3E,
+	  0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xEA, 0x0C, 0xDF, 0x50, 0x40, 0xDF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xFF, 0xFD,
+	  0x5F, 0x4F, 0x00, 0x54, 0x00, 0x0B, 0xDF, 0x00,
+	  0xEA, 0x0C, 0x2E, 0x00, 0x4F, 0xDF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x56, 0xDD, 0x5E, 0xEA, 0x87, 0x44, 0x8F, 0x55,
+	  0x0A, 0x8F, 0x55, 0x0A, 0x00, 0x00, 0x18, 0x00,
+	  0x11, 0x10, 0x0B, 0x0A, 0x0A, 0x0A, 0x0A, 0x00,
+	  },
+	 },
+	{
+	 /*  mode#1: 640 x 480  24Bpp  60Hz */
+	 640, 480, 24, 60,
+	 /*  Init_MISC */
+	 0xE3,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x00, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xFF, 0xBE, 0xEF, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x32, 0x03, 0xA0, 0x09, 0xC0, 0x32, 0x32, 0x32,
+	  0x32, 0x32, 0x32, 0x32, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x32, 0x32, 0x32,
+	  0x04, 0x24, 0x63, 0x4F, 0x52, 0x0B, 0xDF, 0xEA,
+	  0x04, 0x50, 0x19, 0x32, 0x32, 0x00, 0x00, 0x32,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x07, 0x82, 0x07, 0x04,
+	  0x00, 0x45, 0x30, 0x30, 0x40, 0x30,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x32,
+	  0xF7, 0x00, 0x00, 0x00, 0xEF, 0xFF, 0x32, 0x32,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
+	  0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xFF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0x5F, 0x4F, 0x4F, 0x00, 0x53, 0x1F, 0x0B, 0x3E,
+	  0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xEA, 0x0C, 0xDF, 0x50, 0x40, 0xDF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xFF, 0xFD,
+	  0x5F, 0x4F, 0x00, 0x54, 0x00, 0x0B, 0xDF, 0x00,
+	  0xEA, 0x0C, 0x2E, 0x00, 0x4F, 0xDF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x56, 0xDD, 0x5E, 0xEA, 0x87, 0x44, 0x8F, 0x55,
+	  0x0A, 0x8F, 0x55, 0x0A, 0x00, 0x00, 0x18, 0x00,
+	  0x11, 0x10, 0x0B, 0x0A, 0x0A, 0x0A, 0x0A, 0x00,
+	  },
+	 },
+	{
+	 /*  mode#0: 640 x 480  32Bpp  60Hz */
+	 640, 480, 32, 60,
+	 /*  Init_MISC */
+	 0xE3,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x00, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xFF, 0xBE, 0xEF, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x32, 0x03, 0xA0, 0x09, 0xC0, 0x32, 0x32, 0x32,
+	  0x32, 0x32, 0x32, 0x32, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x32, 0x32, 0x32,
+	  0x04, 0x24, 0x63, 0x4F, 0x52, 0x0B, 0xDF, 0xEA,
+	  0x04, 0x50, 0x19, 0x32, 0x32, 0x00, 0x00, 0x32,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x07, 0x82, 0x07, 0x04,
+	  0x00, 0x45, 0x30, 0x30, 0x40, 0x30,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x32,
+	  0xF7, 0x00, 0x00, 0x00, 0xEF, 0xFF, 0x32, 0x32,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
+	  0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xFF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0x5F, 0x4F, 0x4F, 0x00, 0x53, 0x1F, 0x0B, 0x3E,
+	  0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xEA, 0x0C, 0xDF, 0x50, 0x40, 0xDF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xFF, 0xFD,
+	  0x5F, 0x4F, 0x00, 0x54, 0x00, 0x0B, 0xDF, 0x00,
+	  0xEA, 0x0C, 0x2E, 0x00, 0x4F, 0xDF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x56, 0xDD, 0x5E, 0xEA, 0x87, 0x44, 0x8F, 0x55,
+	  0x0A, 0x8F, 0x55, 0x0A, 0x00, 0x00, 0x18, 0x00,
+	  0x11, 0x10, 0x0B, 0x0A, 0x0A, 0x0A, 0x0A, 0x00,
+	  },
+	 },
+
+	{			/*  mode#2: 800 x 600  16Bpp  60Hz */
+	 800, 600, 16, 60,
+	 /*  Init_MISC */
+	 0x2B,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xFF, 0xBE, 0xEE, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x34, 0x03, 0x20, 0x09, 0xC0, 0x24, 0x24, 0x24,
+	  0x24, 0x24, 0x24, 0x24, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x38, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x24, 0x24, 0x24,
+	  0x04, 0x48, 0x83, 0x63, 0x68, 0x72, 0x57, 0x58,
+	  0x04, 0x55, 0x59, 0x24, 0x24, 0x00, 0x00, 0x24,
+	  0x01, 0x80, 0x7A, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x1C, 0x85, 0x35, 0x13,
+	  0x02, 0x45, 0x30, 0x35, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0x00, 0x00, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x24,
+	  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x24, 0x24,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
+	  0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0x7F, 0x63, 0x63, 0x00, 0x68, 0x18, 0x72, 0xF0,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x58, 0x0C, 0x57, 0x64, 0x40, 0x57, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x03, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xBF, 0xFD,
+	  0x7F, 0x63, 0x00, 0x69, 0x18, 0x72, 0x57, 0x00,
+	  0x58, 0x0C, 0xE0, 0x20, 0x63, 0x57,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x56, 0x4B, 0x5E, 0x55, 0x86, 0x9D, 0x8E, 0xAA,
+	  0xDB, 0x2A, 0xDF, 0x33, 0x00, 0x00, 0x18, 0x00,
+	  0x20, 0x1F, 0x1A, 0x19, 0x0F, 0x0F, 0x0F, 0x00,
+	  },
+	 },
+	{			/*  mode#3: 800 x 600  24Bpp  60Hz */
+	 800, 600, 24, 60,
+	 0x2B,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xFF, 0xBE, 0xEE, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x36, 0x03, 0x20, 0x09, 0xC0, 0x36, 0x36, 0x36,
+	  0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x36, 0x36, 0x36,
+	  0x04, 0x48, 0x83, 0x63, 0x68, 0x72, 0x57, 0x58,
+	  0x04, 0x55, 0x59, 0x36, 0x36, 0x00, 0x00, 0x36,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x1C, 0x85, 0x35, 0x13,
+	  0x02, 0x45, 0x30, 0x30, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x36,
+	  0xF7, 0x00, 0x00, 0x00, 0xEF, 0xFF, 0x36, 0x36,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
+	  0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0x7F, 0x63, 0x63, 0x00, 0x68, 0x18, 0x72, 0xF0,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x58, 0x0C, 0x57, 0x64, 0x40, 0x57, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x03, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xBF, 0xFD,
+	  0x7F, 0x63, 0x00, 0x69, 0x18, 0x72, 0x57, 0x00,
+	  0x58, 0x0C, 0xE0, 0x20, 0x63, 0x57,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x56, 0x4B, 0x5E, 0x55, 0x86, 0x9D, 0x8E, 0xAA,
+	  0xDB, 0x2A, 0xDF, 0x33, 0x00, 0x00, 0x18, 0x00,
+	  0x20, 0x1F, 0x1A, 0x19, 0x0F, 0x0F, 0x0F, 0x00,
+	  },
+	 },
+	{			/*  mode#7: 800 x 600  32Bpp  60Hz */
+	 800, 600, 32, 60,
+	 /*  Init_MISC */
+	 0x2B,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xFF, 0xBE, 0xEE, 0xFF, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x34, 0x03, 0x20, 0x09, 0xC0, 0x24, 0x24, 0x24,
+	  0x24, 0x24, 0x24, 0x24, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x38, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x24, 0x24, 0x24,
+	  0x04, 0x48, 0x83, 0x63, 0x68, 0x72, 0x57, 0x58,
+	  0x04, 0x55, 0x59, 0x24, 0x24, 0x00, 0x00, 0x24,
+	  0x01, 0x80, 0x7A, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x1C, 0x85, 0x35, 0x13,
+	  0x02, 0x45, 0x30, 0x35, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0x00, 0x00, 0x00, 0x6F, 0x7F, 0x7F, 0xFF, 0x24,
+	  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x24, 0x24,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFF, 0xBF, 0xFF, 0xFF, 0xED, 0xED, 0xED,
+	  0x7B, 0xFF, 0xFF, 0xFF, 0xBF, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0x7F, 0x63, 0x63, 0x00, 0x68, 0x18, 0x72, 0xF0,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x58, 0x0C, 0x57, 0x64, 0x40, 0x57, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x03, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xE7, 0xBF, 0xFD,
+	  0x7F, 0x63, 0x00, 0x69, 0x18, 0x72, 0x57, 0x00,
+	  0x58, 0x0C, 0xE0, 0x20, 0x63, 0x57,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x56, 0x4B, 0x5E, 0x55, 0x86, 0x9D, 0x8E, 0xAA,
+	  0xDB, 0x2A, 0xDF, 0x33, 0x00, 0x00, 0x18, 0x00,
+	  0x20, 0x1F, 0x1A, 0x19, 0x0F, 0x0F, 0x0F, 0x00,
+	  },
+	 },
+	/* We use 1024x768 table to light 1024x600 panel for lemote */
+	{			/*  mode#4: 1024 x 600  16Bpp  60Hz  */
+	 1024, 600, 16, 60,
+	 /*  Init_MISC */
+	 0xEB,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x00, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xC8, 0x40, 0x14, 0x60, 0x00, 0x0A, 0x17, 0x20,
+	  0x51, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x00, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x22, 0x03, 0x24, 0x09, 0xC0, 0x22, 0x22, 0x22,
+	  0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x22, 0x22, 0x22,
+	  0x06, 0x68, 0xA7, 0x7F, 0x83, 0x24, 0xFF, 0x03,
+	  0x00, 0x60, 0x59, 0x22, 0x22, 0x00, 0x00, 0x22,
+	  0x01, 0x80, 0x7A, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x16, 0x02, 0x0D, 0x82, 0x09, 0x02,
+	  0x04, 0x45, 0x3F, 0x30, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A,
+	  0xF7, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3A, 0x3A,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFB, 0x9F, 0x01, 0x00, 0xED, 0xED, 0xED,
+	  0x7B, 0xFB, 0xFF, 0xFF, 0x97, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0xA3, 0x7F, 0x7F, 0x00, 0x85, 0x16, 0x24, 0xF5,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x03, 0x09, 0xFF, 0x80, 0x40, 0xFF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xFF, 0xBF, 0xFF,
+	  0xA3, 0x7F, 0x00, 0x82, 0x0b, 0x6f, 0x57, 0x00,
+	  0x5c, 0x0f, 0xE0, 0xe0, 0x7F, 0x57,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x55, 0xD9, 0x5D, 0xE1, 0x86, 0x1B, 0x8E, 0x26,
+	  0xDA, 0x8D, 0xDE, 0x94, 0x00, 0x00, 0x18, 0x00,
+	  0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x15, 0x03,
+	  },
+	 },
+	{			/*  mode#5: 1024 x 768  24Bpp  60Hz */
+	 1024, 768, 24, 60,
+	 /*  Init_MISC */
+	 0xEB,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xF3, 0xB6, 0xC0, 0xDD, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x30, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x38, 0x03, 0x20, 0x09, 0xC0, 0x3A, 0x3A, 0x3A,
+	  0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x00, 0x00, 0x3A,
+	  0x06, 0x68, 0xA7, 0x7F, 0x83, 0x24, 0xFF, 0x03,
+	  0x00, 0x60, 0x59, 0x3A, 0x3A, 0x00, 0x00, 0x3A,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x3B, 0x0D, 0x09, 0x02,
+	  0x04, 0x45, 0x30, 0x30, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A,
+	  0xF7, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3A, 0x3A,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFB, 0x9F, 0x01, 0x00, 0xED, 0xED, 0xED,
+	  0x7B, 0xFB, 0xFF, 0xFF, 0x97, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0xA3, 0x7F, 0x7F, 0x00, 0x85, 0x16, 0x24, 0xF5,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x03, 0x09, 0xFF, 0x80, 0x40, 0xFF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xFF, 0xBF, 0xFF,
+	  0xA3, 0x7F, 0x00, 0x86, 0x15, 0x24, 0xFF, 0x00,
+	  0x01, 0x07, 0xE5, 0x20, 0x7F, 0xFF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x55, 0xD9, 0x5D, 0xE1, 0x86, 0x1B, 0x8E, 0x26,
+	  0xDA, 0x8D, 0xDE, 0x94, 0x00, 0x00, 0x18, 0x00,
+	  0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x15, 0x03,
+	  },
+	 },
+	{			/*  mode#4: 1024 x 768  32Bpp  60Hz */
+	 1024, 768, 32, 60,
+	 /*  Init_MISC */
+	 0xEB,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xF3, 0xB6, 0xC0, 0xDD, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x32, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x38, 0x03, 0x20, 0x09, 0xC0, 0x3A, 0x3A, 0x3A,
+	  0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x00, 0x00, 0x3A,
+	  0x06, 0x68, 0xA7, 0x7F, 0x83, 0x24, 0xFF, 0x03,
+	  0x00, 0x60, 0x59, 0x3A, 0x3A, 0x00, 0x00, 0x3A,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x3B, 0x0D, 0x09, 0x02,
+	  0x04, 0x45, 0x30, 0x30, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A,
+	  0xF7, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3A, 0x3A,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFB, 0x9F, 0x01, 0x00, 0xED, 0xED, 0xED,
+	  0x7B, 0xFB, 0xFF, 0xFF, 0x97, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0xA3, 0x7F, 0x7F, 0x00, 0x85, 0x16, 0x24, 0xF5,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x03, 0x09, 0xFF, 0x80, 0x40, 0xFF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x20,
+	  0x00, 0x00, 0x00, 0x40, 0x00, 0xFF, 0xBF, 0xFF,
+	  0xA3, 0x7F, 0x00, 0x86, 0x15, 0x24, 0xFF, 0x00,
+	  0x01, 0x07, 0xE5, 0x20, 0x7F, 0xFF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x55, 0xD9, 0x5D, 0xE1, 0x86, 0x1B, 0x8E, 0x26,
+	  0xDA, 0x8D, 0xDE, 0x94, 0x00, 0x00, 0x18, 0x00,
+	  0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x15, 0x03,
+	  },
+	 },
+	{			/*  mode#6: 320 x 240  16Bpp  60Hz */
+	 320, 240, 16, 60,
+	 /*  Init_MISC */
+	 0xEB,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xF3, 0xB6, 0xC0, 0xDD, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x32, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x38, 0x03, 0x20, 0x09, 0xC0, 0x3A, 0x3A, 0x3A,
+	  0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x00, 0x00, 0x3A,
+	  0x06, 0x68, 0xA7, 0x7F, 0x83, 0x24, 0xFF, 0x03,
+	  0x00, 0x60, 0x59, 0x3A, 0x3A, 0x00, 0x00, 0x3A,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x08, 0x43, 0x08, 0x43,
+	  0x04, 0x45, 0x30, 0x30, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A,
+	  0xF7, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3A, 0x3A,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFB, 0x9F, 0x01, 0x00, 0xED, 0xED, 0xED,
+	  0x7B, 0xFB, 0xFF, 0xFF, 0x97, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0xA3, 0x7F, 0x7F, 0x00, 0x85, 0x16, 0x24, 0xF5,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x03, 0x09, 0xFF, 0x80, 0x40, 0xFF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x20,
+	  0x00, 0x00, 0x30, 0x40, 0x00, 0xFF, 0xBF, 0xFF,
+	  0x2E, 0x27, 0x00, 0x2b, 0x0c, 0x0F, 0xEF, 0x00,
+	  0xFe, 0x0f, 0x01, 0xC0, 0x27, 0xEF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x55, 0xD9, 0x5D, 0xE1, 0x86, 0x1B, 0x8E, 0x26,
+	  0xDA, 0x8D, 0xDE, 0x94, 0x00, 0x00, 0x18, 0x00,
+	  0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x15, 0x03,
+	  },
+	 },
+
+	{			/*  mode#8: 320 x 240  32Bpp  60Hz */
+	 320, 240, 32, 60,
+	 /*  Init_MISC */
+	 0xEB,
+	 {			/*  Init_SR0_SR4 */
+	  0x03, 0x01, 0x0F, 0x03, 0x0E,
+	  },
+	 {			/*  Init_SR10_SR24 */
+	  0xF3, 0xB6, 0xC0, 0xDD, 0x00, 0x0E, 0x17, 0x2C,
+	  0x99, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0xC4, 0x32, 0x02, 0x01, 0x01,
+	  },
+	 {			/*  Init_SR30_SR75 */
+	  0x38, 0x03, 0x20, 0x09, 0xC0, 0x3A, 0x3A, 0x3A,
+	  0x3A, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x03, 0xFF,
+	  0x00, 0xFC, 0x00, 0x00, 0x20, 0x18, 0x00, 0xFC,
+	  0x20, 0x0C, 0x44, 0x20, 0x00, 0x00, 0x00, 0x3A,
+	  0x06, 0x68, 0xA7, 0x7F, 0x83, 0x24, 0xFF, 0x03,
+	  0x00, 0x60, 0x59, 0x3A, 0x3A, 0x00, 0x00, 0x3A,
+	  0x01, 0x80, 0x7E, 0x1A, 0x1A, 0x00, 0x00, 0x00,
+	  0x50, 0x03, 0x74, 0x14, 0x08, 0x43, 0x08, 0x43,
+	  0x04, 0x45, 0x30, 0x30, 0x40, 0x20,
+	  },
+	 {			/*  Init_SR80_SR93 */
+	  0xFF, 0x07, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A,
+	  0xF7, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3A, 0x3A,
+	  0x00, 0x00, 0x00, 0x00,
+	  },
+	 {			/*  Init_SRA0_SRAF */
+	  0x00, 0xFB, 0x9F, 0x01, 0x00, 0xED, 0xED, 0xED,
+	  0x7B, 0xFB, 0xFF, 0xFF, 0x97, 0xEF, 0xBF, 0xDF,
+	  },
+	 {			/*  Init_GR00_GR08 */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
+	  0xFF,
+	  },
+	 {			/*  Init_AR00_AR14 */
+	  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	  0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+	  0x41, 0x00, 0x0F, 0x00, 0x00,
+	  },
+	 {			/*  Init_CR00_CR18 */
+	  0xA3, 0x7F, 0x7F, 0x00, 0x85, 0x16, 0x24, 0xF5,
+	  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x03, 0x09, 0xFF, 0x80, 0x40, 0xFF, 0x00, 0xE3,
+	  0xFF,
+	  },
+	 {			/*  Init_CR30_CR4D */
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x20,
+	  0x00, 0x00, 0x30, 0x40, 0x00, 0xFF, 0xBF, 0xFF,
+	  0x2E, 0x27, 0x00, 0x2b, 0x0c, 0x0F, 0xEF, 0x00,
+	  0xFe, 0x0f, 0x01, 0xC0, 0x27, 0xEF,
+	  },
+	 {			/*  Init_CR90_CRA7 */
+	  0x55, 0xD9, 0x5D, 0xE1, 0x86, 0x1B, 0x8E, 0x26,
+	  0xDA, 0x8D, 0xDE, 0x94, 0x00, 0x00, 0x18, 0x00,
+	  0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x15, 0x03,
+	  },
+	 },
+};
+
+#define numVGAModes		ARRAY_SIZE(VGAMode)
diff --git a/drivers/video/output.c b/drivers/video/output.c
new file mode 100644
index 0000000..1446c49
--- /dev/null
+++ b/drivers/video/output.c
@@ -0,0 +1,133 @@
+/*
+ *  output.c - Display Output Switch driver
+ *
+ *  Copyright (C) 2006 Luming Yu <luming.yu@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at
+ *  your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/module.h>
+#include <linux/video_output.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/ctype.h>
+
+
+MODULE_DESCRIPTION("Display Output Switcher Lowlevel Control Abstraction");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Luming Yu <luming.yu@intel.com>");
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	ssize_t ret_size = 0;
+	struct output_device *od = to_output_device(dev);
+	if (od->props)
+		ret_size = sprintf(buf,"%.8x\n",od->props->get_status(od));
+	return ret_size;
+}
+
+static ssize_t state_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf,size_t count)
+{
+	char *endp;
+	struct output_device *od = to_output_device(dev);
+	int request_state = simple_strtoul(buf,&endp,0);
+	size_t size = endp - buf;
+
+	if (isspace(*endp))
+		size++;
+	if (size != count)
+		return -EINVAL;
+
+	if (od->props) {
+		od->request_state = request_state;
+		od->props->set_state(od);
+	}
+	return count;
+}
+static DEVICE_ATTR_RW(state);
+
+static void video_output_release(struct device *dev)
+{
+	struct output_device *od = to_output_device(dev);
+	kfree(od);
+}
+
+static struct attribute *video_output_attrs[] = {
+	&dev_attr_state.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(video_output);
+
+static struct class video_output_class = {
+	.name = "video_output",
+	.dev_release = video_output_release,
+	.dev_groups = video_output_groups,
+};
+
+struct output_device *video_output_register(const char *name,
+	struct device *dev,
+	void *devdata,
+	struct output_properties *op)
+{
+	struct output_device *new_dev;
+	int ret_code = 0;
+
+	new_dev = kzalloc(sizeof(struct output_device),GFP_KERNEL);
+	if (!new_dev) {
+		ret_code = -ENOMEM;
+		goto error_return;
+	}
+	new_dev->props = op;
+	new_dev->dev.class = &video_output_class;
+	new_dev->dev.parent = dev;
+	dev_set_name(&new_dev->dev, "%s", name);
+	dev_set_drvdata(&new_dev->dev, devdata);
+	ret_code = device_register(&new_dev->dev);
+	if (ret_code) {
+		kfree(new_dev);
+		goto error_return;
+	}
+	return new_dev;
+
+error_return:
+	return ERR_PTR(ret_code);
+}
+EXPORT_SYMBOL(video_output_register);
+
+void video_output_unregister(struct output_device *dev)
+{
+	if (!dev)
+		return;
+	device_unregister(&dev->dev);
+}
+EXPORT_SYMBOL(video_output_unregister);
+
+static void __exit video_output_class_exit(void)
+{
+	class_unregister(&video_output_class);
+}
+
+static int __init video_output_class_init(void)
+{
+	return class_register(&video_output_class);
+}
+
+postcore_initcall(video_output_class_init);
+module_exit(video_output_class_exit);
diff --git a/include/linux/sm501.h b/include/linux/sm501.h
index 02fde50..a8677f0 100644
--- a/include/linux/sm501.h
+++ b/include/linux/sm501.h
@@ -27,6 +27,9 @@
 extern unsigned long sm501_find_clock(struct device *dev,
 				      int clksrc, unsigned long req_freq);
 
+extern void sm501_configure_gpio(struct device *dev,
+				unsigned int gpio, unsigned char mode);
+
 /* sm501_misc_control
  *
  * Modify the SM501's MISC_CONTROL register
@@ -122,6 +125,7 @@
 #define SM501_USE_AC97		(1<<7)
 #define SM501_USE_I2S		(1<<8)
 #define SM501_USE_GPIO		(1<<9)
+#define SM501_USE_PWM		(1<<10)
 
 #define SM501_USE_ALL		(0xffffffff)
 
diff --git a/include/linux/video_output.h b/include/linux/video_output.h
new file mode 100644
index 0000000..ed5cdeb
--- /dev/null
+++ b/include/linux/video_output.h
@@ -0,0 +1,57 @@
+/*
+ *
+ *  Copyright (C) 2006 Luming Yu <luming.yu@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at
+ *  your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#ifndef _LINUX_VIDEO_OUTPUT_H
+#define _LINUX_VIDEO_OUTPUT_H
+#include <linux/device.h>
+#include <linux/err.h>
+struct output_device;
+struct output_properties {
+	int (*set_state)(struct output_device *);
+	int (*get_status)(struct output_device *);
+};
+struct output_device {
+	int request_state;
+	struct output_properties *props;
+	struct device dev;
+};
+#define to_output_device(obj) container_of(obj, struct output_device, dev)
+#if	defined(CONFIG_VIDEO_OUTPUT_CONTROL) || defined(CONFIG_VIDEO_OUTPUT_CONTROL_MODULE)
+struct output_device *video_output_register(const char *name,
+	struct device *dev,
+	void *devdata,
+	struct output_properties *op);
+void video_output_unregister(struct output_device *dev);
+#else
+static struct output_device *video_output_register(const char *name,
+        struct device *dev,
+        void *devdata,
+        struct output_properties *op)
+{
+	return ERR_PTR(-ENODEV);
+}
+static void video_output_unregister(struct output_device *dev)
+{
+	return;
+}
+#endif
+#endif
diff --git a/init/calibrate.c b/init/calibrate.c
index ce635dc..10e775d 100644
--- a/init/calibrate.c
+++ b/init/calibrate.c
@@ -21,6 +21,7 @@
 
 __setup("lpj=", lpj_setup);
 
+#ifndef ARCH_HAS_PREPARED_LPJ
 #ifdef ARCH_HAS_READ_CURRENT_TIMER
 
 /* This routine uses the read_current_timer() routine and gets the
@@ -171,6 +172,7 @@
 	return 0;
 }
 #endif
+#endif	/* ARCH_HAS_PREPARED_LPJ */
 
 /*
  * This is the number of bits of precision for the loops_per_jiffy.  Each
@@ -291,6 +293,7 @@
 		lpj = lpj_fine;
 		pr_info("Calibrating delay loop (skipped), "
 			"value calculated using timer frequency.. ");
+#ifndef ARCH_HAS_PREPARED_LPJ
 	} else if ((lpj = calibrate_delay_is_known())) {
 		;
 	} else if ((lpj = calibrate_delay_direct()) != 0) {
@@ -301,6 +304,7 @@
 		if (!printed)
 			pr_info("Calibrating delay loop... ");
 		lpj = calibrate_delay_converge();
+#endif	/* ARCH_HAS_PREPARED_LPJ */
 	}
 	per_cpu(cpu_loops_per_jiffy, this_cpu) = lpj;
 	if (!printed)
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index fa7cd79..616abb5 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -111,7 +111,7 @@
 static DEFINE_MUTEX(rfkill_global_mutex);
 static LIST_HEAD(rfkill_fds);	/* list of open fds of /dev/rfkill */
 
-static unsigned int rfkill_default_state = 1;
+static unsigned int rfkill_default_state;	/* default: 0 = radio off */
 module_param_named(default_state, rfkill_default_state, uint, 0444);
 MODULE_PARM_DESC(default_state,
 		 "Default initial state for all radio types, 0 = radio off");
diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl
index 537c38c..0af330e 100755
--- a/scripts/recordmcount.pl
+++ b/scripts/recordmcount.pl
@@ -304,14 +304,33 @@
     $cc .= " -m64";
     $objcopy .= " -O elf64-sparc";
 } elsif ($arch eq "mips") {
-    # To enable module support, we need to enable the -mlong-calls option
-    # of gcc for module, after using this option, we can not get the real
-    # offset of the calling to _mcount, but the offset of the lui
-    # instruction or the addiu one. herein, we record the address of the
-    # first one, and then we can replace this instruction by a branch
-    # instruction to jump over the profiling function to filter the
-    # indicated functions, or swith back to the lui instruction to trace
-    # them, which means dynamic tracing.
+    # <For kernel>
+    # To disable tracing, just replace "jal _mcount" with nop;
+    # to enable tracing, replace back. so, the offset 14 is
+    # needed to be recorded.
+    #
+    #     10:   03e0082d        move    at,ra
+    #	  14:   0c000000        jal     0
+    #                    14: R_MIPS_26   _mcount
+    #                    14: R_MIPS_NONE *ABS*
+    #                    14: R_MIPS_NONE *ABS*
+    #	 18:   00020021        nop
+    #
+    # <For module>
+    #
+    # If no long call(-mlong-calls), the same to kernel.
+    #
+    # If the module space differs from the kernel space, long
+    # call is needed, as a result, the address of _mcount is
+    # needed to be recorded in a register and then jump from
+    # module space to kernel space via "jalr <register>". To
+    # disable tracing, "jalr <register>" can be replaced by
+    # nop; to enable tracing, replace it back. Since the
+    # offset of "jalr <register>" is not easy to be matched,
+    # the offset of the 1st _mcount below is recorded and to
+    # disable tracing, "lui v1, 0x0" is substituted with "b
+    # label", which jumps over "jalr <register>"; to enable
+    # tracing, replace it back.
     #
     #       c:	3c030000 	lui	v1,0x0
     #			c: R_MIPS_HI16	_mcount
@@ -323,19 +342,12 @@
     #			10: R_MIPS_NONE	*ABS*
     #      14:	03e0082d 	move	at,ra
     #      18:	0060f809 	jalr	v1
+    #                     label:
     #
-    # for the kernel:
-    #
-    #     10:   03e0082d        move    at,ra
-    #	  14:   0c000000        jal     0 <loongson_halt>
-    #                    14: R_MIPS_26   _mcount
-    #                    14: R_MIPS_NONE *ABS*
-    #                    14: R_MIPS_NONE *ABS*
-    #	 18:   00020021        nop
     if ($is_module eq "0") {
 	    $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_26\\s+_mcount\$";
     } else {
-	    $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_HI16\\s+_mcount\$";
+	    $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_(HI16|26)\\s+_mcount\$";
     }
     $objdump .= " -Melf-trad".$endian."mips ";
 
diff --git a/scripts/sstrip.sh b/scripts/sstrip.sh
new file mode 100755
index 0000000..49b973a
--- /dev/null
+++ b/scripts/sstrip.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+# sstrip.sh -- strip the section table of an elf file
+#
+# Copyright (C) 2010 Wu Zhangjin, wuzhangjin@gmail.com
+# Licensed under the GPLv2
+#
+# Since the section table is useless for the embedded device, it can be
+# stripped out.
+#
+# Note: Some bootloader may check the section table but most of the time, it
+# may be not really used, If it really need the section table, it may need the
+# decompressed kernel image.
+
+# Usage
+
+function usage
+{
+cat <<EOF
+
+	# sstrip.sh -- strip the section table of an elf file
+
+	# Input: elf file
+	# Output: truncated elf file without the section table
+	# Usage: sstrip.sh /path/to/image
+
+EOF
+}
+
+# Do some necessary check
+IMAGE=$1
+
+[ -z "${IMAGE}" ] && echo "$0 : No indicated file to be stripped" && usage && exit -1
+[ ! -f "${IMAGE}" ] && echo "$0 : ${IMAGE} : No such file" && exit -1
+FILE_TYPE=`dd if=${IMAGE} bs=1 skip=1 count=3 2>/dev/null`
+[ "xELF" != "x${FILE_TYPE}" ] && echo "$0: ${IMAGE} is not an ELF file" && exit -1
+
+[ "x${V}" == "x1" ] && orig_filesz=`wc -c ${IMAGE} | cut -d' ' -f1`
+
+# Get the offset of the section table, here get the end of the program section
+filesz=$((`${OBJDUMP} -p ${IMAGE} | grep -m1 filesz | tr -s ' ' | cut -d' ' -f3`))
+
+# Truncate it via the dd tool
+dd if=/dev/null bs=1 of=${IMAGE} seek=${filesz} 2>/dev/null
+
+# Clear the section table information in the ELF header
+# The last 6 bytes of the ELF header are the section table information
+echo -ne "\x00\x00\x00\x00\x00\x00" | dd of=${IMAGE} bs=1 seek=46 count=6 conv=notrunc 2>/dev/null
+
+# Debug
+if [ "x${V}" == "x1" ]; then
+	echo "----------------------------------------------------------------"
+	echo "Strip the section table at ${filesz} of ${IMAGE}"
+	echo "----------------------------------------------------------------"
+	echo "       sstrip: $0"
+	echo "      objdump: ${OBJDUMP}"
+	echo "original size: ${orig_filesz}"
+	echo "current  size: ${filesz}"
+	echo "reduced  size: $((${orig_filesz} - ${filesz}))"
+fi