summary refs log tree commit diff
path: root/gnu/packages/patches/linux-libre-yeeloong.patch
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/packages/patches/linux-libre-yeeloong.patch')
-rw-r--r--gnu/packages/patches/linux-libre-yeeloong.patch3451
1 files changed, 3451 insertions, 0 deletions
diff --git a/gnu/packages/patches/linux-libre-yeeloong.patch b/gnu/packages/patches/linux-libre-yeeloong.patch
new file mode 100644
index 0000000000..3b8503eb57
--- /dev/null
+++ b/gnu/packages/patches/linux-libre-yeeloong.patch
@@ -0,0 +1,3451 @@
+Add support for Lemote machines based on the Loongson 2F.  This selection of
+patches was derived from looking at the differences between the linux-stable
+and loongson-community git repositories.
+
+diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
+index f501665..a672872 100644
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -344,7 +344,7 @@ config LASAT
+ 
+ config MACH_LOONGSON
+ 	bool "Loongson family of machines"
+-	select SYS_SUPPORTS_ZBOOT
++	select SYS_SUPPORTS_ZBOOT_UART16550
+ 	help
+ 	  This enables the support of Loongson family of machines.
+ 
+@@ -1715,6 +1715,7 @@ config CPU_LOONGSON2
+ 	select CPU_SUPPORTS_64BIT_KERNEL
+ 	select CPU_SUPPORTS_HIGHMEM
+ 	select CPU_SUPPORTS_HUGEPAGES
++	select ARCH_WANT_OPTIONAL_GPIOLIB
+ 
+ config CPU_LOONGSON1
+ 	bool
+@@ -2392,7 +2393,7 @@ config CPU_SUPPORTS_MSA
+ 
+ config ARCH_FLATMEM_ENABLE
+ 	def_bool y
+-	depends on !NUMA && !CPU_LOONGSON2
++	depends on !NUMA && !(CPU_LOONGSON2 && HIBERNATION)
+ 
+ config ARCH_DISCONTIGMEM_ENABLE
+ 	bool
+diff --git a/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h b/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
+index a0ee0cb..4e18add 100644
+--- a/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
++++ b/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
+@@ -301,5 +301,40 @@ extern void _wrmsr(u32 msr, u32 hi, u32 lo);
+ /* GPIO : I/O SPACE; REG : 32BITS */
+ #define GPIOL_OUT_VAL		0x00
+ #define GPIOL_OUT_EN		0x04
++#define GPIOL_OUT_AUX1_SEL	0x10
++/* SMB : I/O SPACE, REG : 8BITS WIDTH */
++#define	SMB_SDA			0x00
++#define	SMB_STS			0x01
++#define	SMB_STS_SLVSTP		(1 << 7)
++#define	SMB_STS_SDAST		(1 << 6)
++#define	SMB_STS_BER		(1 << 5)
++#define	SMB_STS_NEGACK		(1 << 4)
++#define	SMB_STS_STASTR		(1 << 3)
++#define	SMB_STS_NMATCH		(1 << 2)
++#define	SMB_STS_MASTER		(1 << 1)
++#define	SMB_STS_XMIT		(1 << 0)
++#define	SMB_CTRL_STS		0x02
++#define	SMB_CSTS_TGSTL		(1 << 5)
++#define	SMB_CSTS_TSDA		(1 << 4)
++#define	SMB_CSTS_GCMTCH		(1 << 3)
++#define	SMB_CSTS_MATCH		(1 << 2)
++#define	SMB_CSTS_BB		(1 << 1)
++#define	SMB_CSTS_BUSY		(1 << 0)
++#define	SMB_CTRL1		0x03
++#define	SMB_CTRL1_STASTRE	(1 << 7)
++#define	SMB_CTRL1_NMINTE	(1 << 6)
++#define	SMB_CTRL1_GCMEN		(1 << 5)
++#define	SMB_CTRL1_ACK		(1 << 4)
++#define	SMB_CTRL1_RSVD		(1 << 3)
++#define	SMB_CTRL1_INTEN		(1 << 2)
++#define	SMB_CTRL1_STOP		(1 << 1)
++#define	SMB_CTRL1_START		(1 << 0)
++#define	SMB_ADDR		0x04
++#define	SMB_ADDR_SAEN		(1 << 7)
++#define	SMB_CONTROLLER_ADDR	(0xef << 0)
++#define	SMB_CTRL2		0x05
++#define	SMB_FREQ		(0x20 << 1)
++#define	SMB_ENABLE		(0x01 << 0)
++#define	SMB_CTRL3		0x06
+ 
+ #endif				/* _CS5536_H */
+diff --git a/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h b/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
+index 021d017..50aafca 100644
+--- a/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
++++ b/arch/mips/include/asm/mach-loongson/cs5536/cs5536_mfgpt.h
+@@ -28,8 +28,19 @@ static inline void __maybe_unused enable_mfgpt0_counter(void)
+ #define COMPARE	 ((MFGPT_TICK_RATE + HZ/2) / HZ)
+ 
+ #define MFGPT_BASE	mfgpt_base
++#define MFGPT0_CMP1	(MFGPT_BASE + 0)
+ #define MFGPT0_CMP2	(MFGPT_BASE + 2)
+ #define MFGPT0_CNT	(MFGPT_BASE + 4)
+ #define MFGPT0_SETUP	(MFGPT_BASE + 6)
+ 
++#define MFGPT1_CMP1	(MFGPT_BASE + 0x08)
++#define MFGPT1_CMP2	(MFGPT_BASE + 0x0A)
++#define MFGPT1_CNT	(MFGPT_BASE + 0x0C)
++#define MFGPT1_SETUP	(MFGPT_BASE + 0x0E)
++
++#define MFGPT2_CMP1	(MFGPT_BASE + 0x10)
++#define MFGPT2_CMP2	(MFGPT_BASE + 0x12)
++#define MFGPT2_CNT	(MFGPT_BASE + 0x14)
++#define MFGPT2_SETUP	(MFGPT_BASE + 0x16)
++
+ #endif /*!_CS5536_MFGPT_H */
+diff --git a/arch/mips/include/asm/mach-loongson/loongson.h b/arch/mips/include/asm/mach-loongson/loongson.h
+index 9783103..e6febaf 100644
+--- a/arch/mips/include/asm/mach-loongson/loongson.h
++++ b/arch/mips/include/asm/mach-loongson/loongson.h
+@@ -46,6 +46,12 @@ static inline void prom_init_uart_base(void)
+ #endif
+ }
+ 
++/*
++ * Copy kernel command line from arcs_cmdline
++ */
++#include <asm/setup.h>
++extern char loongson_cmdline[COMMAND_LINE_SIZE];
++
+ /* irq operation functions */
+ extern void bonito_irqdispatch(void);
+ extern void __init bonito_irq_init(void);
+diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c
+index 8d01709..9cd25da 100644
+--- a/arch/mips/kernel/time.c
++++ b/arch/mips/kernel/time.c
+@@ -119,6 +119,11 @@ static __init int cpu_has_mfc0_count_bug(void)
+ 
+ void __init time_init(void)
+ {
++#ifdef CONFIG_HR_SCHED_CLOCK
++	if (!mips_clockevent_init() || !cpu_has_mfc0_count_bug())
++		write_c0_count(0);
++#endif
++
+ 	plat_time_init();
+ 
+ 	/*
+diff --git a/arch/mips/loongson/Kconfig b/arch/mips/loongson/Kconfig
+index 156de85..659ca91 100644
+--- a/arch/mips/loongson/Kconfig
++++ b/arch/mips/loongson/Kconfig
+@@ -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_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.
+diff --git a/arch/mips/loongson/common/cmdline.c b/arch/mips/loongson/common/cmdline.c
+index 72fed00..679a18a 100644
+--- a/arch/mips/loongson/common/cmdline.c
++++ b/arch/mips/loongson/common/cmdline.c
+@@ -17,10 +17,15 @@
+  * Free Software Foundation;  either version 2 of the  License, or (at your
+  * option) any later version.
+  */
++#include <linux/module.h>
+ #include <asm/bootinfo.h>
+ 
+ #include <loongson.h>
+ 
++/* the kernel command line copied from arcs_cmdline */
++char loongson_cmdline[COMMAND_LINE_SIZE];
++EXPORT_SYMBOL(loongson_cmdline);
++
+ void __init prom_init_cmdline(void)
+ {
+ 	int prom_argc;
+@@ -45,4 +50,26 @@ 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;
++	default:
++		break;
++	}
++
++	/* copy arcs_cmdline into loongson_cmdline */
++	strncpy(loongson_cmdline, arcs_cmdline, COMMAND_LINE_SIZE);
+ }
+diff --git a/arch/mips/loongson/lemote-2f/Makefile b/arch/mips/loongson/lemote-2f/Makefile
+index 4f9eaa3..f945bd7 100644
+--- a/arch/mips/loongson/lemote-2f/Makefile
++++ b/arch/mips/loongson/lemote-2f/Makefile
+@@ -2,7 +2,7 @@
+ # Makefile for lemote loongson2f family machines
+ #
+ 
+-obj-y += clock.o machtype.o irq.o reset.o ec_kb3310b.o
++obj-y += clock.o machtype.o irq.o reset.o ec_kb3310b.o platform.o
+ 
+ #
+ # Suspend Support
+diff --git a/arch/mips/loongson/lemote-2f/platform.c b/arch/mips/loongson/lemote-2f/platform.c
+new file mode 100644
+index 0000000..5316360
+--- /dev/null
++++ b/arch/mips/loongson/lemote-2f/platform.c
+@@ -0,0 +1,48 @@
++/*
++ * Copyright (C) 2009 Lemote Inc.
++ * Author: Wu Zhangjin, wuzhangjin@gmail.com
++ *
++ * This program is free software; you can redistribute  it and/or modify it
++ * under  the terms of  the GNU General  Public License as published by the
++ * Free Software Foundation;  either version 2 of the  License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/err.h>
++#include <linux/platform_device.h>
++
++#include <asm/bootinfo.h>
++
++static struct platform_device yeeloong_pdev = {
++	.name = "yeeloong_laptop",
++	.id = -1,
++};
++
++static struct platform_device lynloong_pdev = {
++	.name = "lynloong_pc",
++	.id = -1,
++};
++
++static int __init lemote2f_platform_init(void)
++{
++	struct platform_device *pdev = NULL;
++
++	switch (mips_machtype) {
++	case MACH_LEMOTE_YL2F89:
++		pdev = &yeeloong_pdev;
++		break;
++	case MACH_LEMOTE_LL2F:
++		pdev = &lynloong_pdev;
++		break;
++	default:
++		break;
++
++	}
++
++	if (pdev != NULL)
++		return platform_device_register(pdev);
++
++	return -ENODEV;
++}
++
++arch_initcall(lemote2f_platform_init);
+diff --git a/drivers/ide/ide-iops.c b/drivers/ide/ide-iops.c
+index 376f2dc..b576801 100644
+--- a/drivers/ide/ide-iops.c
++++ b/drivers/ide/ide-iops.c
+@@ -27,6 +27,10 @@
+ #include <asm/uaccess.h>
+ #include <asm/io.h>
+ 
++#ifdef CONFIG_LEMOTE_MACH2F
++#include <asm/bootinfo.h>
++#endif
++
+ void SELECT_MASK(ide_drive_t *drive, int mask)
+ {
+ 	const struct ide_port_ops *port_ops = drive->hwif->port_ops;
+@@ -300,6 +304,11 @@ void ide_check_nien_quirk_list(ide_drive_t *drive)
+ {
+ 	const char **list, *m = (char *)&drive->id[ATA_ID_PROD];
+ 
++#ifdef CONFIG_LEMOTE_MACH2F
++	if (mips_machtype != MACH_LEMOTE_YL2F89)
++		return;
++#endif
++
+ 	for (list = nien_quirk_list; *list != NULL; list++)
+ 		if (strstr(m, *list) != NULL) {
+ 			drive->dev_flags |= IDE_DFLAG_NIEN_QUIRK;
+diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
+index 125e569..c80714b 100644
+--- a/drivers/platform/mips/Kconfig
++++ b/drivers/platform/mips/Kconfig
+@@ -15,6 +15,38 @@ menuconfig MIPS_PLATFORM_DEVICES
+ 
+ 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 MIPS_ACPI
+ 	bool
+ 	default y if LOONGSON_MACH3X
+diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile
+index 4341284..0e21bfb 100644
+--- a/drivers/platform/mips/Makefile
++++ b/drivers/platform/mips/Makefile
+@@ -1,2 +1,11 @@
++#
++# 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_MIPS_ACPI) += acpi_init.o
+ obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o
+diff --git a/drivers/platform/mips/lynloong_pc.c b/drivers/platform/mips/lynloong_pc.c
+new file mode 100644
+index 0000000..68f29e4
+--- /dev/null
++++ b/drivers/platform/mips/lynloong_pc.c
+@@ -0,0 +1,515 @@
++/*
++ * Driver for LynLoong PC extras
++ *
++ *  Copyright (C) 2009 Lemote Inc.
++ *  Author: Wu Zhangjin <wuzhangjin@gmail.com>, Xiang Yu <xiangy@lemote.com>
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License version 2 as
++ *  published by the Free Software Foundation.
++ */
++
++#include <linux/err.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/backlight.h>	/* for backlight subdriver */
++#include <linux/fb.h>
++#include <linux/video_output.h>	/* for video output subdriver */
++#include <linux/delay.h>	/* for suspend support */
++
++#include <cs5536/cs5536.h>
++#include <cs5536/cs5536_mfgpt.h>
++
++#include <loongson.h>
++
++static u32 gpio_base, mfgpt_base;
++
++static void set_gpio_reg_high(int gpio, int reg)
++{
++	u32 val;
++
++	val = inl(gpio_base + reg);
++	val |= (1 << gpio);
++	val &= ~(1 << (16 + gpio));
++	outl(val, gpio_base + reg);
++	mmiowb();
++}
++
++static void set_gpio_reg_low(int gpio, int reg)
++{
++	u32 val;
++
++	val = inl(gpio_base + reg);
++	val |= (1 << (16 + gpio));
++	val &= ~(1 << gpio);
++	outl(val, gpio_base + reg);
++	mmiowb();
++}
++
++static void set_gpio_output_low(int gpio)
++{
++	set_gpio_reg_high(gpio, GPIOL_OUT_EN);
++	set_gpio_reg_low(gpio, GPIOL_OUT_VAL);
++}
++
++static void set_gpio_output_high(int gpio)
++{
++	set_gpio_reg_high(gpio, GPIOL_OUT_EN);
++	set_gpio_reg_high(gpio, GPIOL_OUT_VAL);
++}
++
++/* backlight subdriver */
++
++#define MAX_BRIGHTNESS 100
++#define DEFAULT_BRIGHTNESS 50
++#define MIN_BRIGHTNESS 0
++static unsigned int level;
++
++DEFINE_SPINLOCK(backlight_lock);
++/* Tune the brightness */
++static void setup_mfgpt2(void)
++{
++	unsigned long flags;
++
++	spin_lock_irqsave(&backlight_lock, flags);
++
++	/* Set MFGPT2 comparator 1,2 */
++	outw(MAX_BRIGHTNESS-level, MFGPT2_CMP1);
++	outw(MAX_BRIGHTNESS, MFGPT2_CMP2);
++	/* Clear MFGPT2 UP COUNTER */
++	outw(0, MFGPT2_CNT);
++	/* Enable counter, compare mode, 32k */
++	outw(0x8280, MFGPT2_SETUP);
++
++	spin_unlock_irqrestore(&backlight_lock, flags);
++}
++
++static int lynloong_set_brightness(struct backlight_device *bd)
++{
++	level = (bd->props.fb_blank == FB_BLANK_UNBLANK &&
++		 bd->props.power == FB_BLANK_UNBLANK) ?
++	    bd->props.brightness : 0;
++
++	if (level > MAX_BRIGHTNESS)
++		level = MAX_BRIGHTNESS;
++	else if (level < MIN_BRIGHTNESS)
++		level = MIN_BRIGHTNESS;
++
++	setup_mfgpt2();
++
++	return 0;
++}
++
++static int lynloong_get_brightness(struct backlight_device *bd)
++{
++	return level;
++}
++
++static struct backlight_ops backlight_ops = {
++	.get_brightness = lynloong_get_brightness,
++	.update_status = lynloong_set_brightness,
++};
++
++static struct backlight_device *lynloong_backlight_dev;
++
++static int lynloong_backlight_init(void)
++{
++	int ret;
++	u32 hi;
++	struct backlight_properties props;
++
++	/* Get gpio_base */
++	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &gpio_base);
++	/* Get mfgpt_base */
++	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_MFGPT), &hi, &mfgpt_base);
++	/* Get gpio_base */
++	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &gpio_base);
++
++	/* Select for mfgpt */
++	set_gpio_reg_high(7, GPIOL_OUT_AUX1_SEL);
++	/* Enable brightness controlling */
++	set_gpio_output_high(7);
++
++	memset(&props, 0, sizeof(struct backlight_properties));
++	props.max_brightness = MAX_BRIGHTNESS;
++	props.type = BACKLIGHT_PLATFORM;
++	lynloong_backlight_dev = backlight_device_register("backlight0", NULL,
++			NULL, &backlight_ops, &props);
++
++	if (IS_ERR(lynloong_backlight_dev)) {
++		ret = PTR_ERR(lynloong_backlight_dev);
++		return ret;
++	}
++
++	lynloong_backlight_dev->props.brightness = DEFAULT_BRIGHTNESS;
++	backlight_update_status(lynloong_backlight_dev);
++
++	return 0;
++}
++
++static void lynloong_backlight_exit(void)
++{
++	if (lynloong_backlight_dev) {
++		backlight_device_unregister(lynloong_backlight_dev);
++		lynloong_backlight_dev = NULL;
++	}
++	/* Disable brightness controlling */
++	set_gpio_output_low(7);
++}
++
++/* video output driver */
++static int vo_status = 1;
++
++static int lcd_video_output_get(struct output_device *od)
++{
++	return vo_status;
++}
++
++static int lcd_video_output_set(struct output_device *od)
++{
++	int i;
++	unsigned long status;
++
++	status = !!od->request_state;
++
++	if (status == 0) {
++		/* Set the current status as off */
++		vo_status = 0;
++		/* Turn off the backlight */
++		set_gpio_output_low(11);
++		for (i = 0; i < 0x500; i++)
++			delay();
++		/* Turn off the LCD */
++		set_gpio_output_high(8);
++	} else {
++		/* Turn on the LCD */
++		set_gpio_output_low(8);
++		for (i = 0; i < 0x500; i++)
++			delay();
++		/* Turn on the backlight */
++		set_gpio_output_high(11);
++		/* Set the current status as on */
++		vo_status = 1;
++	}
++
++	return 0;
++}
++
++static struct output_properties lcd_output_properties = {
++	.set_state = lcd_video_output_set,
++	.get_status = lcd_video_output_get,
++};
++
++static struct output_device *lcd_output_dev;
++
++static void lynloong_lcd_vo_set(int status)
++{
++	lcd_output_dev->request_state = status;
++	lcd_video_output_set(lcd_output_dev);
++}
++
++static int lynloong_vo_init(void)
++{
++	int ret;
++
++	/* Register video output device: lcd */
++	lcd_output_dev = video_output_register("LCD", NULL, NULL,
++			&lcd_output_properties);
++
++	if (IS_ERR(lcd_output_dev)) {
++		ret = PTR_ERR(lcd_output_dev);
++		lcd_output_dev = NULL;
++		return ret;
++	}
++	/* Ensure LCD is on by default */
++	lynloong_lcd_vo_set(1);
++
++	return 0;
++}
++
++static void lynloong_vo_exit(void)
++{
++	if (lcd_output_dev) {
++		video_output_unregister(lcd_output_dev);
++		lcd_output_dev = NULL;
++	}
++}
++
++/* suspend support */
++
++#ifdef CONFIG_PM
++
++static u32 smb_base;
++
++/* I2C operations */
++
++static int i2c_wait(void)
++{
++	char c;
++	int i;
++
++	udelay(1000);
++	for (i = 0; i < 20; i++) {
++		c = inb(smb_base | SMB_STS);
++		if (c & (SMB_STS_BER | SMB_STS_NEGACK))
++			return -1;
++		if (c & SMB_STS_SDAST)
++			return 0;
++		udelay(100);
++	}
++	return -2;
++}
++
++static void i2c_read_single(int addr, int regNo, char *value)
++{
++	unsigned char c;
++
++	/* Start condition */
++	c = inb(smb_base | SMB_CTRL1);
++	outb(c | SMB_CTRL1_START, smb_base | SMB_CTRL1);
++	i2c_wait();
++
++	/* Send slave address */
++	outb(addr & 0xfe, smb_base | SMB_SDA);
++	i2c_wait();
++
++	/* Acknowledge smbus */
++	c = inb(smb_base | SMB_CTRL1);
++	outb(c | SMB_CTRL1_ACK, smb_base | SMB_CTRL1);
++
++	/* Send register index */
++	outb(regNo, smb_base | SMB_SDA);
++	i2c_wait();
++
++	/* Acknowledge smbus */
++	c = inb(smb_base | SMB_CTRL1);
++	outb(c | SMB_CTRL1_ACK, smb_base | SMB_CTRL1);
++
++	/* Start condition again */
++	c = inb(smb_base | SMB_CTRL1);
++	outb(c | SMB_CTRL1_START, smb_base | SMB_CTRL1);
++	i2c_wait();
++
++	/* Send salve address again */
++	outb(1 | addr, smb_base | SMB_SDA);
++	i2c_wait();
++
++	/* Acknowledge smbus */
++	c = inb(smb_base | SMB_CTRL1);
++	outb(c | SMB_CTRL1_ACK, smb_base | SMB_CTRL1);
++
++	/* Read data */
++	*value = inb(smb_base | SMB_SDA);
++
++	/* Stop condition */
++	outb(SMB_CTRL1_STOP, smb_base | SMB_CTRL1);
++	i2c_wait();
++}
++
++static void i2c_write_single(int addr, int regNo, char value)
++{
++	unsigned char c;
++
++	/* Start condition */
++	c = inb(smb_base | SMB_CTRL1);
++	outb(c | SMB_CTRL1_START, smb_base | SMB_CTRL1);
++	i2c_wait();
++	/* Send slave address */
++	outb(addr & 0xfe, smb_base | SMB_SDA);
++	i2c_wait();;
++
++	/* Send register index */
++	outb(regNo, smb_base | SMB_SDA);
++	i2c_wait();
++
++	/* Write data */
++	outb(value, smb_base | SMB_SDA);
++	i2c_wait();
++	/* Stop condition */
++	outb(SMB_CTRL1_STOP, smb_base | SMB_CTRL1);
++	i2c_wait();
++}
++
++static void stop_clock(int clk_reg, int clk_sel)
++{
++	u8 value;
++
++	i2c_read_single(0xd3, clk_reg, &value);
++	value &= ~(1 << clk_sel);
++	i2c_write_single(0xd2, clk_reg, value);
++}
++
++static void enable_clock(int clk_reg, int clk_sel)
++{
++	u8 value;
++
++	i2c_read_single(0xd3, clk_reg, &value);
++	value |= (1 << clk_sel);
++	i2c_write_single(0xd2, clk_reg, value);
++}
++
++static char cached_clk_freq;
++static char cached_pci_fixed_freq;
++
++static void decrease_clk_freq(void)
++{
++	char value;
++
++	i2c_read_single(0xd3, 1, &value);
++	cached_clk_freq = value;
++
++	/* Select frequency by software */
++	value |= (1 << 1);
++	/* CPU, 3V66, PCI : 100, 66, 33(1) */
++	value |= (1 << 2);
++	i2c_write_single(0xd2, 1, value);
++
++	/* Cache the pci frequency */
++	i2c_read_single(0xd3, 14, &value);
++	cached_pci_fixed_freq = value;
++
++	/* Enable PCI fix mode */
++	value |= (1 << 5);
++	/* 3V66, PCI : 64MHz, 32MHz */
++	value |= (1 << 3);
++	i2c_write_single(0xd2, 14, value);
++
++}
++
++static void resume_clk_freq(void)
++{
++	i2c_write_single(0xd2, 1, cached_clk_freq);
++	i2c_write_single(0xd2, 14, cached_pci_fixed_freq);
++}
++
++static void stop_clocks(void)
++{
++	/* CPU Clock Register */
++	stop_clock(2, 5);	/* not used */
++	stop_clock(2, 6);	/* not used */
++	stop_clock(2, 7);	/* not used */
++
++	/* PCI Clock Register */
++	stop_clock(3, 1);	/* 8100 */
++	stop_clock(3, 5);	/* SIS */
++	stop_clock(3, 0);	/* not used */
++	stop_clock(3, 6);	/* not used */
++
++	/* PCI 48M Clock Register */
++	stop_clock(4, 6);	/* USB grounding */
++	stop_clock(4, 5);	/* REF(5536_14M) */
++
++	/* 3V66 Control Register */
++	stop_clock(5, 0);	/* VCH_CLK..., grounding */
++}
++
++static void enable_clocks(void)
++{
++	enable_clock(3, 1);	/* 8100 */
++	enable_clock(3, 5);	/* SIS */
++
++	enable_clock(4, 6);
++	enable_clock(4, 5);	/* REF(5536_14M) */
++
++	enable_clock(5, 0);	/* VCH_CLOCK, grounding */
++}
++
++static int lynloong_suspend(struct device *dev)
++{
++	/* Disable AMP */
++	set_gpio_output_high(6);
++	/* Turn off LCD */
++	lynloong_lcd_vo_set(0);
++
++	/* Stop the clocks of some devices */
++	stop_clocks();
++
++	/* Decrease the external clock frequency */
++	decrease_clk_freq();
++
++	return 0;
++}
++
++static int lynloong_resume(struct device *dev)
++{
++	/* Turn on the LCD */
++	lynloong_lcd_vo_set(1);
++
++	/* Resume clock frequency, enable the relative clocks */
++	resume_clk_freq();
++	enable_clocks();
++
++	/* Enable AMP */
++	set_gpio_output_low(6);
++
++	return 0;
++}
++
++static const SIMPLE_DEV_PM_OPS(lynloong_pm_ops, lynloong_suspend,
++	lynloong_resume);
++#endif	/* !CONFIG_PM */
++
++static struct platform_device_id platform_device_ids[] = {
++	{
++		.name = "lynloong_pc",
++	},
++	{}
++};
++
++MODULE_DEVICE_TABLE(platform, platform_device_ids);
++
++static struct platform_driver platform_driver = {
++	.driver = {
++		.name = "lynloong_pc",
++		.owner = THIS_MODULE,
++#ifdef CONFIG_PM
++		.pm = &lynloong_pm_ops,
++#endif
++	},
++	.id_table = platform_device_ids,
++};
++
++static int __init lynloong_init(void)
++{
++	int ret;
++
++	pr_info("LynLoong platform specific driver loaded.\n");
++
++	/* Register platform stuff */
++	ret = platform_driver_register(&platform_driver);
++	if (ret) {
++		pr_err("Failed to register LynLoong platform driver.\n");
++		return ret;
++	}
++
++	ret = lynloong_backlight_init();
++	if (ret) {
++		pr_err("Failed to register LynLoong backlight driver.\n");
++		return ret;
++	}
++
++	ret = lynloong_vo_init();
++	if (ret) {
++		pr_err("Failed to register LynLoong backlight driver.\n");
++		lynloong_vo_exit();
++		return ret;
++	}
++
++	return 0;
++}
++
++static void __exit lynloong_exit(void)
++{
++	lynloong_vo_exit();
++	lynloong_backlight_exit();
++	platform_driver_unregister(&platform_driver);
++
++	pr_info("LynLoong platform specific driver unloaded.\n");
++}
++
++module_init(lynloong_init);
++module_exit(lynloong_exit);
++
++MODULE_AUTHOR("Wu Zhangjin <wuzhangjin@gmail.com>; Xiang Yu <xiangy@lemote.com>");
++MODULE_DESCRIPTION("LynLoong PC driver");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/platform/mips/yeeloong_ecrom.c b/drivers/platform/mips/yeeloong_ecrom.c
+new file mode 100644
+index 0000000..1bfe4cf
+--- /dev/null
++++ b/drivers/platform/mips/yeeloong_ecrom.c
+@@ -0,0 +1,944 @@
++/*
++ * Driver for flushing/dumping ROM of EC on YeeLoong laptop
++ *
++ * Copyright (C) 2009 Lemote Inc.
++ * Author: liujl <liujl@lemote.com>
++ *
++ * NOTE :
++ * 	The EC resources accessing and programming are supported.
++ */
++
++#include <linux/module.h>
++#include <linux/proc_fs.h>
++#include <linux/miscdevice.h>
++#include <linux/init.h>
++#include <linux/delay.h>
++
++#include <ec_kb3310b.h>
++
++#define	EC_MISC_DEV		"ec_misc"
++#define EC_IOC_MAGIC		'E'
++
++/* ec registers range */
++#define	EC_MAX_REGADDR	0xFFFF
++#define	EC_MIN_REGADDR	0xF000
++#define	EC_RAM_ADDR	0xF800
++
++/* version burned address */
++#define	VER_ADDR	0xf7a1
++#define	VER_MAX_SIZE	7
++#define	EC_ROM_MAX_SIZE	0x10000
++
++/* ec internal register */
++#define	REG_POWER_MODE		0xF710
++#define	FLAG_NORMAL_MODE	0x00
++#define	FLAG_IDLE_MODE		0x01
++#define	FLAG_RESET_MODE		0x02
++
++/* ec update program flag */
++#define	PROGRAM_FLAG_NONE	0x00
++#define	PROGRAM_FLAG_IE		0x01
++#define	PROGRAM_FLAG_ROM	0x02
++
++/* XBI relative registers */
++#define REG_XBISEG0     0xFEA0
++#define REG_XBISEG1     0xFEA1
++#define REG_XBIRSV2     0xFEA2
++#define REG_XBIRSV3     0xFEA3
++#define REG_XBIRSV4     0xFEA4
++#define REG_XBICFG      0xFEA5
++#define REG_XBICS       0xFEA6
++#define REG_XBIWE       0xFEA7
++#define REG_XBISPIA0    0xFEA8
++#define REG_XBISPIA1    0xFEA9
++#define REG_XBISPIA2    0xFEAA
++#define REG_XBISPIDAT   0xFEAB
++#define REG_XBISPICMD   0xFEAC
++#define REG_XBISPICFG   0xFEAD
++#define REG_XBISPIDATR  0xFEAE
++#define REG_XBISPICFG2  0xFEAF
++
++/* commands definition for REG_XBISPICMD */
++#define	SPICMD_WRITE_STATUS		0x01
++#define	SPICMD_BYTE_PROGRAM		0x02
++#define	SPICMD_READ_BYTE		0x03
++#define	SPICMD_WRITE_DISABLE	0x04
++#define	SPICMD_READ_STATUS		0x05
++#define	SPICMD_WRITE_ENABLE		0x06
++#define	SPICMD_HIGH_SPEED_READ	0x0B
++#define	SPICMD_POWER_DOWN		0xB9
++#define	SPICMD_SST_EWSR			0x50
++#define	SPICMD_SST_SEC_ERASE	0x20
++#define	SPICMD_SST_BLK_ERASE	0x52
++#define	SPICMD_SST_CHIP_ERASE	0x60
++#define	SPICMD_FRDO				0x3B
++#define	SPICMD_SEC_ERASE		0xD7
++#define	SPICMD_BLK_ERASE		0xD8
++#define SPICMD_CHIP_ERASE		0xC7
++
++/* bits definition for REG_XBISPICFG */
++#define	SPICFG_AUTO_CHECK		0x01
++#define	SPICFG_SPI_BUSY			0x02
++#define	SPICFG_DUMMY_READ		0x04
++#define	SPICFG_EN_SPICMD		0x08
++#define	SPICFG_LOW_SPICS		0x10
++#define	SPICFG_EN_SHORT_READ	0x20
++#define	SPICFG_EN_OFFSET_READ	0x40
++#define	SPICFG_EN_FAST_READ		0x80
++
++/* watchdog timer registers */
++#define	REG_WDTCFG				0xfe80
++#define	REG_WDTPF				0xfe81
++#define REG_WDT					0xfe82
++
++/* lpc configure register */
++#define	REG_LPCCFG				0xfe95
++
++/* 8051 reg */
++#define	REG_PXCFG				0xff14
++
++/* Fan register in KB3310 */
++#define	REG_ECFAN_SPEED_LEVEL	0xf4e4
++#define	REG_ECFAN_SWITCH		0xf4d2
++
++/* the ec flash rom id number */
++#define	EC_ROM_PRODUCT_ID_SPANSION	0x01
++#define	EC_ROM_PRODUCT_ID_MXIC		0xC2
++#define	EC_ROM_PRODUCT_ID_AMIC		0x37
++#define	EC_ROM_PRODUCT_ID_EONIC		0x1C
++
++/* misc ioctl operations */
++#define	IOCTL_RDREG		_IOR(EC_IOC_MAGIC, 1, int)
++#define	IOCTL_WRREG		_IOW(EC_IOC_MAGIC, 2, int)
++#define	IOCTL_READ_EC		_IOR(EC_IOC_MAGIC, 3, int)
++#define	IOCTL_PROGRAM_IE	_IOW(EC_IOC_MAGIC, 4, int)
++#define	IOCTL_PROGRAM_EC	_IOW(EC_IOC_MAGIC, 5, int)
++
++/* start address for programming of EC content or IE */
++/*  ec running code start address */
++#define	EC_START_ADDR	0x00000000
++/*  ec information element storing address */
++#define	IE_START_ADDR	0x00020000
++
++/* EC state */
++#define	EC_STATE_IDLE	0x00	/*  ec in idle state */
++#define	EC_STATE_BUSY	0x01	/*  ec in busy state */
++
++/* timeout value for programming */
++#define	EC_FLASH_TIMEOUT	0x1000	/*  ec program timeout */
++/* command checkout timeout including cmd to port or state flag check */
++#define	EC_CMD_TIMEOUT		0x1000
++#define	EC_SPICMD_STANDARD_TIMEOUT	(4 * 1000)	/*  unit : us */
++#define	EC_MAX_DELAY_UNIT	(10)	/*  every time for polling */
++#define	SPI_FINISH_WAIT_TIME	10
++/* EC content max size */
++#define	EC_CONTENT_MAX_SIZE	(64 * 1024)
++#define	IE_CONTENT_MAX_SIZE	(0x100000 - IE_START_ADDR)
++
++/* the register operation access struct */
++struct ec_reg {
++	u32 addr;		/* the address of kb3310 registers */
++	u8 val;			/* the register value */
++};
++
++struct ec_info {
++	u32 start_addr;
++	u32 size;
++	u8 *buf;
++};
++
++/* open for using rom protection action */
++#define	EC_ROM_PROTECTION
++
++/* enable the chip reset mode */
++static int ec_init_reset_mode(void)
++{
++	int timeout;
++	unsigned char status = 0;
++	int ret = 0;
++
++	/* make chip goto reset mode */
++	ret = ec_query_seq(CMD_INIT_RESET_MODE);
++	if (ret < 0) {
++		printk(KERN_ERR "ec init reset mode failed.\n");
++		goto out;
++	}
++
++	/* make the action take active */
++	timeout = EC_CMD_TIMEOUT;
++	status = ec_read(REG_POWER_MODE) & FLAG_RESET_MODE;
++	while (timeout--) {
++		if (status) {
++			udelay(EC_REG_DELAY);
++			break;
++		}
++		status = ec_read(REG_POWER_MODE) & FLAG_RESET_MODE;
++		udelay(EC_REG_DELAY);
++	}
++	if (timeout <= 0) {
++		printk(KERN_ERR "ec rom fixup : can't check reset status.\n");
++		ret = -EINVAL;
++	} else
++		printk(KERN_INFO "(%d/%d)reset 0xf710 :  0x%x\n", timeout,
++			   EC_CMD_TIMEOUT - timeout, status);
++
++	/* set MCU to reset mode */
++	udelay(EC_REG_DELAY);
++	status = ec_read(REG_PXCFG);
++	status |= (1 << 0);
++	ec_write(REG_PXCFG, status);
++	udelay(EC_REG_DELAY);
++
++	/* disable FWH/LPC */
++	udelay(EC_REG_DELAY);
++	status = ec_read(REG_LPCCFG);
++	status &= ~(1 << 7);
++	ec_write(REG_LPCCFG, status);
++	udelay(EC_REG_DELAY);
++
++	printk(KERN_INFO "entering reset mode ok..............\n");
++
++ out:
++	return ret;
++}
++
++/* make ec exit from reset mode */
++static void ec_exit_reset_mode(void)
++{
++	unsigned char regval;
++
++	udelay(EC_REG_DELAY);
++	regval = ec_read(REG_LPCCFG);
++	regval |= (1 << 7);
++	ec_write(REG_LPCCFG, regval);
++	regval = ec_read(REG_PXCFG);
++	regval &= ~(1 << 0);
++	ec_write(REG_PXCFG, regval);
++	printk(KERN_INFO "exit reset mode ok..................\n");
++
++	return;
++}
++
++/* make ec disable WDD */
++static void ec_disable_WDD(void)
++{
++	unsigned char status;
++
++	udelay(EC_REG_DELAY);
++	status = ec_read(REG_WDTCFG);
++	ec_write(REG_WDTPF, 0x03);
++	ec_write(REG_WDTCFG, (status & 0x80) | 0x48);
++	printk(KERN_INFO "Disable WDD ok..................\n");
++
++	return;
++}
++
++/* make ec enable WDD */
++static void ec_enable_WDD(void)
++{
++	unsigned char status;
++
++	udelay(EC_REG_DELAY);
++	status = ec_read(REG_WDTCFG);
++	ec_write(REG_WDT, 0x28);	/* set WDT 5sec(0x28) */
++	ec_write(REG_WDTCFG, (status & 0x80) | 0x03);
++	printk(KERN_INFO "Enable WDD ok..................\n");
++
++	return;
++}
++
++/* make ec goto idle mode */
++static int ec_init_idle_mode(void)
++{
++	int timeout;
++	unsigned char status = 0;
++	int ret = 0;
++
++	ec_query_seq(CMD_INIT_IDLE_MODE);
++
++	/* make the action take active */
++	timeout = EC_CMD_TIMEOUT;
++	status = ec_read(REG_POWER_MODE) & FLAG_IDLE_MODE;
++	while (timeout--) {
++		if (status) {
++			udelay(EC_REG_DELAY);
++			break;
++		}
++		status = ec_read(REG_POWER_MODE) & FLAG_IDLE_MODE;
++		udelay(EC_REG_DELAY);
++	}
++	if (timeout <= 0) {
++		printk(KERN_ERR "ec rom fixup : can't check out the status.\n");
++		ret = -EINVAL;
++	} else
++		printk(KERN_INFO "(%d/%d)0xf710 :  0x%x\n", timeout,
++			   EC_CMD_TIMEOUT - timeout, ec_read(REG_POWER_MODE));
++
++	printk(KERN_INFO "entering idle mode ok...................\n");
++
++	return ret;
++}
++
++/* make ec exit from idle mode */
++static int ec_exit_idle_mode(void)
++{
++
++	ec_query_seq(CMD_EXIT_IDLE_MODE);
++
++	printk(KERN_INFO "exit idle mode ok...................\n");
++
++	return 0;
++}
++
++static int ec_instruction_cycle(void)
++{
++	unsigned long timeout;
++	int ret = 0;
++
++	timeout = EC_FLASH_TIMEOUT;
++	while (timeout-- >= 0) {
++		if (!(ec_read(REG_XBISPICFG) & SPICFG_SPI_BUSY))
++			break;
++	}
++	if (timeout <= 0) {
++		printk(KERN_ERR
++		       "EC_INSTRUCTION_CYCLE : timeout for check flag.\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++ out:
++	return ret;
++}
++
++/* To see if the ec is in busy state or not. */
++static inline int ec_flash_busy(unsigned long timeout)
++{
++	/* assurance the first command be going to rom */
++	if (ec_instruction_cycle() < 0)
++		return EC_STATE_BUSY;
++#if 1
++	timeout = timeout / EC_MAX_DELAY_UNIT;
++	while (timeout-- > 0) {
++		/* check the rom's status of busy flag */
++		ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
++		if (ec_instruction_cycle() < 0)
++			return EC_STATE_BUSY;
++		if ((ec_read(REG_XBISPIDAT) & 0x01) == 0x00)
++			return EC_STATE_IDLE;
++		udelay(EC_MAX_DELAY_UNIT);
++	}
++	if (timeout <= 0) {
++		printk(KERN_ERR
++		       "EC_FLASH_BUSY : timeout for check rom flag.\n");
++		return EC_STATE_BUSY;
++	}
++#else
++	/* check the rom's status of busy flag */
++	ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
++	if (ec_instruction_cycle() < 0)
++		return EC_STATE_BUSY;
++
++	timeout = timeout / EC_MAX_DELAY_UNIT;
++	while (timeout-- > 0) {
++		if ((ec_read(REG_XBISPIDAT) & 0x01) == 0x00)
++			return EC_STATE_IDLE;
++		udelay(EC_MAX_DELAY_UNIT);
++	}
++	if (timeout <= 0) {
++		printk(KERN_ERR
++		       "EC_FLASH_BUSY : timeout for check rom flag.\n");
++		return EC_STATE_BUSY;
++	}
++#endif
++
++	return EC_STATE_IDLE;
++}
++
++static int rom_instruction_cycle(unsigned char cmd)
++{
++	unsigned long timeout = 0;
++
++	switch (cmd) {
++	case SPICMD_READ_STATUS:
++	case SPICMD_WRITE_ENABLE:
++	case SPICMD_WRITE_DISABLE:
++	case SPICMD_READ_BYTE:
++	case SPICMD_HIGH_SPEED_READ:
++		timeout = 0;
++		break;
++	case SPICMD_WRITE_STATUS:
++		timeout = 300 * 1000;
++		break;
++	case SPICMD_BYTE_PROGRAM:
++		timeout = 5 * 1000;
++		break;
++	case SPICMD_SST_SEC_ERASE:
++	case SPICMD_SEC_ERASE:
++		timeout = 1000 * 1000;
++		break;
++	case SPICMD_SST_BLK_ERASE:
++	case SPICMD_BLK_ERASE:
++		timeout = 3 * 1000 * 1000;
++		break;
++	case SPICMD_SST_CHIP_ERASE:
++	case SPICMD_CHIP_ERASE:
++		timeout = 20 * 1000 * 1000;
++		break;
++	default:
++		timeout = EC_SPICMD_STANDARD_TIMEOUT;
++	}
++	if (timeout == 0)
++		return ec_instruction_cycle();
++	if (timeout < EC_SPICMD_STANDARD_TIMEOUT)
++		timeout = EC_SPICMD_STANDARD_TIMEOUT;
++
++	return ec_flash_busy(timeout);
++}
++
++/* delay for start/stop action */
++static void delay_spi(int n)
++{
++	while (n--)
++		inb(EC_IO_PORT_HIGH);
++}
++
++/* start the action to spi rom function */
++static void ec_start_spi(void)
++{
++	unsigned char val;
++
++	delay_spi(SPI_FINISH_WAIT_TIME);
++	val = ec_read(REG_XBISPICFG) | SPICFG_EN_SPICMD | SPICFG_AUTO_CHECK;
++	ec_write(REG_XBISPICFG, val);
++	delay_spi(SPI_FINISH_WAIT_TIME);
++}
++
++/* stop the action to spi rom function */
++static void ec_stop_spi(void)
++{
++	unsigned char val;
++
++	delay_spi(SPI_FINISH_WAIT_TIME);
++	val =
++	    ec_read(REG_XBISPICFG) & (~(SPICFG_EN_SPICMD | SPICFG_AUTO_CHECK));
++	ec_write(REG_XBISPICFG, val);
++	delay_spi(SPI_FINISH_WAIT_TIME);
++}
++
++/* read one byte from xbi interface */
++static int ec_read_byte(unsigned int addr, unsigned char *byte)
++{
++	int ret = 0;
++
++	/* enable spicmd writing. */
++	ec_start_spi();
++
++	/* enable write spi flash */
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
++	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
++		printk(KERN_ERR "EC_READ_BYTE : SPICMD_WRITE_ENABLE failed.\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++	/* write the address */
++	ec_write(REG_XBISPIA2, (addr & 0xff0000) >> 16);
++	ec_write(REG_XBISPIA1, (addr & 0x00ff00) >> 8);
++	ec_write(REG_XBISPIA0, (addr & 0x0000ff) >> 0);
++	/* start action */
++	ec_write(REG_XBISPICMD, SPICMD_HIGH_SPEED_READ);
++	if (rom_instruction_cycle(SPICMD_HIGH_SPEED_READ) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_READ_BYTE : SPICMD_HIGH_SPEED_READ failed.\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++	*byte = ec_read(REG_XBISPIDAT);
++
++ out:
++	/* disable spicmd writing. */
++	ec_stop_spi();
++
++	return ret;
++}
++
++/* write one byte to ec rom */
++static int ec_write_byte(unsigned int addr, unsigned char byte)
++{
++	int ret = 0;
++
++	/* enable spicmd writing. */
++	ec_start_spi();
++
++	/* enable write spi flash */
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
++	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_WRITE_BYTE : SPICMD_WRITE_ENABLE failed.\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++	/* write the address */
++	ec_write(REG_XBISPIA2, (addr & 0xff0000) >> 16);
++	ec_write(REG_XBISPIA1, (addr & 0x00ff00) >> 8);
++	ec_write(REG_XBISPIA0, (addr & 0x0000ff) >> 0);
++	ec_write(REG_XBISPIDAT, byte);
++	/* start action */
++	ec_write(REG_XBISPICMD, SPICMD_BYTE_PROGRAM);
++	if (rom_instruction_cycle(SPICMD_BYTE_PROGRAM) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_WRITE_BYTE : SPICMD_BYTE_PROGRAM failed.\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++ out:
++	/* disable spicmd writing. */
++	ec_stop_spi();
++
++	return ret;
++}
++
++/* unprotect SPI ROM */
++/* EC_ROM_unprotect function code */
++static int EC_ROM_unprotect(void)
++{
++	unsigned char status;
++
++	/* enable write spi flash */
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
++	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_UNIT_ERASE : SPICMD_WRITE_ENABLE failed.\n");
++		return 1;
++	}
++
++	/* unprotect the status register of rom */
++	ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
++	if (rom_instruction_cycle(SPICMD_READ_STATUS) == EC_STATE_BUSY) {
++		printk(KERN_ERR "EC_UNIT_ERASE : SPICMD_READ_STATUS failed.\n");
++		return 1;
++	}
++	status = ec_read(REG_XBISPIDAT);
++	ec_write(REG_XBISPIDAT, status & 0x02);
++	if (ec_instruction_cycle() < 0) {
++		printk(KERN_ERR "EC_UNIT_ERASE : write status value failed.\n");
++		return 1;
++	}
++
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_STATUS);
++	if (rom_instruction_cycle(SPICMD_WRITE_STATUS) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_UNIT_ERASE : SPICMD_WRITE_STATUS failed.\n");
++		return 1;
++	}
++
++	/* enable write spi flash */
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
++	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_UNIT_ERASE : SPICMD_WRITE_ENABLE failed.\n");
++		return 1;
++	}
++
++	return 0;
++}
++
++/* erase one block or chip or sector as needed */
++static int ec_unit_erase(unsigned char erase_cmd, unsigned int addr)
++{
++	unsigned char status;
++	int ret = 0, i = 0;
++	int unprotect_count = 3;
++	int check_flag = 0;
++
++	/* enable spicmd writing. */
++	ec_start_spi();
++
++#ifdef EC_ROM_PROTECTION
++	/* added for re-check SPICMD_READ_STATUS */
++	while (unprotect_count-- > 0) {
++		if (EC_ROM_unprotect()) {
++			ret = -EINVAL;
++			goto out;
++		}
++
++		/* first time:500ms --> 5.5sec -->10.5sec */
++		for (i = 0; i < ((2 - unprotect_count) * 100 + 10); i++)
++			udelay(50000);
++		ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
++		if (rom_instruction_cycle(SPICMD_READ_STATUS)
++				== EC_STATE_BUSY) {
++			printk(KERN_ERR
++			       "EC_PROGRAM_ROM : SPICMD_READ_STATUS failed.\n");
++		} else {
++			status = ec_read(REG_XBISPIDAT);
++			printk(KERN_INFO "Read unprotect status : 0x%x\n",
++				   status);
++			if ((status & 0x1C) == 0x00) {
++				printk(KERN_INFO
++					   "Read unprotect status OK1 : 0x%x\n",
++					   status & 0x1C);
++				check_flag = 1;
++				break;
++			}
++		}
++	}
++
++	if (!check_flag) {
++		printk(KERN_INFO "SPI ROM unprotect fail.\n");
++		return 1;
++	}
++#endif
++
++	/* block address fill */
++	if (erase_cmd == SPICMD_BLK_ERASE) {
++		ec_write(REG_XBISPIA2, (addr & 0x00ff0000) >> 16);
++		ec_write(REG_XBISPIA1, (addr & 0x0000ff00) >> 8);
++		ec_write(REG_XBISPIA0, (addr & 0x000000ff) >> 0);
++	}
++
++	/* erase the whole chip first */
++	ec_write(REG_XBISPICMD, erase_cmd);
++	if (rom_instruction_cycle(erase_cmd) == EC_STATE_BUSY) {
++		printk(KERN_ERR "EC_UNIT_ERASE : erase failed.\n");
++		ret = -EINVAL;
++		goto out;
++	}
++
++ out:
++	/* disable spicmd writing. */
++	ec_stop_spi();
++
++	return ret;
++}
++
++/* update the whole rom content with H/W mode
++ * PLEASE USING ec_unit_erase() FIRSTLY
++ */
++static int ec_program_rom(struct ec_info *info, int flag)
++{
++	unsigned int addr = 0;
++	unsigned long size = 0;
++	unsigned char *ptr = NULL;
++	unsigned char data;
++	unsigned char val = 0;
++	int ret = 0;
++	int i, j;
++	unsigned char status;
++
++	/* modify for program serial No.
++	 * set IE_START_ADDR & use idle mode,
++	 * disable WDD
++	 */
++	if (flag == PROGRAM_FLAG_ROM) {
++		ret = ec_init_reset_mode();
++		addr = info->start_addr + EC_START_ADDR;
++		printk(KERN_INFO "PROGRAM_FLAG_ROM..............\n");
++	} else if (flag == PROGRAM_FLAG_IE) {
++		ret = ec_init_idle_mode();
++		ec_disable_WDD();
++		addr = info->start_addr + IE_START_ADDR;
++		printk(KERN_INFO "PROGRAM_FLAG_IE..............\n");
++	} else {
++		return 0;
++	}
++
++	if (ret < 0) {
++		if (flag == PROGRAM_FLAG_IE)
++			ec_enable_WDD();
++		return ret;
++	}
++
++	size = info->size;
++	ptr = info->buf;
++	printk(KERN_INFO "starting update ec ROM..............\n");
++
++	ret = ec_unit_erase(SPICMD_BLK_ERASE, addr);
++	if (ret) {
++		printk(KERN_ERR "program ec : erase block failed.\n");
++		goto out;
++	}
++	printk(KERN_ERR "program ec : erase block OK.\n");
++
++	i = 0;
++	while (i < size) {
++		data = *(ptr + i);
++		ec_write_byte(addr, data);
++		ec_read_byte(addr, &val);
++		if (val != data) {
++			ec_write_byte(addr, data);
++			ec_read_byte(addr, &val);
++			if (val != data) {
++				printk(KERN_INFO
++				"EC : Second flash program failed at:\t");
++				printk(KERN_INFO
++				"addr : 0x%x, source : 0x%x, dest: 0x%x\n",
++				     addr, data, val);
++				printk(KERN_INFO "This should not happen... STOP\n");
++				break;
++			}
++		}
++		i++;
++		addr++;
++	}
++
++#ifdef	EC_ROM_PROTECTION
++	/* we should start spi access firstly */
++	ec_start_spi();
++
++	/* enable write spi flash */
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_ENABLE);
++	if (rom_instruction_cycle(SPICMD_WRITE_ENABLE) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_PROGRAM_ROM : SPICMD_WRITE_ENABLE failed.\n");
++		goto out1;
++	}
++
++	/* protect the status register of rom */
++	ec_write(REG_XBISPICMD, SPICMD_READ_STATUS);
++	if (rom_instruction_cycle(SPICMD_READ_STATUS) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_PROGRAM_ROM : SPICMD_READ_STATUS failed.\n");
++		goto out1;
++	}
++	status = ec_read(REG_XBISPIDAT);
++
++	ec_write(REG_XBISPIDAT, status | 0x1C);
++	if (ec_instruction_cycle() < 0) {
++		printk(KERN_ERR
++		       "EC_PROGRAM_ROM : write status value failed.\n");
++		goto out1;
++	}
++
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_STATUS);
++	if (rom_instruction_cycle(SPICMD_WRITE_STATUS) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_PROGRAM_ROM : SPICMD_WRITE_STATUS failed.\n");
++		goto out1;
++	}
++#endif
++
++	/* disable the write action to spi rom */
++	ec_write(REG_XBISPICMD, SPICMD_WRITE_DISABLE);
++	if (rom_instruction_cycle(SPICMD_WRITE_DISABLE) == EC_STATE_BUSY) {
++		printk(KERN_ERR
++		       "EC_PROGRAM_ROM : SPICMD_WRITE_DISABLE failed.\n");
++		goto out1;
++	}
++
++ out1:
++	/* we should stop spi access firstly */
++	ec_stop_spi();
++ out:
++	/* for security */
++	for (j = 0; j < 2000; j++)
++		udelay(1000);
++
++	/* modify for program serial No.
++	 * after program No exit idle mode
++	 * and enable WDD
++	 */
++	if (flag == PROGRAM_FLAG_ROM) {
++		/* exit from the reset mode */
++		ec_exit_reset_mode();
++	} else {
++		/* ec exit from idle mode */
++		ret = ec_exit_idle_mode();
++		ec_enable_WDD();
++		if (ret < 0)
++			return ret;
++	}
++
++	return 0;
++}
++
++/* ioctl  */
++static int misc_ioctl(struct inode *inode, struct file *filp, u_int cmd,
++		      u_long arg)
++{
++	struct ec_info ecinfo;
++	void __user *ptr = (void __user *)arg;
++	struct ec_reg *ecreg = (struct ec_reg *)(filp->private_data);
++	int ret = 0;
++
++	switch (cmd) {
++	case IOCTL_RDREG:
++		ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg));
++		if (ret) {
++			printk(KERN_ERR "reg read : copy from user error.\n");
++			return -EFAULT;
++		}
++		if ((ecreg->addr > EC_MAX_REGADDR)
++		    || (ecreg->addr < EC_MIN_REGADDR)) {
++			printk(KERN_ERR
++			       "reg read : out of register address range.\n");
++			return -EINVAL;
++		}
++		ecreg->val = ec_read(ecreg->addr);
++		ret = copy_to_user(ptr, ecreg, sizeof(struct ec_reg));
++		if (ret) {
++			printk(KERN_ERR "reg read : copy to user error.\n");
++			return -EFAULT;
++		}
++		break;
++	case IOCTL_WRREG:
++		ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg));
++		if (ret) {
++			printk(KERN_ERR "reg write : copy from user error.\n");
++			return -EFAULT;
++		}
++		if ((ecreg->addr > EC_MAX_REGADDR)
++		    || (ecreg->addr < EC_MIN_REGADDR)) {
++			printk(KERN_ERR
++			       "reg write : out of register address range.\n");
++			return -EINVAL;
++		}
++		ec_write(ecreg->addr, ecreg->val);
++		break;
++	case IOCTL_READ_EC:
++		ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg));
++		if (ret) {
++			printk(KERN_ERR "spi read : copy from user error.\n");
++			return -EFAULT;
++		}
++		if ((ecreg->addr > EC_RAM_ADDR)
++		    && (ecreg->addr < EC_MAX_REGADDR)) {
++			printk(KERN_ERR
++			       "spi read : out of register address range.\n");
++			return -EINVAL;
++		}
++		ec_read_byte(ecreg->addr, &(ecreg->val));
++		ret = copy_to_user(ptr, ecreg, sizeof(struct ec_reg));
++		if (ret) {
++			printk(KERN_ERR "spi read : copy to user error.\n");
++			return -EFAULT;
++		}
++		break;
++	case IOCTL_PROGRAM_IE:
++		ecinfo.start_addr = EC_START_ADDR;
++		ecinfo.size = EC_CONTENT_MAX_SIZE;
++		ecinfo.buf = (u8 *) kmalloc(ecinfo.size, GFP_KERNEL);
++		if (ecinfo.buf == NULL) {
++			printk(KERN_ERR "program ie : kmalloc failed.\n");
++			return -ENOMEM;
++		}
++		ret = copy_from_user(ecinfo.buf, (u8 *) ptr, ecinfo.size);
++		if (ret) {
++			printk(KERN_ERR "program ie : copy from user error.\n");
++			kfree(ecinfo.buf);
++			ecinfo.buf = NULL;
++			return -EFAULT;
++		}
++
++		/* use ec_program_rom to write serial No */
++		ec_program_rom(&ecinfo, PROGRAM_FLAG_IE);
++
++		kfree(ecinfo.buf);
++		ecinfo.buf = NULL;
++		break;
++	case IOCTL_PROGRAM_EC:
++		ecinfo.start_addr = EC_START_ADDR;
++		if (get_user((ecinfo.size), (u32 *) ptr)) {
++			printk(KERN_ERR "program ec : get user error.\n");
++			return -EFAULT;
++		}
++		if ((ecinfo.size) > EC_CONTENT_MAX_SIZE) {
++			printk(KERN_ERR "program ec : size out of limited.\n");
++			return -EINVAL;
++		}
++		ecinfo.buf = (u8 *) kmalloc(ecinfo.size, GFP_KERNEL);
++		if (ecinfo.buf == NULL) {
++			printk(KERN_ERR "program ec : kmalloc failed.\n");
++			return -ENOMEM;
++		}
++		ret = copy_from_user(ecinfo.buf, ((u8 *) ptr + 4), ecinfo.size);
++		if (ret) {
++			printk(KERN_ERR "program ec : copy from user error.\n");
++			kfree(ecinfo.buf);
++			ecinfo.buf = NULL;
++			return -EFAULT;
++		}
++
++		ec_program_rom(&ecinfo, PROGRAM_FLAG_ROM);
++
++		kfree(ecinfo.buf);
++		ecinfo.buf = NULL;
++		break;
++
++	default:
++		break;
++	}
++
++	return 0;
++}
++
++static long misc_compat_ioctl(struct file *file, unsigned int cmd,
++			      unsigned long arg)
++{
++	return misc_ioctl(file->f_dentry->d_inode, file, cmd, arg);
++}
++
++static int misc_open(struct inode *inode, struct file *filp)
++{
++	struct ec_reg *ecreg = NULL;
++	ecreg = kmalloc(sizeof(struct ec_reg), GFP_KERNEL);
++	if (ecreg)
++		filp->private_data = ecreg;
++
++	return ecreg ? 0 : -ENOMEM;
++}
++
++static int misc_release(struct inode *inode, struct file *filp)
++{
++	struct ec_reg *ecreg = (struct ec_reg *)(filp->private_data);
++
++	filp->private_data = NULL;
++	kfree(ecreg);
++
++	return 0;
++}
++
++static const struct file_operations ecmisc_fops = {
++	.open = misc_open,
++	.release = misc_release,
++	.read = NULL,
++	.write = NULL,
++#ifdef	CONFIG_64BIT
++	.compat_ioctl = misc_compat_ioctl,
++#else
++	.ioctl = misc_ioctl,
++#endif
++};
++
++static struct miscdevice ecmisc_device = {
++	.minor = MISC_DYNAMIC_MINOR,
++	.name = EC_MISC_DEV,
++	.fops = &ecmisc_fops
++};
++
++static int __init ecmisc_init(void)
++{
++	int ret;
++
++	printk(KERN_INFO "EC misc device init.\n");
++	ret = misc_register(&ecmisc_device);
++
++	return ret;
++}
++
++static void __exit ecmisc_exit(void)
++{
++	printk(KERN_INFO "EC misc device exit.\n");
++	misc_deregister(&ecmisc_device);
++}
++
++module_init(ecmisc_init);
++module_exit(ecmisc_exit);
++
++MODULE_AUTHOR("liujl <liujl@lemote.com>");
++MODULE_DESCRIPTION("Driver for flushing/dumping ROM of EC on YeeLoong laptop");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/platform/mips/yeeloong_laptop.c b/drivers/platform/mips/yeeloong_laptop.c
+new file mode 100644
+index 0000000..9f2d81b
+--- /dev/null
++++ b/drivers/platform/mips/yeeloong_laptop.c
+@@ -0,0 +1,1368 @@
++/*
++ * 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_desc yeeloong_ac_desc, yeeloong_bat_desc;
++
++#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_desc yeeloong_ac_desc = {
++	.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_desc yeeloong_bat_desc = {
++	.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 struct power_supply *yeeloong_ac, *yeeloong_bat;
++
++static int yeeloong_bat_init(void)
++{
++	struct power_supply *ac, *bat;
++
++	ac = power_supply_register(NULL, &yeeloong_ac_desc, NULL);
++	if (IS_ERR(ac))
++		return PTR_ERR(ac);
++	
++	bat = power_supply_register(NULL, &yeeloong_bat_desc, NULL);
++	if (IS_ERR(bat)) {
++		power_supply_unregister(ac);
++		return PTR_ERR(bat);
++	}
++
++	yeeloong_ac = ac;
++	yeeloong_bat = bat;
++
++	return 0;
++}
++
++static void yeeloong_bat_exit(void)
++{
++	struct power_supply *temp;
++
++	temp = yeeloong_ac;
++	yeeloong_ac = NULL;
++	power_supply_unregister(temp);
++
++	temp = yeeloong_bat;
++	yeeloong_bat = NULL;
++	power_supply_unregister(temp);
++}
++/* hwmon subdriver */
++
++#define MIN_FAN_SPEED 0
++#define MAX_FAN_SPEED 3
++
++#define get_fan(type) \
++	ec_read(REG_FAN_##type)
++
++#define set_fan(type, val) \
++	ec_write(REG_FAN_##type, val)
++
++static inline int get_fan_speed_level(void)
++{
++	return get_fan(SPEED_LEVEL);
++}
++static inline void set_fan_speed_level(int speed)
++{
++	set_fan(SPEED_LEVEL, speed);
++}
++
++static inline int get_fan_mode(void)
++{
++	return get_fan(AUTO_MAN_SWITCH);
++}
++static inline void set_fan_mode(int mode)
++{
++	set_fan(AUTO_MAN_SWITCH, mode);
++}
++
++/*
++ * 3 different modes: Full speed(0); manual mode(1); auto mode(2)
++ */
++static int get_fan_pwm_enable(void)
++{
++	return (get_fan_mode() == BIT_FAN_AUTO) ? 2 :
++		(get_fan_speed_level() == MAX_FAN_SPEED) ? 0 : 1;
++}
++
++static void set_fan_pwm_enable(int mode)
++{
++	set_fan_mode((mode == 2) ? BIT_FAN_AUTO : BIT_FAN_MANUAL);
++	if (mode == 0)
++		set_fan_speed_level(MAX_FAN_SPEED);
++}
++
++static int get_fan_pwm(void)
++{
++	return get_fan_speed_level();
++}
++
++static void set_fan_pwm(int value)
++{
++	if (get_fan_mode() != BIT_FAN_MANUAL)
++		return;
++
++	value = clamp_val(value, MIN_FAN_SPEED, MAX_FAN_SPEED);
++
++	/* We must ensure the fan is on */
++	if (value > 0)
++		set_fan(CONTROL, ON);
++
++	set_fan_speed_level(value);
++}
++
++static inline int get_fan_speed(void)
++{
++	return ((get_fan(SPEED_HIGH) & 0x0f) << 8) | get_fan(SPEED_LOW);
++}
++
++static int get_fan_rpm(void)
++{
++	return FAN_SPEED_DIVIDER / get_fan_speed();
++}
++
++static int get_cpu_temp(void)
++{
++	return (s8)ec_read(REG_TEMPERATURE_VALUE) * 1000;
++}
++
++static int get_cpu_temp_max(void)
++{
++	return 60 * 1000;
++}
++
++static int get_bat_temp_alarm(void)
++{
++	return !!(get_bat(CHARGE_STATUS) & BIT_BAT_CHARGE_STATUS_OVERTEMP);
++}
++
++static ssize_t store_sys_hwmon(void (*set) (int), const char *buf, size_t count)
++{
++	int ret;
++	unsigned long value;
++
++	if (!count)
++		return 0;
++
++	ret = kstrtoul(buf, 10, &value);
++	if (ret)
++		return ret;
++
++	set(value);
++
++	return count;
++}
++
++static ssize_t show_sys_hwmon(int (*get) (void), char *buf)
++{
++	return sprintf(buf, "%d\n", get());
++}
++
++#define CREATE_SENSOR_ATTR(_name, _mode, _set, _get)		\
++	static ssize_t show_##_name(struct device *dev,			\
++				    struct device_attribute *attr,	\
++				    char *buf)				\
++	{								\
++		return show_sys_hwmon(_set, buf);			\
++	}								\
++	static ssize_t store_##_name(struct device *dev,		\
++				     struct device_attribute *attr,	\
++				     const char *buf, size_t count)	\
++	{								\
++		return store_sys_hwmon(_get, buf, count);		\
++	}								\
++	static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
++
++CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, get_fan_rpm, NULL);
++CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR, get_fan_pwm, set_fan_pwm);
++CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, get_fan_pwm_enable,
++		set_fan_pwm_enable);
++CREATE_SENSOR_ATTR(temp1_input, S_IRUGO, get_cpu_temp, NULL);
++CREATE_SENSOR_ATTR(temp1_max, S_IRUGO, get_cpu_temp_max, NULL);
++CREATE_SENSOR_ATTR(temp2_input, S_IRUGO, get_bat_temp, NULL);
++CREATE_SENSOR_ATTR(temp2_max_alarm, S_IRUGO, get_bat_temp_alarm, NULL);
++CREATE_SENSOR_ATTR(curr1_input, S_IRUGO, get_bat_current, NULL);
++CREATE_SENSOR_ATTR(in1_input, S_IRUGO, get_bat_voltage, NULL);
++
++static ssize_t
++show_name(struct device *dev, struct device_attribute *attr, char *buf)
++{
++	return sprintf(buf, "yeeloong\n");
++}
++
++static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
++
++static struct attribute *hwmon_attributes[] = {
++	&sensor_dev_attr_pwm1.dev_attr.attr,
++	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
++	&sensor_dev_attr_fan1_input.dev_attr.attr,
++	&sensor_dev_attr_temp1_input.dev_attr.attr,
++	&sensor_dev_attr_temp1_max.dev_attr.attr,
++	&sensor_dev_attr_temp2_input.dev_attr.attr,
++	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
++	&sensor_dev_attr_curr1_input.dev_attr.attr,
++	&sensor_dev_attr_in1_input.dev_attr.attr,
++	&sensor_dev_attr_name.dev_attr.attr,
++	NULL
++};
++
++static struct attribute_group hwmon_attribute_group = {
++	.attrs = hwmon_attributes
++};
++
++static struct device *yeeloong_hwmon_dev;
++
++static int yeeloong_hwmon_init(void)
++{
++	int ret;
++
++	yeeloong_hwmon_dev = hwmon_device_register(NULL);
++	if (IS_ERR(yeeloong_hwmon_dev)) {
++		yeeloong_hwmon_dev = NULL;
++		return PTR_ERR(yeeloong_hwmon_dev);
++	}
++	ret = sysfs_create_group(&yeeloong_hwmon_dev->kobj,
++				 &hwmon_attribute_group);
++	if (ret) {
++		hwmon_device_unregister(yeeloong_hwmon_dev);
++		yeeloong_hwmon_dev = NULL;
++		return ret;
++	}
++	/* ensure fan is set to auto mode */
++	set_fan_pwm_enable(2);
++
++	return 0;
++}
++
++static void yeeloong_hwmon_exit(void)
++{
++	if (yeeloong_hwmon_dev) {
++		sysfs_remove_group(&yeeloong_hwmon_dev->kobj,
++				   &hwmon_attribute_group);
++		hwmon_device_unregister(yeeloong_hwmon_dev);
++		yeeloong_hwmon_dev = NULL;
++	}
++}
++
++/* video output subdriver */
++
++#define LCD	0
++#define CRT	1
++#define VOD_NUM	2	/* The total number of video output device*/
++
++static struct output_device *vod[VOD_NUM];
++
++static int vor[] = {REG_DISPLAY_LCD, REG_CRT_DETECT};
++
++static int get_vo_dev(struct output_device *od)
++{
++	int i, dev;
++
++	dev = -1;
++	for (i = 0; i < VOD_NUM; i++)
++		if (od == vod[i])
++			dev = i;
++
++	return dev;
++}
++
++static int vo_get_status(int dev)
++{
++	return ec_read(vor[dev]);
++}
++
++static int yeeloong_vo_get_status(struct output_device *od)
++{
++	int vd;
++
++	vd = get_vo_dev(od);
++	if (vd != -1)
++		return vo_get_status(vd);
++
++	return -ENODEV;
++}
++
++static void vo_set_state(int dev, int state)
++{
++	int addr;
++	unsigned long value;
++
++	switch (dev) {
++	case LCD:
++		addr = 0x31;
++		break;
++	case CRT:
++		addr = 0x21;
++		break;
++	default:
++		/* return directly if the wrong video output device */
++		return;
++	}
++
++	outb(addr, 0x3c4);
++	value = inb(0x3c5);
++
++	switch (dev) {
++	case LCD:
++		value |= (state ? 0x03 : 0x02);
++		break;
++	case CRT:
++		if (state)
++			clear_bit(7, &value);
++		else
++			set_bit(7, &value);
++		break;
++	default:
++		break;
++	}
++
++	outb(addr, 0x3c4);
++	outb(value, 0x3c5);
++
++	if (dev == LCD)
++		ec_write(REG_BACKLIGHT_CTRL, state);
++}
++
++static int yeeloong_vo_set_state(struct output_device *od)
++{
++	int vd;
++
++	vd = get_vo_dev(od);
++	if (vd == -1)
++		return -ENODEV;
++
++	if (vd == CRT && !vo_get_status(vd))
++		return 0;
++
++	vo_set_state(vd, !!od->request_state);
++
++	return 0;
++}
++
++static struct output_properties vop = {
++	.set_state = yeeloong_vo_set_state,
++	.get_status = yeeloong_vo_get_status,
++};
++
++static int yeeloong_vo_init(void)
++{
++	int ret, i;
++	char dev_name[VOD_NUM][4] = {"LCD", "CRT"};
++
++	/* Register video output device: lcd, crt */
++	for (i = 0; i < VOD_NUM; i++) {
++		vod[i] = video_output_register(dev_name[i], NULL, NULL, &vop);
++		if (IS_ERR(vod[i])) {
++			if (i != 0)
++				video_output_unregister(vod[i-1]);
++			ret = PTR_ERR(vod[i]);
++			vod[i] = NULL;
++			return ret;
++		}
++	}
++	/* Ensure LCD is on by default */
++	vo_set_state(LCD, ON);
++
++	/*
++	 * Turn off CRT by default, and will be enabled when the CRT
++	 * connectting event reported by SCI
++	 */
++	vo_set_state(CRT, OFF);
++
++	return 0;
++}
++
++static void yeeloong_vo_exit(void)
++{
++	int i;
++
++	for (i = 0; i < VOD_NUM; i++) {
++		if (vod[i]) {
++			video_output_unregister(vod[i]);
++			vod[i] = NULL;
++		}
++	}
++}
++
++/* lcd subdriver */
++
++struct lcd_device *lcd[VOD_NUM];
++
++static int get_lcd_dev(struct lcd_device *ld)
++{
++	int i, dev;
++
++	dev = -1;
++	for (i = 0; i < VOD_NUM; i++)
++		if (ld == lcd[i])
++			dev = i;
++
++	return dev;
++}
++
++static int yeeloong_lcd_set_power(struct lcd_device *ld, int power)
++{
++	int dev = get_lcd_dev(ld);
++
++	if (power == FB_BLANK_UNBLANK)
++		vo_set_state(dev, ON);
++	if (power == FB_BLANK_POWERDOWN)
++		vo_set_state(dev, OFF);
++
++	return 0;
++}
++
++static int yeeloong_lcd_get_power(struct lcd_device *ld)
++{
++	return vo_get_status(get_lcd_dev(ld));
++}
++
++static struct lcd_ops lcd_ops = {
++	.set_power = yeeloong_lcd_set_power,
++	.get_power = yeeloong_lcd_get_power,
++};
++
++static int yeeloong_lcd_init(void)
++{
++	int ret, i;
++	char dev_name[VOD_NUM][4] = {"LCD", "CRT"};
++
++	/* Register video output device: lcd, crt */
++	for (i = 0; i < VOD_NUM; i++) {
++		lcd[i] = lcd_device_register(dev_name[i], NULL, NULL, &lcd_ops);
++		if (IS_ERR(lcd[i])) {
++			if (i != 0)
++				lcd_device_unregister(lcd[i-1]);
++			ret = PTR_ERR(lcd[i]);
++			lcd[i] = NULL;
++			return ret;
++		}
++	}
++#if 0
++	/* This has been done by the vide output driver */
++
++	/* Ensure LCD is on by default */
++	vo_set_state(LCD, ON);
++
++	/*
++	 * Turn off CRT by default, and will be enabled when the CRT
++	 * connectting event reported by SCI
++	 */
++	vo_set_state(CRT, OFF);
++#endif
++	return 0;
++}
++
++static void yeeloong_lcd_exit(void)
++{
++	int i;
++
++	for (i = 0; i < VOD_NUM; i++) {
++		if (lcd[i]) {
++			lcd_device_unregister(lcd[i]);
++			lcd[i] = NULL;
++		}
++	}
++}
++
++/* hotkey subdriver */
++
++static struct input_dev *yeeloong_hotkey_dev;
++
++static atomic_t reboot_flag, sleep_flag;
++#define in_sleep() (&sleep_flag)
++#define in_reboot() (&reboot_flag)
++
++static const struct key_entry yeeloong_keymap[] = {
++	{KE_SW, EVENT_LID, { SW_LID } },
++	{KE_KEY, EVENT_CAMERA, { KEY_CAMERA } }, /* Fn + ESC */
++	{KE_KEY, EVENT_SLEEP, { KEY_SLEEP } }, /* Fn + F1 */
++	{KE_KEY, EVENT_BLACK_SCREEN, { KEY_DISPLAYTOGGLE } }, /* Fn + F2 */
++	{KE_KEY, EVENT_DISPLAY_TOGGLE, { KEY_SWITCHVIDEOMODE } }, /* Fn + F3 */
++	{KE_KEY, EVENT_AUDIO_MUTE, { KEY_MUTE } }, /* Fn + F4 */
++	{KE_KEY, EVENT_WLAN, { KEY_WLAN } }, /* Fn + F5 */
++	{KE_KEY, EVENT_DISPLAY_BRIGHTNESS, { KEY_BRIGHTNESSUP } }, /* Fn + up */
++	{KE_KEY, EVENT_DISPLAY_BRIGHTNESS, { KEY_BRIGHTNESSDOWN } }, /* Fn + down */
++	{KE_KEY, EVENT_AUDIO_VOLUME, { KEY_VOLUMEUP } }, /* Fn + right */
++	{KE_KEY, EVENT_AUDIO_VOLUME, { KEY_VOLUMEDOWN } }, /* Fn + left */
++	{KE_END, 0}
++};
++
++static int is_fake_event(u16 keycode)
++{
++	switch (keycode) {
++	case KEY_SLEEP:
++	case SW_LID:
++		return atomic_read(in_sleep()) | atomic_read(in_reboot());
++		break;
++	default:
++		break;
++	}
++	return 0;
++}
++
++static struct key_entry *get_event_key_entry(int event, int status)
++{
++	struct key_entry *ke;
++	static int old_brightness_status = -1;
++	static int old_volume_status = -1;
++
++	ke = sparse_keymap_entry_from_scancode(yeeloong_hotkey_dev, event);
++	if (!ke)
++		return NULL;
++
++	switch (event) {
++	case EVENT_DISPLAY_BRIGHTNESS:
++		/* current status > old one, means up */
++		if ((status < old_brightness_status) || (0 == status))
++			ke++;
++		old_brightness_status = status;
++		break;
++	case EVENT_AUDIO_VOLUME:
++		if ((status < old_volume_status) || (0 == status))
++			ke++;
++		old_volume_status = status;
++		break;
++	default:
++		break;
++	}
++
++	return ke;
++}
++
++static int report_lid_switch(int status)
++{
++	static int old_status;
++
++	/*
++	 * LID is a switch button, so, two continuous same status should be
++	 * ignored
++	 */
++	if (old_status != status) {
++		input_report_switch(yeeloong_hotkey_dev, SW_LID, !status);
++		input_sync(yeeloong_hotkey_dev);
++	}
++	old_status = status;
++
++	return status;
++}
++
++static int crt_detect_handler(int status)
++{
++	/*
++	 * When CRT is inserted, enable its output and disable the LCD output,
++	 * otherwise, do reversely.
++	 */
++	vo_set_state(CRT, status);
++	vo_set_state(LCD, !status);
++
++	return status;
++}
++
++static int displaytoggle_handler(int status)
++{
++	/* EC(>=PQ1D26) does this job for us, we can not do it again,
++	 * otherwise, the brightness will not resume to the normal level! */
++	if (ec_version_before("EC_VER=PQ1D26"))
++		vo_set_state(LCD, status);
++
++	return status;
++}
++
++static int mypow(int x, int y)
++{
++	int i, j = x;
++
++	for (i = 1; i < y; i++)
++		j *= j;
++
++	return j;
++}
++
++static int switchvideomode_handler(int status)
++{
++	/* Default status: CRT|LCD = 0|1 = 1 */
++	static int bin_state = 1;
++	int i;
++
++	/*
++	 * Only enable switch video output button
++	 * when CRT is connected
++	 */
++	if (!vo_get_status(CRT))
++		return 0;
++	/*
++	 * 2. no CRT connected: LCD on, CRT off
++	 * 3. BOTH on
++	 * 0. BOTH off
++	 * 1. LCD off, CRT on
++	 */
++
++	bin_state++;
++	if (bin_state > mypow(2, VOD_NUM) - 1)
++		bin_state = 0;
++	
++	for (i = 0; i < VOD_NUM; i++)
++		vo_set_state(i, bin_state & (1 << i));
++
++	return bin_state;
++}
++
++static int camera_handler(int status)
++{
++	int value;
++
++	value = ec_read(REG_CAMERA_CONTROL);
++	ec_write(REG_CAMERA_CONTROL, value | (1 << 1));
++
++	return status;
++}
++
++static int usb2_handler(int status)
++{
++	pr_emerg("USB2 Over Current occurred\n");
++
++	return status;
++}
++
++static int usb0_handler(int status)
++{
++	pr_emerg("USB0 Over Current occurred\n");
++
++	return status;
++}
++
++static int ac_bat_handler(int status)
++{
++	if (yeeloong_ac)
++		power_supply_changed(yeeloong_ac);
++	if (yeeloong_bat)
++		power_supply_changed(yeeloong_bat);
++
++	return status;
++}
++
++struct sci_event {
++	int reg;
++	sci_handler handler;
++};
++
++static const struct sci_event se[] = {
++	[EVENT_AC_BAT] = {0, ac_bat_handler},
++	[EVENT_AUDIO_MUTE] = {REG_AUDIO_MUTE, NULL},
++	[EVENT_AUDIO_VOLUME] = {REG_AUDIO_VOLUME, NULL},
++	[EVENT_CRT_DETECT] = {REG_CRT_DETECT, crt_detect_handler},
++	[EVENT_CAMERA] = {REG_CAMERA_STATUS, camera_handler},
++	[EVENT_BLACK_SCREEN] = {REG_DISPLAY_LCD, displaytoggle_handler},
++	[EVENT_DISPLAY_BRIGHTNESS] = {REG_DISPLAY_BRIGHTNESS, NULL},
++	[EVENT_LID] = {REG_LID_DETECT, NULL},
++	[EVENT_DISPLAY_TOGGLE] = {0, switchvideomode_handler},
++	[EVENT_USB_OC0] = {REG_USB2_FLAG, usb0_handler},
++	[EVENT_USB_OC2] = {REG_USB2_FLAG, usb2_handler},
++	[EVENT_WLAN] = {REG_WLAN, NULL},
++};
++
++static void do_event_action(int event)
++{
++	int status = -1;
++	struct key_entry *ke;
++	struct sci_event *sep;
++
++	sep = (struct sci_event *)&se[event];
++
++	if (sep->reg != 0)
++		status = ec_read(sep->reg);
++
++	if (status == -1) {
++		/* ec_read hasn't been called, status is invalid */
++		return;
++	}
++
++	if (sep->handler != NULL)
++		status = sep->handler(status);
++
++	pr_debug("%s: event: %d status: %d\n", __func__, event, status);
++
++	/* Report current key to user-space */
++	ke = get_event_key_entry(event, status);
++
++	/*
++	 * Ignore the LID and SLEEP event when we are already in sleep or
++	 * reboot state, this will avoid the recursive pm operations. but note:
++	 * the report_lid_switch() called in arch/mips/loongson/lemote-2f/pm.c
++	 * is necessary, because it is used to wake the system from sleep
++	 * state. In the future, perhaps SW_LID should works like SLEEP, no
++	 * need to function as a SWITCH, just report the state when the LID is
++	 * closed is enough, this event can tell the software to "SLEEP", no
++	 * need to tell the softwares when we are resuming from "SLEEP".
++	 */
++	if (ke && !is_fake_event(ke->keycode)) {
++		if (ke->keycode == SW_LID)
++			report_lid_switch(status);
++		else
++			sparse_keymap_report_entry(yeeloong_hotkey_dev, ke, 1,
++					true);
++	}
++}
++
++/*
++ * SCI(system control interrupt) main interrupt routine
++ *
++ * We will do the query and get event number together so the interrupt routine
++ * should be longer than 120us now at least 3ms elpase for it.
++ */
++static irqreturn_t sci_irq_handler(int irq, void *dev_id)
++{
++	int ret, event;
++
++	if (SCI_IRQ_NUM != irq)
++		return IRQ_NONE;
++
++	/* Query the event number */
++	ret = ec_query_event_num();
++	if (ret < 0)
++		return IRQ_NONE;
++
++	event = ec_get_event_num();
++	if (event < EVENT_START || event > EVENT_END)
++		return IRQ_NONE;
++
++	/* Execute corresponding actions */
++	do_event_action(event);
++
++	return IRQ_HANDLED;
++}
++
++/*
++ * Config and init some msr and gpio register properly.
++ */
++static int sci_irq_init(void)
++{
++	u32 hi, lo;
++	u32 gpio_base;
++	unsigned long flags;
++	int ret;
++
++	/* Get gpio base */
++	_rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &lo);
++	gpio_base = lo & 0xff00;
++
++	/* Filter the former kb3310 interrupt for security */
++	ret = ec_query_event_num();
++	if (ret)
++		return ret;
++
++	/* For filtering next number interrupt */
++	udelay(10000);
++
++	/* Set gpio native registers and msrs for GPIO27 SCI EVENT PIN
++	 * gpio :
++	 *      input, pull-up, no-invert, event-count and value 0,
++	 *      no-filter, no edge mode
++	 *      gpio27 map to Virtual gpio0
++	 * msr :
++	 *      no primary and lpc
++	 *      Unrestricted Z input to IG10 from Virtual gpio 0.
++	 */
++	local_irq_save(flags);
++	_rdmsr(0x80000024, &hi, &lo);
++	lo &= ~(1 << 10);
++	_wrmsr(0x80000024, hi, lo);
++	_rdmsr(0x80000025, &hi, &lo);
++	lo &= ~(1 << 10);
++	_wrmsr(0x80000025, hi, lo);
++	_rdmsr(0x80000023, &hi, &lo);
++	lo |= (0x0a << 0);
++	_wrmsr(0x80000023, hi, lo);
++	local_irq_restore(flags);
++
++	/* Set gpio27 as sci interrupt
++	 *
++	 * input, pull-up, no-fliter, no-negedge, invert
++	 * the sci event is just about 120us
++	 */
++	asm(".set noreorder\n");
++	/*  input enable */
++	outl(0x00000800, (gpio_base | 0xA0));
++	/*  revert the input */
++	outl(0x00000800, (gpio_base | 0xA4));
++	/*  event-int enable */
++	outl(0x00000800, (gpio_base | 0xB8));
++	asm(".set reorder\n");
++
++	return 0;
++}
++
++static int notify_reboot(struct notifier_block *nb, unsigned long event, void *buf)
++{
++	switch (event) {
++	case SYS_RESTART:
++	case SYS_HALT:
++	case SYS_POWER_OFF:
++		atomic_set(in_reboot(), 1);
++		break;
++	default:
++		return NOTIFY_DONE;
++	}
++
++	return NOTIFY_OK;
++}
++
++static int notify_pm(struct notifier_block *nb, unsigned long event, void *buf)
++{
++	switch (event) {
++	case PM_HIBERNATION_PREPARE:
++	case PM_SUSPEND_PREPARE:
++		atomic_inc(in_sleep());
++		break;
++	case PM_POST_HIBERNATION:
++	case PM_POST_SUSPEND:
++	case PM_RESTORE_PREPARE:	/* do we need this ?? */
++		atomic_dec(in_sleep());
++		break;
++	default:
++		return NOTIFY_DONE;
++	}
++
++	pr_debug("%s: event = %lu, in_sleep() = %d\n", __func__, event,
++			atomic_read(in_sleep()));
++
++	return NOTIFY_OK;
++}
++
++static struct notifier_block reboot_notifier = {
++	.notifier_call = notify_reboot,
++};
++
++static struct notifier_block pm_notifier = {
++	.notifier_call = notify_pm,
++};
++
++static int yeeloong_hotkey_init(void)
++{
++	int ret = 0;
++
++	ret = register_reboot_notifier(&reboot_notifier);
++	if (ret) {
++		pr_err("Can't register reboot notifier\n");
++		goto end;
++	}
++
++	ret = register_pm_notifier(&pm_notifier);
++	if (ret) {
++		pr_err("Can't register pm notifier\n");
++		goto free_reboot_notifier;
++	}
++
++	ret = sci_irq_init();
++	if (ret) {
++		pr_err("Can't init SCI interrupt\n");
++		goto free_pm_notifier;
++	}
++
++	ret = request_threaded_irq(SCI_IRQ_NUM, NULL, &sci_irq_handler,
++			IRQF_ONESHOT, "sci", NULL);
++	if (ret) {
++		pr_err("Can't thread SCI interrupt handler\n");
++		goto free_pm_notifier;
++	}
++
++	yeeloong_hotkey_dev = input_allocate_device();
++
++	if (!yeeloong_hotkey_dev) {
++		ret = -ENOMEM;
++		goto free_irq;
++	}
++
++	yeeloong_hotkey_dev->name = "HotKeys";
++	yeeloong_hotkey_dev->phys = "button/input0";
++	yeeloong_hotkey_dev->id.bustype = BUS_HOST;
++	yeeloong_hotkey_dev->dev.parent = NULL;
++
++	ret = sparse_keymap_setup(yeeloong_hotkey_dev, yeeloong_keymap, NULL);
++	if (ret) {
++		pr_err("Failed to setup input device keymap\n");
++		goto free_dev;
++	}
++
++	ret = input_register_device(yeeloong_hotkey_dev);
++	if (ret)
++		goto free_keymap;
++
++	/* Update the current status of LID */
++	report_lid_switch(ON);
++
++#ifdef CONFIG_LOONGSON_SUSPEND
++	/* Install the real yeeloong_report_lid_status for pm.c */
++	yeeloong_report_lid_status = report_lid_switch;
++#endif
++	return 0;
++
++free_keymap:
++	sparse_keymap_free(yeeloong_hotkey_dev);
++free_dev:
++	input_free_device(yeeloong_hotkey_dev);
++free_irq:
++	free_irq(SCI_IRQ_NUM, NULL);
++free_pm_notifier:
++	unregister_pm_notifier(&pm_notifier);
++free_reboot_notifier:
++	unregister_reboot_notifier(&reboot_notifier);
++end:
++	return ret;
++}
++
++static void yeeloong_hotkey_exit(void)
++{
++	/* Free irq */
++	free_irq(SCI_IRQ_NUM, NULL);
++
++#ifdef CONFIG_LOONGSON_SUSPEND
++	/* Uninstall yeeloong_report_lid_status for pm.c */
++	if (yeeloong_report_lid_status == report_lid_switch)
++		yeeloong_report_lid_status = NULL;
++#endif
++
++	if (yeeloong_hotkey_dev) {
++		sparse_keymap_free(yeeloong_hotkey_dev);
++		input_unregister_device(yeeloong_hotkey_dev);
++		yeeloong_hotkey_dev = NULL;
++	}
++}
++
++#ifdef CONFIG_PM
++static void usb_ports_set(int status)
++{
++	status = !!status;
++
++	ec_write(REG_USB0_FLAG, status);
++	ec_write(REG_USB1_FLAG, status);
++	ec_write(REG_USB2_FLAG, status);
++}
++
++static int yeeloong_suspend(struct device *dev)
++
++{
++	if (ec_version_before("EC_VER=PQ1D27"))
++		vo_set_state(LCD, OFF);
++	vo_set_state(CRT, OFF);
++	usb_ports_set(OFF);
++
++	return 0;
++}
++
++static int yeeloong_resume(struct device *dev)
++{
++	int ret;
++
++	if (ec_version_before("EC_VER=PQ1D27"))
++		vo_set_state(LCD, ON);
++	vo_set_state(CRT, ON);
++	usb_ports_set(ON);
++
++	ret = sci_irq_init();
++	if (ret)
++		return -EFAULT;
++
++	return 0;
++}
++
++static const SIMPLE_DEV_PM_OPS(yeeloong_pm_ops, yeeloong_suspend,
++	yeeloong_resume);
++#endif
++
++static struct platform_device_id platform_device_ids[] = {
++	{
++		.name = "yeeloong_laptop",
++	},
++	{}
++};
++
++MODULE_DEVICE_TABLE(platform, platform_device_ids);
++
++static struct platform_driver platform_driver = {
++	.driver = {
++		.name = "yeeloong_laptop",
++		.owner = THIS_MODULE,
++#ifdef CONFIG_PM
++		.pm = &yeeloong_pm_ops,
++#endif
++	},
++	.id_table = platform_device_ids,
++};
++
++static int __init yeeloong_init(void)
++{
++	int ret;
++
++	pr_info("YeeLoong Laptop platform specific driver loaded.\n");
++
++	/* Register platform stuff */
++	ret = platform_driver_register(&platform_driver);
++	if (ret) {
++		pr_err("Failed to register YeeLoong platform driver.\n");
++		return ret;
++	}
++
++#define yeeloong_init_drv(drv, alias) do {			\
++	pr_info("Registered YeeLoong " alias " driver.\n");	\
++	ret = yeeloong_ ## drv ## _init();			\
++	if (ret) {						\
++		pr_err("Failed to register YeeLoong " alias " driver.\n");	\
++		yeeloong_ ## drv ## _exit();			\
++		return ret;					\
++	}							\
++} while (0)
++
++	yeeloong_init_drv(backlight, "backlight");
++	yeeloong_init_drv(bat, "battery and AC");
++	yeeloong_init_drv(hwmon, "hardware monitor");
++	yeeloong_init_drv(vo, "video output");
++	yeeloong_init_drv(lcd, "lcd output");
++	yeeloong_init_drv(hotkey, "hotkey input");
++
++	return 0;
++}
++
++static void __exit yeeloong_exit(void)
++{
++	yeeloong_hotkey_exit();
++	yeeloong_lcd_exit();
++	yeeloong_vo_exit();
++	yeeloong_hwmon_exit();
++	yeeloong_bat_exit();
++	yeeloong_backlight_exit();
++	platform_driver_unregister(&platform_driver);
++
++	pr_info("YeeLoong platform specific driver unloaded.\n");
++}
++
++module_init(yeeloong_init);
++module_exit(yeeloong_exit);
++
++MODULE_AUTHOR("Wu Zhangjin <wuzhangjin@gmail.com>; Liu Junliang <liujl@lemote.com>");
++MODULE_DESCRIPTION("YeeLoong laptop driver");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
+index 8bf495f..f6a15b6 100644
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -36,6 +36,12 @@ config VGASTATE
+        tristate
+        default n
+ 
++config VIDEO_OUTPUT_CONTROL
++	tristate "Lowlevel video output switch controls"
++	help
++	  This framework adds support for low-level control of the video 
++	  output switch.
++
+ config VIDEOMODE_HELPERS
+ 	bool
+ 
+diff --git a/drivers/video/Makefile b/drivers/video/Makefile
+index 9ad3c17..3d869d9 100644
+--- a/drivers/video/Makefile
++++ b/drivers/video/Makefile
+@@ -7,6 +7,8 @@ obj-y				  += backlight/
+ 
+ obj-y				  += fbdev/
+ 
++#video output switch sysfs driver
++obj-$(CONFIG_VIDEO_OUTPUT_CONTROL) += output.o
+ obj-$(CONFIG_VIDEOMODE_HELPERS) += display_timing.o videomode.o
+ ifeq ($(CONFIG_OF),y)
+ obj-$(CONFIG_VIDEOMODE_HELPERS) += of_display_timing.o of_videomode.o
+diff --git a/drivers/video/output.c b/drivers/video/output.c
+new file mode 100644
+index 0000000..1446c49
+--- /dev/null
++++ b/drivers/video/output.c
+@@ -0,0 +1,133 @@
++/*
++ *  output.c - Display Output Switch driver
++ *
++ *  Copyright (C) 2006 Luming Yu <luming.yu@intel.com>
++ *
++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; either version 2 of the License, or (at
++ *  your option) any later version.
++ *
++ *  This program is distributed in the hope that it will be useful, but
++ *  WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ *  General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License along
++ *  with this program; if not, write to the Free Software Foundation, Inc.,
++ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++ */
++#include <linux/module.h>
++#include <linux/video_output.h>
++#include <linux/slab.h>
++#include <linux/err.h>
++#include <linux/ctype.h>
++
++
++MODULE_DESCRIPTION("Display Output Switcher Lowlevel Control Abstraction");
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Luming Yu <luming.yu@intel.com>");
++
++static ssize_t state_show(struct device *dev, struct device_attribute *attr,
++			  char *buf)
++{
++	ssize_t ret_size = 0;
++	struct output_device *od = to_output_device(dev);
++	if (od->props)
++		ret_size = sprintf(buf,"%.8x\n",od->props->get_status(od));
++	return ret_size;
++}
++
++static ssize_t state_store(struct device *dev, struct device_attribute *attr,
++			   const char *buf,size_t count)
++{
++	char *endp;
++	struct output_device *od = to_output_device(dev);
++	int request_state = simple_strtoul(buf,&endp,0);
++	size_t size = endp - buf;
++
++	if (isspace(*endp))
++		size++;
++	if (size != count)
++		return -EINVAL;
++
++	if (od->props) {
++		od->request_state = request_state;
++		od->props->set_state(od);
++	}
++	return count;
++}
++static DEVICE_ATTR_RW(state);
++
++static void video_output_release(struct device *dev)
++{
++	struct output_device *od = to_output_device(dev);
++	kfree(od);
++}
++
++static struct attribute *video_output_attrs[] = {
++	&dev_attr_state.attr,
++	NULL,
++};
++ATTRIBUTE_GROUPS(video_output);
++
++static struct class video_output_class = {
++	.name = "video_output",
++	.dev_release = video_output_release,
++	.dev_groups = video_output_groups,
++};
++
++struct output_device *video_output_register(const char *name,
++	struct device *dev,
++	void *devdata,
++	struct output_properties *op)
++{
++	struct output_device *new_dev;
++	int ret_code = 0;
++
++	new_dev = kzalloc(sizeof(struct output_device),GFP_KERNEL);
++	if (!new_dev) {
++		ret_code = -ENOMEM;
++		goto error_return;
++	}
++	new_dev->props = op;
++	new_dev->dev.class = &video_output_class;
++	new_dev->dev.parent = dev;
++	dev_set_name(&new_dev->dev, "%s", name);
++	dev_set_drvdata(&new_dev->dev, devdata);
++	ret_code = device_register(&new_dev->dev);
++	if (ret_code) {
++		kfree(new_dev);
++		goto error_return;
++	}
++	return new_dev;
++
++error_return:
++	return ERR_PTR(ret_code);
++}
++EXPORT_SYMBOL(video_output_register);
++
++void video_output_unregister(struct output_device *dev)
++{
++	if (!dev)
++		return;
++	device_unregister(&dev->dev);
++}
++EXPORT_SYMBOL(video_output_unregister);
++
++static void __exit video_output_class_exit(void)
++{
++	class_unregister(&video_output_class);
++}
++
++static int __init video_output_class_init(void)
++{
++	return class_register(&video_output_class);
++}
++
++postcore_initcall(video_output_class_init);
++module_exit(video_output_class_exit);
+diff --git a/include/linux/video_output.h b/include/linux/video_output.h
+new file mode 100644
+index 0000000..ed5cdeb
+--- /dev/null
++++ b/include/linux/video_output.h
+@@ -0,0 +1,57 @@
++/*
++ *
++ *  Copyright (C) 2006 Luming Yu <luming.yu@intel.com>
++ *
++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; either version 2 of the License, or (at
++ *  your option) any later version.
++ *
++ *  This program is distributed in the hope that it will be useful, but
++ *  WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ *  General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License along
++ *  with this program; if not, write to the Free Software Foundation, Inc.,
++ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++ */
++#ifndef _LINUX_VIDEO_OUTPUT_H
++#define _LINUX_VIDEO_OUTPUT_H
++#include <linux/device.h>
++#include <linux/err.h>
++struct output_device;
++struct output_properties {
++	int (*set_state)(struct output_device *);
++	int (*get_status)(struct output_device *);
++};
++struct output_device {
++	int request_state;
++	struct output_properties *props;
++	struct device dev;
++};
++#define to_output_device(obj) container_of(obj, struct output_device, dev)
++#if	defined(CONFIG_VIDEO_OUTPUT_CONTROL) || defined(CONFIG_VIDEO_OUTPUT_CONTROL_MODULE)
++struct output_device *video_output_register(const char *name,
++	struct device *dev,
++	void *devdata,
++	struct output_properties *op);
++void video_output_unregister(struct output_device *dev);
++#else
++static struct output_device *video_output_register(const char *name,
++        struct device *dev,
++        void *devdata,
++        struct output_properties *op)
++{
++	return ERR_PTR(-ENODEV);
++}
++static void video_output_unregister(struct output_device *dev)
++{
++	return;
++}
++#endif
++#endif
+-- 
+2.4.3
+