summary refs log tree commit diff
path: root/gnu/packages/patches/linux-libre-gdium.patch
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/packages/patches/linux-libre-gdium.patch')
-rw-r--r--gnu/packages/patches/linux-libre-gdium.patch2549
1 files changed, 2549 insertions, 0 deletions
diff --git a/gnu/packages/patches/linux-libre-gdium.patch b/gnu/packages/patches/linux-libre-gdium.patch
new file mode 100644
index 0000000000..6f71d65215
--- /dev/null
+++ b/gnu/packages/patches/linux-libre-gdium.patch
@@ -0,0 +1,2549 @@
+Add support for the gdium laptop.  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/include/asm/mach-loongson/machine.h b/arch/mips/include/asm/mach-loongson/machine.h
+index cb2b602..78fcd38 100644
+--- a/arch/mips/include/asm/mach-loongson/machine.h
++++ b/arch/mips/include/asm/mach-loongson/machine.h
+@@ -24,6 +24,12 @@
+ 
+ #endif
+ 
++#ifdef CONFIG_DEXXON_GDIUM
++
++#define LOONGSON_MACHTYPE MACH_DEXXON_GDIUM2F10
++
++#endif
++
+ #ifdef CONFIG_LOONGSON_MACH3X
+ 
+ #define LOONGSON_MACHTYPE MACH_LOONGSON_GENERIC
+diff --git a/arch/mips/loongson/Kconfig b/arch/mips/loongson/Kconfig
+index 659ca91..e6a9ac3 100644
+--- a/arch/mips/loongson/Kconfig
++++ b/arch/mips/loongson/Kconfig
+@@ -59,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 LOONGSON_MACH3X
+ 	bool "Generic Loongson 3 family machines"
+ 	select ARCH_SPARSEMEM_ENABLE
+@@ -151,6 +176,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 --git a/arch/mips/loongson/Makefile b/arch/mips/loongson/Makefile
+index 7429994..63214c8 100644
+--- a/arch/mips/loongson/Makefile
++++ b/arch/mips/loongson/Makefile
+@@ -17,6 +17,12 @@ obj-$(CONFIG_LEMOTE_FULOONG2E)	+= fuloong-2e/
+ obj-$(CONFIG_LEMOTE_MACH2F)  += lemote-2f/
+ 
+ #
++# Dexxon gdium netbook, based on loongson 2F and SM502
++#
++
++obj-$(CONFIG_DEXXON_GDIUM)  += gdium/
++
++#
+ # All Loongson-3 family machines
+ #
+ 
+diff --git a/arch/mips/loongson/Platform b/arch/mips/loongson/Platform
+index 0ac20eb..cd957dd 100644
+--- a/arch/mips/loongson/Platform
++++ b/arch/mips/loongson/Platform
+@@ -30,4 +30,5 @@ platform-$(CONFIG_MACH_LOONGSON) += loongson/
+ cflags-$(CONFIG_MACH_LOONGSON) += -I$(srctree)/arch/mips/include/asm/mach-loongson -mno-branch-likely
+ load-$(CONFIG_LEMOTE_FULOONG2E) += 0xffffffff80100000
+ load-$(CONFIG_LEMOTE_MACH2F) += 0xffffffff80200000
++load-$(CONFIG_DEXXON_GDIUM) += 0xffffffff80200000
+ load-$(CONFIG_LOONGSON_MACH3X) += 0xffffffff80200000
+diff --git a/arch/mips/loongson/common/cmdline.c b/arch/mips/loongson/common/cmdline.c
+index 679a18a..96d5919 100644
+--- a/arch/mips/loongson/common/cmdline.c
++++ b/arch/mips/loongson/common/cmdline.c
+@@ -66,6 +66,11 @@ void __init prom_init_cmdline(void)
+ 		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;
+ 	}
+diff --git a/arch/mips/loongson/gdium/Makefile b/arch/mips/loongson/gdium/Makefile
+new file mode 100644
+index 0000000..f3f4f51
+--- /dev/null
++++ b/arch/mips/loongson/gdium/Makefile
+@@ -0,0 +1,6 @@
++# Makefile for gdium
++
++obj-y += irq.o reset.o platform.o
++
++obj-$(CONFIG_MFD_SM501) += sm501-pwm.o
++obj-$(CONFIG_GDIUM_PWM_CLOCK) += gdium-clock.o
+diff --git a/arch/mips/loongson/gdium/gdium-clock.c b/arch/mips/loongson/gdium/gdium-clock.c
+new file mode 100644
+index 0000000..fdbf42a
+--- /dev/null
++++ b/arch/mips/loongson/gdium/gdium-clock.c
+@@ -0,0 +1,234 @@
++/*
++ * Doesn't work really well. When used, the clocksource is producing
++ * bad timings and the clockevent can't be used (don't have one shot feature
++ * thus can't switch on the fly and the pwm is initialised too late to be able
++ * to use it at boot time).
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/platform_device.h>
++#include <linux/interrupt.h>
++#include <linux/delay.h>
++#include <linux/pwm.h>
++#include <linux/clocksource.h>
++#include <linux/debugfs.h>
++#include <asm/irq_cpu.h>
++#include <asm/mipsregs.h>
++#include <asm/mips-boards/bonito64.h>
++#include <asm/time.h>
++
++#include <loongson.h>
++
++#define CLOCK_PWM		1
++#define CLOCK_PWM_FREQ		1500000				/* Freq in Hz */
++#define CLOCK_LATCH		((CLOCK_PWM_FREQ + HZ/2) / HZ)
++#define CLOCK_PWM_PERIOD	(1000000000/CLOCK_PWM_FREQ)	/* period ns  */
++#define CLOCK_PWM_DUTY		50
++#define CLOCK_PWM_IRQ		(MIPS_CPU_IRQ_BASE + 4)
++
++static const char drv_name[] = "gdium-clock";
++
++static struct pwm_device *clock_pwm;
++
++static DEFINE_SPINLOCK(clock_pwm_lock);
++static uint64_t clock_tick;
++
++static irqreturn_t gdium_pwm_clock_interrupt(int irq, void *dev_id)
++{
++	struct clock_event_device *cd = dev_id;
++	unsigned long flag;
++
++	spin_lock_irqsave(&clock_pwm_lock, flag);
++	clock_tick++;
++	/* wait intn2 to finish */
++	do {
++		LOONGSON_INTENCLR = (1 << 13);
++	} while (LOONGSON_INTISR & (1 << 13));
++	spin_unlock_irqrestore(&clock_pwm_lock, flag);
++
++	if (cd && cd->event_handler)
++		cd->event_handler(cd);
++
++	return IRQ_HANDLED;
++}
++
++static cycle_t gdium_pwm_clock_read(struct clocksource *cs)
++{
++	unsigned long flag;
++	uint32_t jifs;
++	uint64_t ticks;
++
++	spin_lock_irqsave(&clock_pwm_lock, flag);
++	jifs = jiffies;
++	ticks = clock_tick;
++	spin_unlock_irqrestore(&clock_pwm_lock, flag);
++	/* return (cycle_t)ticks; */
++	return (cycle_t)(CLOCK_LATCH * jifs);
++}
++
++static struct clocksource gdium_pwm_clock_clocksource = {
++	.name   = "gdium_csrc",
++	.read   = gdium_pwm_clock_read,
++	.mask   = CLOCKSOURCE_MASK(64),
++	.flags	= CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_MUST_VERIFY,
++	.shift	= 20,
++};
++
++/* Debug fs */
++static int gdium_pwm_clock_show(struct seq_file *s, void *p)
++{
++	unsigned long flag;
++	uint64_t ticks;
++
++	spin_lock_irqsave(&clock_pwm_lock, flag);
++	ticks = clock_tick;
++	spin_unlock_irqrestore(&clock_pwm_lock, flag);
++	seq_printf(s, "%lld\n", ticks);
++	return 0;
++}
++
++static int gdium_pwm_clock_open(struct inode *inode, struct file *file)
++{
++	return single_open(file, gdium_pwm_clock_show, inode->i_private);
++}
++
++static const struct file_operations gdium_pwm_clock_fops = {
++	.open		= gdium_pwm_clock_open,
++	.read		= seq_read,
++	.llseek		= seq_lseek,
++	.release	= single_release,
++	.owner		= THIS_MODULE,
++};
++static struct dentry   *debugfs_file;
++
++static void gdium_pwm_clock_set_mode(enum clock_event_mode mode,
++		struct clock_event_device *evt)
++{
++	/* Nothing to do ...  */
++}
++
++static struct clock_event_device gdium_pwm_clock_cevt = {
++	.name           = "gdium_cevt",
++	.features       = CLOCK_EVT_FEAT_PERIODIC,
++	/* .mult, .shift, .max_delta_ns and .min_delta_ns left uninitialized */
++	.rating         = 299,
++	.irq            = CLOCK_PWM_IRQ,
++	.set_mode       = gdium_pwm_clock_set_mode,
++};
++
++static struct platform_device_id platform_device_ids[] = {
++	{
++		.name = "gdium-pwmclk",
++	},
++	{}
++};
++MODULE_DEVICE_TABLE(platform, platform_device_ids);
++
++static struct platform_driver gdium_pwm_clock_driver = {
++	.driver		= {
++		.name   = drv_name,
++		.owner  = THIS_MODULE,
++	},
++	.id_table = platform_device_ids,
++};
++
++static int gdium_pwm_clock_drvinit(void)
++{
++	int ret;
++	struct clocksource *cs = &gdium_pwm_clock_clocksource;
++	struct clock_event_device *cd = &gdium_pwm_clock_cevt;
++	unsigned int cpu = smp_processor_id();
++
++	clock_tick = 0;
++
++	clock_pwm = pwm_request(CLOCK_PWM, drv_name);
++	if (clock_pwm == NULL) {
++		pr_err("unable to request PWM for Gdium clock\n");
++		return -EBUSY;
++	}
++	ret = pwm_config(clock_pwm, CLOCK_PWM_DUTY, CLOCK_PWM_PERIOD);
++	if (ret) {
++		pr_err("unable to configure PWM for Gdium clock\n");
++		goto err_pwm_request;
++	}
++	ret = pwm_enable(clock_pwm);
++	if (ret) {
++		pr_err("unable to enable PWM for Gdium clock\n");
++		goto err_pwm_request;
++	}
++
++	cd->cpumask = cpumask_of(cpu);
++
++	cd->shift = 22;
++	cd->mult  = div_sc(CLOCK_PWM_FREQ, NSEC_PER_SEC, cd->shift);
++	cd->max_delta_ns = clockevent_delta2ns(0x7FFF, cd);
++	cd->min_delta_ns = clockevent_delta2ns(0xF, cd);
++	clockevents_register_device(&gdium_pwm_clock_cevt);
++
++	/* SM501 PWM1 connected to intn2 <->ip4 */
++	LOONGSON_INTPOL = (1 << 13);
++	LOONGSON_INTEDGE &= ~(1 << 13);
++	ret = request_irq(CLOCK_PWM_IRQ, gdium_pwm_clock_interrupt, IRQF_DISABLED, drv_name, &gdium_pwm_clock_cevt);
++	if (ret) {
++		pr_err("Can't claim irq\n");
++		goto err_pwm_disable;
++	}
++
++	cs->rating = 200;
++	cs->mult = clocksource_hz2mult(CLOCK_PWM_FREQ, cs->shift);
++	ret = clocksource_register(&gdium_pwm_clock_clocksource);
++	if (ret) {
++		pr_err("Can't register clocksource\n");
++		goto err_irq;
++	}
++	pr_info("Clocksource registered with shift %d and mult %d\n",
++			cs->shift, cs->mult);
++
++	debugfs_file = debugfs_create_file(drv_name, S_IFREG | S_IRUGO,
++			NULL, NULL, &gdium_pwm_clock_fops);
++
++	return 0;
++
++err_irq:
++	free_irq(CLOCK_PWM_IRQ, &gdium_pwm_clock_cevt);
++err_pwm_disable:
++	pwm_disable(clock_pwm);
++err_pwm_request:
++	pwm_free(clock_pwm);
++	return ret;
++}
++
++static void gdium_pwm_clock_drvexit(void)
++{
++	free_irq(CLOCK_PWM_IRQ, &gdium_pwm_clock_cevt);
++	pwm_disable(clock_pwm);
++	pwm_free(clock_pwm);
++}
++
++
++static int __devinit gdium_pwm_clock_init(void)
++{
++	int ret = gdium_pwm_clock_drvinit();
++
++	if (ret) {
++		pr_err("Fail to register gdium clock driver\n");
++		return ret;
++	}
++
++	return platform_driver_register(&gdium_pwm_clock_driver);
++}
++
++static void __exit gdium_pwm_clock_cleanup(void)
++{
++	gdium_pwm_clock_drvexit();
++	platform_driver_unregister(&gdium_pwm_clock_driver);
++}
++
++module_init(gdium_pwm_clock_init);
++module_exit(gdium_pwm_clock_cleanup);
++
++MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
++MODULE_DESCRIPTION("Gdium PWM clock driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:gdium-pwmclk");
+diff --git a/arch/mips/loongson/gdium/irq.c b/arch/mips/loongson/gdium/irq.c
+new file mode 100644
+index 0000000..2415d20
+--- /dev/null
++++ b/arch/mips/loongson/gdium/irq.c
+@@ -0,0 +1,55 @@
++/*
++ * Copyright (C) 2007 Lemote Inc.
++ * Author: Fuxin Zhang, zhangfx@lemote.com
++ *
++ * Copyright (c) 2010 yajin <yajin@vm-kernel.org>
++ *
++ *  This program is free software; you can redistribute  it and/or modify it
++ *  under  the terms of  the GNU General  Public License as published by the
++ *  Free Software Foundation;  either version 2 of the  License, or (at your
++ *  option) any later version.
++ */
++
++#include <linux/interrupt.h>
++#include <linux/module.h>
++
++#include <loongson.h>
++#include <machine.h>
++
++#define LOONGSON_TIMER_IRQ      (MIPS_CPU_IRQ_BASE + 7) /* cpu timer */
++#define LOONGSON_NORTH_BRIDGE_IRQ       (MIPS_CPU_IRQ_BASE + 6) /* bonito */
++#define LOONGSON_UART_IRQ       (MIPS_CPU_IRQ_BASE + 3) /* cpu serial port */
++
++void mach_irq_dispatch(unsigned int pending)
++{
++	if (pending & CAUSEF_IP7)
++		do_IRQ(LOONGSON_TIMER_IRQ);
++	else if (pending & CAUSEF_IP6) {        /* North Bridge, Perf counter */
++		do_perfcnt_IRQ();
++		bonito_irqdispatch();
++	} else if (pending & CAUSEF_IP3)        /* CPU UART */
++		do_IRQ(LOONGSON_UART_IRQ);
++#if defined(CONFIG_GDIUM_PWM_CLOCK) || defined(CONFIG_GDIUM_PWM_CLOCK_MODULE)
++	else if (pending & CAUSEF_IP4)		/* SM501 PWM clock */
++		do_IRQ(MIPS_CPU_IRQ_BASE + 4);
++#endif
++	else
++		spurious_interrupt();
++}
++
++static irqreturn_t ip6_action(int cpl, void *dev_id)
++{
++	return IRQ_HANDLED;
++}
++
++struct irqaction ip6_irqaction = {
++	.handler = ip6_action,
++	.name = "cascade",
++	.flags = IRQF_SHARED,
++};
++
++void __init mach_init_irq(void)
++{
++	/* setup north bridge irq (bonito) */
++	setup_irq(LOONGSON_NORTH_BRIDGE_IRQ, &ip6_irqaction);
++}
+diff --git a/arch/mips/loongson/gdium/platform.c b/arch/mips/loongson/gdium/platform.c
+new file mode 100644
+index 0000000..ffafba4
+--- /dev/null
++++ b/arch/mips/loongson/gdium/platform.c
+@@ -0,0 +1,135 @@
++/*
++ * Copyright (c) 2009 Philippe Vachon <philippe@cowpig.ca>
++ *
++ * This program is free software; you can redistribute  it and/or modify it
++ * under  the terms of  the GNU General  Public License as published by the
++ * Free Software Foundation;  either version 2 of the  License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/platform_device.h>
++#include <linux/pwm_backlight.h>
++#include <linux/i2c.h>
++#include <linux/i2c-gpio.h>
++
++#define GDIUM_GPIO_BASE 224
++
++static struct i2c_board_info __initdata sm502dev_i2c_devices[] = {
++	{
++		I2C_BOARD_INFO("lm75", 0x48),
++	},
++	{
++		I2C_BOARD_INFO("m41t83", 0x68),
++	},
++	{
++		I2C_BOARD_INFO("gdium-laptop", 0x40),
++	},
++};
++
++static int sm502dev_backlight_init(struct device *dev)
++{
++	/* Add gpio request stuff here */
++	return 0;
++}
++
++static void sm502dev_backlight_exit(struct device *dev)
++{
++	/* Add gpio free stuff here */
++}
++
++static struct platform_pwm_backlight_data backlight_data = {
++	.pwm_id		= 0,
++	.max_brightness	= 15,
++	.dft_brightness	= 8,
++	.pwm_period_ns	= 50000, /* 20 kHz */
++	.init		= sm502dev_backlight_init,
++	.exit		= sm502dev_backlight_exit,
++};
++
++static struct platform_device backlight = {
++	.name = "pwm-backlight",
++	.dev  = {
++		.platform_data = &backlight_data,
++	},
++	.id   = -1,
++};
++
++/*
++ * Warning this stunt is very dangerous
++ * as the sm501 gpio have dynamic numbers...
++ */
++/* bus 0 is the one for the ST7, DS75 etc... */
++static struct i2c_gpio_platform_data i2c_gpio0_data = {
++#if CONFIG_GDIUM_VERSION > 2
++	.sda_pin	= GDIUM_GPIO_BASE + 13,
++	.scl_pin	= GDIUM_GPIO_BASE + 6,
++#else
++	.sda_pin        = 192+15,
++	.scl_pin        = 192+14,
++#endif
++	.udelay		= 5,
++	.timeout	= HZ / 10,
++	.sda_is_open_drain = 0,
++	.scl_is_open_drain = 0,
++};
++
++static struct platform_device i2c_gpio0_device = {
++	.name	= "i2c-gpio",
++	.id	= 0,
++	.dev	= { .platform_data  = &i2c_gpio0_data, },
++};
++
++/* bus 1 is for the CRT/VGA external screen */
++static struct i2c_gpio_platform_data i2c_gpio1_data = {
++	.sda_pin	= GDIUM_GPIO_BASE + 10,
++	.scl_pin	= GDIUM_GPIO_BASE + 9,
++	.udelay		= 5,
++	.timeout	= HZ / 10,
++	.sda_is_open_drain = 0,
++	.scl_is_open_drain = 0,
++};
++
++static struct platform_device i2c_gpio1_device = {
++	.name	= "i2c-gpio",
++	.id	= 1,
++	.dev	= { .platform_data  = &i2c_gpio1_data, },
++};
++
++static struct platform_device gdium_clock = {
++	.name		= "gdium-pwmclk",
++	.id		= -1,
++};
++
++static struct platform_device *devices[] __initdata = {
++	&i2c_gpio0_device,
++	&i2c_gpio1_device,
++	&backlight,
++	&gdium_clock,
++};
++
++static int __init gdium_platform_devices_setup(void)
++{
++	int ret;
++
++	pr_info("Registering gdium platform devices\n");
++
++	ret = i2c_register_board_info(0, sm502dev_i2c_devices,
++		ARRAY_SIZE(sm502dev_i2c_devices));
++
++	if (ret != 0) {
++		pr_info("Error while registering platform devices: %d\n", ret);
++		return ret;
++	}
++
++	platform_add_devices(devices, ARRAY_SIZE(devices));
++
++	return 0;
++}
++
++/*
++ * some devices are on the pwm stuff which is behind the mfd which is
++ * behind the pci bus so arch_initcall can't work because too early
++ */
++late_initcall(gdium_platform_devices_setup);
+diff --git a/arch/mips/loongson/gdium/reset.c b/arch/mips/loongson/gdium/reset.c
+new file mode 100644
+index 0000000..8289f95
+--- /dev/null
++++ b/arch/mips/loongson/gdium/reset.c
+@@ -0,0 +1,22 @@
++/* Board-specific reboot/shutdown routines
++ *
++ * Copyright (C) 2010 yajin <yajin@vm-kernel.org>
++ *
++ * This program is free software; you can redistribute  it and/or modify it
++ * under  the terms of  the GNU General  Public License as published by the
++ * Free Software Foundation;  either version 2 of the  License, or (at your
++ * option) any later version.
++ */
++#include <loongson.h>
++
++void mach_prepare_shutdown(void)
++{
++	LOONGSON_GPIOIE &= ~(1<<1);
++	LOONGSON_GPIODATA |= (1<<1);
++}
++
++void mach_prepare_reboot(void)
++{
++	LOONGSON_GPIOIE &= ~(1<<2);
++	LOONGSON_GPIODATA &= ~(1<<2);
++}
+diff --git a/arch/mips/loongson/gdium/sm501-pwm.c b/arch/mips/loongson/gdium/sm501-pwm.c
+new file mode 100644
+index 0000000..5af3b23
+--- /dev/null
++++ b/arch/mips/loongson/gdium/sm501-pwm.c
+@@ -0,0 +1,465 @@
++/*
++ * SM501 PWM clock
++ * Copyright (C) 2009-2010 Arnaud Patard <apatard@mandriva.com>
++ *
++ * This program is free software; you can redistribute  it and/or modify it
++ * under  the terms of  the GNU General  Public License as published by the
++ * Free Software Foundation;  either version 2 of the  License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/interrupt.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/pwm.h>
++#include <linux/sm501.h>
++#include <linux/sm501-regs.h>
++#include <linux/debugfs.h>
++#include <linux/seq_file.h>
++
++static const char drv_name[] = "sm501-pwm";
++
++#define INPUT_CLOCK		96 /* MHz */
++#define PWM_COUNT		3
++
++#define SM501PWM_HIGH_COUNTER	(1<<20)
++#define SM501PWM_LOW_COUNTER	(1<<8)
++#define SM501PWM_CLOCK_DIVIDE	(1>>4)
++#define SM501PWM_IP		(1<<3)
++#define SM501PWM_I		(1<<2)
++#define SM501PWM_E		(1<<0)
++
++struct pwm_device {
++	struct list_head	node;
++	struct device		*dev;
++	void __iomem		*regs;
++	int			duty_ns;
++	int			period_ns;
++	char			enabled;
++	void			(*handler)(struct pwm_device *pwm);
++
++	const char		*label;
++	unsigned int		use_count;
++	unsigned int		pwm_id;
++};
++
++struct sm501pwm_info {
++	void __iomem	*regs;
++	int		irq;
++	struct resource *res;
++	struct device	*dev;
++	struct dentry	*debugfs;
++
++	struct pwm_device pwm[3];
++};
++
++int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
++{
++	unsigned int high, low, divider;
++	int divider1, divider2;
++	unsigned long long delay;
++
++	if (!pwm || !pwm->regs || period_ns == 0 || duty_ns > period_ns)
++		return -EINVAL;
++
++	/* Get delay
++	 * We're loosing some precision but multiplying then dividing
++	 * will overflow
++	 */
++	if (period_ns > 1000) {
++		delay = period_ns / 1000;
++		delay *= INPUT_CLOCK;
++	} else {
++		delay = period_ns * 96;
++		delay /= 1000;
++	}
++
++	/* Get the number of clock low and high */
++	high  = delay * duty_ns / period_ns;
++	low = delay - high;
++
++	/* Get divider to make 'low' and 'high' fit into 12 bits */
++	/* No need to say that the divider must be >= 0 */
++	divider1 = fls(low)-12;
++	divider2 = fls(high)-12;
++
++	if (divider1 < 0)
++		divider1 = 0;
++	if (divider2 < 0)
++		divider2 = 0;
++
++	divider = max(divider1, divider2);
++
++	low >>= divider;
++	high >>= divider;
++
++	pwm->duty_ns = duty_ns;
++	pwm->period_ns = period_ns;
++
++	writel((high<<20)|(low<<8)|(divider<<4), pwm->regs);
++	return 0;
++}
++EXPORT_SYMBOL(pwm_config);
++
++int pwm_enable(struct pwm_device *pwm)
++{
++	u32 reg;
++
++	if (!pwm)
++		return -EINVAL;
++
++	switch (pwm->pwm_id) {
++	case 0:
++		sm501_configure_gpio(pwm->dev->parent, 29, 1);
++		break;
++	case 1:
++		sm501_configure_gpio(pwm->dev->parent, 30, 1);
++		break;
++	case 2:
++		sm501_configure_gpio(pwm->dev->parent, 31, 1);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	reg = readl(pwm->regs);
++	reg |= (SM501PWM_IP | SM501PWM_E);
++	writel(reg, pwm->regs);
++	pwm->enabled = 1;
++
++	return 0;
++}
++EXPORT_SYMBOL(pwm_enable);
++
++void pwm_disable(struct pwm_device *pwm)
++{
++	u32 reg;
++
++	if (!pwm)
++		return;
++
++	reg = readl(pwm->regs);
++	reg &= ~(SM501PWM_IP | SM501PWM_E);
++	writel(reg, pwm->regs);
++
++	switch (pwm->pwm_id) {
++	case 0:
++		sm501_configure_gpio(pwm->dev->parent, 29, 0);
++		break;
++	case 1:
++		sm501_configure_gpio(pwm->dev->parent, 30, 0);
++		break;
++	case 2:
++		sm501_configure_gpio(pwm->dev->parent, 31, 0);
++		break;
++	default:
++		break;
++	}
++	pwm->enabled = 0;
++}
++EXPORT_SYMBOL(pwm_disable);
++
++static DEFINE_MUTEX(pwm_lock);
++static LIST_HEAD(pwm_list);
++
++struct pwm_device *pwm_request(int pwm_id, const char *label)
++{
++	struct pwm_device *pwm;
++	int found = 0;
++
++	mutex_lock(&pwm_lock);
++
++	list_for_each_entry(pwm, &pwm_list, node) {
++		if (pwm->pwm_id == pwm_id && pwm->use_count == 0) {
++			pwm->use_count++;
++			pwm->label = label;
++			found = 1;
++			break;
++		}
++	}
++
++	mutex_unlock(&pwm_lock);
++
++	return (found) ? pwm : NULL;
++}
++EXPORT_SYMBOL(pwm_request);
++
++void pwm_free(struct pwm_device *pwm)
++{
++	mutex_lock(&pwm_lock);
++
++	if (pwm->use_count) {
++		pwm->use_count--;
++		pwm->label = NULL;
++	} else
++		dev_warn(pwm->dev, "PWM device already freed\n");
++
++	mutex_unlock(&pwm_lock);
++}
++EXPORT_SYMBOL(pwm_free);
++
++int pwm_int_enable(struct pwm_device *pwm)
++{
++	unsigned long conf;
++
++	if (!pwm || !pwm->regs || !pwm->handler)
++		return -EINVAL;
++
++	conf = readl(pwm->regs);
++	conf |= SM501PWM_I;
++	writel(conf, pwm->regs);
++	return 0;
++}
++EXPORT_SYMBOL(pwm_int_enable);
++
++int pwm_int_disable(struct pwm_device *pwm)
++{
++	unsigned long conf;
++
++	if (!pwm || !pwm->regs || !pwm->handler)
++		return -EINVAL;
++
++	conf = readl(pwm->regs);
++	conf &= ~SM501PWM_I;
++	writel(conf, pwm->regs);
++	return 0;
++}
++EXPORT_SYMBOL(pwm_int_disable);
++
++int pwm_set_handler(struct pwm_device *pwm,
++		    void (*handler)(struct pwm_device *pwm))
++{
++	if (!pwm || !handler)
++		return -EINVAL;
++	pwm->handler = handler;
++	return 0;
++}
++EXPORT_SYMBOL(pwm_set_handler);
++
++static irqreturn_t sm501pwm_irq(int irq, void *dev_id)
++{
++	unsigned long value;
++	struct sm501pwm_info *info = (struct sm501pwm_info *)dev_id;
++	struct pwm_device *pwm;
++	int i;
++
++	value = sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 0);
++
++	/* Check is the interrupt is for us */
++	if (value & (1<<22)) {
++		for (i = 0 ; i < PWM_COUNT ; i++) {
++			/*
++			 * Find which pwm triggered the interrupt
++			 * and ack
++			 */
++			value = readl(info->regs + i*4);
++			if (value & SM501PWM_IP)
++				writel(value | SM501PWM_IP, info->regs + i*4);
++
++			pwm = &info->pwm[i];
++			if (pwm->handler)
++				pwm->handler(pwm);
++		}
++		return IRQ_HANDLED;
++	}
++
++	return IRQ_NONE;
++}
++
++static void add_pwm(int id, struct sm501pwm_info *info)
++{
++	struct pwm_device *pwm = &info->pwm[id];
++
++	pwm->use_count	= 0;
++	pwm->pwm_id	= id;
++	pwm->dev	= info->dev;
++	pwm->regs	= info->regs + id * 4;
++
++	mutex_lock(&pwm_lock);
++	list_add_tail(&pwm->node, &pwm_list);
++	mutex_unlock(&pwm_lock);
++}
++
++static void del_pwm(int id, struct sm501pwm_info *info)
++{
++	struct pwm_device *pwm = &info->pwm[id];
++
++	pwm->use_count  = 0;
++	pwm->pwm_id     = -1;
++	mutex_lock(&pwm_lock);
++	list_del(&pwm->node);
++	mutex_unlock(&pwm_lock);
++}
++
++/* Debug fs */
++static int sm501pwm_show(struct seq_file *s, void *p)
++{
++	struct pwm_device *pwm;
++
++	mutex_lock(&pwm_lock);
++	list_for_each_entry(pwm, &pwm_list, node) {
++		if (pwm->use_count) {
++			seq_printf(s, "pwm-%d (%12s) %d %d %s\n",
++					pwm->pwm_id, pwm->label,
++					pwm->duty_ns, pwm->period_ns,
++					pwm->enabled ? "on" : "off");
++			seq_printf(s, "       %08x\n", readl(pwm->regs));
++		}
++	}
++	mutex_unlock(&pwm_lock);
++
++	return 0;
++}
++
++static int sm501pwm_open(struct inode *inode, struct file *file)
++{
++	return single_open(file, sm501pwm_show, inode->i_private);
++}
++
++static const struct file_operations sm501pwm_fops = {
++	.open		= sm501pwm_open,
++	.read		= seq_read,
++	.llseek		= seq_lseek,
++	.release	= single_release,
++	.owner		= THIS_MODULE,
++};
++
++static int __init sm501pwm_probe(struct platform_device *pdev)
++{
++	struct sm501pwm_info *info;
++	struct device   *dev = &pdev->dev;
++	struct resource *res;
++	int ret = 0;
++	int res_len;
++	int i;
++
++	info = kzalloc(sizeof(struct sm501pwm_info), GFP_KERNEL);
++	if (!info) {
++		dev_err(dev, "Allocation failure\n");
++		ret = -ENOMEM;
++		goto err;
++	}
++	info->dev = dev;
++	platform_set_drvdata(pdev, info);
++
++	/* Get irq number */
++	info->irq = platform_get_irq(pdev, 0);
++	if (!info->irq) {
++		dev_err(dev, "no irq found\n");
++		ret = -ENODEV;
++		goto err_alloc;
++	}
++
++	/* Get regs address */
++	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++	if (res == NULL) {
++		dev_err(dev, "No memory resource found\n");
++		ret = -ENODEV;
++		goto err_alloc;
++	}
++	info->res = res;
++	res_len = (res->end - res->start)+1;
++
++	if (!request_mem_region(res->start, res_len, drv_name)) {
++		dev_err(dev, "Can't request iomem resource\n");
++		ret = -EBUSY;
++		goto err_alloc;
++	}
++
++	info->regs = ioremap(res->start, res_len);
++	if (!info->regs) {
++		dev_err(dev, "ioremap failed\n");
++		ret = -ENOMEM;
++		goto err_mem;
++	}
++
++	ret = request_irq(info->irq, sm501pwm_irq, IRQF_SHARED, drv_name, info);
++	if (ret != 0) {
++		dev_err(dev, "can't get irq\n");
++		goto err_map;
++	}
++
++
++	sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 1);
++
++	for (i = 0; i < 3; i++)
++		add_pwm(i, info);
++
++	dev_info(dev, "SM501 PWM Found at %lx irq %d\n",
++		 (unsigned long)info->res->start, info->irq);
++
++	info->debugfs = debugfs_create_file("pwm", S_IFREG | S_IRUGO,
++				NULL, info, &sm501pwm_fops);
++
++
++	return 0;
++
++err_map:
++	iounmap(info->regs);
++
++err_mem:
++	release_mem_region(res->start, res_len);
++
++err_alloc:
++	kfree(info);
++	platform_set_drvdata(pdev, NULL);
++err:
++	return ret;
++}
++
++static int sm501pwm_remove(struct platform_device *pdev)
++{
++	struct sm501pwm_info *info = platform_get_drvdata(pdev);
++	int i;
++
++	if (info->debugfs)
++		debugfs_remove(info->debugfs);
++
++	for (i = 0; i < 3; i++) {
++		pwm_disable(&info->pwm[i]);
++		del_pwm(i, info);
++	}
++
++	sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 0);
++	sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 1<<22);
++
++	free_irq(info->irq, info);
++	iounmap(info->regs);
++	release_mem_region(info->res->start,
++			   (info->res->end - info->res->start)+1);
++	kfree(info);
++	platform_set_drvdata(pdev, NULL);
++
++	return 0;
++}
++
++static struct platform_driver sm501pwm_driver = {
++	.probe		= sm501pwm_probe,
++	.remove		= sm501pwm_remove,
++	.driver		= {
++		.name	= drv_name,
++		.owner	= THIS_MODULE,
++	},
++};
++
++static int __devinit sm501pwm_init(void)
++{
++	return platform_driver_register(&sm501pwm_driver);
++}
++
++static void __exit sm501pwm_cleanup(void)
++{
++	platform_driver_unregister(&sm501pwm_driver);
++}
++
++module_init(sm501pwm_init);
++module_exit(sm501pwm_cleanup);
++
++MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
++MODULE_DESCRIPTION("SM501 PWM driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:sm501-pwm");
+diff --git a/arch/mips/pci/Makefile b/arch/mips/pci/Makefile
+index 2eda01e..8adaa3d 100644
+--- a/arch/mips/pci/Makefile
++++ b/arch/mips/pci/Makefile
+@@ -30,6 +30,7 @@ obj-$(CONFIG_LASAT)		+= pci-lasat.o
+ obj-$(CONFIG_MIPS_COBALT)	+= fixup-cobalt.o
+ obj-$(CONFIG_LEMOTE_FULOONG2E)	+= fixup-fuloong2e.o ops-loongson2.o
+ obj-$(CONFIG_LEMOTE_MACH2F)	+= fixup-lemote2f.o ops-loongson2.o
++obj-$(CONFIG_DEXXON_GDIUM)      += fixup-gdium.o ops-loongson2.o
+ obj-$(CONFIG_LOONGSON_MACH3X)	+= fixup-loongson3.o ops-loongson3.o
+ obj-$(CONFIG_MIPS_MALTA)	+= fixup-malta.o pci-malta.o
+ obj-$(CONFIG_PMC_MSP7120_GW)	+= fixup-pmcmsp.o ops-pmcmsp.o
+diff --git a/arch/mips/pci/fixup-gdium.c b/arch/mips/pci/fixup-gdium.c
+new file mode 100644
+index 0000000..b296220
+--- /dev/null
++++ b/arch/mips/pci/fixup-gdium.c
+@@ -0,0 +1,90 @@
++/*
++ * Copyright (C) 2010 yajin <yajin@vm-kernel.org>
++ *
++ *  This program is free software; you can redistribute  it and/or modify it
++ *  under  the terms of  the GNU General  Public License as published by the
++ *  Free Software Foundation;  either version 2 of the  License, or (at your
++ *  option) any later version.
++ */
++
++#include <linux/init.h>
++#include <linux/pci.h>
++
++#include <loongson.h>
++/*
++ * http://www.pcidatabase.com
++ * GDIUM has different PCI mapping
++ *  slot 13 (0x1814/0x0301) -> RaLink rt2561 Wireless-G PCI
++ *  slog 14 (0x126f/0x0501) -> sm501
++ *  slot 15 (0x1033/0x0035) -> NEC Dual OHCI controllers
++ *                             plus Single EHCI controller
++ *  slot 16 (0x10ec/0x8139) -> Realtek 8139c
++ *  slot 17 (0x1033/0x00e0) -> NEC USB 2.0 Host Controller
++ */
++
++#undef INT_IRQA
++#undef INT_IRQB
++#undef INT_IRQC
++#undef INT_IRQD
++#define INT_IRQA 36
++#define INT_IRQB 37
++#define INT_IRQC 38
++#define INT_IRQD 39
++
++int __init pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
++{
++	int irq = 0;
++
++	switch (slot) {
++	case 13:
++		irq = INT_IRQC + ((pin - 1) & 3);
++		break;
++	case 14:
++		irq = INT_IRQA;
++		break;
++	case 15:
++#if CONFIG_GDIUM_VERSION > 2
++		irq = INT_IRQB;
++#else
++		irq = INT_IRQA + ((pin - 1) & 3);
++#endif
++		break;
++	case 16:
++		irq = INT_IRQD;
++		break;
++#if CONFIG_GDIUM_VERSION > 2
++	case 17:
++		irq = INT_IRQC;
++		break;
++#endif
++	default:
++		pr_info(" strange pci slot number %d on gdium.\n", slot);
++		break;
++	}
++	return irq;
++}
++
++/* Do platform specific device initialization at pci_enable_device() time */
++int pcibios_plat_dev_init(struct pci_dev *dev)
++{
++	return 0;
++}
++
++/* Fixups for the USB host controllers */
++static void __init gdium_usb_host_fixup(struct pci_dev *dev)
++{
++	unsigned int val;
++	pci_read_config_dword(dev, 0xe0, &val);
++#if CONFIG_GDIUM_VERSION > 2
++	pci_write_config_dword(dev, 0xe0, (val & ~3) | 0x3);
++#else
++	pci_write_config_dword(dev, 0xe0, (val & ~7) | 0x5);
++	pci_write_config_dword(dev, 0xe4, 1<<5);
++#endif
++}
++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_USB,
++				gdium_usb_host_fixup);
++#if CONFIG_GDIUM_VERSION > 2
++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_CT_65550,
++				gdium_usb_host_fixup);
++#endif
+diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
+index 15338af..e839ebf 100644
+--- a/drivers/hid/Kconfig
++++ b/drivers/hid/Kconfig
+@@ -864,6 +864,13 @@ config HID_ZYDACRON
+ 	---help---
+ 	Support for Zydacron remote control.
+ 
++config HID_GDIUM
++	bool "Gdium Fn keys support" if EMBEDDED
++	depends on USB_HID && DEXXON_GDIUM
++	default !EMBEDDED
++	---help---
++	Support for Functions keys available on Gdiums.
++
+ config HID_SENSOR_HUB
+ 	tristate "HID Sensors framework support"
+ 	depends on HID && HAS_IOMEM
+diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
+index e4a21df..51fc941 100644
+--- a/drivers/hid/Makefile
++++ b/drivers/hid/Makefile
+@@ -98,6 +98,7 @@ obj-$(CONFIG_HID_ZYDACRON)	+= hid-zydacron.o
+ wacom-objs			:= wacom_wac.o wacom_sys.o
+ obj-$(CONFIG_HID_WACOM)		+= wacom.o
+ obj-$(CONFIG_HID_WALTOP)	+= hid-waltop.o
++obj-$(CONFIG_HID_GDIUM)		+= hid-gdium.o
+ obj-$(CONFIG_HID_WIIMOTE)	+= hid-wiimote.o
+ obj-$(CONFIG_HID_SENSOR_HUB)	+= hid-sensor-hub.o
+ obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR)	+= hid-sensor-custom.o
+diff --git a/drivers/hid/hid-gdium.c b/drivers/hid/hid-gdium.c
+new file mode 100644
+index 0000000..67cc095
+--- /dev/null
++++ b/drivers/hid/hid-gdium.c
+@@ -0,0 +1,210 @@
++/*
++ * hid-gdium  --  Gdium laptop function keys
++ *
++ * Arnaud Patard <apatard@mandriva.com>
++ *
++ * Based on hid-apple.c
++ *
++ *  This program is free software; you can redistribute  it and/or modify it
++ *  under  the terms of  the GNU General  Public License as published by the
++ *  Free Software Foundation;  either version 2 of the  License, or (at your
++ *  option) any later version.
++ */
++
++
++#include <linux/device.h>
++#include <linux/hid.h>
++#include <linux/module.h>
++#include <linux/usb.h>
++
++#include "hid-ids.h"
++
++#define GDIUM_FN_ON	1
++
++static int fnmode = GDIUM_FN_ON;
++module_param(fnmode, int, 0644);
++MODULE_PARM_DESC(fnmode, "Mode of fn key on Gdium (0 = disabled, 1 = Enabled)");
++
++struct gdium_data {
++	unsigned int fn_on;
++};
++
++
++struct gdium_key_translation {
++	u16 from;
++	u16 to;
++};
++
++static struct gdium_key_translation gdium_fn_keys[] = {
++	{ KEY_F1,	KEY_CAMERA },
++	{ KEY_F2,	KEY_CONNECT },
++	{ KEY_F3,	KEY_MUTE },
++	{ KEY_F4,	KEY_VOLUMEUP},
++	{ KEY_F5,	KEY_VOLUMEDOWN },
++	{ KEY_F6,	KEY_SWITCHVIDEOMODE },
++	{ KEY_F7,	KEY_F19 }, /* F7+12. Have to use existant keycodes */
++	{ KEY_F8,	KEY_BRIGHTNESSUP },
++	{ KEY_F9,	KEY_BRIGHTNESSDOWN },
++	{ KEY_F10,	KEY_SLEEP },
++	{ KEY_F11,	KEY_PROG1 },
++	{ KEY_F12,	KEY_PROG2 },
++	{ KEY_UP,	KEY_PAGEUP },
++	{ KEY_DOWN,	KEY_PAGEDOWN },
++	{ KEY_INSERT,	KEY_NUMLOCK },
++	{ KEY_DELETE,	KEY_SCROLLLOCK },
++	{ KEY_T,	KEY_STOPCD },
++	{ KEY_F,	KEY_PREVIOUSSONG },
++	{ KEY_H,	KEY_NEXTSONG },
++	{ KEY_G,        KEY_PLAYPAUSE },
++	{ }
++};
++
++static struct gdium_key_translation *gdium_find_translation(
++		struct gdium_key_translation *table, u16 from)
++{
++	struct gdium_key_translation *trans;
++
++	/* Look for the translation */
++	for (trans = table; trans->from; trans++)
++		if (trans->from == from)
++			return trans;
++	return NULL;
++}
++
++static int hidinput_gdium_event(struct hid_device *hid, struct input_dev *input,
++		struct hid_usage *usage, __s32 value)
++{
++	struct gdium_data *data = hid_get_drvdata(hid);
++	struct gdium_key_translation *trans;
++	int do_translate;
++
++	if (usage->type != EV_KEY)
++		return 0;
++
++	if ((usage->code == KEY_FN)) {
++		data->fn_on = !!value;
++		input_event(input, usage->type, usage->code, value);
++		return 1;
++	}
++
++	if (fnmode) {
++		trans = gdium_find_translation(gdium_fn_keys, usage->code);
++		if (trans) {
++			do_translate = data->fn_on;
++			if (do_translate) {
++				input_event(input, usage->type, trans->to, value);
++				return 1;
++			}
++		}
++	}
++
++	return 0;
++}
++
++static int gdium_input_event(struct hid_device *hdev, struct hid_field *field,
++			struct hid_usage *usage, __s32 value)
++{
++	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput || !usage->type)
++		return 0;
++
++	if (hidinput_gdium_event(hdev, field->hidinput->input, usage, value))
++		return 1;
++
++	return 0;
++}
++
++
++static void gdium_input_setup(struct input_dev *input)
++{
++	struct gdium_key_translation *trans;
++
++	set_bit(KEY_NUMLOCK, input->keybit);
++
++	/* Enable all needed keys */
++	for (trans = gdium_fn_keys; trans->from; trans++)
++		set_bit(trans->to, input->keybit);
++}
++
++static int gdium_input_mapping(struct hid_device *hdev, struct hid_input *hi,
++		struct hid_field *field, struct hid_usage *usage,
++		unsigned long **bit, int *max)
++{
++	if (((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD)
++			&& ((usage->hid & HID_USAGE) == 0x82)) {
++		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_FN);
++		gdium_input_setup(hi->input);
++		return 1;
++	}
++	return 0;
++}
++
++static int gdium_input_probe(struct hid_device *hdev, const struct hid_device_id *id)
++{
++	struct gdium_data *data;
++	int ret;
++
++	data = kzalloc(sizeof(*data), GFP_KERNEL);
++	if (!data) {
++		dev_err(&hdev->dev, "can't alloc gdium keyboard data\n");
++		return -ENOMEM;
++	}
++
++	hid_set_drvdata(hdev, data);
++
++	ret = hid_parse(hdev);
++	if (ret) {
++		dev_err(&hdev->dev, "parse failed\n");
++		goto err_free;
++	}
++
++	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
++	if (ret) {
++		dev_err(&hdev->dev, "hw start failed\n");
++		goto err_free;
++	}
++
++	return 0;
++err_free:
++	kfree(data);
++	return ret;
++}
++static void gdium_input_remove(struct hid_device *hdev)
++{
++	hid_hw_stop(hdev);
++	kfree(hid_get_drvdata(hdev));
++}
++
++static const struct hid_device_id gdium_input_devices[] = {
++	{ HID_USB_DEVICE(USB_VENDOR_ID_GDIUM, USB_DEVICE_ID_GDIUM) },
++	{}
++};
++MODULE_DEVICE_TABLE(hid, gdium_input_devices);
++
++static struct hid_driver gdium_input_driver = {
++	.name = "gdium-fnkeys",
++	.id_table = gdium_input_devices,
++	.probe = gdium_input_probe,
++	.remove = gdium_input_remove,
++	.event = gdium_input_event,
++	.input_mapping = gdium_input_mapping,
++};
++
++static int gdium_input_init(void)
++{
++	int ret;
++
++	ret = hid_register_driver(&gdium_input_driver);
++	if (ret)
++		 pr_err("can't register gdium keyboard driver\n");
++
++	return ret;
++}
++static void gdium_input_exit(void)
++{
++	hid_unregister_driver(&gdium_input_driver);
++}
++
++module_init(gdium_input_init);
++module_exit(gdium_input_exit);
++MODULE_LICENSE("GPL");
++
+diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
+index 7ce93d9..da52392 100644
+--- a/drivers/hid/hid-ids.h
++++ b/drivers/hid/hid-ids.h
+@@ -1031,6 +1031,9 @@
+ #define USB_VENDOR_ID_ZYTRONIC		0x14c8
+ #define USB_DEVICE_ID_ZYTRONIC_ZXY100	0x0005
+ 
++#define USB_VENDOR_ID_GDIUM		0x04B4
++#define USB_DEVICE_ID_GDIUM		0xe001
++
+ #define USB_VENDOR_ID_PRIMAX	0x0461
+ #define USB_DEVICE_ID_PRIMAX_MOUSE_4D22	0x4d22
+ #define USB_DEVICE_ID_PRIMAX_KEYBOARD	0x4e05
+diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c
+index 91077ef..2cfd9ff 100644
+--- a/drivers/mfd/sm501.c
++++ b/drivers/mfd/sm501.c
+@@ -58,7 +58,7 @@ struct sm501_gpio {
+ struct sm501_gpio {
+ 	/* no gpio support, empty definition for sm501_devdata. */
+ };
+-#endif
++#endif	/* CONFIG_MFD_SM501_GPIO */
+ 
+ struct sm501_devdata {
+ 	spinlock_t			 reg_lock;
+@@ -1124,6 +1124,22 @@ static inline int sm501_gpio_isregistered(struct sm501_devdata *sm)
+ {
+ 	return sm->gpio.registered;
+ }
++
++void sm501_configure_gpio(struct device *dev, unsigned int gpio, unsigned
++		char mode)
++{
++	unsigned long set, reg, offset = gpio;
++
++	if (offset >= 32) {
++		reg = SM501_GPIO63_32_CONTROL;
++		offset = gpio - 32;
++	} else
++		reg = SM501_GPIO31_0_CONTROL;
++
++	set = mode ? 1 << offset : 0;
++
++	sm501_modify_reg(dev, reg, set, 0);
++}
+ #else
+ static inline int sm501_register_gpio(struct sm501_devdata *sm)
+ {
+@@ -1143,7 +1159,13 @@ static inline int sm501_gpio_isregistered(struct sm501_devdata *sm)
+ {
+ 	return 0;
+ }
+-#endif
++
++void sm501_configure_gpio(struct device *dev, unsigned int gpio,
++			 unsigned char mode)
++{
++}
++#endif	/* CONFIG_MFD_SM501_GPIO */
++EXPORT_SYMBOL_GPL(sm501_configure_gpio);
+ 
+ static int sm501_register_gpio_i2c_instance(struct sm501_devdata *sm,
+ 					    struct sm501_platdata_gpio_i2c *iic)
+@@ -1198,6 +1220,20 @@ static int sm501_register_gpio_i2c(struct sm501_devdata *sm,
+ 	return 0;
+ }
+ 
++/* register sm501 PWM device */
++static int sm501_register_pwm(struct sm501_devdata *sm)
++{
++	struct platform_device *pdev;
++
++	pdev = sm501_create_subdev(sm, "sm501-pwm", 2, 0);
++	if (!pdev)
++		return -ENOMEM;
++	sm501_create_subio(sm, &pdev->resource[0], 0x10020, 0xC);
++	sm501_create_irq(sm, &pdev->resource[1]);
++
++	return sm501_register_device(sm, pdev);
++}
++
+ /* sm501_dbg_regs
+  *
+  * Debug attribute to attach to parent device to show core registers
+@@ -1356,6 +1392,8 @@ static int sm501_init_dev(struct sm501_devdata *sm)
+ 			sm501_register_uart(sm, idata->devices);
+ 		if (idata->devices & SM501_USE_GPIO)
+ 			sm501_register_gpio(sm);
++		if (idata->devices & SM501_USE_PWM)
++			sm501_register_pwm(sm);
+ 	}
+ 
+ 	if (pdata && pdata->gpio_i2c != NULL && pdata->gpio_i2c_nr > 0) {
+@@ -1542,10 +1580,15 @@ static struct sm501_initdata sm501_pci_initdata = {
+ 	.devices	= SM501_USE_ALL,
+ 
+ 	/* Errata AB-3 says that 72MHz is the fastest available
+-	 * for 33MHZ PCI with proper bus-mastering operation */
+-
++	 * for 33MHZ PCI with proper bus-mastering operation
++	 * For gdium, it works under 84&112M clock freq.*/
++#ifdef CONFIG_DEXXON_GDIUM
++	.mclk		= 84 * MHZ,
++	.m1xclk		= 112 * MHZ,
++#else
+ 	.mclk		= 72 * MHZ,
+ 	.m1xclk		= 144 * MHZ,
++#endif
+ };
+ 
+ static struct sm501_platdata_fbsub sm501_pdata_fbsub = {
+diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
+index c80714b..70a607d 100644
+--- a/drivers/platform/mips/Kconfig
++++ b/drivers/platform/mips/Kconfig
+@@ -47,6 +47,17 @@ config LEMOTE_LYNLOONG2F
+ 	  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.
++
+ config MIPS_ACPI
+ 	bool
+ 	default y if LOONGSON_MACH3X
+diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile
+index 0e21bfb..0bc93de 100644
+--- a/drivers/platform/mips/Makefile
++++ b/drivers/platform/mips/Makefile
+@@ -7,5 +7,7 @@ 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
++
+ obj-$(CONFIG_MIPS_ACPI) += acpi_init.o
+ obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o
+diff --git a/drivers/platform/mips/gdium_laptop.c b/drivers/platform/mips/gdium_laptop.c
+new file mode 100644
+index 0000000..41a65ad
+--- /dev/null
++++ b/drivers/platform/mips/gdium_laptop.c
+@@ -0,0 +1,927 @@
++/*
++ * gdium_laptop  --  Gdium laptop extras
++ *
++ * Arnaud Patard <apatard@mandriva.com>
++ *
++ *  This program is free software; you can redistribute  it and/or modify it
++ *  under  the terms of  the GNU General  Public License as published by the
++ *  Free Software Foundation;  either version 2 of the  License, or (at your
++ *  option) any later version.
++ *
++ */
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/platform_device.h>
++#include <linux/input-polldev.h>
++#include <linux/debugfs.h>
++#include <linux/seq_file.h>
++#include <linux/i2c.h>
++#include <linux/mutex.h>
++#include <linux/power_supply.h>
++#include <linux/workqueue.h>
++#include <linux/delay.h>
++#include <linux/slab.h>
++#include <asm/gpio.h>
++
++/* For input device */
++#define SCAN_INTERVAL		150
++
++/* For battery status */
++#define BAT_SCAN_INTERVAL	500
++
++#define EC_FIRM_VERSION		0
++
++#if CONFIG_GDIUM_VERSION > 2
++#define EC_REG_BASE		1
++#else
++#define EC_REG_BASE		0
++#endif
++
++#define EC_STATUS		(EC_REG_BASE+0)
++#define EC_STATUS_LID		(1<<0)
++#define EC_STATUS_PWRBUT	(1<<1)
++#define EC_STATUS_BATID		(1<<2)		/* this bit has no real meaning on v2.         */
++						/* Same as EC_STATUS_ADAPT                     */
++						/* but on v3 it's BATID which mean bat present */
++#define EC_STATUS_SYS_POWER	(1<<3)
++#define EC_STATUS_WLAN		(1<<4)
++#define EC_STATUS_ADAPT		(1<<5)
++
++#define EC_CTRL			(EC_REG_BASE+1)
++#define EC_CTRL_DDR_CLK		(1<<0)
++#define EC_CTRL_CHARGE_LED	(1<<1)
++#define EC_CTRL_BEEP		(1<<2)
++#define EC_CTRL_SUSB		(1<<3)	/* memory power */
++#define EC_CTRL_TRICKLE		(1<<4)
++#define EC_CTRL_WLAN_EN		(1<<5)
++#define EC_CTRL_SUSC		(1<<6) /* main power */
++#define EC_CTRL_CHARGE_EN	(1<<7)
++
++#define EC_BAT_LOW		(EC_REG_BASE+2)
++#define EC_BAT_HIGH		(EC_REG_BASE+3)
++
++#define EC_SIGN			(EC_REG_BASE+4)
++#define EC_SIGN_OS		0xAE /* write 0xae to control pm stuff */
++#define EC_SIGN_EC		0x00 /* write 0x00 to let the st7 manage pm stuff */
++
++#if 0
++#define EC_TEST			(EC_REG_BASE+5) /* Depending on firmware version this register */
++						/* may be the programmation register so don't play */
++						/* with it */
++#endif
++
++#define BAT_VOLT_PRESENT	500000	/* Min voltage to consider battery present uV */
++#define BAT_MIN			7000000	/* Min battery voltage in uV */
++#define BAT_MIN_MV		7000	/* Min battery voltage in mV */
++#define BAT_TRICKLE_EN		8000000	/* Charging at 1.4A before  8.0V and then charging at 0.25A */
++#define BAT_MAX			7950000	/* Max battery voltage ~8V in V */
++#define BAT_MAX_MV		7950	/* Max battery voltage ~8V in V */
++#define BAT_READ_ERROR		300000	/* battery read error of 0.3V */
++#define BAT_READ_ERROR_MV	300	/* battery read error of 0.3V */
++
++#define SM502_WLAN_ON		(224+16)/* SM502 GPIO16 may be used on gdium v2 (v3?) as wlan_on */
++					/* when R422 is connected */
++
++static unsigned char verbose;
++static unsigned char gpio16;
++static unsigned char ec;
++module_param(verbose, byte, S_IRUGO | S_IWUSR);
++MODULE_PARM_DESC(verbose, "Add some debugging messages");
++module_param(gpio16, byte, S_IRUGO);
++MODULE_PARM_DESC(gpio16, "Enable wlan_on signal on SM502");
++module_param(ec, byte, S_IRUGO);
++MODULE_PARM_DESC(ec, "Let the ST7 handle the battery (default OS)");
++
++struct gdium_laptop_data {
++	struct i2c_client		*client;
++	struct input_polled_dev		*input_polldev;
++	struct dentry			*debugfs;
++	struct mutex			mutex;
++	struct platform_device		*bat_pdev;
++	struct power_supply		gdium_ac;
++	struct power_supply		gdium_battery;
++	struct workqueue_struct		*workqueue;
++	struct delayed_work		work;
++	char				charge_cmd;
++	/* important registers value */
++	char				status;
++	char				ctrl;
++	/* mV */
++	int				battery_level;
++	char				version;
++};
++
++/**********************************************************************/
++/* Low level I2C functions                                            */
++/* All are supposed to be called with mutex held                      */
++/**********************************************************************/
++/*
++ * Return battery voltage in mV
++ * >= 0 battery voltage
++ * < 0 error
++ */
++static s32 ec_read_battery(struct i2c_client *client)
++{
++	unsigned char bat_low, bat_high;
++	s32 data;
++	unsigned int ret;
++
++	/*
++	 * a = battery high
++	 * b = battery low
++	 * bat = a << 2 | b & 0x03;
++	 * battery voltage = (bat / 1024) * 5 * 2
++	 */
++	data = i2c_smbus_read_byte_data(client, EC_BAT_LOW);
++	if (data < 0) {
++		dev_err(&client->dev, "ec_read_bat: read bat_low failed\n");
++		return data;
++	}
++	bat_low = data & 0xff;
++	if (verbose)
++		dev_info(&client->dev, "bat_low %x\n", bat_low);
++
++	data = i2c_smbus_read_byte_data(client, EC_BAT_HIGH);
++	if (data < 0) {
++		dev_err(&client->dev, "ec_read_bat: read bat_high failed\n");
++		return data;
++	}
++	bat_high = data & 0xff;
++	if (verbose)
++		dev_info(&client->dev, "bat_high %x\n", bat_high);
++
++	ret = (bat_high << 2) | (bat_low & 3);
++	/*
++	 * mV
++	 */
++	ret = (ret * 5 * 2) * 1000 / 1024;
++
++	return ret;
++}
++
++static s32 ec_read_version(struct i2c_client *client)
++{
++#if CONFIG_GDIUM_VERSION > 2
++	return i2c_smbus_read_byte_data(client, EC_FIRM_VERSION);
++#else
++	return 0;
++#endif
++}
++
++static s32 ec_read_status(struct i2c_client *client)
++{
++	return i2c_smbus_read_byte_data(client, EC_STATUS);
++}
++
++static s32 ec_read_ctrl(struct i2c_client *client)
++{
++	return i2c_smbus_read_byte_data(client, EC_CTRL);
++}
++
++static s32 ec_write_ctrl(struct i2c_client *client, unsigned char newvalue)
++{
++	return i2c_smbus_write_byte_data(client, EC_CTRL, newvalue);
++}
++
++static s32 ec_read_sign(struct i2c_client *client)
++{
++	return i2c_smbus_read_byte_data(client, EC_SIGN);
++}
++
++static s32 ec_write_sign(struct i2c_client *client, unsigned char sign)
++{
++	unsigned char value;
++	s32 ret;
++
++	ret = i2c_smbus_write_byte_data(client, EC_SIGN, sign);
++	if (ret < 0) {
++		dev_err(&client->dev, "ec_set_control: write failed\n");
++		return ret;
++	}
++
++	value = ec_read_sign(client);
++	if (value != sign) {
++		dev_err(&client->dev, "Failed to set control to %s\n",
++				sign == EC_SIGN_OS ? "OS" : "EC");
++		return -EIO;
++	}
++
++	return 0;
++}
++
++#if 0
++static int ec_power_off(struct i2c_client *client)
++{
++	char value;
++	int ret;
++
++	value = ec_read_ctrl(client);
++	if (value < 0) {
++		dev_err(&client->dev, "ec_power_off: read failed\n");
++		return value;
++	}
++	value &= ~(EC_CTRL_SUSB | EC_CTRL_SUSC);
++	ret = ec_write_ctrl(client, value);
++	if (ret < 0) {
++		dev_err(&client->dev, "ec_power_off: write failed\n");
++		return ret;
++	}
++
++	return 0;
++}
++#endif
++
++static s32 ec_wlan_status(struct i2c_client *client)
++{
++	s32 value;
++
++	value = ec_read_ctrl(client);
++	if (value < 0)
++		return value;
++
++	return (value & EC_CTRL_WLAN_EN) ? 1 : 0;
++}
++
++static s32 ec_wlan_en(struct i2c_client *client, int on)
++{
++	s32 value;
++
++	value = ec_read_ctrl(client);
++	if (value < 0)
++		return value;
++
++	value &= ~EC_CTRL_WLAN_EN;
++	if (on)
++		value |= EC_CTRL_WLAN_EN;
++
++	return ec_write_ctrl(client, value&0xff);
++}
++
++#if 0
++static s32 ec_led_status(struct i2c_client *client)
++{
++	s32 value;
++
++	value = ec_read_ctrl(client);
++	if (value < 0)
++		return value;
++
++	return (value & EC_CTRL_CHARGE_LED) ? 1 : 0;
++}
++#endif
++
++/* Changing the charging led status has never worked */
++static s32 ec_led_en(struct i2c_client *client, int on)
++{
++#if 0
++	s32 value;
++
++	value = ec_read_ctrl(client);
++	if (value < 0)
++		return value;
++
++	value &= ~EC_CTRL_CHARGE_LED;
++	if (on)
++		value |= EC_CTRL_CHARGE_LED;
++	return ec_write_ctrl(client, value&0xff);
++#else
++	return 0;
++#endif
++}
++
++static s32 ec_charge_en(struct i2c_client *client, int on, int trickle)
++{
++	s32 value;
++	s32 set = 0;
++
++	value = ec_read_ctrl(client);
++	if (value < 0)
++		return value;
++
++	if (on)
++		set |= EC_CTRL_CHARGE_EN;
++	if (trickle)
++		set |= EC_CTRL_TRICKLE;
++
++	/* Be clever : don't change values if you don't need to */
++	if ((value & (EC_CTRL_CHARGE_EN | EC_CTRL_TRICKLE)) == set)
++		return 0;
++
++	value &= ~(EC_CTRL_CHARGE_EN | EC_CTRL_TRICKLE);
++	value |= set;
++	ec_led_en(client, on);
++	return ec_write_ctrl(client, (unsigned char)(value&0xff));
++
++}
++
++/**********************************************************************/
++/* Input functions                                                    */
++/**********************************************************************/
++struct gdium_keys {
++	int last_state;
++	int key_code;
++	int mask;
++	int type;
++};
++
++static struct gdium_keys gkeys[] = {
++	{
++		.key_code	= KEY_WLAN,
++		.mask		= EC_STATUS_WLAN,
++		.type		= EV_KEY,
++	},
++	{
++		.key_code	= KEY_POWER,
++		.mask		= EC_STATUS_PWRBUT,
++		.type		= EV_KEY, /*EV_PWR,*/
++	},
++	{
++		.key_code	= SW_LID,
++		.mask		= EC_STATUS_LID,
++		.type		= EV_SW,
++	},
++};
++
++static void gdium_laptop_keys_poll(struct input_polled_dev *dev)
++{
++	int state, i;
++	struct gdium_laptop_data *data = dev->private;
++	struct i2c_client *client = data->client;
++	struct input_dev *input = dev->input;
++	s32 status;
++
++	mutex_lock(&data->mutex);
++	status = ec_read_status(client);
++	mutex_unlock(&data->mutex);
++
++	if (status < 0) {
++		/*
++		 * Don't know exactly  which version of the firmware
++		 * has this bug but when the power button is pressed
++		 * there are i2c read errors :(
++		 */
++		if ((data->version >= 0x13) && !gkeys[1].last_state) {
++			input_event(input, EV_KEY, KEY_POWER, 1);
++			input_sync(input);
++			gkeys[1].last_state = 1;
++		}
++		return;
++	}
++
++	for (i = 0; i < ARRAY_SIZE(gkeys); i++) {
++		state = status & gkeys[i].mask;
++		if (state != gkeys[i].last_state) {
++			gkeys[i].last_state = state;
++			/* for power key, we want power & key press/release event */
++			if (gkeys[i].type == EV_PWR) {
++				input_event(input, EV_KEY, gkeys[i].key_code, !!state);
++				input_sync(input);
++			}
++			/* Disable wifi on key press but not key release */
++			/*
++			 * On firmware >= 0x13 the EC_STATUS_WLAN has it's
++			 * original meaning of Wifi status and no more the
++			 * wifi button status so we have to ignore the event
++			 * on theses versions
++			 */
++			if (state && (gkeys[i].key_code == KEY_WLAN) && (data->version < 0x13)) {
++				mutex_lock(&data->mutex);
++				ec_wlan_en(client, !ec_wlan_status(client));
++				if (gpio16)
++					gpio_set_value(SM502_WLAN_ON, !ec_wlan_status(client));
++				mutex_unlock(&data->mutex);
++			}
++
++			input_event(input, gkeys[i].type, gkeys[i].key_code, !!state);
++			input_sync(input);
++		}
++	}
++}
++
++static int gdium_laptop_input_init(struct gdium_laptop_data *data)
++{
++	struct i2c_client *client = data->client;
++	struct input_dev *input;
++	int ret, i;
++
++	data->input_polldev = input_allocate_polled_device();
++	if (!data->input_polldev) {
++		ret = -ENOMEM;
++		goto err;
++	}
++
++	input = data->input_polldev->input;
++	input->evbit[0] = BIT(EV_KEY) | BIT_MASK(EV_PWR) | BIT_MASK(EV_SW);
++	data->input_polldev->poll = gdium_laptop_keys_poll;
++	data->input_polldev->poll_interval = SCAN_INTERVAL;
++	data->input_polldev->private = data;
++	input->name = "gdium-keys";
++	input->dev.parent = &client->dev;
++
++	input->id.bustype = BUS_HOST;
++	input->id.vendor = 0x0001;
++	input->id.product = 0x0001;
++	input->id.version = 0x0100;
++
++	for (i = 0; i < ARRAY_SIZE(gkeys); i++)
++		input_set_capability(input, gkeys[i].type, gkeys[i].key_code);
++
++	ret = input_register_polled_device(data->input_polldev);
++	if (ret) {
++		dev_err(&client->dev, "Unable to register button device\n");
++		goto err_poll_dev;
++	}
++
++	return 0;
++
++err_poll_dev:
++	input_free_polled_device(data->input_polldev);
++err:
++	return ret;
++}
++
++static void gdium_laptop_input_exit(struct gdium_laptop_data *data)
++{
++	input_unregister_polled_device(data->input_polldev);
++	input_free_polled_device(data->input_polldev);
++}
++
++/**********************************************************************/
++/* Battery management                                                 */
++/**********************************************************************/
++static int gdium_ac_get_props(struct power_supply *psy,
++		enum power_supply_property psp,
++		union power_supply_propval *val)
++{
++	char status;
++	struct gdium_laptop_data *data = container_of(psy, struct gdium_laptop_data, gdium_ac);
++	int ret = 0;
++
++	if (!data) {
++		pr_err("gdium-ac: gdium_laptop_data not found\n");
++		return -EINVAL;
++	}
++
++	status = data->status;
++	switch (psp) {
++	case POWER_SUPPLY_PROP_ONLINE:
++		val->intval = !!(status & EC_STATUS_ADAPT);
++		break;
++	default:
++		ret = -EINVAL;
++		break;
++	}
++
++	return ret;
++}
++
++#undef RET
++#define RET (val->intval)
++
++static int gdium_battery_get_props(struct power_supply *psy,
++		enum power_supply_property psp,
++		union power_supply_propval *val)
++{
++	char status, ctrl;
++	struct gdium_laptop_data *data = container_of(psy, struct gdium_laptop_data, gdium_battery);
++	int percentage_capacity = 0, charge_now = 0, time_to_empty = 0;
++	int ret = 0, tmp;
++
++	if (!data) {
++		pr_err("gdium-battery: gdium_laptop_data not found\n");
++		return -EINVAL;
++	}
++
++	status = data->status;
++	ctrl   = data->ctrl;
++	switch (psp) {
++	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
++		/* uAh */
++		RET = 5000000;
++		break;
++	case POWER_SUPPLY_PROP_CURRENT_NOW:
++	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
++		/* This formula is gotten by gnuplot with the statistic data */
++		time_to_empty = (data->battery_level - BAT_MIN_MV + BAT_READ_ERROR_MV) * 113 - 29870;
++		if (psp == POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW) {
++			/* seconds */
++			RET = time_to_empty / 10;
++			break;
++		}
++		/* fall through */
++	case POWER_SUPPLY_PROP_CHARGE_NOW:
++	case POWER_SUPPLY_PROP_CAPACITY: {
++		tmp = data->battery_level * 1000;
++		/* > BAT_MIN to avoid negative values */
++		percentage_capacity = 0;
++		if ((status & EC_STATUS_BATID) && (tmp > BAT_MIN))
++			percentage_capacity = (tmp-BAT_MIN)*100/(BAT_MAX-BAT_MIN);
++
++		if (percentage_capacity > 100)
++			percentage_capacity = 100;
++
++		if (psp == POWER_SUPPLY_PROP_CAPACITY) {
++			RET = percentage_capacity;
++			break;
++		}
++		charge_now = 50000 * percentage_capacity;
++		if (psp == POWER_SUPPLY_PROP_CHARGE_NOW) {
++			/* uAh */
++			RET = charge_now;
++			break;
++		}
++	}	/* fall through */
++	case POWER_SUPPLY_PROP_STATUS: {
++		if (status & EC_STATUS_ADAPT)
++			if (ctrl & EC_CTRL_CHARGE_EN)
++				RET = POWER_SUPPLY_STATUS_CHARGING;
++			else
++				RET = POWER_SUPPLY_STATUS_NOT_CHARGING;
++		else
++			RET = POWER_SUPPLY_STATUS_DISCHARGING;
++
++		if (psp == POWER_SUPPLY_PROP_STATUS)
++			break;
++		/* mAh -> µA */
++		switch (RET) {
++		case POWER_SUPPLY_STATUS_CHARGING:
++			RET = -(data->charge_cmd == 2) ? 1400000 : 250000;
++			break;
++		case POWER_SUPPLY_STATUS_DISCHARGING:
++			RET = charge_now / time_to_empty * 36000;
++			break;
++		case POWER_SUPPLY_STATUS_NOT_CHARGING:
++		default:
++			RET = 0;
++			break;
++		}
++	} break;
++	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
++		RET = BAT_MAX+BAT_READ_ERROR;
++		break;
++	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
++		RET = BAT_MIN-BAT_READ_ERROR;
++		break;
++	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
++		/* mV -> uV */
++		RET = data->battery_level * 1000;
++		break;
++	case POWER_SUPPLY_PROP_PRESENT:
++#if CONFIG_GDIUM_VERSION > 2
++		RET = !!(status & EC_STATUS_BATID);
++#else
++		RET = !!(data->battery_level > BAT_VOLT_PRESENT);
++#endif
++		break;
++	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
++		tmp = data->battery_level * 1000;
++		RET = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
++		if (status & EC_STATUS_BATID) {
++			if (tmp >= BAT_MAX) {
++				RET = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
++				if (tmp >= BAT_MAX+BAT_READ_ERROR)
++					RET = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
++			} else if (tmp <= BAT_MIN+BAT_READ_ERROR) {
++				RET = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
++				if (tmp <= BAT_MIN)
++					RET = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
++			} else
++				RET = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
++		}
++		break;
++	case POWER_SUPPLY_PROP_CHARGE_TYPE:
++		if (ctrl & EC_CTRL_TRICKLE)
++			RET = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
++		else if (ctrl & EC_CTRL_CHARGE_EN)
++			RET = POWER_SUPPLY_CHARGE_TYPE_FAST;
++		else
++			RET = POWER_SUPPLY_CHARGE_TYPE_NONE;
++		break;
++	case POWER_SUPPLY_PROP_CURRENT_MAX:
++		/* 1.4A ? */
++		RET = 1400000;
++		break;
++	default:
++		break;
++	}
++
++	return ret;
++}
++#undef RET
++
++static enum power_supply_property gdium_ac_props[] = {
++	POWER_SUPPLY_PROP_ONLINE,
++};
++
++static enum power_supply_property gdium_battery_props[] = {
++	POWER_SUPPLY_PROP_STATUS,
++	POWER_SUPPLY_PROP_PRESENT,
++	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
++	POWER_SUPPLY_PROP_CHARGE_NOW,
++	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
++	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
++	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
++	POWER_SUPPLY_PROP_VOLTAGE_NOW,
++	POWER_SUPPLY_PROP_CURRENT_MAX,
++	POWER_SUPPLY_PROP_CURRENT_NOW,
++	POWER_SUPPLY_PROP_CAPACITY,
++	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
++	POWER_SUPPLY_PROP_CHARGE_TYPE,
++};
++
++static void gdium_laptop_battery_work(struct work_struct *work)
++{
++	struct gdium_laptop_data *data = container_of(work, struct gdium_laptop_data, work.work);
++	struct i2c_client *client;
++	int ret;
++	char old_status, old_charge_cmd;
++	char present;
++	s32 status;
++
++	mutex_lock(&data->mutex);
++	client	= data->client;
++	status	= ec_read_status(client);
++	ret	= ec_read_battery(client);
++
++	if ((status < 0) || (ret < 0))
++		goto i2c_read_error;
++
++	old_status = data->status;
++	old_charge_cmd = data->charge_cmd;
++	data->status = status;
++
++	/*
++	 * Charge only if :
++	 * - battery present
++	 * - ac adapter plugged in
++	 * - battery not fully charged
++	 */
++#if CONFIG_GDIUM_VERSION > 2
++	present = !!(data->status & EC_STATUS_BATID);
++#else
++	present = !!(ret > BAT_VOLT_PRESENT);
++#endif
++	data->battery_level = 0;
++	if (present) {
++		data->battery_level = (unsigned int)ret;
++		if (data->status & EC_STATUS_ADAPT)
++			data->battery_level -= BAT_READ_ERROR_MV;
++	}
++
++	data->charge_cmd = 0;
++	if ((data->status & EC_STATUS_ADAPT) && present && (data->battery_level <= BAT_MAX_MV))
++		data->charge_cmd = (ret < BAT_TRICKLE_EN) ? 2 : 3;
++
++	ec_charge_en(client, (data->charge_cmd >> 1) & 1, data->charge_cmd & 1);
++
++	/*
++	 * data->ctrl must be set _after_ calling ec_charge_en as this will change the
++	 * control register content
++	 */
++	data->ctrl = ec_read_ctrl(client);
++
++	if ((data->status & EC_STATUS_ADAPT) != (old_status & EC_STATUS_ADAPT)) {
++		power_supply_changed(&data->gdium_ac);
++		/* Send charging/discharging state change */
++		power_supply_changed(&data->gdium_battery);
++	} else if ((data->status & EC_STATUS_ADAPT) &&
++			((old_charge_cmd&2) != (data->charge_cmd&2)))
++		power_supply_changed(&data->gdium_battery);
++
++i2c_read_error:
++	mutex_unlock(&data->mutex);
++	queue_delayed_work(data->workqueue, &data->work, msecs_to_jiffies(BAT_SCAN_INTERVAL));
++}
++
++static int gdium_laptop_battery_init(struct gdium_laptop_data *data)
++{
++	int ret;
++
++	data->bat_pdev = platform_device_register_simple("gdium-battery", 0, NULL, 0);
++	if (IS_ERR(data->bat_pdev))
++		return PTR_ERR(data->bat_pdev);
++
++	data->gdium_battery.name		= data->bat_pdev->name;
++	data->gdium_battery.properties		= gdium_battery_props;
++	data->gdium_battery.num_properties	= ARRAY_SIZE(gdium_battery_props);
++	data->gdium_battery.get_property	= gdium_battery_get_props;
++	data->gdium_battery.use_for_apm		= 1;
++
++	ret = power_supply_register(&data->bat_pdev->dev, &data->gdium_battery);
++	if (ret)
++		goto err_platform;
++
++	data->gdium_ac.name			= "gdium-ac";
++	data->gdium_ac.type			= POWER_SUPPLY_TYPE_MAINS;
++	data->gdium_ac.properties		= gdium_ac_props;
++	data->gdium_ac.num_properties		= ARRAY_SIZE(gdium_ac_props);
++	data->gdium_ac.get_property		= gdium_ac_get_props;
++/*	data->gdium_ac.use_for_apm_ac		= 1,	*/
++
++	ret = power_supply_register(&data->bat_pdev->dev, &data->gdium_ac);
++	if (ret)
++		goto err_battery;
++
++	if (!ec) {
++		INIT_DELAYED_WORK(&data->work, gdium_laptop_battery_work);
++		data->workqueue = create_singlethread_workqueue("gdium-battery-work");
++		if (!data->workqueue) {
++			ret = -ESRCH;
++			goto err_work;
++		}
++		queue_delayed_work(data->workqueue, &data->work, msecs_to_jiffies(BAT_SCAN_INTERVAL));
++	}
++
++	return 0;
++
++err_work:
++err_battery:
++	power_supply_unregister(&data->gdium_battery);
++err_platform:
++	platform_device_unregister(data->bat_pdev);
++
++	return ret;
++}
++static void gdium_laptop_battery_exit(struct gdium_laptop_data *data)
++{
++	if (!ec) {
++		cancel_rearming_delayed_workqueue(data->workqueue, &data->work);
++		destroy_workqueue(data->workqueue);
++	}
++	power_supply_unregister(&data->gdium_battery);
++	power_supply_unregister(&data->gdium_ac);
++	platform_device_unregister(data->bat_pdev);
++}
++
++/* Debug fs */
++static int gdium_laptop_regs_show(struct seq_file *s, void *p)
++{
++	struct gdium_laptop_data *data = s->private;
++	struct i2c_client *client = data->client;
++
++	mutex_lock(&data->mutex);
++	seq_printf(s, "Version    : 0x%02x\n", (unsigned char)ec_read_version(client));
++	seq_printf(s, "Status     : 0x%02x\n", (unsigned char)ec_read_status(client));
++	seq_printf(s, "Ctrl       : 0x%02x\n", (unsigned char)ec_read_ctrl(client));
++	seq_printf(s, "Sign       : 0x%02x\n", (unsigned char)ec_read_sign(client));
++	seq_printf(s, "Bat Lo     : 0x%02x\n", (unsigned char)i2c_smbus_read_byte_data(client, EC_BAT_LOW));
++	seq_printf(s, "Bat Hi     : 0x%02x\n", (unsigned char)i2c_smbus_read_byte_data(client, EC_BAT_HIGH));
++	seq_printf(s, "Battery    : %d uV\n",  (unsigned int)ec_read_battery(client) * 1000);
++	seq_printf(s, "Charge cmd : %s %s\n", data->charge_cmd & 2 ? "C" : " ", data->charge_cmd & 1 ? "T" : " ");
++
++	mutex_unlock(&data->mutex);
++	return 0;
++}
++
++static int gdium_laptop_regs_open(struct inode *inode,
++					 struct file *file)
++{
++	return single_open(file, gdium_laptop_regs_show, inode->i_private);
++}
++
++static const struct file_operations gdium_laptop_regs_fops = {
++	.open		= gdium_laptop_regs_open,
++	.read		= seq_read,
++	.llseek		= seq_lseek,
++	.release	= single_release,
++	.owner		= THIS_MODULE,
++};
++
++
++static int gdium_laptop_probe(struct i2c_client *client, const struct i2c_device_id *id)
++{
++	struct gdium_laptop_data *data;
++	int ret;
++
++	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
++		dev_err(&client->dev,
++				"%s: no smbus_byte support !\n", __func__);
++		return -ENODEV;
++	}
++
++	data = kzalloc(sizeof(struct gdium_laptop_data), GFP_KERNEL);
++	if (!data)
++		return -ENOMEM;
++
++	i2c_set_clientdata(client, data);
++	data->client = client;
++	mutex_init(&data->mutex);
++
++	ret = ec_read_version(client);
++	if (ret < 0)
++		goto err_alloc;
++
++	data->version = (unsigned char)ret;
++
++	ret = gdium_laptop_input_init(data);
++	if (ret)
++		goto err_alloc;
++
++	ret = gdium_laptop_battery_init(data);
++	if (ret)
++		goto err_input;
++
++
++	if (!ec) {
++		ret = ec_write_sign(client, EC_SIGN_OS);
++		if (ret)
++			goto err_sign;
++	}
++
++	if (gpio16) {
++		ret = gpio_request(SM502_WLAN_ON, "wlan-on");
++		if (ret < 0)
++			goto err_sign;
++		gpio_set_value(SM502_WLAN_ON, ec_wlan_status(client));
++		gpio_direction_output(SM502_WLAN_ON, 1);
++	}
++
++	dev_info(&client->dev, "Found firmware 0x%02x\n", data->version);
++	data->debugfs = debugfs_create_file("gdium_laptop", S_IFREG | S_IRUGO,
++				NULL, data, &gdium_laptop_regs_fops);
++
++	return 0;
++
++err_sign:
++	gdium_laptop_battery_exit(data);
++err_input:
++	gdium_laptop_input_exit(data);
++err_alloc:
++	kfree(data);
++	return ret;
++}
++
++static int gdium_laptop_remove(struct i2c_client *client)
++{
++	struct gdium_laptop_data *data = i2c_get_clientdata(client);
++
++	if (gpio16)
++		gpio_free(SM502_WLAN_ON);
++	ec_write_sign(client, EC_SIGN_EC);
++	if (data->debugfs)
++		debugfs_remove(data->debugfs);
++
++	gdium_laptop_battery_exit(data);
++	gdium_laptop_input_exit(data);
++
++	kfree(data);
++	return 0;
++}
++
++#ifdef CONFIG_PM
++static int gdium_laptop_suspend(struct i2c_client *client, pm_message_t msg)
++{
++	struct gdium_laptop_data *data = i2c_get_clientdata(client);
++
++	if (!ec)
++		cancel_rearming_delayed_workqueue(data->workqueue, &data->work);
++	return 0;
++}
++
++static int gdium_laptop_resume(struct i2c_client *client)
++{
++	struct gdium_laptop_data *data = i2c_get_clientdata(client);
++
++	if (!ec)
++		queue_delayed_work(data->workqueue, &data->work, msecs_to_jiffies(BAT_SCAN_INTERVAL));
++	return 0;
++}
++#else
++#define gdium_laptop_suspend NULL
++#define gdium_laptop_resume NULL
++#endif
++static const struct i2c_device_id gdium_id[] = {
++	{ "gdium-laptop" },
++	{},
++};
++MODULE_DEVICE_TABLE(i2c, gdium_id);
++
++static struct i2c_driver gdium_laptop_driver = {
++	.driver = {
++		.name = "gdium-laptop",
++		.owner = THIS_MODULE,
++	},
++	.probe = gdium_laptop_probe,
++	.remove = gdium_laptop_remove,
++	.shutdown = gdium_laptop_remove,
++	.suspend = gdium_laptop_suspend,
++	.resume = gdium_laptop_resume,
++	.id_table = gdium_id,
++};
++
++static int __init gdium_laptop_init(void)
++{
++	return i2c_add_driver(&gdium_laptop_driver);
++}
++
++static void __exit gdium_laptop_exit(void)
++{
++	i2c_del_driver(&gdium_laptop_driver);
++}
++
++module_init(gdium_laptop_init);
++module_exit(gdium_laptop_exit);
++
++MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
++MODULE_DESCRIPTION("Gdium laptop extras");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
+index 0fe4ad8..45e46d4 100644
+--- a/drivers/rtc/Kconfig
++++ b/drivers/rtc/Kconfig
+@@ -737,6 +737,7 @@ comment "Platform RTC drivers"
+ config RTC_DRV_CMOS
+ 	tristate "PC-style 'CMOS'"
+ 	depends on X86 || ARM || M32R || PPC || MIPS || SPARC64
++	depends on !DEXXON_GDIUM
+ 	default y if X86
+ 	help
+ 	  Say "yes" here to get direct support for the real time clock
+diff --git a/include/linux/sm501.h b/include/linux/sm501.h
+index 02fde50..a8677f0 100644
+--- a/include/linux/sm501.h
++++ b/include/linux/sm501.h
+@@ -27,6 +27,9 @@ extern unsigned long sm501_set_clock(struct device *dev,
+ extern unsigned long sm501_find_clock(struct device *dev,
+ 				      int clksrc, unsigned long req_freq);
+ 
++extern void sm501_configure_gpio(struct device *dev,
++				unsigned int gpio, unsigned char mode);
++
+ /* sm501_misc_control
+  *
+  * Modify the SM501's MISC_CONTROL register
+@@ -122,6 +125,7 @@ struct sm501_reg_init {
+ #define SM501_USE_AC97		(1<<7)
+ #define SM501_USE_I2S		(1<<8)
+ #define SM501_USE_GPIO		(1<<9)
++#define SM501_USE_PWM		(1<<10)
+ 
+ #define SM501_USE_ALL		(0xffffffff)
+ 
+-- 
+2.4.3
+