diff -uprN linux-3.16.orig/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h linux-3.16/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
--- linux-3.16.orig/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h	2014-09-21 00:59:27.986267984 +0800
@@ -301,5 +301,40 @@ extern void _wrmsr(u32 msr, u32 hi, u32
 /* 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 -uprN linux-3.16.orig/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h linux-3.16/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
--- linux-3.16.orig/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h	2014-09-21 00:59:27.986267984 +0800
@@ -28,8 +28,19 @@ static inline void __maybe_unused enable
 #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 -uprN linux-3.16.orig/arch/mips/include/asm/mach-loongson/loongson.h linux-3.16/arch/mips/include/asm/mach-loongson/loongson.h
--- linux-3.16.orig/arch/mips/include/asm/mach-loongson/loongson.h	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/include/asm/mach-loongson/loongson.h	2014-09-21 00:59:27.986267984 +0800
@@ -46,6 +46,12 @@ static inline void prom_init_uart_base(v
 #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 -uprN linux-3.16.orig/arch/mips/include/asm/mach-loongson/machine.h linux-3.16/arch/mips/include/asm/mach-loongson/machine.h
--- linux-3.16.orig/arch/mips/include/asm/mach-loongson/machine.h	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/include/asm/mach-loongson/machine.h	2014-09-21 00:59:27.986267984 +0800
@@ -24,6 +24,12 @@
 
 #endif
 
+#ifdef CONFIG_DEXXON_GDIUM
+
+#define LOONGSON_MACHTYPE MACH_DEXXON_GDIUM2F10
+
+#endif
+
 #ifdef CONFIG_LEMOTE_MACH3A
 
 #define LOONGSON_MACHTYPE MACH_LEMOTE_A1101
diff -uprN linux-3.16.orig/arch/mips/include/asm/mach-loongson1/clock.h linux-3.16/arch/mips/include/asm/mach-loongson1/clock.h
--- linux-3.16.orig/arch/mips/include/asm/mach-loongson1/clock.h	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/include/asm/mach-loongson1/clock.h	2014-09-21 00:59:27.986267984 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/include/asm/mach-loongson1/regs-intc.h linux-3.16/arch/mips/include/asm/mach-loongson1/regs-intc.h
--- linux-3.16.orig/arch/mips/include/asm/mach-loongson1/regs-intc.h	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/include/asm/mach-loongson1/regs-intc.h	2014-09-21 00:59:27.986267984 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/common/cmdline.c linux-3.16/arch/mips/loongson/common/cmdline.c
--- linux-3.16.orig/arch/mips/loongson/common/cmdline.c	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/loongson/common/cmdline.c	2014-09-21 00:59:27.987268017 +0800
@@ -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 @@ void __init prom_init_cmdline(void)
 	}
 
 	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 -uprN linux-3.16.orig/arch/mips/loongson/gdium/gdium-clock.c linux-3.16/arch/mips/loongson/gdium/gdium-clock.c
--- linux-3.16.orig/arch/mips/loongson/gdium/gdium-clock.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/gdium/gdium-clock.c	2014-09-21 00:59:27.987268017 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/gdium/irq.c linux-3.16/arch/mips/loongson/gdium/irq.c
--- linux-3.16.orig/arch/mips/loongson/gdium/irq.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/gdium/irq.c	2014-09-21 00:59:27.987268017 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/gdium/Makefile linux-3.16/arch/mips/loongson/gdium/Makefile
--- linux-3.16.orig/arch/mips/loongson/gdium/Makefile	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/gdium/Makefile	2014-09-21 00:59:27.987268017 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/gdium/platform.c linux-3.16/arch/mips/loongson/gdium/platform.c
--- linux-3.16.orig/arch/mips/loongson/gdium/platform.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/gdium/platform.c	2014-09-21 00:59:27.987268017 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/gdium/reset.c linux-3.16/arch/mips/loongson/gdium/reset.c
--- linux-3.16.orig/arch/mips/loongson/gdium/reset.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/gdium/reset.c	2014-09-21 00:59:27.987268017 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/gdium/sm501-pwm.c linux-3.16/arch/mips/loongson/gdium/sm501-pwm.c
--- linux-3.16.orig/arch/mips/loongson/gdium/sm501-pwm.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/gdium/sm501-pwm.c	2014-09-21 00:59:27.987268017 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/Kconfig linux-3.16/arch/mips/loongson/Kconfig
--- linux-3.16.orig/arch/mips/loongson/Kconfig	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/loongson/Kconfig	2014-09-21 00:59:27.988268050 +0800
@@ -32,12 +32,12 @@ config LEMOTE_FULOONG2E
 
 config LEMOTE_MACH2F
 	bool "Lemote Loongson 2F family machines"
-	select ARCH_SPARSEMEM_ENABLE
+	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
+	select CS5536 if PCI
 	select CSRC_R4K if ! MIPS_EXTERNAL_TIMER
 	select DMA_NONCOHERENT
 	select GENERIC_ISA_DMA_SUPPORT_BROKEN
@@ -45,14 +45,13 @@ config LEMOTE_MACH2F
 	select HW_HAS_PCI
 	select I8259
 	select IRQ_CPU
-	select ISA
 	select SYS_HAS_CPU_LOONGSON2F
 	select SYS_HAS_EARLY_PRINTK
 	select SYS_SUPPORTS_32BIT_KERNEL
 	select SYS_SUPPORTS_64BIT_KERNEL
-	select SYS_SUPPORTS_HIGHMEM
+	select SYS_SUPPORTS_HIGHMEM if ! EMBEDDED
 	select SYS_SUPPORTS_LITTLE_ENDIAN
-	select LOONGSON_MC146818
+	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.
@@ -60,6 +59,31 @@ config LEMOTE_MACH2F
 	  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 CSRC_R4K if ! MIPS_EXTERNAL_TIMER
+	select DMA_NONCOHERENT
+	select GENERIC_ISA_DMA_SUPPORT_BROKEN
+	select HW_HAS_PCI
+	select I8259
+	select IRQ_CPU
+	select ISA
+	select SYS_HAS_CPU_LOONGSON2F
+	select SYS_HAS_EARLY_PRINTK
+	select SYS_SUPPORTS_32BIT_KERNEL
+	select SYS_SUPPORTS_64BIT_KERNEL
+	select SYS_SUPPORTS_HIGHMEM
+	select SYS_SUPPORTS_LITTLE_ENDIAN
+	select ARCH_REQUIRE_GPIOLIB
+	select HAVE_PWM if MFD_SM501
+	help
+	  Dexxon gdium netbook based on Loongson 2F and SM502.
+
 config LEMOTE_MACH3A
 	bool "Lemote Loongson 3A family machines"
 	select ARCH_SPARSEMEM_ENABLE
@@ -134,6 +158,24 @@ config LOONGSON_MC146818
 	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 -uprN linux-3.16.orig/arch/mips/loongson/lemote-2f/clock.c linux-3.16/arch/mips/loongson/lemote-2f/clock.c
--- linux-3.16.orig/arch/mips/loongson/lemote-2f/clock.c	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/loongson/lemote-2f/clock.c	2014-09-21 00:59:59.195298850 +0800
@@ -91,6 +91,7 @@ EXPORT_SYMBOL(clk_put);
 
 int clk_set_rate(struct clk *clk, unsigned long rate)
 {
+	unsigned int rate_khz = rate / 1000;
 	struct cpufreq_frequency_table *pos;
 	int ret = 0;
 	int regval;
@@ -107,9 +108,9 @@ int clk_set_rate(struct clk *clk, unsign
 		propagate_rate(clk);
 
 	cpufreq_for_each_valid_entry(pos, loongson2_clockmod_table)
-		if (rate == pos->frequency)
+		if (rate_khz == pos->frequency)
 			break;
-	if (rate != pos->frequency)
+	if (rate_khz != pos->frequency)
 		return -ENOTSUPP;
 
 	clk->rate = rate;
diff -uprN linux-3.16.orig/arch/mips/loongson/lemote-2f/Makefile linux-3.16/arch/mips/loongson/lemote-2f/Makefile
--- linux-3.16.orig/arch/mips/loongson/lemote-2f/Makefile	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/loongson/lemote-2f/Makefile	2014-09-21 00:59:27.988268050 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/lemote-2f/platform.c linux-3.16/arch/mips/loongson/lemote-2f/platform.c
--- linux-3.16.orig/arch/mips/loongson/lemote-2f/platform.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/arch/mips/loongson/lemote-2f/platform.c	2014-09-21 00:59:27.988268050 +0800
@@ -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 -uprN linux-3.16.orig/arch/mips/loongson/Makefile linux-3.16/arch/mips/loongson/Makefile
--- linux-3.16.orig/arch/mips/loongson/Makefile	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/loongson/Makefile	2014-09-21 00:59:27.988268050 +0800
@@ -17,6 +17,12 @@ obj-$(CONFIG_LEMOTE_FULOONG2E)	+= fuloon
 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 -uprN linux-3.16.orig/arch/mips/loongson/Platform linux-3.16/arch/mips/loongson/Platform
--- linux-3.16.orig/arch/mips/loongson/Platform	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/arch/mips/loongson/Platform	2014-09-21 00:59:27.988268050 +0800
@@ -30,4 +30,5 @@ platform-$(CONFIG_MACH_LOONGSON) += loon
 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_CPU_LOONGSON3) += 0xffffffff80200000
diff -uprN linux-3.16.orig/drivers/platform/Kconfig linux-3.16/drivers/platform/Kconfig
--- linux-3.16.orig/drivers/platform/Kconfig	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/drivers/platform/Kconfig	2014-09-21 00:59:27.988268050 +0800
@@ -4,5 +4,8 @@ endif
 if GOLDFISH
 source "drivers/platform/goldfish/Kconfig"
 endif
+if MIPS
+source "drivers/platform/mips/Kconfig"
+endif
 
 source "drivers/platform/chrome/Kconfig"
diff -uprN linux-3.16.orig/drivers/platform/Makefile linux-3.16/drivers/platform/Makefile
--- linux-3.16.orig/drivers/platform/Makefile	2014-08-04 06:25:02.000000000 +0800
+++ linux-3.16/drivers/platform/Makefile	2014-09-21 00:59:27.988268050 +0800
@@ -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 -uprN linux-3.16.orig/drivers/platform/mips/gdium_laptop.c linux-3.16/drivers/platform/mips/gdium_laptop.c
--- linux-3.16.orig/drivers/platform/mips/gdium_laptop.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/drivers/platform/mips/gdium_laptop.c	2014-09-21 00:59:27.989268083 +0800
@@ -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 -uprN linux-3.16.orig/drivers/platform/mips/Kconfig linux-3.16/drivers/platform/mips/Kconfig
--- linux-3.16.orig/drivers/platform/mips/Kconfig	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/drivers/platform/mips/Kconfig	2014-09-21 00:59:27.989268083 +0800
@@ -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 -uprN linux-3.16.orig/drivers/platform/mips/lynloong_pc.c linux-3.16/drivers/platform/mips/lynloong_pc.c
--- linux-3.16.orig/drivers/platform/mips/lynloong_pc.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/drivers/platform/mips/lynloong_pc.c	2014-09-21 00:59:27.989268083 +0800
@@ -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 -uprN linux-3.16.orig/drivers/platform/mips/Makefile linux-3.16/drivers/platform/mips/Makefile
--- linux-3.16.orig/drivers/platform/mips/Makefile	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/drivers/platform/mips/Makefile	2014-09-21 00:59:27.989268083 +0800
@@ -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 -uprN linux-3.16.orig/drivers/platform/mips/yeeloong_ecrom.c linux-3.16/drivers/platform/mips/yeeloong_ecrom.c
--- linux-3.16.orig/drivers/platform/mips/yeeloong_ecrom.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/drivers/platform/mips/yeeloong_ecrom.c	2014-09-21 00:59:27.990268116 +0800
@@ -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 -uprN linux-3.16.orig/drivers/platform/mips/yeeloong_laptop.c linux-3.16/drivers/platform/mips/yeeloong_laptop.c
--- linux-3.16.orig/drivers/platform/mips/yeeloong_laptop.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-3.16/drivers/platform/mips/yeeloong_laptop.c	2014-09-21 00:59:27.991268149 +0800
@@ -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 = strict_strtoul(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");
