summaryrefslogtreecommitdiffstats
path: root/lemote/gnewsense/tags/4.1.49-gnu_0freeloong/4.1-rc7-db21bcc5d0-loongson-community.patch
diff options
context:
space:
mode:
Diffstat (limited to 'lemote/gnewsense/tags/4.1.49-gnu_0freeloong/4.1-rc7-db21bcc5d0-loongson-community.patch')
-rw-r--r--lemote/gnewsense/tags/4.1.49-gnu_0freeloong/4.1-rc7-db21bcc5d0-loongson-community.patch9812
1 files changed, 9812 insertions, 0 deletions
diff --git a/lemote/gnewsense/tags/4.1.49-gnu_0freeloong/4.1-rc7-db21bcc5d0-loongson-community.patch b/lemote/gnewsense/tags/4.1.49-gnu_0freeloong/4.1-rc7-db21bcc5d0-loongson-community.patch
new file mode 100644
index 000000000..04b7979f3
--- /dev/null
+++ b/lemote/gnewsense/tags/4.1.49-gnu_0freeloong/4.1-rc7-db21bcc5d0-loongson-community.patch
@@ -0,0 +1,9812 @@
+diff --git a/Makefile b/Makefile
+index 40a8b06..b061aaa 100644
+--- a/Makefile
++++ b/Makefile
+@@ -298,8 +298,8 @@ CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
+
+ HOSTCC = gcc
+ HOSTCXX = g++
+-HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
+-HOSTCXXFLAGS = -O2
++HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O3 -fomit-frame-pointer -std=gnu89
++HOSTCXXFLAGS = -O3
+
+ ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1)
+ HOSTCFLAGS += -Wno-unused-value -Wno-unused-parameter \
+@@ -614,7 +614,7 @@ KBUILD_CFLAGS += $(call cc-option,-fno-delete-null-pointer-checks,)
+ ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
+ KBUILD_CFLAGS += -Os $(call cc-disable-warning,maybe-uninitialized,)
+ else
+-KBUILD_CFLAGS += -O2
++KBUILD_CFLAGS += -O3
+ endif
+
+ # Tell gcc to never replace conditional load with a non-conditional one
+diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
+index f501665..c5e2eef 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.
+
+@@ -1712,6 +1712,15 @@ config CPU_LOONGSON2
+ bool
+ select CPU_SUPPORTS_32BIT_KERNEL
+ select CPU_SUPPORTS_64BIT_KERNEL
++ select CPU_SUPPORTS_HIGHMEM if ! EMBEDDED
++ select ARCH_WANT_OPTIONAL_GPIOLIB
++
++config CPU_LOONGSON1
++ bool
++ select CPU_MIPS32
++ select CPU_MIPSR2
++ select CPU_HAS_PREFETCH
++ select CPU_SUPPORTS_32BIT_KERNEL
+ select CPU_SUPPORTS_HIGHMEM
+ select CPU_SUPPORTS_HUGEPAGES
+
+@@ -2391,7 +2400,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/boot/compressed/Makefile b/arch/mips/boot/compressed/Makefile
+index dc91bde..dc1502d 100644
+--- a/arch/mips/boot/compressed/Makefile
++++ b/arch/mips/boot/compressed/Makefile
+@@ -82,9 +82,18 @@ quiet_cmd_zld = LD $@
+ cmd_zld = $(LD) $(LDFLAGS) -Ttext $(VMLINUZ_LOAD_ADDRESS) -T $< $(vmlinuzobjs-y) -o $@
+ quiet_cmd_strip = STRIP $@
+ cmd_strip = $(STRIP) -s $@
++ifdef CONFIG_EMBEDDED
++quiet_cmd_sstrip = SSTRIP $@
++ cmd_sstrip = $(srctree)/scripts/sstrip.sh $@
++endif
+ vmlinuz: $(src)/ld.script $(vmlinuzobjs-y) $(obj)/calc_vmlinuz_load_addr
+ $(call cmd,zld)
+ $(call cmd,strip)
++ $(call cmd,sstrip)
++
++vmlinuz.unsstrip: $(src)/ld.script $(vmlinuzobjs-y) $(obj)/calc_vmlinuz_load_addr
++ $(call cmd,zld)
++ $(call cmd,strip)
+
+ #
+ # Some DECstations need all possible sections of an ECOFF executable
+@@ -97,14 +106,14 @@ endif
+ hostprogs-y += ../elf2ecoff
+
+ ifdef CONFIG_32BIT
+- VMLINUZ = vmlinuz
++ VMLINUZ = vmlinuz.unsstrip
+ else
+ VMLINUZ = vmlinuz.32
+ endif
+
+ quiet_cmd_32 = OBJCOPY $@
+ cmd_32 = $(OBJCOPY) -O $(32bit-bfd) $(OBJCOPYFLAGS) $< $@
+-vmlinuz.32: vmlinuz
++vmlinuz.32: vmlinuz.unsstrip
+ $(call cmd,32)
+
+ quiet_cmd_ecoff = ECOFF $@
+@@ -113,11 +122,11 @@ vmlinuz.ecoff: $(obj)/../elf2ecoff $(VMLINUZ)
+ $(call cmd,ecoff)
+
+ OBJCOPYFLAGS_vmlinuz.bin := $(OBJCOPYFLAGS) -O binary
+-vmlinuz.bin: vmlinuz
++vmlinuz.bin: vmlinuz.unsstrip
+ $(call cmd,objcopy)
+
+ OBJCOPYFLAGS_vmlinuz.srec := $(OBJCOPYFLAGS) -S -O srec
+-vmlinuz.srec: vmlinuz
++vmlinuz.srec: vmlinuz.unsstrip
+ $(call cmd,objcopy)
+
+-clean-files := $(objtree)/vmlinuz $(objtree)/vmlinuz.{32,ecoff,bin,srec}
++clean-files := $(objtree)/vmlinuz $(objtree)/vmlinuz.{32,ecoff,bin,srec,unsstrip}
+diff --git a/arch/mips/boot/compressed/ld.script b/arch/mips/boot/compressed/ld.script
+index 5a33409..de04ac9 100644
+--- a/arch/mips/boot/compressed/ld.script
++++ b/arch/mips/boot/compressed/ld.script
+@@ -49,5 +49,6 @@ SECTIONS
+ *(.reginfo)
+ *(.comment)
+ *(.note)
++ *(.gnu.attributes)
+ }
+ }
+diff --git a/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h b/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
+index a0ee0cb..4e18add 100644
+--- a/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
++++ b/arch/mips/include/asm/mach-loongson/cs5536/cs5536.h
+@@ -301,5 +301,40 @@ 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..e6febafd 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/include/asm/mach-loongson/machine.h b/arch/mips/include/asm/mach-loongson/machine.h
+index cb2b602..78fcd38 100644
+--- a/arch/mips/include/asm/mach-loongson/machine.h
++++ b/arch/mips/include/asm/mach-loongson/machine.h
+@@ -24,6 +24,12 @@
+
+ #endif
+
++#ifdef CONFIG_DEXXON_GDIUM
++
++#define LOONGSON_MACHTYPE MACH_DEXXON_GDIUM2F10
++
++#endif
++
+ #ifdef CONFIG_LOONGSON_MACH3X
+
+ #define LOONGSON_MACHTYPE MACH_LOONGSON_GENERIC
+diff --git a/arch/mips/include/asm/mach-loongson1/clock.h b/arch/mips/include/asm/mach-loongson1/clock.h
+new file mode 100644
+index 0000000..dd1afdb
+--- /dev/null
++++ b/arch/mips/include/asm/mach-loongson1/clock.h
+@@ -0,0 +1,53 @@
++#ifndef __ASM_MACH_LOONGSON1_CLOCK_H
++#define __ASM_MACH_LOONGSON1_CLOCK_H
++
++#include <linux/kref.h>
++#include <linux/list.h>
++#include <linux/seq_file.h>
++#include <linux/clk.h>
++
++extern void (*cpu_wait) (void);
++
++struct clk;
++
++struct clk_ops {
++ void (*init) (struct clk *clk);
++ void (*enable) (struct clk *clk);
++ void (*disable) (struct clk *clk);
++ void (*recalc) (struct clk *clk);
++ int (*set_rate) (struct clk *clk, unsigned long rate, int algo_id);
++ long (*round_rate) (struct clk *clk, unsigned long rate);
++};
++
++struct clk {
++ struct list_head node;
++ const char *name;
++ int id;
++ struct module *owner;
++
++ struct clk *parent;
++ struct clk_ops *ops;
++
++ struct kref kref;
++
++ unsigned long rate;
++ unsigned long flags;
++};
++
++#define CLK_ALWAYS_ENABLED (1 << 0)
++#define CLK_RATE_PROPAGATES (1 << 1)
++
++/* Should be defined by processor-specific code */
++void arch_init_clk_ops(struct clk_ops **, int type);
++
++int clk_init(void);
++
++int __clk_enable(struct clk *);
++void __clk_disable(struct clk *);
++
++void clk_recalc_rate(struct clk *);
++
++int clk_register(struct clk *);
++void clk_unregister(struct clk *);
++
++#endif /* __ASM_MIPS_CLOCK_H */
+diff --git a/arch/mips/include/asm/mach-loongson1/regs-intc.h b/arch/mips/include/asm/mach-loongson1/regs-intc.h
+new file mode 100644
+index 0000000..6d5db23
+--- /dev/null
++++ b/arch/mips/include/asm/mach-loongson1/regs-intc.h
+@@ -0,0 +1,25 @@
++/*
++ * Copyright (c) 2011 Zhang, Keguang <keguang.zhang@gmail.com>
++ *
++ * Loongson1 Interrupt register definitions.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License as published by the
++ * Free Software Foundation; either version 2 of the License, or (at your
++ * option) any later version.
++ */
++
++#ifndef __ASM_MACH_LOONGSON1_REGS_INTC_H
++#define __ASM_MACH_LOONGSON1_REGS_INTC_H
++
++#define LS1X_INTC_REG(n, x) \
++ (ioremap(LS1X_INTC_BASE + (n * 0x18) + (x), 4))
++
++#define LS1X_INTC_INTISR(n) LS1X_INTC_REG(n, 0x0)
++#define LS1X_INTC_INTIEN(n) LS1X_INTC_REG(n, 0x4)
++#define LS1X_INTC_INTSET(n) LS1X_INTC_REG(n, 0x8)
++#define LS1X_INTC_INTCLR(n) LS1X_INTC_REG(n, 0xc)
++#define LS1X_INTC_INTPOL(n) LS1X_INTC_REG(n, 0x10)
++#define LS1X_INTC_INTEDGE(n) LS1X_INTC_REG(n, 0x14)
++
++#endif /* __ASM_MACH_LOONGSON1_REGS_INTC_H */
+diff --git a/arch/mips/include/asm/sparsemem.h b/arch/mips/include/asm/sparsemem.h
+index b1071c1..8b8e551 100644
+--- a/arch/mips/include/asm/sparsemem.h
++++ b/arch/mips/include/asm/sparsemem.h
+@@ -11,7 +11,11 @@
+ #else
+ # define SECTION_SIZE_BITS 28
+ #endif
+-#define MAX_PHYSMEM_BITS 48
++#if !defined(CONFIG_MACH_LOONGSON) || !defined(CONFIG_CPU_LOONGSON2) /* Commit c461731836 broke Loongson2. */
++# define MAX_PHYSMEM_BITS 48
++#else
++# define MAX_PHYSMEM_BITS 35
++#endif
+
+ #endif /* CONFIG_SPARSEMEM */
+ #endif /* _MIPS_SPARSEMEM_H */
+diff --git a/arch/mips/include/asm/timex.h b/arch/mips/include/asm/timex.h
+index b05bb70..44c9a69 100644
+--- a/arch/mips/include/asm/timex.h
++++ b/arch/mips/include/asm/timex.h
+@@ -11,6 +11,10 @@
+
+ #ifdef __KERNEL__
+
++#ifdef CONFIG_CSRC_R4K
++#define ARCH_HAS_PREPARED_LPJ
++#endif
++
+ #include <linux/compiler.h>
+
+ #include <asm/cpu.h>
+diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h
+index fc0cf5a..99e6430 100644
+--- a/arch/mips/include/uapi/asm/inst.h
++++ b/arch/mips/include/uapi/asm/inst.h
+@@ -65,6 +65,8 @@ enum spec_op {
+ enum spec2_op {
+ madd_op, maddu_op, mul_op, spec2_3_unused_op,
+ msub_op, msubu_op, /* more unused ops */
++ loongson_madd_op = 0x18, loongson_msub_op,
++ loongson_nmadd_op, loongson_nmsub_op,
+ clz_op = 0x20, clo_op,
+ dclz_op = 0x24, dclo_op,
+ sdbpp_op = 0x3f
+@@ -151,7 +153,7 @@ enum cop0_com_func {
+ */
+ enum cop1_fmt {
+ s_fmt, d_fmt, e_fmt, q_fmt,
+- w_fmt, l_fmt
++ w_fmt, l_fmt, ps_fmt
+ };
+
+ /*
+@@ -180,7 +182,8 @@ enum cop1_sdw_func {
+ enum cop1x_func {
+ lwxc1_op = 0x00, ldxc1_op = 0x01,
+ swxc1_op = 0x08, sdxc1_op = 0x09,
+- pfetch_op = 0x0f, madd_s_op = 0x20,
++ pfetch_op = 0x0f,
++ prefx_op = 0x17, madd_s_op = 0x20,
+ madd_d_op = 0x21, madd_e_op = 0x22,
+ msub_s_op = 0x28, msub_d_op = 0x29,
+ msub_e_op = 0x2a, nmadd_s_op = 0x30,
+diff --git a/arch/mips/kernel/scall64-o32.S b/arch/mips/kernel/scall64-o32.S
+index d07b210..69996f2 100644
+--- a/arch/mips/kernel/scall64-o32.S
++++ b/arch/mips/kernel/scall64-o32.S
+@@ -26,6 +26,18 @@
+
+ .align 5
+ NESTED(handle_sys, PT_SIZE, sp)
++#ifdef CONFIG_MIPS_USER_RDTSC
++ MFC0 k0, CP0_EPC
++ lw k1, 0(k0)
++ sltiu k1, k1, 0x1c
++ bne k1, zero, 1f # Normal syscall code: 0x0c < 0x1c
++ nop
++ mfc0 v0, CP0_COUNT # Get TSC
++ PTR_ADDIU k0, 4 # ret from syscall
++ MTC0 k0, CP0_EPC
++ eret
++1:
++#endif /* CONFIG_MIPS_USER_RDTSC */
+ .set noat
+ SAVE_SOME
+ TRACE_IRQS_ON_RELOAD
+diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c
+index 8d01709..9cd25da 100644
+--- a/arch/mips/kernel/time.c
++++ b/arch/mips/kernel/time.c
+@@ -119,6 +119,11 @@ 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/lib/Makefile b/arch/mips/lib/Makefile
+index 1e9e900..36fcccc 100644
+--- a/arch/mips/lib/Makefile
++++ b/arch/mips/lib/Makefile
+@@ -2,10 +2,14 @@
+ # Makefile for MIPS-specific library files..
+ #
+
+-lib-y += bitops.o csum_partial.o delay.o memcpy.o memset.o \
++lib-y += bitops.o csum_partial.o memcpy.o memset.o \
+ mips-atomic.o strlen_user.o strncpy_user.o \
+ strnlen_user.o uncached.o
+
++ifndef CONFIG_CSRC_R4K
++lib-y += delay.o
++endif
++
+ obj-y += iomap.o
+ obj-$(CONFIG_PCI) += iomap-pci.o
+ lib-$(CONFIG_GENERIC_CSUM) := $(filter-out csum_partial.o, $(lib-y))
+diff --git a/arch/mips/loongson/Kconfig b/arch/mips/loongson/Kconfig
+index 156de85..0a433e6 100644
+--- a/arch/mips/loongson/Kconfig
++++ b/arch/mips/loongson/Kconfig
+@@ -32,12 +32,12 @@ config LEMOTE_FULOONG2E
+
+ config LEMOTE_MACH2F
+ bool "Lemote Loongson 2F family machines"
+- select ARCH_SPARSEMEM_ENABLE
++ select ARCH_SPARSEMEM_ENABLE if HIBERNATION
+ select BOARD_SCACHE
+ select BOOT_ELF32
+ select CEVT_R4K if ! MIPS_EXTERNAL_TIMER
+ select CPU_HAS_WB
+- select CS5536
++ select CS5536 if PCI
+ select CSRC_R4K if ! MIPS_EXTERNAL_TIMER
+ select DMA_NONCOHERENT
+ select GENERIC_ISA_DMA_SUPPORT_BROKEN
+@@ -45,14 +45,13 @@ config LEMOTE_MACH2F
+ select HW_HAS_PCI
+ select I8259
+ select IRQ_CPU
+- select ISA
+ select SYS_HAS_CPU_LOONGSON2F
+ select SYS_HAS_EARLY_PRINTK
+ select SYS_SUPPORTS_32BIT_KERNEL
+ select SYS_SUPPORTS_64BIT_KERNEL
+- select SYS_SUPPORTS_HIGHMEM
++ select SYS_SUPPORTS_HIGHMEM if ! EMBEDDED
+ select SYS_SUPPORTS_LITTLE_ENDIAN
+- select LOONGSON_MC146818
++ select LOONGSON_MC146818 if RTC_DRV_CMOS
+ help
+ Lemote Loongson 2F family machines utilize the 2F revision of
+ Loongson processor and the AMD CS5536 south bridge.
+@@ -60,6 +59,31 @@ config LEMOTE_MACH2F
+ These family machines include fuloong2f mini PC, yeeloong2f notebook,
+ LingLoong allinone PC and so forth.
+
++config DEXXON_GDIUM
++ bool "Dexxon Gdium Netbook"
++ select ARCH_SPARSEMEM_ENABLE
++ select BOARD_SCACHE
++ select BOOT_ELF32
++ select CEVT_R4K if ! MIPS_EXTERNAL_TIMER
++ select CPU_HAS_WB
++ select CSRC_R4K if ! MIPS_EXTERNAL_TIMER
++ select DMA_NONCOHERENT
++ select GENERIC_ISA_DMA_SUPPORT_BROKEN
++ select HW_HAS_PCI
++ select I8259
++ select IRQ_CPU
++ select ISA
++ select SYS_HAS_CPU_LOONGSON2F
++ select SYS_HAS_EARLY_PRINTK
++ select SYS_SUPPORTS_32BIT_KERNEL
++ select SYS_SUPPORTS_64BIT_KERNEL
++ select SYS_SUPPORTS_HIGHMEM
++ select SYS_SUPPORTS_LITTLE_ENDIAN
++ select ARCH_REQUIRE_GPIOLIB
++ select HAVE_PWM if MFD_SM501
++ help
++ Dexxon gdium netbook based on Loongson 2F and SM502.
++
+ config LOONGSON_MACH3X
+ bool "Generic Loongson 3 family machines"
+ select ARCH_SPARSEMEM_ENABLE
+@@ -152,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 72fed00..96d5919 100644
+--- a/arch/mips/loongson/common/cmdline.c
++++ b/arch/mips/loongson/common/cmdline.c
+@@ -17,10 +17,15 @@
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
++#include <linux/module.h>
+ #include <asm/bootinfo.h>
+
+ #include <loongson.h>
+
++/* the kernel command line copied from arcs_cmdline */
++char loongson_cmdline[COMMAND_LINE_SIZE];
++EXPORT_SYMBOL(loongson_cmdline);
++
+ void __init prom_init_cmdline(void)
+ {
+ int prom_argc;
+@@ -45,4 +50,31 @@ void __init prom_init_cmdline(void)
+ }
+
+ prom_init_machtype();
++
++ /* append machine specific command line */
++ switch (mips_machtype) {
++ case MACH_LEMOTE_LL2F:
++ if ((strstr(arcs_cmdline, "video=")) == NULL)
++ strcat(arcs_cmdline, " video=sisfb:1360x768-16@60");
++ break;
++ case MACH_LEMOTE_FL2F:
++ if ((strstr(arcs_cmdline, "ide_core.ignore_cable=")) == NULL)
++ strcat(arcs_cmdline, " ide_core.ignore_cable=0");
++ break;
++ case MACH_LEMOTE_ML2F7:
++ /* Mengloong-2F has a 800x480 screen */
++ if ((strstr(arcs_cmdline, "vga=")) == NULL)
++ strcat(arcs_cmdline, " vga=0x313");
++ break;
++ case MACH_DEXXON_GDIUM2F10:
++ /* gdium has a 1024x600 screen */
++ if ((strstr(arcs_cmdline, "video=")) == NULL)
++ strcat(arcs_cmdline, " video=sm501fb:1024x600@60");
++ break;
++ default:
++ break;
++ }
++
++ /* copy arcs_cmdline into loongson_cmdline */
++ strncpy(loongson_cmdline, arcs_cmdline, COMMAND_LINE_SIZE);
+ }
+diff --git a/arch/mips/loongson/common/env.c b/arch/mips/loongson/common/env.c
+index 22f04ca..fc454f2 100644
+--- a/arch/mips/loongson/common/env.c
++++ b/arch/mips/loongson/common/env.c
+@@ -29,6 +29,7 @@ struct efi_memory_map_loongson *loongson_memmap;
+ struct loongson_system_configuration loongson_sysconf;
+
+ u64 loongson_chipcfg[MAX_PACKAGES] = {0xffffffffbfc00180};
++EXPORT_SYMBOL_GPL(loongson_chipcfg);
+ u64 loongson_chiptemp[MAX_PACKAGES];
+ u64 loongson_freqctrl[MAX_PACKAGES];
+
+diff --git a/arch/mips/loongson/gdium/Makefile b/arch/mips/loongson/gdium/Makefile
+new file mode 100644
+index 0000000..f3f4f51
+--- /dev/null
++++ b/arch/mips/loongson/gdium/Makefile
+@@ -0,0 +1,6 @@
++# Makefile for gdium
++
++obj-y += irq.o reset.o platform.o
++
++obj-$(CONFIG_MFD_SM501) += sm501-pwm.o
++obj-$(CONFIG_GDIUM_PWM_CLOCK) += gdium-clock.o
+diff --git a/arch/mips/loongson/gdium/gdium-clock.c b/arch/mips/loongson/gdium/gdium-clock.c
+new file mode 100644
+index 0000000..fdbf42a
+--- /dev/null
++++ b/arch/mips/loongson/gdium/gdium-clock.c
+@@ -0,0 +1,234 @@
++/*
++ * Doesn't work really well. When used, the clocksource is producing
++ * bad timings and the clockevent can't be used (don't have one shot feature
++ * thus can't switch on the fly and the pwm is initialised too late to be able
++ * to use it at boot time).
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/platform_device.h>
++#include <linux/interrupt.h>
++#include <linux/delay.h>
++#include <linux/pwm.h>
++#include <linux/clocksource.h>
++#include <linux/debugfs.h>
++#include <asm/irq_cpu.h>
++#include <asm/mipsregs.h>
++#include <asm/mips-boards/bonito64.h>
++#include <asm/time.h>
++
++#include <loongson.h>
++
++#define CLOCK_PWM 1
++#define CLOCK_PWM_FREQ 1500000 /* Freq in Hz */
++#define CLOCK_LATCH ((CLOCK_PWM_FREQ + HZ/2) / HZ)
++#define CLOCK_PWM_PERIOD (1000000000/CLOCK_PWM_FREQ) /* period ns */
++#define CLOCK_PWM_DUTY 50
++#define CLOCK_PWM_IRQ (MIPS_CPU_IRQ_BASE + 4)
++
++static const char drv_name[] = "gdium-clock";
++
++static struct pwm_device *clock_pwm;
++
++static DEFINE_SPINLOCK(clock_pwm_lock);
++static uint64_t clock_tick;
++
++static irqreturn_t gdium_pwm_clock_interrupt(int irq, void *dev_id)
++{
++ struct clock_event_device *cd = dev_id;
++ unsigned long flag;
++
++ spin_lock_irqsave(&clock_pwm_lock, flag);
++ clock_tick++;
++ /* wait intn2 to finish */
++ do {
++ LOONGSON_INTENCLR = (1 << 13);
++ } while (LOONGSON_INTISR & (1 << 13));
++ spin_unlock_irqrestore(&clock_pwm_lock, flag);
++
++ if (cd && cd->event_handler)
++ cd->event_handler(cd);
++
++ return IRQ_HANDLED;
++}
++
++static cycle_t gdium_pwm_clock_read(struct clocksource *cs)
++{
++ unsigned long flag;
++ uint32_t jifs;
++ uint64_t ticks;
++
++ spin_lock_irqsave(&clock_pwm_lock, flag);
++ jifs = jiffies;
++ ticks = clock_tick;
++ spin_unlock_irqrestore(&clock_pwm_lock, flag);
++ /* return (cycle_t)ticks; */
++ return (cycle_t)(CLOCK_LATCH * jifs);
++}
++
++static struct clocksource gdium_pwm_clock_clocksource = {
++ .name = "gdium_csrc",
++ .read = gdium_pwm_clock_read,
++ .mask = CLOCKSOURCE_MASK(64),
++ .flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_MUST_VERIFY,
++ .shift = 20,
++};
++
++/* Debug fs */
++static int gdium_pwm_clock_show(struct seq_file *s, void *p)
++{
++ unsigned long flag;
++ uint64_t ticks;
++
++ spin_lock_irqsave(&clock_pwm_lock, flag);
++ ticks = clock_tick;
++ spin_unlock_irqrestore(&clock_pwm_lock, flag);
++ seq_printf(s, "%lld\n", ticks);
++ return 0;
++}
++
++static int gdium_pwm_clock_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, gdium_pwm_clock_show, inode->i_private);
++}
++
++static const struct file_operations gdium_pwm_clock_fops = {
++ .open = gdium_pwm_clock_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = single_release,
++ .owner = THIS_MODULE,
++};
++static struct dentry *debugfs_file;
++
++static void gdium_pwm_clock_set_mode(enum clock_event_mode mode,
++ struct clock_event_device *evt)
++{
++ /* Nothing to do ... */
++}
++
++static struct clock_event_device gdium_pwm_clock_cevt = {
++ .name = "gdium_cevt",
++ .features = CLOCK_EVT_FEAT_PERIODIC,
++ /* .mult, .shift, .max_delta_ns and .min_delta_ns left uninitialized */
++ .rating = 299,
++ .irq = CLOCK_PWM_IRQ,
++ .set_mode = gdium_pwm_clock_set_mode,
++};
++
++static struct platform_device_id platform_device_ids[] = {
++ {
++ .name = "gdium-pwmclk",
++ },
++ {}
++};
++MODULE_DEVICE_TABLE(platform, platform_device_ids);
++
++static struct platform_driver gdium_pwm_clock_driver = {
++ .driver = {
++ .name = drv_name,
++ .owner = THIS_MODULE,
++ },
++ .id_table = platform_device_ids,
++};
++
++static int gdium_pwm_clock_drvinit(void)
++{
++ int ret;
++ struct clocksource *cs = &gdium_pwm_clock_clocksource;
++ struct clock_event_device *cd = &gdium_pwm_clock_cevt;
++ unsigned int cpu = smp_processor_id();
++
++ clock_tick = 0;
++
++ clock_pwm = pwm_request(CLOCK_PWM, drv_name);
++ if (clock_pwm == NULL) {
++ pr_err("unable to request PWM for Gdium clock\n");
++ return -EBUSY;
++ }
++ ret = pwm_config(clock_pwm, CLOCK_PWM_DUTY, CLOCK_PWM_PERIOD);
++ if (ret) {
++ pr_err("unable to configure PWM for Gdium clock\n");
++ goto err_pwm_request;
++ }
++ ret = pwm_enable(clock_pwm);
++ if (ret) {
++ pr_err("unable to enable PWM for Gdium clock\n");
++ goto err_pwm_request;
++ }
++
++ cd->cpumask = cpumask_of(cpu);
++
++ cd->shift = 22;
++ cd->mult = div_sc(CLOCK_PWM_FREQ, NSEC_PER_SEC, cd->shift);
++ cd->max_delta_ns = clockevent_delta2ns(0x7FFF, cd);
++ cd->min_delta_ns = clockevent_delta2ns(0xF, cd);
++ clockevents_register_device(&gdium_pwm_clock_cevt);
++
++ /* SM501 PWM1 connected to intn2 <->ip4 */
++ LOONGSON_INTPOL = (1 << 13);
++ LOONGSON_INTEDGE &= ~(1 << 13);
++ ret = request_irq(CLOCK_PWM_IRQ, gdium_pwm_clock_interrupt, IRQF_DISABLED, drv_name, &gdium_pwm_clock_cevt);
++ if (ret) {
++ pr_err("Can't claim irq\n");
++ goto err_pwm_disable;
++ }
++
++ cs->rating = 200;
++ cs->mult = clocksource_hz2mult(CLOCK_PWM_FREQ, cs->shift);
++ ret = clocksource_register(&gdium_pwm_clock_clocksource);
++ if (ret) {
++ pr_err("Can't register clocksource\n");
++ goto err_irq;
++ }
++ pr_info("Clocksource registered with shift %d and mult %d\n",
++ cs->shift, cs->mult);
++
++ debugfs_file = debugfs_create_file(drv_name, S_IFREG | S_IRUGO,
++ NULL, NULL, &gdium_pwm_clock_fops);
++
++ return 0;
++
++err_irq:
++ free_irq(CLOCK_PWM_IRQ, &gdium_pwm_clock_cevt);
++err_pwm_disable:
++ pwm_disable(clock_pwm);
++err_pwm_request:
++ pwm_free(clock_pwm);
++ return ret;
++}
++
++static void gdium_pwm_clock_drvexit(void)
++{
++ free_irq(CLOCK_PWM_IRQ, &gdium_pwm_clock_cevt);
++ pwm_disable(clock_pwm);
++ pwm_free(clock_pwm);
++}
++
++
++static int __devinit gdium_pwm_clock_init(void)
++{
++ int ret = gdium_pwm_clock_drvinit();
++
++ if (ret) {
++ pr_err("Fail to register gdium clock driver\n");
++ return ret;
++ }
++
++ return platform_driver_register(&gdium_pwm_clock_driver);
++}
++
++static void __exit gdium_pwm_clock_cleanup(void)
++{
++ gdium_pwm_clock_drvexit();
++ platform_driver_unregister(&gdium_pwm_clock_driver);
++}
++
++module_init(gdium_pwm_clock_init);
++module_exit(gdium_pwm_clock_cleanup);
++
++MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
++MODULE_DESCRIPTION("Gdium PWM clock driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:gdium-pwmclk");
+diff --git a/arch/mips/loongson/gdium/irq.c b/arch/mips/loongson/gdium/irq.c
+new file mode 100644
+index 0000000..2415d20
+--- /dev/null
++++ b/arch/mips/loongson/gdium/irq.c
+@@ -0,0 +1,55 @@
++/*
++ * Copyright (C) 2007 Lemote Inc.
++ * Author: Fuxin Zhang, zhangfx@lemote.com
++ *
++ * Copyright (c) 2010 yajin <yajin@vm-kernel.org>
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License as published by the
++ * Free Software Foundation; either version 2 of the License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/interrupt.h>
++#include <linux/module.h>
++
++#include <loongson.h>
++#include <machine.h>
++
++#define LOONGSON_TIMER_IRQ (MIPS_CPU_IRQ_BASE + 7) /* cpu timer */
++#define LOONGSON_NORTH_BRIDGE_IRQ (MIPS_CPU_IRQ_BASE + 6) /* bonito */
++#define LOONGSON_UART_IRQ (MIPS_CPU_IRQ_BASE + 3) /* cpu serial port */
++
++void mach_irq_dispatch(unsigned int pending)
++{
++ if (pending & CAUSEF_IP7)
++ do_IRQ(LOONGSON_TIMER_IRQ);
++ else if (pending & CAUSEF_IP6) { /* North Bridge, Perf counter */
++ do_perfcnt_IRQ();
++ bonito_irqdispatch();
++ } else if (pending & CAUSEF_IP3) /* CPU UART */
++ do_IRQ(LOONGSON_UART_IRQ);
++#if defined(CONFIG_GDIUM_PWM_CLOCK) || defined(CONFIG_GDIUM_PWM_CLOCK_MODULE)
++ else if (pending & CAUSEF_IP4) /* SM501 PWM clock */
++ do_IRQ(MIPS_CPU_IRQ_BASE + 4);
++#endif
++ else
++ spurious_interrupt();
++}
++
++static irqreturn_t ip6_action(int cpl, void *dev_id)
++{
++ return IRQ_HANDLED;
++}
++
++struct irqaction ip6_irqaction = {
++ .handler = ip6_action,
++ .name = "cascade",
++ .flags = IRQF_SHARED,
++};
++
++void __init mach_init_irq(void)
++{
++ /* setup north bridge irq (bonito) */
++ setup_irq(LOONGSON_NORTH_BRIDGE_IRQ, &ip6_irqaction);
++}
+diff --git a/arch/mips/loongson/gdium/platform.c b/arch/mips/loongson/gdium/platform.c
+new file mode 100644
+index 0000000..ffafba4
+--- /dev/null
++++ b/arch/mips/loongson/gdium/platform.c
+@@ -0,0 +1,135 @@
++/*
++ * Copyright (c) 2009 Philippe Vachon <philippe@cowpig.ca>
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License as published by the
++ * Free Software Foundation; either version 2 of the License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/platform_device.h>
++#include <linux/pwm_backlight.h>
++#include <linux/i2c.h>
++#include <linux/i2c-gpio.h>
++
++#define GDIUM_GPIO_BASE 224
++
++static struct i2c_board_info __initdata sm502dev_i2c_devices[] = {
++ {
++ I2C_BOARD_INFO("lm75", 0x48),
++ },
++ {
++ I2C_BOARD_INFO("m41t83", 0x68),
++ },
++ {
++ I2C_BOARD_INFO("gdium-laptop", 0x40),
++ },
++};
++
++static int sm502dev_backlight_init(struct device *dev)
++{
++ /* Add gpio request stuff here */
++ return 0;
++}
++
++static void sm502dev_backlight_exit(struct device *dev)
++{
++ /* Add gpio free stuff here */
++}
++
++static struct platform_pwm_backlight_data backlight_data = {
++ .pwm_id = 0,
++ .max_brightness = 15,
++ .dft_brightness = 8,
++ .pwm_period_ns = 50000, /* 20 kHz */
++ .init = sm502dev_backlight_init,
++ .exit = sm502dev_backlight_exit,
++};
++
++static struct platform_device backlight = {
++ .name = "pwm-backlight",
++ .dev = {
++ .platform_data = &backlight_data,
++ },
++ .id = -1,
++};
++
++/*
++ * Warning this stunt is very dangerous
++ * as the sm501 gpio have dynamic numbers...
++ */
++/* bus 0 is the one for the ST7, DS75 etc... */
++static struct i2c_gpio_platform_data i2c_gpio0_data = {
++#if CONFIG_GDIUM_VERSION > 2
++ .sda_pin = GDIUM_GPIO_BASE + 13,
++ .scl_pin = GDIUM_GPIO_BASE + 6,
++#else
++ .sda_pin = 192+15,
++ .scl_pin = 192+14,
++#endif
++ .udelay = 5,
++ .timeout = HZ / 10,
++ .sda_is_open_drain = 0,
++ .scl_is_open_drain = 0,
++};
++
++static struct platform_device i2c_gpio0_device = {
++ .name = "i2c-gpio",
++ .id = 0,
++ .dev = { .platform_data = &i2c_gpio0_data, },
++};
++
++/* bus 1 is for the CRT/VGA external screen */
++static struct i2c_gpio_platform_data i2c_gpio1_data = {
++ .sda_pin = GDIUM_GPIO_BASE + 10,
++ .scl_pin = GDIUM_GPIO_BASE + 9,
++ .udelay = 5,
++ .timeout = HZ / 10,
++ .sda_is_open_drain = 0,
++ .scl_is_open_drain = 0,
++};
++
++static struct platform_device i2c_gpio1_device = {
++ .name = "i2c-gpio",
++ .id = 1,
++ .dev = { .platform_data = &i2c_gpio1_data, },
++};
++
++static struct platform_device gdium_clock = {
++ .name = "gdium-pwmclk",
++ .id = -1,
++};
++
++static struct platform_device *devices[] __initdata = {
++ &i2c_gpio0_device,
++ &i2c_gpio1_device,
++ &backlight,
++ &gdium_clock,
++};
++
++static int __init gdium_platform_devices_setup(void)
++{
++ int ret;
++
++ pr_info("Registering gdium platform devices\n");
++
++ ret = i2c_register_board_info(0, sm502dev_i2c_devices,
++ ARRAY_SIZE(sm502dev_i2c_devices));
++
++ if (ret != 0) {
++ pr_info("Error while registering platform devices: %d\n", ret);
++ return ret;
++ }
++
++ platform_add_devices(devices, ARRAY_SIZE(devices));
++
++ return 0;
++}
++
++/*
++ * some devices are on the pwm stuff which is behind the mfd which is
++ * behind the pci bus so arch_initcall can't work because too early
++ */
++late_initcall(gdium_platform_devices_setup);
+diff --git a/arch/mips/loongson/gdium/reset.c b/arch/mips/loongson/gdium/reset.c
+new file mode 100644
+index 0000000..8289f95
+--- /dev/null
++++ b/arch/mips/loongson/gdium/reset.c
+@@ -0,0 +1,22 @@
++/* Board-specific reboot/shutdown routines
++ *
++ * Copyright (C) 2010 yajin <yajin@vm-kernel.org>
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License as published by the
++ * Free Software Foundation; either version 2 of the License, or (at your
++ * option) any later version.
++ */
++#include <loongson.h>
++
++void mach_prepare_shutdown(void)
++{
++ LOONGSON_GPIOIE &= ~(1<<1);
++ LOONGSON_GPIODATA |= (1<<1);
++}
++
++void mach_prepare_reboot(void)
++{
++ LOONGSON_GPIOIE &= ~(1<<2);
++ LOONGSON_GPIODATA &= ~(1<<2);
++}
+diff --git a/arch/mips/loongson/gdium/sm501-pwm.c b/arch/mips/loongson/gdium/sm501-pwm.c
+new file mode 100644
+index 0000000..5af3b23
+--- /dev/null
++++ b/arch/mips/loongson/gdium/sm501-pwm.c
+@@ -0,0 +1,465 @@
++/*
++ * SM501 PWM clock
++ * Copyright (C) 2009-2010 Arnaud Patard <apatard@mandriva.com>
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License as published by the
++ * Free Software Foundation; either version 2 of the License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/interrupt.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/pwm.h>
++#include <linux/sm501.h>
++#include <linux/sm501-regs.h>
++#include <linux/debugfs.h>
++#include <linux/seq_file.h>
++
++static const char drv_name[] = "sm501-pwm";
++
++#define INPUT_CLOCK 96 /* MHz */
++#define PWM_COUNT 3
++
++#define SM501PWM_HIGH_COUNTER (1<<20)
++#define SM501PWM_LOW_COUNTER (1<<8)
++#define SM501PWM_CLOCK_DIVIDE (1>>4)
++#define SM501PWM_IP (1<<3)
++#define SM501PWM_I (1<<2)
++#define SM501PWM_E (1<<0)
++
++struct pwm_device {
++ struct list_head node;
++ struct device *dev;
++ void __iomem *regs;
++ int duty_ns;
++ int period_ns;
++ char enabled;
++ void (*handler)(struct pwm_device *pwm);
++
++ const char *label;
++ unsigned int use_count;
++ unsigned int pwm_id;
++};
++
++struct sm501pwm_info {
++ void __iomem *regs;
++ int irq;
++ struct resource *res;
++ struct device *dev;
++ struct dentry *debugfs;
++
++ struct pwm_device pwm[3];
++};
++
++int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
++{
++ unsigned int high, low, divider;
++ int divider1, divider2;
++ unsigned long long delay;
++
++ if (!pwm || !pwm->regs || period_ns == 0 || duty_ns > period_ns)
++ return -EINVAL;
++
++ /* Get delay
++ * We're loosing some precision but multiplying then dividing
++ * will overflow
++ */
++ if (period_ns > 1000) {
++ delay = period_ns / 1000;
++ delay *= INPUT_CLOCK;
++ } else {
++ delay = period_ns * 96;
++ delay /= 1000;
++ }
++
++ /* Get the number of clock low and high */
++ high = delay * duty_ns / period_ns;
++ low = delay - high;
++
++ /* Get divider to make 'low' and 'high' fit into 12 bits */
++ /* No need to say that the divider must be >= 0 */
++ divider1 = fls(low)-12;
++ divider2 = fls(high)-12;
++
++ if (divider1 < 0)
++ divider1 = 0;
++ if (divider2 < 0)
++ divider2 = 0;
++
++ divider = max(divider1, divider2);
++
++ low >>= divider;
++ high >>= divider;
++
++ pwm->duty_ns = duty_ns;
++ pwm->period_ns = period_ns;
++
++ writel((high<<20)|(low<<8)|(divider<<4), pwm->regs);
++ return 0;
++}
++EXPORT_SYMBOL(pwm_config);
++
++int pwm_enable(struct pwm_device *pwm)
++{
++ u32 reg;
++
++ if (!pwm)
++ return -EINVAL;
++
++ switch (pwm->pwm_id) {
++ case 0:
++ sm501_configure_gpio(pwm->dev->parent, 29, 1);
++ break;
++ case 1:
++ sm501_configure_gpio(pwm->dev->parent, 30, 1);
++ break;
++ case 2:
++ sm501_configure_gpio(pwm->dev->parent, 31, 1);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ reg = readl(pwm->regs);
++ reg |= (SM501PWM_IP | SM501PWM_E);
++ writel(reg, pwm->regs);
++ pwm->enabled = 1;
++
++ return 0;
++}
++EXPORT_SYMBOL(pwm_enable);
++
++void pwm_disable(struct pwm_device *pwm)
++{
++ u32 reg;
++
++ if (!pwm)
++ return;
++
++ reg = readl(pwm->regs);
++ reg &= ~(SM501PWM_IP | SM501PWM_E);
++ writel(reg, pwm->regs);
++
++ switch (pwm->pwm_id) {
++ case 0:
++ sm501_configure_gpio(pwm->dev->parent, 29, 0);
++ break;
++ case 1:
++ sm501_configure_gpio(pwm->dev->parent, 30, 0);
++ break;
++ case 2:
++ sm501_configure_gpio(pwm->dev->parent, 31, 0);
++ break;
++ default:
++ break;
++ }
++ pwm->enabled = 0;
++}
++EXPORT_SYMBOL(pwm_disable);
++
++static DEFINE_MUTEX(pwm_lock);
++static LIST_HEAD(pwm_list);
++
++struct pwm_device *pwm_request(int pwm_id, const char *label)
++{
++ struct pwm_device *pwm;
++ int found = 0;
++
++ mutex_lock(&pwm_lock);
++
++ list_for_each_entry(pwm, &pwm_list, node) {
++ if (pwm->pwm_id == pwm_id && pwm->use_count == 0) {
++ pwm->use_count++;
++ pwm->label = label;
++ found = 1;
++ break;
++ }
++ }
++
++ mutex_unlock(&pwm_lock);
++
++ return (found) ? pwm : NULL;
++}
++EXPORT_SYMBOL(pwm_request);
++
++void pwm_free(struct pwm_device *pwm)
++{
++ mutex_lock(&pwm_lock);
++
++ if (pwm->use_count) {
++ pwm->use_count--;
++ pwm->label = NULL;
++ } else
++ dev_warn(pwm->dev, "PWM device already freed\n");
++
++ mutex_unlock(&pwm_lock);
++}
++EXPORT_SYMBOL(pwm_free);
++
++int pwm_int_enable(struct pwm_device *pwm)
++{
++ unsigned long conf;
++
++ if (!pwm || !pwm->regs || !pwm->handler)
++ return -EINVAL;
++
++ conf = readl(pwm->regs);
++ conf |= SM501PWM_I;
++ writel(conf, pwm->regs);
++ return 0;
++}
++EXPORT_SYMBOL(pwm_int_enable);
++
++int pwm_int_disable(struct pwm_device *pwm)
++{
++ unsigned long conf;
++
++ if (!pwm || !pwm->regs || !pwm->handler)
++ return -EINVAL;
++
++ conf = readl(pwm->regs);
++ conf &= ~SM501PWM_I;
++ writel(conf, pwm->regs);
++ return 0;
++}
++EXPORT_SYMBOL(pwm_int_disable);
++
++int pwm_set_handler(struct pwm_device *pwm,
++ void (*handler)(struct pwm_device *pwm))
++{
++ if (!pwm || !handler)
++ return -EINVAL;
++ pwm->handler = handler;
++ return 0;
++}
++EXPORT_SYMBOL(pwm_set_handler);
++
++static irqreturn_t sm501pwm_irq(int irq, void *dev_id)
++{
++ unsigned long value;
++ struct sm501pwm_info *info = (struct sm501pwm_info *)dev_id;
++ struct pwm_device *pwm;
++ int i;
++
++ value = sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 0);
++
++ /* Check is the interrupt is for us */
++ if (value & (1<<22)) {
++ for (i = 0 ; i < PWM_COUNT ; i++) {
++ /*
++ * Find which pwm triggered the interrupt
++ * and ack
++ */
++ value = readl(info->regs + i*4);
++ if (value & SM501PWM_IP)
++ writel(value | SM501PWM_IP, info->regs + i*4);
++
++ pwm = &info->pwm[i];
++ if (pwm->handler)
++ pwm->handler(pwm);
++ }
++ return IRQ_HANDLED;
++ }
++
++ return IRQ_NONE;
++}
++
++static void add_pwm(int id, struct sm501pwm_info *info)
++{
++ struct pwm_device *pwm = &info->pwm[id];
++
++ pwm->use_count = 0;
++ pwm->pwm_id = id;
++ pwm->dev = info->dev;
++ pwm->regs = info->regs + id * 4;
++
++ mutex_lock(&pwm_lock);
++ list_add_tail(&pwm->node, &pwm_list);
++ mutex_unlock(&pwm_lock);
++}
++
++static void del_pwm(int id, struct sm501pwm_info *info)
++{
++ struct pwm_device *pwm = &info->pwm[id];
++
++ pwm->use_count = 0;
++ pwm->pwm_id = -1;
++ mutex_lock(&pwm_lock);
++ list_del(&pwm->node);
++ mutex_unlock(&pwm_lock);
++}
++
++/* Debug fs */
++static int sm501pwm_show(struct seq_file *s, void *p)
++{
++ struct pwm_device *pwm;
++
++ mutex_lock(&pwm_lock);
++ list_for_each_entry(pwm, &pwm_list, node) {
++ if (pwm->use_count) {
++ seq_printf(s, "pwm-%d (%12s) %d %d %s\n",
++ pwm->pwm_id, pwm->label,
++ pwm->duty_ns, pwm->period_ns,
++ pwm->enabled ? "on" : "off");
++ seq_printf(s, " %08x\n", readl(pwm->regs));
++ }
++ }
++ mutex_unlock(&pwm_lock);
++
++ return 0;
++}
++
++static int sm501pwm_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, sm501pwm_show, inode->i_private);
++}
++
++static const struct file_operations sm501pwm_fops = {
++ .open = sm501pwm_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = single_release,
++ .owner = THIS_MODULE,
++};
++
++static int __init sm501pwm_probe(struct platform_device *pdev)
++{
++ struct sm501pwm_info *info;
++ struct device *dev = &pdev->dev;
++ struct resource *res;
++ int ret = 0;
++ int res_len;
++ int i;
++
++ info = kzalloc(sizeof(struct sm501pwm_info), GFP_KERNEL);
++ if (!info) {
++ dev_err(dev, "Allocation failure\n");
++ ret = -ENOMEM;
++ goto err;
++ }
++ info->dev = dev;
++ platform_set_drvdata(pdev, info);
++
++ /* Get irq number */
++ info->irq = platform_get_irq(pdev, 0);
++ if (!info->irq) {
++ dev_err(dev, "no irq found\n");
++ ret = -ENODEV;
++ goto err_alloc;
++ }
++
++ /* Get regs address */
++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ if (res == NULL) {
++ dev_err(dev, "No memory resource found\n");
++ ret = -ENODEV;
++ goto err_alloc;
++ }
++ info->res = res;
++ res_len = (res->end - res->start)+1;
++
++ if (!request_mem_region(res->start, res_len, drv_name)) {
++ dev_err(dev, "Can't request iomem resource\n");
++ ret = -EBUSY;
++ goto err_alloc;
++ }
++
++ info->regs = ioremap(res->start, res_len);
++ if (!info->regs) {
++ dev_err(dev, "ioremap failed\n");
++ ret = -ENOMEM;
++ goto err_mem;
++ }
++
++ ret = request_irq(info->irq, sm501pwm_irq, IRQF_SHARED, drv_name, info);
++ if (ret != 0) {
++ dev_err(dev, "can't get irq\n");
++ goto err_map;
++ }
++
++
++ sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 1);
++
++ for (i = 0; i < 3; i++)
++ add_pwm(i, info);
++
++ dev_info(dev, "SM501 PWM Found at %lx irq %d\n",
++ (unsigned long)info->res->start, info->irq);
++
++ info->debugfs = debugfs_create_file("pwm", S_IFREG | S_IRUGO,
++ NULL, info, &sm501pwm_fops);
++
++
++ return 0;
++
++err_map:
++ iounmap(info->regs);
++
++err_mem:
++ release_mem_region(res->start, res_len);
++
++err_alloc:
++ kfree(info);
++ platform_set_drvdata(pdev, NULL);
++err:
++ return ret;
++}
++
++static int sm501pwm_remove(struct platform_device *pdev)
++{
++ struct sm501pwm_info *info = platform_get_drvdata(pdev);
++ int i;
++
++ if (info->debugfs)
++ debugfs_remove(info->debugfs);
++
++ for (i = 0; i < 3; i++) {
++ pwm_disable(&info->pwm[i]);
++ del_pwm(i, info);
++ }
++
++ sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 0);
++ sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 1<<22);
++
++ free_irq(info->irq, info);
++ iounmap(info->regs);
++ release_mem_region(info->res->start,
++ (info->res->end - info->res->start)+1);
++ kfree(info);
++ platform_set_drvdata(pdev, NULL);
++
++ return 0;
++}
++
++static struct platform_driver sm501pwm_driver = {
++ .probe = sm501pwm_probe,
++ .remove = sm501pwm_remove,
++ .driver = {
++ .name = drv_name,
++ .owner = THIS_MODULE,
++ },
++};
++
++static int __devinit sm501pwm_init(void)
++{
++ return platform_driver_register(&sm501pwm_driver);
++}
++
++static void __exit sm501pwm_cleanup(void)
++{
++ platform_driver_unregister(&sm501pwm_driver);
++}
++
++module_init(sm501pwm_init);
++module_exit(sm501pwm_cleanup);
++
++MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
++MODULE_DESCRIPTION("SM501 PWM driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:sm501-pwm");
+diff --git a/arch/mips/loongson/lemote-2f/Makefile b/arch/mips/loongson/lemote-2f/Makefile
+index 4f9eaa3..f945bd7a 100644
+--- a/arch/mips/loongson/lemote-2f/Makefile
++++ b/arch/mips/loongson/lemote-2f/Makefile
+@@ -2,7 +2,7 @@
+ # Makefile for lemote loongson2f family machines
+ #
+
+-obj-y += clock.o machtype.o irq.o reset.o ec_kb3310b.o
++obj-y += clock.o machtype.o irq.o reset.o ec_kb3310b.o platform.o
+
+ #
+ # Suspend Support
+diff --git a/arch/mips/loongson/lemote-2f/platform.c b/arch/mips/loongson/lemote-2f/platform.c
+new file mode 100644
+index 0000000..5316360
+--- /dev/null
++++ b/arch/mips/loongson/lemote-2f/platform.c
+@@ -0,0 +1,48 @@
++/*
++ * Copyright (C) 2009 Lemote Inc.
++ * Author: Wu Zhangjin, wuzhangjin@gmail.com
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License as published by the
++ * Free Software Foundation; either version 2 of the License, or (at your
++ * option) any later version.
++ */
++
++#include <linux/err.h>
++#include <linux/platform_device.h>
++
++#include <asm/bootinfo.h>
++
++static struct platform_device yeeloong_pdev = {
++ .name = "yeeloong_laptop",
++ .id = -1,
++};
++
++static struct platform_device lynloong_pdev = {
++ .name = "lynloong_pc",
++ .id = -1,
++};
++
++static int __init lemote2f_platform_init(void)
++{
++ struct platform_device *pdev = NULL;
++
++ switch (mips_machtype) {
++ case MACH_LEMOTE_YL2F89:
++ pdev = &yeeloong_pdev;
++ break;
++ case MACH_LEMOTE_LL2F:
++ pdev = &lynloong_pdev;
++ break;
++ default:
++ break;
++
++ }
++
++ if (pdev != NULL)
++ return platform_device_register(pdev);
++
++ return -ENODEV;
++}
++
++arch_initcall(lemote2f_platform_init);
+diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c
+index 22b9b2c..86ec4f2 100644
+--- a/arch/mips/math-emu/cp1emu.c
++++ b/arch/mips/math-emu/cp1emu.c
+@@ -7,6 +7,9 @@
+ * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com
+ * Copyright (C) 2000 MIPS Technologies, Inc.
+ *
++ * Loongson instruction support
++ * Copyright (C) 2011 Mark H Weaver <mhw@netris.org>
++ *
+ * This program is free software; you can distribute it and/or modify it
+ * under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+@@ -61,6 +64,11 @@ static int fpu_emu(struct pt_regs *, struct mips_fpu_struct *,
+ static int fpux_emu(struct pt_regs *,
+ struct mips_fpu_struct *, mips_instruction, void *__user *);
+
++#ifdef CONFIG_MACH_LOONGSON
++static int loongson_spec2_emu(struct pt_regs *,
++ struct mips_fpu_struct *, mips_instruction, void *__user *);
++#endif
++
+ /* Control registers */
+
+ #define FPCREG_RID 0 /* $0 = revision id */
+@@ -843,6 +851,14 @@ do { \
+ #define DPFROMREG(dp, x) DIFROMREG((dp).bits, x)
+ #define DPTOREG(dp, x) DITOREG((dp).bits, x)
+
++/* Support for Loongson paired single floating-point format */
++#define PSIFROMREG(si1, si2, x) ({ u64 di; DIFROMREG(di, x); \
++ (si1) = (u32)di; (si2) = (u32)(di >> 32); })
++#define PSITOREG(si1, si2, x) DITOREG((si1) | ((u64)(si2) << 32), x)
++
++#define PSPFROMREG(sp1, sp2, x) PSIFROMREG((sp1).bits, (sp2).bits, x)
++#define PSPTOREG(sp1, sp2, x) PSITOREG((sp1).bits, (sp2).bits, x)
++
+ /*
+ * Emulate a CFC1 instruction.
+ */
+@@ -1349,6 +1365,16 @@ emul:
+ xcp->regs[MIPSInst_RD(ir)] =
+ xcp->regs[MIPSInst_RS(ir)];
+ break;
++
++#ifdef CONFIG_MACH_LOONGSON
++ case spec2_op:{
++ int sig = loongson_spec2_emu(xcp, ctx, ir, fault_addr);
++ if (sig)
++ return sig;
++ break;
++ }
++#endif
++
+ default:
+ sigill:
+ return SIGILL;
+@@ -1426,6 +1452,172 @@ DEF3OP(msub, dp, ieee754dp_mul, ieee754dp_sub, );
+ DEF3OP(nmadd, dp, ieee754dp_mul, ieee754dp_add, ieee754dp_neg);
+ DEF3OP(nmsub, dp, ieee754dp_mul, ieee754dp_sub, ieee754dp_neg);
+
++#ifdef CONFIG_MACH_LOONGSON
++static int loongson_spec2_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
++ mips_instruction ir, void *__user *fault_addr)
++{
++ int rfmt; /* resulting format */
++ unsigned rcsr = 0; /* resulting csr */
++ union {
++ union ieee754dp d;
++ struct {
++ union ieee754sp s;
++ union ieee754sp s2;
++ };
++ } rv; /* resulting value */
++
++ /* XXX maybe add a counter for loongson spec2 fp instructions? */
++ /* MIPS_FPU_EMU_INC_STATS(cp1xops); */
++
++ switch (rfmt = (MIPSInst_FFMT(ir) & 0xf)) {
++ case s_fmt:{
++ union ieee754sp(*handler) (union ieee754sp, union ieee754sp, union ieee754sp);
++ union ieee754sp fd, fs, ft;
++
++ switch (MIPSInst_FUNC(ir)) {
++ case loongson_madd_op:
++ handler = fpemu_sp_madd;
++ goto scoptop;
++ case loongson_msub_op:
++ handler = fpemu_sp_msub;
++ goto scoptop;
++ case loongson_nmadd_op:
++ handler = fpemu_sp_nmadd;
++ goto scoptop;
++ case loongson_nmsub_op:
++ handler = fpemu_sp_nmsub;
++ goto scoptop;
++
++ scoptop:
++ SPFROMREG(fd, MIPSInst_FD(ir));
++ SPFROMREG(fs, MIPSInst_FS(ir));
++ SPFROMREG(ft, MIPSInst_FT(ir));
++ rv.s = (*handler) (fd, fs, ft);
++
++ copcsr:
++ if (ieee754_cxtest(IEEE754_INEXACT))
++ rcsr |= FPU_CSR_INE_X | FPU_CSR_INE_S;
++ if (ieee754_cxtest(IEEE754_UNDERFLOW))
++ rcsr |= FPU_CSR_UDF_X | FPU_CSR_UDF_S;
++ if (ieee754_cxtest(IEEE754_OVERFLOW))
++ rcsr |= FPU_CSR_OVF_X | FPU_CSR_OVF_S;
++ if (ieee754_cxtest(IEEE754_INVALID_OPERATION))
++ rcsr |= FPU_CSR_INV_X | FPU_CSR_INV_S;
++
++ break;
++
++ default:
++ return SIGILL;
++ }
++ break;
++ }
++
++ case d_fmt:{
++ union ieee754dp(*handler) (union ieee754dp, union ieee754dp, union ieee754dp);
++ union ieee754dp fd, fs, ft;
++
++ switch (MIPSInst_FUNC(ir)) {
++ case loongson_madd_op:
++ handler = fpemu_dp_madd;
++ goto dcoptop;
++ case loongson_msub_op:
++ handler = fpemu_dp_msub;
++ goto dcoptop;
++ case loongson_nmadd_op:
++ handler = fpemu_dp_nmadd;
++ goto dcoptop;
++ case loongson_nmsub_op:
++ handler = fpemu_dp_nmsub;
++ goto dcoptop;
++
++ dcoptop:
++ DPFROMREG(fd, MIPSInst_FD(ir));
++ DPFROMREG(fs, MIPSInst_FS(ir));
++ DPFROMREG(ft, MIPSInst_FT(ir));
++ rv.d = (*handler) (fd, fs, ft);
++ goto copcsr;
++
++ default:
++ return SIGILL;
++ }
++ break;
++ }
++
++ case ps_fmt:{
++ union ieee754sp(*handler) (union ieee754sp, union ieee754sp, union ieee754sp);
++ struct _ieee754_csr ieee754_csr_save;
++ union ieee754sp fd1, fs1, ft1;
++ union ieee754sp fd2, fs2, ft2;
++
++ switch (MIPSInst_FUNC(ir)) {
++ case loongson_madd_op:
++ handler = fpemu_sp_madd;
++ goto pscoptop;
++ case loongson_msub_op:
++ handler = fpemu_sp_msub;
++ goto pscoptop;
++ case loongson_nmadd_op:
++ handler = fpemu_sp_nmadd;
++ goto pscoptop;
++ case loongson_nmsub_op:
++ handler = fpemu_sp_nmsub;
++ goto pscoptop;
++
++ pscoptop:
++ PSPFROMREG(fd1, fd2, MIPSInst_FD(ir));
++ PSPFROMREG(fs1, fs2, MIPSInst_FS(ir));
++ PSPFROMREG(ft1, ft2, MIPSInst_FT(ir));
++ rv.s = (*handler) (fd1, fs1, ft1);
++ ieee754_csr_save = ieee754_csr;
++ rv.s2 = (*handler) (fd2, fs2, ft2);
++ ieee754_csr.cx |= ieee754_csr_save.cx;
++ ieee754_csr.sx |= ieee754_csr_save.sx;
++ goto copcsr;
++
++ default:
++ return SIGILL;
++ }
++ break;
++ }
++
++ default:
++ return SIGILL;
++ }
++
++ /*
++ * Update the fpu CSR register for this operation.
++ * If an exception is required, generate a tidy SIGFPE exception,
++ * without updating the result register.
++ * Note: cause exception bits do not accumulate, they are rewritten
++ * for each op; only the flag/sticky bits accumulate.
++ */
++ ctx->fcr31 = (ctx->fcr31 & ~FPU_CSR_ALL_X) | rcsr;
++ if ((ctx->fcr31 >> 5) & ctx->fcr31 & FPU_CSR_ALL_E) {
++ /*printk ("SIGFPE: fpu csr = %08x\n",ctx->fcr31); */
++ return SIGFPE;
++ }
++
++ /*
++ * Now we can safely write the result back to the register file.
++ */
++ switch (rfmt) {
++ case d_fmt:
++ DPTOREG(rv.d, MIPSInst_FD(ir));
++ break;
++ case s_fmt:
++ SPTOREG(rv.s, MIPSInst_FD(ir));
++ break;
++ case ps_fmt:
++ PSPTOREG(rv.s, rv.s2, MIPSInst_FD(ir));
++ break;
++ default:
++ return SIGILL;
++ }
++
++ return 0;
++}
++#endif
++
+ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+ mips_instruction ir, void *__user *fault_addr)
+ {
+@@ -1527,7 +1719,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+ break;
+
+ default:
+- return SIGILL;
++ goto SIGILL_unless_prefx_op;
+ }
+ break;
+ }
+@@ -1597,7 +1789,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+ goto copcsr;
+
+ default:
+- return SIGILL;
++ goto SIGILL_unless_prefx_op;
+ }
+ break;
+ }
+@@ -1610,6 +1802,11 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+ break;
+
+ default:
++ SIGILL_unless_prefx_op:
++ if (MIPSInst_FUNC(ir) == prefx_op) {
++ /* ignore prefx operation */
++ break;
++ }
+ return SIGILL;
+ }
+
+@@ -1631,7 +1828,12 @@ static int fpu_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+ unsigned cond;
+ union {
+ union ieee754dp d;
+- union ieee754sp s;
++ struct {
++ union ieee754sp s;
++#ifdef CONFIG_MACH_LOONGSON
++ union ieee754sp s2; /* for Loongson paired singles */
++#endif
++ };
+ int w;
+ s64 l;
+ } rv; /* resulting value */
+@@ -1728,7 +1930,7 @@ static int fpu_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
+ case fmov_op:
+ /* an easy one */
+ SPFROMREG(rv.s, MIPSInst_FS(ir));
+- goto copcsr;
++ break;
+
+ /* binary op on handler */
+ scopbop:
+@@ -1925,7 +2127,7 @@ copcsr:
+ case fmov_op:
+ /* an easy one */
+ DPFROMREG(rv.d, MIPSInst_FS(ir));
+- goto copcsr;
++ break;
+
+ /* binary op on handler */
+ dcopbop:
+@@ -2042,6 +2244,83 @@ dcopuop:
+ break;
+ }
+
++#ifdef CONFIG_MACH_LOONGSON
++ case ps_fmt:{ /* 6 */
++ /* Support for Loongson paired single fp instructions */
++ union {
++ union ieee754sp(*b) (union ieee754sp, union ieee754sp);
++ union ieee754sp(*u) (union ieee754sp);
++ } handler;
++
++ switch (MIPSInst_FUNC(ir)) {
++ /* binary ops */
++ case fadd_op:
++ handler.b = ieee754sp_add;
++ goto pscopbop;
++ case fsub_op:
++ handler.b = ieee754sp_sub;
++ goto pscopbop;
++ case fmul_op:
++ handler.b = ieee754sp_mul;
++ goto pscopbop;
++
++ /* unary ops */
++ case fabs_op:
++ handler.u = ieee754sp_abs;
++ goto pscopuop;
++ case fneg_op:
++ handler.u = ieee754sp_neg;
++ goto pscopuop;
++ case fmov_op:
++ /* an easy one */
++ PSPFROMREG(rv.s, rv.s2, MIPSInst_FS(ir));
++ break;
++
++ pscopbop: /* paired binary op handler */
++ {
++ struct _ieee754_csr ieee754_csr_save;
++ union ieee754sp fs1, ft1;
++ union ieee754sp fs2, ft2;
++
++ PSPFROMREG(fs1, fs2, MIPSInst_FS(ir));
++ PSPFROMREG(ft1, ft2, MIPSInst_FT(ir));
++ rv.s = (*handler.b) (fs1, ft1);
++ ieee754_csr_save = ieee754_csr;
++ rv.s2 = (*handler.b) (fs2, ft2);
++ ieee754_csr.cx |= ieee754_csr_save.cx;
++ ieee754_csr.sx |= ieee754_csr_save.sx;
++ goto copcsr;
++ }
++ pscopuop: /* paired unary op handler */
++ {
++ struct _ieee754_csr ieee754_csr_save;
++ union ieee754sp fs1;
++ union ieee754sp fs2;
++
++ PSPFROMREG(fs1, fs2, MIPSInst_FS(ir));
++ rv.s = (*handler.u) (fs1);
++ ieee754_csr_save = ieee754_csr;
++ rv.s2 = (*handler.u) (fs2);
++ ieee754_csr.cx |= ieee754_csr_save.cx;
++ ieee754_csr.sx |= ieee754_csr_save.sx;
++ goto copcsr;
++ }
++ break;
++
++ default:
++ if (MIPSInst_FUNC(ir) >= fcmp_op) {
++ /* Loongson fp hardware handles all
++ cases of fp compare insns, so we
++ shouldn't have to */
++ printk ("Loongson paired-single fp compare"
++ " unimplemented in cp1emu.c\n");
++ }
++ return SIGILL;
++ }
++ break;
++ }
++#endif
++
+ case l_fmt:
+
+ if (!cpu_has_mips_3_4_5_64_r2_r6)
+@@ -2113,6 +2392,11 @@ dcopuop:
+
+ DITOREG(rv.l, MIPSInst_FD(ir));
+ break;
++#ifdef CONFIG_MACH_LOONGSON
++ case ps_fmt:
++ PSPTOREG(rv.s, rv.s2, MIPSInst_FD(ir));
++ break;
++#endif
+ default:
+ return SIGILL;
+ }
+diff --git a/arch/mips/pci/Makefile b/arch/mips/pci/Makefile
+index 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/cpufreq/loongson2_cpufreq.c b/drivers/cpufreq/loongson2_cpufreq.c
+index fc897ba..ac60f6b 100644
+--- a/drivers/cpufreq/loongson2_cpufreq.c
++++ b/drivers/cpufreq/loongson2_cpufreq.c
+@@ -161,20 +161,32 @@ static int __init cpufreq_init(void)
+ /* Register platform stuff */
+ ret = platform_driver_register(&platform_driver);
+ if (ret)
+- return ret;
++ goto err_return;
+
+ pr_info("cpufreq: Loongson-2F CPU frequency driver.\n");
+
+- cpufreq_register_notifier(&loongson2_cpufreq_notifier_block,
+- CPUFREQ_TRANSITION_NOTIFIER);
++ ret = cpufreq_register_notifier(&loongson2_cpufreq_notifier_block,
++ CPUFREQ_TRANSITION_NOTIFIER);
++ if (ret)
++ goto err_platform_driver_unregister;
+
+ ret = cpufreq_register_driver(&loongson2_cpufreq_driver);
++ if (ret)
++ goto err_cpufreq_unregister_notifier;
+
+- if (!ret && !nowait) {
++ if (!nowait) {
+ saved_cpu_wait = cpu_wait;
+ cpu_wait = loongson2_cpu_wait;
+ }
+
++ return 0;
++
++ err_cpufreq_unregister_notifier:
++ cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block,
++ CPUFREQ_TRANSITION_NOTIFIER);
++ err_platform_driver_unregister:
++ platform_driver_unregister(&platform_driver);
++ err_return:
+ return ret;
+ }
+
+diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
+index 15338afd..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/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/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/net/titan_ge.c b/drivers/net/titan_ge.c
+new file mode 100644
+index 0000000..dc137bf8
+--- /dev/null
++++ b/drivers/net/titan_ge.c
+@@ -0,0 +1,2069 @@
++/*
++ * drivers/net/titan_ge.c - Driver for Titan ethernet ports
++ *
++ * Copyright (C) 2003 PMC-Sierra Inc.
++ * Author : Manish Lachwani (lachwani@pmc-sierra.com)
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++ */
++
++/*
++ * The MAC unit of the Titan consists of the following:
++ *
++ * -> XDMA Engine to move data to from the memory to the MAC packet FIFO
++ * -> FIFO is where the incoming and outgoing data is placed
++ * -> TRTG is the unit that pulls the data from the FIFO for Tx and pushes
++ * the data into the FIFO for Rx
++ * -> TMAC is the outgoing MAC interface and RMAC is the incoming.
++ * -> AFX is the address filtering block
++ * -> GMII block to communicate with the PHY
++ *
++ * Rx will look like the following:
++ * GMII --> RMAC --> AFX --> TRTG --> Rx FIFO --> XDMA --> CPU memory
++ *
++ * Tx will look like the following:
++ * CPU memory --> XDMA --> Tx FIFO --> TRTG --> TMAC --> GMII
++ *
++ * The Titan driver has support for the following performance features:
++ * -> Rx side checksumming
++ * -> Jumbo Frames
++ * -> Interrupt Coalscing
++ * -> Rx NAPI
++ * -> SKB Recycling
++ * -> Transmit/Receive descriptors in SRAM
++ * -> Fast routing for IP forwarding
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/ioport.h>
++#include <linux/interrupt.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++#include <linux/errno.h>
++#include <linux/ip.h>
++#include <linux/init.h>
++#include <linux/in.h>
++#include <linux/platform_device.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/skbuff.h>
++#include <linux/mii.h>
++#include <linux/delay.h>
++#include <linux/skbuff.h>
++#include <linux/prefetch.h>
++
++/* For MII specifc registers, titan_mdio.h should be included */
++#include <net/ip.h>
++
++#include <asm/bitops.h>
++#include <asm/io.h>
++#include <asm/types.h>
++#include <asm/pgtable.h>
++#include <asm/system.h>
++#include <asm/titan_dep.h>
++
++#include "titan_ge.h"
++#include "titan_mdio.h"
++
++/* Static Function Declarations */
++static int titan_ge_eth_open(struct net_device *);
++static void titan_ge_eth_stop(struct net_device *);
++static struct net_device_stats *titan_ge_get_stats(struct net_device *);
++static int titan_ge_init_rx_desc_ring(titan_ge_port_info *, int, int,
++ unsigned long, unsigned long,
++ unsigned long);
++static int titan_ge_init_tx_desc_ring(titan_ge_port_info *, int,
++ unsigned long, unsigned long);
++
++static int titan_ge_open(struct net_device *);
++static int titan_ge_start_xmit(struct sk_buff *, struct net_device *);
++static int titan_ge_stop(struct net_device *);
++
++static unsigned long titan_ge_tx_coal(unsigned long, int);
++
++static void titan_ge_port_reset(unsigned int);
++static int titan_ge_free_tx_queue(titan_ge_port_info *);
++static int titan_ge_rx_task(struct net_device *, titan_ge_port_info *);
++static int titan_ge_port_start(struct net_device *, titan_ge_port_info *);
++
++static int titan_ge_return_tx_desc(titan_ge_port_info *, int);
++
++/*
++ * Some configuration for the FIFO and the XDMA channel needs
++ * to be done only once for all the ports. This flag controls
++ * that
++ */
++static unsigned long config_done;
++
++/*
++ * One time out of memory flag
++ */
++static unsigned int oom_flag;
++
++static int titan_ge_poll(struct net_device *netdev, int *budget);
++
++static int titan_ge_receive_queue(struct net_device *, unsigned int);
++
++static struct platform_device *titan_ge_device[3];
++
++/* MAC Address */
++extern unsigned char titan_ge_mac_addr_base[6];
++
++unsigned long titan_ge_base;
++static unsigned long titan_ge_sram;
++
++static char titan_string[] = "titan";
++
++/*
++ * The Titan GE has two alignment requirements:
++ * -> skb->data to be cacheline aligned (32 byte)
++ * -> IP header alignment to 16 bytes
++ *
++ * The latter is not implemented. So, that results in an extra copy on
++ * the Rx. This is a big performance hog. For the former case, the
++ * dev_alloc_skb() has been replaced with titan_ge_alloc_skb(). The size
++ * requested is calculated:
++ *
++ * Ethernet Frame Size : 1518
++ * Ethernet Header : 14
++ * Future Titan change for IP header alignment : 2
++ *
++ * Hence, we allocate (1518 + 14 + 2+ 64) = 1580 bytes. For IP header
++ * alignment, we use skb_reserve().
++ */
++
++#define ALIGNED_RX_SKB_ADDR(addr) \
++ ((((unsigned long)(addr) + (64UL - 1UL)) \
++ & ~(64UL - 1UL)) - (unsigned long)(addr))
++
++#define titan_ge_alloc_skb(__length, __gfp_flags) \
++({ struct sk_buff *__skb; \
++ __skb = alloc_skb((__length) + 64, (__gfp_flags)); \
++ if(__skb) { \
++ int __offset = (int) ALIGNED_RX_SKB_ADDR(__skb->data); \
++ if(__offset) \
++ skb_reserve(__skb, __offset); \
++ } \
++ __skb; \
++})
++
++/*
++ * Configure the GMII block of the Titan based on what the PHY tells us
++ */
++static void titan_ge_gmii_config(int port_num)
++{
++ unsigned int reg_data = 0, phy_reg;
++ int err;
++
++ err = titan_ge_mdio_read(port_num, TITAN_GE_MDIO_PHY_STATUS, &phy_reg);
++
++ if (err == TITAN_GE_MDIO_ERROR) {
++ printk(KERN_ERR
++ "Could not read PHY control register 0x11 \n");
++ printk(KERN_ERR
++ "Setting speed to 1000 Mbps and Duplex to Full \n");
++
++ return;
++ }
++
++ err = titan_ge_mdio_write(port_num, TITAN_GE_MDIO_PHY_IE, 0);
++
++ if (phy_reg & 0x8000) {
++ if (phy_reg & 0x2000) {
++ /* Full Duplex and 1000 Mbps */
++ TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
++ (port_num << 12)), 0x201);
++ } else {
++ /* Half Duplex and 1000 Mbps */
++ TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
++ (port_num << 12)), 0x2201);
++ }
++ }
++ if (phy_reg & 0x4000) {
++ if (phy_reg & 0x2000) {
++ /* Full Duplex and 100 Mbps */
++ TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
++ (port_num << 12)), 0x100);
++ } else {
++ /* Half Duplex and 100 Mbps */
++ TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_MODE +
++ (port_num << 12)), 0x2100);
++ }
++ }
++ reg_data = TITAN_GE_READ(TITAN_GE_GMII_CONFIG_GENERAL +
++ (port_num << 12));
++ reg_data |= 0x3;
++ TITAN_GE_WRITE((TITAN_GE_GMII_CONFIG_GENERAL +
++ (port_num << 12)), reg_data);
++}
++
++/*
++ * Enable the TMAC if it is not
++ */
++static void titan_ge_enable_tx(unsigned int port_num)
++{
++ unsigned long reg_data;
++
++ reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 + (port_num << 12));
++ if (!(reg_data & 0x8000)) {
++ printk("TMAC disabled for port %d!! \n", port_num);
++
++ reg_data |= 0x0001; /* Enable TMAC */
++ reg_data |= 0x4000; /* CRC Check Enable */
++ reg_data |= 0x2000; /* Padding enable */
++ reg_data |= 0x0800; /* CRC Add enable */
++ reg_data |= 0x0080; /* PAUSE frame */
++
++ TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 +
++ (port_num << 12)), reg_data);
++ }
++}
++
++/*
++ * Tx Timeout function
++ */
++static void titan_ge_tx_timeout(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++
++ printk(KERN_INFO "%s: TX timeout ", netdev->name);
++ printk(KERN_INFO "Resetting card \n");
++
++ /* Do the reset outside of interrupt context */
++ schedule_work(&titan_ge_eth->tx_timeout_task);
++}
++
++/*
++ * Update the AFX tables for UC and MC for slice 0 only
++ */
++static void titan_ge_update_afx(titan_ge_port_info * titan_ge_eth)
++{
++ int port = titan_ge_eth->port_num;
++ unsigned int i;
++ volatile unsigned long reg_data = 0;
++ u8 p_addr[6];
++
++ memcpy(p_addr, titan_ge_eth->port_mac_addr, 6);
++
++ /* Set the MAC address here for TMAC and RMAC */
++ TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_HI + (port << 12)),
++ ((p_addr[5] << 8) | p_addr[4]));
++ TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_MID + (port << 12)),
++ ((p_addr[3] << 8) | p_addr[2]));
++ TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_LOW + (port << 12)),
++ ((p_addr[1] << 8) | p_addr[0]));
++
++ TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_HI + (port << 12)),
++ ((p_addr[5] << 8) | p_addr[4]));
++ TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_MID + (port << 12)),
++ ((p_addr[3] << 8) | p_addr[2]));
++ TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_LOW + (port << 12)),
++ ((p_addr[1] << 8) | p_addr[0]));
++
++ TITAN_GE_WRITE((0x112c | (port << 12)), 0x1);
++ /* Configure the eight address filters */
++ for (i = 0; i < 8; i++) {
++ /* Select each of the eight filters */
++ TITAN_GE_WRITE((TITAN_GE_AFX_ADDRS_FILTER_CTRL_2 +
++ (port << 12)), i);
++
++ /* Configure the match */
++ reg_data = 0x9; /* Forward Enable Bit */
++ TITAN_GE_WRITE((TITAN_GE_AFX_ADDRS_FILTER_CTRL_0 +
++ (port << 12)), reg_data);
++
++ /* Finally, AFX Exact Match Address Registers */
++ TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_LOW + (port << 12)),
++ ((p_addr[1] << 8) | p_addr[0]));
++ TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_MID + (port << 12)),
++ ((p_addr[3] << 8) | p_addr[2]));
++ TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_HIGH + (port << 12)),
++ ((p_addr[5] << 8) | p_addr[4]));
++
++ /* VLAN id set to 0 */
++ TITAN_GE_WRITE((TITAN_GE_AFX_EXACT_MATCH_VID +
++ (port << 12)), 0);
++ }
++}
++
++/*
++ * Actual Routine to reset the adapter when the timeout occurred
++ */
++static void titan_ge_tx_timeout_task(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ int port = titan_ge_eth->port_num;
++
++ printk("Titan GE: Transmit timed out. Resetting ... \n");
++
++ /* Dump debug info */
++ printk(KERN_ERR "TRTG cause : %x \n",
++ TITAN_GE_READ(0x100c + (port << 12)));
++
++ /* Fix this for the other ports */
++ printk(KERN_ERR "FIFO cause : %x \n", TITAN_GE_READ(0x482c));
++ printk(KERN_ERR "IE cause : %x \n", TITAN_GE_READ(0x0040));
++ printk(KERN_ERR "XDMA GDI ERROR : %x \n",
++ TITAN_GE_READ(0x5008 + (port << 8)));
++ printk(KERN_ERR "CHANNEL ERROR: %x \n",
++ TITAN_GE_READ(TITAN_GE_CHANNEL0_INTERRUPT
++ + (port << 8)));
++
++ netif_device_detach(netdev);
++ titan_ge_port_reset(titan_ge_eth->port_num);
++ titan_ge_port_start(netdev, titan_ge_eth);
++ netif_device_attach(netdev);
++}
++
++/*
++ * Change the MTU of the Ethernet Device
++ */
++static int titan_ge_change_mtu(struct net_device *netdev, int new_mtu)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned long flags;
++
++ if ((new_mtu > 9500) || (new_mtu < 64))
++ return -EINVAL;
++
++ spin_lock_irqsave(&titan_ge_eth->lock, flags);
++
++ netdev->mtu = new_mtu;
++
++ /* Now we have to reopen the interface so that SKBs with the new
++ * size will be allocated */
++
++ if (netif_running(netdev)) {
++ titan_ge_eth_stop(netdev);
++
++ if (titan_ge_eth_open(netdev) != TITAN_OK) {
++ printk(KERN_ERR
++ "%s: Fatal error on opening device\n",
++ netdev->name);
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++ return -1;
++ }
++ }
++
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++ return 0;
++}
++
++/*
++ * Titan Gbe Interrupt Handler. All the three ports send interrupt to one line
++ * only. Once an interrupt is triggered, figure out the port and then check
++ * the channel.
++ */
++static irqreturn_t titan_ge_int_handler(int irq, void *dev_id)
++{
++ struct net_device *netdev = (struct net_device *) dev_id;
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ unsigned int reg_data;
++ unsigned int eth_int_cause_error = 0, is;
++ unsigned long eth_int_cause1;
++ int err = 0;
++#ifdef CONFIG_SMP
++ unsigned long eth_int_cause2;
++#endif
++
++ /* Ack the CPU interrupt */
++ switch (port_num) {
++ case 0:
++ is = OCD_READ(RM9000x2_OCD_INTP0STATUS1);
++ OCD_WRITE(RM9000x2_OCD_INTP0CLEAR1, is);
++
++#ifdef CONFIG_SMP
++ is = OCD_READ(RM9000x2_OCD_INTP1STATUS1);
++ OCD_WRITE(RM9000x2_OCD_INTP1CLEAR1, is);
++#endif
++ break;
++
++ case 1:
++ is = OCD_READ(RM9000x2_OCD_INTP0STATUS0);
++ OCD_WRITE(RM9000x2_OCD_INTP0CLEAR0, is);
++
++#ifdef CONFIG_SMP
++ is = OCD_READ(RM9000x2_OCD_INTP1STATUS0);
++ OCD_WRITE(RM9000x2_OCD_INTP1CLEAR0, is);
++#endif
++ break;
++
++ case 2:
++ is = OCD_READ(RM9000x2_OCD_INTP0STATUS4);
++ OCD_WRITE(RM9000x2_OCD_INTP0CLEAR4, is);
++
++#ifdef CONFIG_SMP
++ is = OCD_READ(RM9000x2_OCD_INTP1STATUS4);
++ OCD_WRITE(RM9000x2_OCD_INTP1CLEAR4, is);
++#endif
++ }
++
++ eth_int_cause1 = TITAN_GE_READ(TITAN_GE_INTR_XDMA_CORE_A);
++#ifdef CONFIG_SMP
++ eth_int_cause2 = TITAN_GE_READ(TITAN_GE_INTR_XDMA_CORE_B);
++#endif
++
++ /* Spurious interrupt */
++#ifdef CONFIG_SMP
++ if ( (eth_int_cause1 == 0) && (eth_int_cause2 == 0)) {
++#else
++ if (eth_int_cause1 == 0) {
++#endif
++ eth_int_cause_error = TITAN_GE_READ(TITAN_GE_CHANNEL0_INTERRUPT +
++ (port_num << 8));
++
++ if (eth_int_cause_error == 0)
++ return IRQ_NONE;
++ }
++
++ /* Handle Tx first. No need to ack interrupts */
++#ifdef CONFIG_SMP
++ if ( (eth_int_cause1 & 0x20202) ||
++ (eth_int_cause2 & 0x20202) )
++#else
++ if (eth_int_cause1 & 0x20202)
++#endif
++ titan_ge_free_tx_queue(titan_ge_eth);
++
++ /* Handle the Rx next */
++#ifdef CONFIG_SMP
++ if ( (eth_int_cause1 & 0x10101) ||
++ (eth_int_cause2 & 0x10101)) {
++#else
++ if (eth_int_cause1 & 0x10101) {
++#endif
++ if (netif_rx_schedule_prep(netdev)) {
++ unsigned int ack;
++
++ ack = TITAN_GE_READ(TITAN_GE_INTR_XDMA_IE);
++ /* Disable Tx and Rx both */
++ if (port_num == 0)
++ ack &= ~(0x3);
++ if (port_num == 1)
++ ack &= ~(0x300);
++
++ if (port_num == 2)
++ ack &= ~(0x30000);
++
++ /* Interrupts have been disabled */
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, ack);
++
++ __netif_rx_schedule(netdev);
++ }
++ }
++
++ /* Handle error interrupts */
++ if (eth_int_cause_error && (eth_int_cause_error != 0x2)) {
++ printk(KERN_ERR
++ "XDMA Channel Error : %x on port %d\n",
++ eth_int_cause_error, port_num);
++
++ printk(KERN_ERR
++ "XDMA GDI Hardware error : %x on port %d\n",
++ TITAN_GE_READ(0x5008 + (port_num << 8)), port_num);
++
++ printk(KERN_ERR
++ "XDMA currently has %d Rx descriptors \n",
++ TITAN_GE_READ(0x5048 + (port_num << 8)));
++
++ printk(KERN_ERR
++ "XDMA currently has prefetcted %d Rx descriptors \n",
++ TITAN_GE_READ(0x505c + (port_num << 8)));
++
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_INTERRUPT +
++ (port_num << 8)), eth_int_cause_error);
++ }
++
++ /*
++ * PHY interrupt to inform abt the changes. Reading the
++ * PHY Status register will clear the interrupt
++ */
++ if ((!(eth_int_cause1 & 0x30303)) &&
++ (eth_int_cause_error == 0)) {
++ err =
++ titan_ge_mdio_read(port_num,
++ TITAN_GE_MDIO_PHY_IS, &reg_data);
++
++ if (reg_data & 0x0400) {
++ /* Link status change */
++ titan_ge_mdio_read(port_num,
++ TITAN_GE_MDIO_PHY_STATUS, &reg_data);
++ if (!(reg_data & 0x0400)) {
++ /* Link is down */
++ netif_carrier_off(netdev);
++ netif_stop_queue(netdev);
++ } else {
++ /* Link is up */
++ netif_carrier_on(netdev);
++ netif_wake_queue(netdev);
++
++ /* Enable the queue */
++ titan_ge_enable_tx(port_num);
++ }
++ }
++ }
++
++ return IRQ_HANDLED;
++}
++
++/*
++ * Multicast and Promiscuous mode set. The
++ * set_multi entry point is called whenever the
++ * multicast address list or the network interface
++ * flags are updated.
++ */
++static void titan_ge_set_multi(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ unsigned long reg_data;
++
++ reg_data = TITAN_GE_READ(TITAN_GE_AFX_ADDRS_FILTER_CTRL_1 +
++ (port_num << 12));
++
++ if (netdev->flags & IFF_PROMISC) {
++ reg_data |= 0x2;
++ }
++ else if (netdev->flags & IFF_ALLMULTI) {
++ reg_data |= 0x01;
++ reg_data |= 0x400; /* Use the 64-bit Multicast Hash bin */
++ }
++ else {
++ reg_data = 0x2;
++ }
++
++ TITAN_GE_WRITE((TITAN_GE_AFX_ADDRS_FILTER_CTRL_1 +
++ (port_num << 12)), reg_data);
++ if (reg_data & 0x01) {
++ TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_LOW +
++ (port_num << 12)), 0xffff);
++ TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_MIDLOW +
++ (port_num << 12)), 0xffff);
++ TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_MIDHI +
++ (port_num << 12)), 0xffff);
++ TITAN_GE_WRITE((TITAN_GE_AFX_MULTICAST_HASH_HI +
++ (port_num << 12)), 0xffff);
++ }
++}
++
++/*
++ * Open the network device
++ */
++static int titan_ge_open(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ unsigned int irq = TITAN_ETH_PORT_IRQ - port_num;
++ int retval;
++
++ retval = request_irq(irq, titan_ge_int_handler,
++ SA_INTERRUPT | SA_SAMPLE_RANDOM , netdev->name, netdev);
++
++ if (retval != 0) {
++ printk(KERN_ERR "Cannot assign IRQ number to TITAN GE \n");
++ return -1;
++ }
++
++ netdev->irq = irq;
++ printk(KERN_INFO "Assigned IRQ %d to port %d\n", irq, port_num);
++
++ spin_lock_irq(&(titan_ge_eth->lock));
++
++ if (titan_ge_eth_open(netdev) != TITAN_OK) {
++ spin_unlock_irq(&(titan_ge_eth->lock));
++ printk("%s: Error opening interface \n", netdev->name);
++ free_irq(netdev->irq, netdev);
++ return -EBUSY;
++ }
++
++ spin_unlock_irq(&(titan_ge_eth->lock));
++
++ return 0;
++}
++
++/*
++ * Allocate the SKBs for the Rx ring. Also used
++ * for refilling the queue
++ */
++static int titan_ge_rx_task(struct net_device *netdev,
++ titan_ge_port_info *titan_ge_port)
++{
++ struct device *device = &titan_ge_device[titan_ge_port->port_num]->dev;
++ volatile titan_ge_rx_desc *rx_desc;
++ struct sk_buff *skb;
++ int rx_used_desc;
++ int count = 0;
++
++ while (titan_ge_port->rx_ring_skbs < titan_ge_port->rx_ring_size) {
++
++ /* First try to get the skb from the recycler */
++#ifdef TITAN_GE_JUMBO_FRAMES
++ skb = titan_ge_alloc_skb(TITAN_GE_JUMBO_BUFSIZE, GFP_ATOMIC);
++#else
++ skb = titan_ge_alloc_skb(TITAN_GE_STD_BUFSIZE, GFP_ATOMIC);
++#endif
++ if (unlikely(!skb)) {
++ /* OOM, set the flag */
++ printk("OOM \n");
++ oom_flag = 1;
++ break;
++ }
++ count++;
++ skb->dev = netdev;
++
++ titan_ge_port->rx_ring_skbs++;
++
++ rx_used_desc = titan_ge_port->rx_used_desc_q;
++ rx_desc = &(titan_ge_port->rx_desc_area[rx_used_desc]);
++
++#ifdef TITAN_GE_JUMBO_FRAMES
++ rx_desc->buffer_addr = dma_map_single(device, skb->data,
++ TITAN_GE_JUMBO_BUFSIZE - 2, DMA_FROM_DEVICE);
++#else
++ rx_desc->buffer_addr = dma_map_single(device, skb->data,
++ TITAN_GE_STD_BUFSIZE - 2, DMA_FROM_DEVICE);
++#endif
++
++ titan_ge_port->rx_skb[rx_used_desc] = skb;
++ rx_desc->cmd_sts = TITAN_GE_RX_BUFFER_OWNED;
++
++ titan_ge_port->rx_used_desc_q =
++ (rx_used_desc + 1) % TITAN_GE_RX_QUEUE;
++ }
++
++ return count;
++}
++
++/*
++ * Actual init of the Tital GE port. There is one register for
++ * the channel configuration
++ */
++static void titan_port_init(struct net_device *netdev,
++ titan_ge_port_info * titan_ge_eth)
++{
++ unsigned long reg_data;
++
++ titan_ge_port_reset(titan_ge_eth->port_num);
++
++ /* First reset the TMAC */
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
++ reg_data |= 0x80000000;
++ TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
++
++ udelay(30);
++
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
++ reg_data &= ~(0xc0000000);
++ TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
++
++ /* Now reset the RMAC */
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
++ reg_data |= 0x00080000;
++ TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
++
++ udelay(30);
++
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG);
++ reg_data &= ~(0x000c0000);
++ TITAN_GE_WRITE(TITAN_GE_CHANNEL0_CONFIG, reg_data);
++}
++
++/*
++ * Start the port. All the hardware specific configuration
++ * for the XDMA, Tx FIFO, Rx FIFO, TMAC, RMAC, TRTG and AFX
++ * go here
++ */
++static int titan_ge_port_start(struct net_device *netdev,
++ titan_ge_port_info * titan_port)
++{
++ volatile unsigned long reg_data, reg_data1;
++ int port_num = titan_port->port_num;
++ int count = 0;
++ unsigned long reg_data_1;
++
++ if (config_done == 0) {
++ reg_data = TITAN_GE_READ(0x0004);
++ reg_data |= 0x100;
++ TITAN_GE_WRITE(0x0004, reg_data);
++
++ reg_data &= ~(0x100);
++ TITAN_GE_WRITE(0x0004, reg_data);
++
++ /* Turn on GMII/MII mode and turn off TBI mode */
++ reg_data = TITAN_GE_READ(TITAN_GE_TSB_CTRL_1);
++ reg_data |= 0x00000700;
++ reg_data &= ~(0x00800000); /* Fencing */
++
++ TITAN_GE_WRITE(0x000c, 0x00001100);
++
++ TITAN_GE_WRITE(TITAN_GE_TSB_CTRL_1, reg_data);
++
++ /* Set the CPU Resource Limit register */
++ TITAN_GE_WRITE(0x00f8, 0x8);
++
++ /* Be conservative when using the BIU buffers */
++ TITAN_GE_WRITE(0x0068, 0x4);
++ }
++
++ titan_port->tx_threshold = 0;
++ titan_port->rx_threshold = 0;
++
++ /* We need to write the descriptors for Tx and Rx */
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_TX_DESC + (port_num << 8)),
++ (unsigned long) titan_port->tx_dma);
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_RX_DESC + (port_num << 8)),
++ (unsigned long) titan_port->rx_dma);
++
++ if (config_done == 0) {
++ /* Step 1: XDMA config */
++ reg_data = TITAN_GE_READ(TITAN_GE_XDMA_CONFIG);
++ reg_data &= ~(0x80000000); /* clear reset */
++ reg_data |= 0x1 << 29; /* sparse tx descriptor spacing */
++ reg_data |= 0x1 << 28; /* sparse rx descriptor spacing */
++ reg_data |= (0x1 << 23) | (0x1 << 24); /* Descriptor Coherency */
++ reg_data |= (0x1 << 21) | (0x1 << 22); /* Data Coherency */
++ TITAN_GE_WRITE(TITAN_GE_XDMA_CONFIG, reg_data);
++ }
++
++ /* IR register for the XDMA */
++ reg_data = TITAN_GE_READ(TITAN_GE_GDI_INTERRUPT_ENABLE + (port_num << 8));
++ reg_data |= 0x80068000; /* No Rx_OOD */
++ TITAN_GE_WRITE((TITAN_GE_GDI_INTERRUPT_ENABLE + (port_num << 8)), reg_data);
++
++ /* Start the Tx and Rx XDMA controller */
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG + (port_num << 8));
++ reg_data &= 0x4fffffff; /* Clear tx reset */
++ reg_data &= 0xfff4ffff; /* Clear rx reset */
++
++#ifdef TITAN_GE_JUMBO_FRAMES
++ reg_data |= 0xa0 | 0x30030000;
++#else
++ reg_data |= 0x40 | 0x20030000;
++#endif
++
++#ifndef CONFIG_SMP
++ reg_data &= ~(0x10);
++ reg_data |= 0x0f; /* All of the packet */
++#endif
++
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_CONFIG + (port_num << 8)), reg_data);
++
++ /* Rx desc count */
++ count = titan_ge_rx_task(netdev, titan_port);
++ TITAN_GE_WRITE((0x5048 + (port_num << 8)), count);
++ count = TITAN_GE_READ(0x5048 + (port_num << 8));
++
++ udelay(30);
++
++ /*
++ * Step 2: Configure the SDQPF, i.e. FIFO
++ */
++ if (config_done == 0) {
++ reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_RXFIFO_CTL);
++ reg_data = 0x1;
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_CTL, reg_data);
++ reg_data &= ~(0x1);
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_CTL, reg_data);
++ reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_RXFIFO_CTL);
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_CTL, reg_data);
++
++ reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_TXFIFO_CTL);
++ reg_data = 0x1;
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_CTL, reg_data);
++ reg_data &= ~(0x1);
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_CTL, reg_data);
++ reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_TXFIFO_CTL);
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_CTL, reg_data);
++ }
++ /*
++ * Enable RX FIFO 0, 4 and 8
++ */
++ if (port_num == 0) {
++ reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_RXFIFO_0);
++
++ reg_data |= 0x100000;
++ reg_data |= (0xff << 10);
++
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_0, reg_data);
++ /*
++ * BAV2,BAV and DAV settings for the Rx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x4844);
++ reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
++ TITAN_GE_WRITE(0x4844, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_RXFIFO_0, reg_data);
++
++ reg_data = TITAN_GE_READ(TITAN_GE_SDQPF_TXFIFO_0);
++ reg_data |= 0x100000;
++
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_0, reg_data);
++
++ reg_data |= (0xff << 10);
++
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_0, reg_data);
++
++ /*
++ * BAV2, BAV and DAV settings for the Tx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x4944);
++ reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
++
++ TITAN_GE_WRITE(0x4944, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(TITAN_GE_SDQPF_TXFIFO_0, reg_data);
++
++ }
++
++ if (port_num == 1) {
++ reg_data = TITAN_GE_READ(0x4870);
++
++ reg_data |= 0x100000;
++ reg_data |= (0xff << 10) | (0xff + 1);
++
++ TITAN_GE_WRITE(0x4870, reg_data);
++ /*
++ * BAV2,BAV and DAV settings for the Rx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x4874);
++ reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
++ TITAN_GE_WRITE(0x4874, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(0x4870, reg_data);
++
++ reg_data = TITAN_GE_READ(0x494c);
++ reg_data |= 0x100000;
++
++ TITAN_GE_WRITE(0x494c, reg_data);
++ reg_data |= (0xff << 10) | (0xff + 1);
++ TITAN_GE_WRITE(0x494c, reg_data);
++
++ /*
++ * BAV2, BAV and DAV settings for the Tx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x4950);
++ reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
++
++ TITAN_GE_WRITE(0x4950, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(0x494c, reg_data);
++ }
++
++ /*
++ * Titan 1.2 revision does support port #2
++ */
++ if (port_num == 2) {
++ /*
++ * Put the descriptors in the SRAM
++ */
++ reg_data = TITAN_GE_READ(0x48a0);
++
++ reg_data |= 0x100000;
++ reg_data |= (0xff << 10) | (2*(0xff + 1));
++
++ TITAN_GE_WRITE(0x48a0, reg_data);
++ /*
++ * BAV2,BAV and DAV settings for the Rx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x48a4);
++ reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
++ TITAN_GE_WRITE(0x48a4, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(0x48a0, reg_data);
++
++ reg_data = TITAN_GE_READ(0x4958);
++ reg_data |= 0x100000;
++
++ TITAN_GE_WRITE(0x4958, reg_data);
++ reg_data |= (0xff << 10) | (2*(0xff + 1));
++ TITAN_GE_WRITE(0x4958, reg_data);
++
++ /*
++ * BAV2, BAV and DAV settings for the Tx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x495c);
++ reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
++
++ TITAN_GE_WRITE(0x495c, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(0x4958, reg_data);
++ }
++
++ if (port_num == 2) {
++ reg_data = TITAN_GE_READ(0x48a0);
++
++ reg_data |= 0x100000;
++ reg_data |= (0xff << 10) | (2*(0xff + 1));
++
++ TITAN_GE_WRITE(0x48a0, reg_data);
++ /*
++ * BAV2,BAV and DAV settings for the Rx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x48a4);
++ reg_data1 |= ( (0x10 << 20) | (0x10 << 10) | 0x1);
++ TITAN_GE_WRITE(0x48a4, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(0x48a0, reg_data);
++
++ reg_data = TITAN_GE_READ(0x4958);
++ reg_data |= 0x100000;
++
++ TITAN_GE_WRITE(0x4958, reg_data);
++ reg_data |= (0xff << 10) | (2*(0xff + 1));
++ TITAN_GE_WRITE(0x4958, reg_data);
++
++ /*
++ * BAV2, BAV and DAV settings for the Tx FIFO
++ */
++ reg_data1 = TITAN_GE_READ(0x495c);
++ reg_data1 = ( (0x1 << 20) | (0x1 << 10) | 0x10);
++
++ TITAN_GE_WRITE(0x495c, reg_data1);
++
++ reg_data &= ~(0x00100000);
++ reg_data |= 0x200000;
++
++ TITAN_GE_WRITE(0x4958, reg_data);
++ }
++
++ /*
++ * Step 3: TRTG block enable
++ */
++ reg_data = TITAN_GE_READ(TITAN_GE_TRTG_CONFIG + (port_num << 12));
++
++ /*
++ * This is the 1.2 revision of the chip. It has fix for the
++ * IP header alignment. Now, the IP header begins at an
++ * aligned address and this wont need an extra copy in the
++ * driver. This performance drawback existed in the previous
++ * versions of the silicon
++ */
++ reg_data_1 = TITAN_GE_READ(0x103c + (port_num << 12));
++ reg_data_1 |= 0x40000000;
++ TITAN_GE_WRITE((0x103c + (port_num << 12)), reg_data_1);
++
++ reg_data_1 |= 0x04000000;
++ TITAN_GE_WRITE((0x103c + (port_num << 12)), reg_data_1);
++
++ mdelay(5);
++
++ reg_data_1 &= ~(0x04000000);
++ TITAN_GE_WRITE((0x103c + (port_num << 12)), reg_data_1);
++
++ mdelay(5);
++
++ reg_data |= 0x0001;
++ TITAN_GE_WRITE((TITAN_GE_TRTG_CONFIG + (port_num << 12)), reg_data);
++
++ /*
++ * Step 4: Start the Tx activity
++ */
++ TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_2 + (port_num << 12)), 0xe197);
++#ifdef TITAN_GE_JUMBO_FRAMES
++ TITAN_GE_WRITE((0x1258 + (port_num << 12)), 0x4000);
++#endif
++ reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 + (port_num << 12));
++ reg_data |= 0x0001; /* Enable TMAC */
++ reg_data |= 0x6c70; /* PAUSE also set */
++
++ TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 + (port_num << 12)), reg_data);
++
++ udelay(30);
++
++ /* Destination Address drop bit */
++ reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_2 + (port_num << 12));
++ reg_data |= 0x218; /* DA_DROP bit and pause */
++ TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_2 + (port_num << 12)), reg_data);
++
++ TITAN_GE_WRITE((0x1218 + (port_num << 12)), 0x3);
++
++#ifdef TITAN_GE_JUMBO_FRAMES
++ TITAN_GE_WRITE((0x1208 + (port_num << 12)), 0x4000);
++#endif
++ /* Start the Rx activity */
++ reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 + (port_num << 12));
++ reg_data |= 0x0001; /* RMAC Enable */
++ reg_data |= 0x0010; /* CRC Check enable */
++ reg_data |= 0x0040; /* Min Frame check enable */
++ reg_data |= 0x4400; /* Max Frame check enable */
++
++ TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 + (port_num << 12)), reg_data);
++
++ udelay(30);
++
++ /*
++ * Enable the Interrupts for Tx and Rx
++ */
++ reg_data1 = TITAN_GE_READ(TITAN_GE_INTR_XDMA_IE);
++
++ if (port_num == 0) {
++ reg_data1 |= 0x3;
++#ifdef CONFIG_SMP
++ TITAN_GE_WRITE(0x0038, 0x003);
++#else
++ TITAN_GE_WRITE(0x0038, 0x303);
++#endif
++ }
++
++ if (port_num == 1) {
++ reg_data1 |= 0x300;
++ }
++
++ if (port_num == 2)
++ reg_data1 |= 0x30000;
++
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, reg_data1);
++ TITAN_GE_WRITE(0x003c, 0x300);
++
++ if (config_done == 0) {
++ TITAN_GE_WRITE(0x0024, 0x04000024); /* IRQ vector */
++ TITAN_GE_WRITE(0x0020, 0x000fb000); /* INTMSG base */
++ }
++
++ /* Priority */
++ reg_data = TITAN_GE_READ(0x1038 + (port_num << 12));
++ reg_data &= ~(0x00f00000);
++ TITAN_GE_WRITE((0x1038 + (port_num << 12)), reg_data);
++
++ /* Step 5: GMII config */
++ titan_ge_gmii_config(port_num);
++
++ if (config_done == 0) {
++ TITAN_GE_WRITE(0x1a80, 0);
++ config_done = 1;
++ }
++
++ return TITAN_OK;
++}
++
++/*
++ * Function to queue the packet for the Ethernet device
++ */
++static void titan_ge_tx_queue(titan_ge_port_info * titan_ge_eth,
++ struct sk_buff * skb)
++{
++ struct device *device = &titan_ge_device[titan_ge_eth->port_num]->dev;
++ unsigned int curr_desc = titan_ge_eth->tx_curr_desc_q;
++ volatile titan_ge_tx_desc *tx_curr;
++ int port_num = titan_ge_eth->port_num;
++
++ tx_curr = &(titan_ge_eth->tx_desc_area[curr_desc]);
++ tx_curr->buffer_addr =
++ dma_map_single(device, skb->data, skb_headlen(skb),
++ DMA_TO_DEVICE);
++
++ titan_ge_eth->tx_skb[curr_desc] = (struct sk_buff *) skb;
++ tx_curr->buffer_len = skb_headlen(skb);
++
++ /* Last descriptor enables interrupt and changes ownership */
++ tx_curr->cmd_sts = 0x1 | (1 << 15) | (1 << 5);
++
++ /* Kick the XDMA to start the transfer from memory to the FIFO */
++ TITAN_GE_WRITE((0x5044 + (port_num << 8)), 0x1);
++
++ /* Current descriptor updated */
++ titan_ge_eth->tx_curr_desc_q = (curr_desc + 1) % TITAN_GE_TX_QUEUE;
++
++ /* Prefetch the next descriptor */
++ prefetch((const void *)
++ &titan_ge_eth->tx_desc_area[titan_ge_eth->tx_curr_desc_q]);
++}
++
++/*
++ * Actually does the open of the Ethernet device
++ */
++static int titan_ge_eth_open(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ struct device *device = &titan_ge_device[port_num]->dev;
++ unsigned long reg_data;
++ unsigned int phy_reg;
++ int err = 0;
++
++ /* Stop the Rx activity */
++ reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 + (port_num << 12));
++ reg_data &= ~(0x00000001);
++ TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 + (port_num << 12)), reg_data);
++
++ /* Clear the port interrupts */
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_INTERRUPT + (port_num << 8)), 0x0);
++
++ if (config_done == 0) {
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0);
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_B, 0);
++ }
++
++ /* Set the MAC Address */
++ memcpy(titan_ge_eth->port_mac_addr, netdev->dev_addr, 6);
++
++ if (config_done == 0)
++ titan_port_init(netdev, titan_ge_eth);
++
++ titan_ge_update_afx(titan_ge_eth);
++
++ /* Allocate the Tx ring now */
++ titan_ge_eth->tx_ring_skbs = 0;
++ titan_ge_eth->tx_ring_size = TITAN_GE_TX_QUEUE;
++
++ /* Allocate space in the SRAM for the descriptors */
++ titan_ge_eth->tx_desc_area = (titan_ge_tx_desc *)
++ (titan_ge_sram + TITAN_TX_RING_BYTES * port_num);
++ titan_ge_eth->tx_dma = TITAN_SRAM_BASE + TITAN_TX_RING_BYTES * port_num;
++
++ if (!titan_ge_eth->tx_desc_area) {
++ printk(KERN_ERR
++ "%s: Cannot allocate Tx Ring (size %d bytes) for port %d\n",
++ netdev->name, TITAN_TX_RING_BYTES, port_num);
++ return -ENOMEM;
++ }
++
++ memset(titan_ge_eth->tx_desc_area, 0, titan_ge_eth->tx_desc_area_size);
++
++ /* Now initialize the Tx descriptor ring */
++ titan_ge_init_tx_desc_ring(titan_ge_eth,
++ titan_ge_eth->tx_ring_size,
++ (unsigned long) titan_ge_eth->tx_desc_area,
++ (unsigned long) titan_ge_eth->tx_dma);
++
++ /* Allocate the Rx ring now */
++ titan_ge_eth->rx_ring_size = TITAN_GE_RX_QUEUE;
++ titan_ge_eth->rx_ring_skbs = 0;
++
++ titan_ge_eth->rx_desc_area =
++ (titan_ge_rx_desc *)(titan_ge_sram + 0x1000 + TITAN_RX_RING_BYTES * port_num);
++
++ titan_ge_eth->rx_dma = TITAN_SRAM_BASE + 0x1000 + TITAN_RX_RING_BYTES * port_num;
++
++ if (!titan_ge_eth->rx_desc_area) {
++ printk(KERN_ERR "%s: Cannot allocate Rx Ring (size %d bytes)\n",
++ netdev->name, TITAN_RX_RING_BYTES);
++
++ printk(KERN_ERR "%s: Freeing previously allocated TX queues...",
++ netdev->name);
++
++ dma_free_coherent(device, titan_ge_eth->tx_desc_area_size,
++ (void *) titan_ge_eth->tx_desc_area,
++ titan_ge_eth->tx_dma);
++
++ return -ENOMEM;
++ }
++
++ memset(titan_ge_eth->rx_desc_area, 0, titan_ge_eth->rx_desc_area_size);
++
++ /* Now initialize the Rx ring */
++#ifdef TITAN_GE_JUMBO_FRAMES
++ if ((titan_ge_init_rx_desc_ring
++ (titan_ge_eth, titan_ge_eth->rx_ring_size, TITAN_GE_JUMBO_BUFSIZE,
++ (unsigned long) titan_ge_eth->rx_desc_area, 0,
++ (unsigned long) titan_ge_eth->rx_dma)) == 0)
++#else
++ if ((titan_ge_init_rx_desc_ring
++ (titan_ge_eth, titan_ge_eth->rx_ring_size, TITAN_GE_STD_BUFSIZE,
++ (unsigned long) titan_ge_eth->rx_desc_area, 0,
++ (unsigned long) titan_ge_eth->rx_dma)) == 0)
++#endif
++ panic("%s: Error initializing RX Ring\n", netdev->name);
++
++ /* Fill the Rx ring with the SKBs */
++ titan_ge_port_start(netdev, titan_ge_eth);
++
++ /*
++ * Check if Interrupt Coalscing needs to be turned on. The
++ * values specified in the register is multiplied by
++ * (8 x 64 nanoseconds) to determine when an interrupt should
++ * be sent to the CPU.
++ */
++
++ if (TITAN_GE_TX_COAL) {
++ titan_ge_eth->tx_int_coal =
++ titan_ge_tx_coal(TITAN_GE_TX_COAL, port_num);
++ }
++
++ err = titan_ge_mdio_read(port_num, TITAN_GE_MDIO_PHY_STATUS, &phy_reg);
++ if (err == TITAN_GE_MDIO_ERROR) {
++ printk(KERN_ERR
++ "Could not read PHY control register 0x11 \n");
++ return TITAN_ERROR;
++ }
++ if (!(phy_reg & 0x0400)) {
++ netif_carrier_off(netdev);
++ netif_stop_queue(netdev);
++ return TITAN_ERROR;
++ } else {
++ netif_carrier_on(netdev);
++ netif_start_queue(netdev);
++ }
++
++ return TITAN_OK;
++}
++
++/*
++ * Queue the packet for Tx. Currently no support for zero copy,
++ * checksum offload and Scatter Gather. The chip does support
++ * Scatter Gather only. But, that wont help here since zero copy
++ * requires support for Tx checksumming also.
++ */
++int titan_ge_start_xmit(struct sk_buff *skb, struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned long flags;
++ struct net_device_stats *stats;
++//printk("titan_ge_start_xmit\n");
++
++ stats = &titan_ge_eth->stats;
++ spin_lock_irqsave(&titan_ge_eth->lock, flags);
++
++ if ((TITAN_GE_TX_QUEUE - titan_ge_eth->tx_ring_skbs) <=
++ (skb_shinfo(skb)->nr_frags + 1)) {
++ netif_stop_queue(netdev);
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++ printk(KERN_ERR "Tx OOD \n");
++ return 1;
++ }
++
++ titan_ge_tx_queue(titan_ge_eth, skb);
++ titan_ge_eth->tx_ring_skbs++;
++
++ if (TITAN_GE_TX_QUEUE <= (titan_ge_eth->tx_ring_skbs + 4)) {
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++ titan_ge_free_tx_queue(titan_ge_eth);
++ spin_lock_irqsave(&titan_ge_eth->lock, flags);
++ }
++
++ stats->tx_bytes += skb->len;
++ stats->tx_packets++;
++
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++
++ netdev->trans_start = jiffies;
++
++ return 0;
++}
++
++/*
++ * Actually does the Rx. Rx side checksumming supported.
++ */
++static int titan_ge_rx(struct net_device *netdev, int port_num,
++ titan_ge_port_info * titan_ge_port,
++ titan_ge_packet * packet)
++{
++ int rx_curr_desc, rx_used_desc;
++ volatile titan_ge_rx_desc *rx_desc;
++
++ rx_curr_desc = titan_ge_port->rx_curr_desc_q;
++ rx_used_desc = titan_ge_port->rx_used_desc_q;
++
++ if (((rx_curr_desc + 1) % TITAN_GE_RX_QUEUE) == rx_used_desc)
++ return TITAN_ERROR;
++
++ rx_desc = &(titan_ge_port->rx_desc_area[rx_curr_desc]);
++
++ if (rx_desc->cmd_sts & TITAN_GE_RX_BUFFER_OWNED)
++ return TITAN_ERROR;
++
++ packet->skb = titan_ge_port->rx_skb[rx_curr_desc];
++ packet->len = (rx_desc->cmd_sts & 0x7fff);
++
++ /*
++ * At this point, we dont know if the checksumming
++ * actually helps relieve CPU. So, keep it for
++ * port 0 only
++ */
++ packet->checksum = ntohs((rx_desc->buffer & 0xffff0000) >> 16);
++ packet->cmd_sts = rx_desc->cmd_sts;
++
++ titan_ge_port->rx_curr_desc_q = (rx_curr_desc + 1) % TITAN_GE_RX_QUEUE;
++
++ /* Prefetch the next descriptor */
++ prefetch((const void *)
++ &titan_ge_port->rx_desc_area[titan_ge_port->rx_curr_desc_q + 1]);
++
++ return TITAN_OK;
++}
++
++/*
++ * Free the Tx queue of the used SKBs
++ */
++static int titan_ge_free_tx_queue(titan_ge_port_info *titan_ge_eth)
++{
++ unsigned long flags;
++
++ /* Take the lock */
++ spin_lock_irqsave(&(titan_ge_eth->lock), flags);
++
++ while (titan_ge_return_tx_desc(titan_ge_eth, titan_ge_eth->port_num) == 0)
++ if (titan_ge_eth->tx_ring_skbs != 1)
++ titan_ge_eth->tx_ring_skbs--;
++
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++
++ return TITAN_OK;
++}
++
++/*
++ * Threshold beyond which we do the cleaning of
++ * Tx queue and new allocation for the Rx
++ * queue
++ */
++#define TX_THRESHOLD 4
++#define RX_THRESHOLD 10
++
++/*
++ * Receive the packets and send it to the kernel.
++ */
++static int titan_ge_receive_queue(struct net_device *netdev, unsigned int max)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ titan_ge_packet packet;
++ struct net_device_stats *stats;
++ struct sk_buff *skb;
++ unsigned long received_packets = 0;
++ unsigned int ack;
++
++ stats = &titan_ge_eth->stats;
++
++ while ((--max)
++ && (titan_ge_rx(netdev, port_num, titan_ge_eth, &packet) == TITAN_OK)) {
++ skb = (struct sk_buff *) packet.skb;
++
++ titan_ge_eth->rx_ring_skbs--;
++
++ if (--titan_ge_eth->rx_work_limit < 0)
++ break;
++ received_packets++;
++
++ stats->rx_packets++;
++ stats->rx_bytes += packet.len;
++
++ if ((packet.cmd_sts & TITAN_GE_RX_PERR) ||
++ (packet.cmd_sts & TITAN_GE_RX_OVERFLOW_ERROR) ||
++ (packet.cmd_sts & TITAN_GE_RX_TRUNC) ||
++ (packet.cmd_sts & TITAN_GE_RX_CRC_ERROR)) {
++ stats->rx_dropped++;
++ dev_kfree_skb_any(skb);
++
++ continue;
++ }
++ /*
++ * Either support fast path or slow path. Decision
++ * making can really slow down the performance. The
++ * idea is to cut down the number of checks and improve
++ * the fastpath.
++ */
++
++ skb_put(skb, packet.len - 2);
++
++ /*
++ * Increment data pointer by two since thats where
++ * the MAC starts
++ */
++ skb_reserve(skb, 2);
++ skb->protocol = eth_type_trans(skb, netdev);
++ netif_receive_skb(skb);
++
++ if (titan_ge_eth->rx_threshold > RX_THRESHOLD) {
++ ack = titan_ge_rx_task(netdev, titan_ge_eth);
++ TITAN_GE_WRITE((0x5048 + (port_num << 8)), ack);
++ titan_ge_eth->rx_threshold = 0;
++ } else
++ titan_ge_eth->rx_threshold++;
++
++ if (titan_ge_eth->tx_threshold > TX_THRESHOLD) {
++ titan_ge_eth->tx_threshold = 0;
++ titan_ge_free_tx_queue(titan_ge_eth);
++ }
++ else
++ titan_ge_eth->tx_threshold++;
++
++ }
++ return received_packets;
++}
++
++
++/*
++ * Enable the Rx side interrupts
++ */
++static void titan_ge_enable_int(unsigned int port_num,
++ titan_ge_port_info *titan_ge_eth,
++ struct net_device *netdev)
++{
++ unsigned long reg_data = TITAN_GE_READ(TITAN_GE_INTR_XDMA_IE);
++
++ if (port_num == 0)
++ reg_data |= 0x3;
++ if (port_num == 1)
++ reg_data |= 0x300;
++ if (port_num == 2)
++ reg_data |= 0x30000;
++
++ /* Re-enable interrupts */
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, reg_data);
++}
++
++/*
++ * Main function to handle the polling for Rx side NAPI.
++ * Receive interrupts have been disabled at this point.
++ * The poll schedules the transmit followed by receive.
++ */
++static int titan_ge_poll(struct net_device *netdev, int *budget)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ int port_num = titan_ge_eth->port_num;
++ int work_done = 0;
++ unsigned long flags, status;
++
++ titan_ge_eth->rx_work_limit = *budget;
++ if (titan_ge_eth->rx_work_limit > netdev->quota)
++ titan_ge_eth->rx_work_limit = netdev->quota;
++
++ do {
++ /* Do the transmit cleaning work here */
++ titan_ge_free_tx_queue(titan_ge_eth);
++
++ /* Ack the Rx interrupts */
++ if (port_num == 0)
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0x3);
++ if (port_num == 1)
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0x300);
++ if (port_num == 2)
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_CORE_A, 0x30000);
++
++ work_done += titan_ge_receive_queue(netdev, 0);
++
++ /* Out of quota and there is work to be done */
++ if (titan_ge_eth->rx_work_limit < 0)
++ goto not_done;
++
++ /* Receive alloc_skb could lead to OOM */
++ if (oom_flag == 1) {
++ oom_flag = 0;
++ goto oom;
++ }
++
++ status = TITAN_GE_READ(TITAN_GE_INTR_XDMA_CORE_A);
++ } while (status & 0x30300);
++
++ /* If we are here, then no more interrupts to process */
++ goto done;
++
++not_done:
++ *budget -= work_done;
++ netdev->quota -= work_done;
++ return 1;
++
++oom:
++ printk(KERN_ERR "OOM \n");
++ netif_rx_complete(netdev);
++ return 0;
++
++done:
++ /*
++ * No more packets on the poll list. Turn the interrupts
++ * back on and we should be able to catch the new
++ * packets in the interrupt handler
++ */
++ if (!work_done)
++ work_done = 1;
++
++ *budget -= work_done;
++ netdev->quota -= work_done;
++
++ spin_lock_irqsave(&titan_ge_eth->lock, flags);
++
++ /* Remove us from the poll list */
++ netif_rx_complete(netdev);
++
++ /* Re-enable interrupts */
++ titan_ge_enable_int(port_num, titan_ge_eth, netdev);
++
++ spin_unlock_irqrestore(&titan_ge_eth->lock, flags);
++
++ return 0;
++}
++
++/*
++ * Close the network device
++ */
++int titan_ge_stop(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++
++ spin_lock_irq(&(titan_ge_eth->lock));
++ titan_ge_eth_stop(netdev);
++ free_irq(netdev->irq, netdev);
++ spin_unlock_irq(&titan_ge_eth->lock);
++
++ return TITAN_OK;
++}
++
++/*
++ * Free the Tx ring
++ */
++static void titan_ge_free_tx_rings(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ unsigned int curr;
++ unsigned long reg_data;
++
++ /* Stop the Tx DMA */
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG +
++ (port_num << 8));
++ reg_data |= 0xc0000000;
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_CONFIG +
++ (port_num << 8)), reg_data);
++
++ /* Disable the TMAC */
++ reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 +
++ (port_num << 12));
++ reg_data &= ~(0x00000001);
++ TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 +
++ (port_num << 12)), reg_data);
++
++ for (curr = 0;
++ (titan_ge_eth->tx_ring_skbs) && (curr < TITAN_GE_TX_QUEUE);
++ curr++) {
++ if (titan_ge_eth->tx_skb[curr]) {
++ dev_kfree_skb(titan_ge_eth->tx_skb[curr]);
++ titan_ge_eth->tx_ring_skbs--;
++ }
++ }
++
++ if (titan_ge_eth->tx_ring_skbs != 0)
++ printk
++ ("%s: Error on Tx descriptor free - could not free %d"
++ " descriptors\n", netdev->name,
++ titan_ge_eth->tx_ring_skbs);
++
++#ifndef TITAN_RX_RING_IN_SRAM
++ dma_free_coherent(&titan_ge_device[port_num]->dev,
++ titan_ge_eth->tx_desc_area_size,
++ (void *) titan_ge_eth->tx_desc_area,
++ titan_ge_eth->tx_dma);
++#endif
++}
++
++/*
++ * Free the Rx ring
++ */
++static void titan_ge_free_rx_rings(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ unsigned int curr;
++ unsigned long reg_data;
++
++ /* Stop the Rx DMA */
++ reg_data = TITAN_GE_READ(TITAN_GE_CHANNEL0_CONFIG +
++ (port_num << 8));
++ reg_data |= 0x000c0000;
++ TITAN_GE_WRITE((TITAN_GE_CHANNEL0_CONFIG +
++ (port_num << 8)), reg_data);
++
++ /* Disable the RMAC */
++ reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 +
++ (port_num << 12));
++ reg_data &= ~(0x00000001);
++ TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 +
++ (port_num << 12)), reg_data);
++
++ for (curr = 0;
++ titan_ge_eth->rx_ring_skbs && (curr < TITAN_GE_RX_QUEUE);
++ curr++) {
++ if (titan_ge_eth->rx_skb[curr]) {
++ dev_kfree_skb(titan_ge_eth->rx_skb[curr]);
++ titan_ge_eth->rx_ring_skbs--;
++ }
++ }
++
++ if (titan_ge_eth->rx_ring_skbs != 0)
++ printk(KERN_ERR
++ "%s: Error in freeing Rx Ring. %d skb's still"
++ " stuck in RX Ring - ignoring them\n", netdev->name,
++ titan_ge_eth->rx_ring_skbs);
++
++#ifndef TITAN_RX_RING_IN_SRAM
++ dma_free_coherent(&titan_ge_device[port_num]->dev,
++ titan_ge_eth->rx_desc_area_size,
++ (void *) titan_ge_eth->rx_desc_area,
++ titan_ge_eth->rx_dma);
++#endif
++}
++
++/*
++ * Actually does the stop of the Ethernet device
++ */
++static void titan_ge_eth_stop(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++
++ netif_stop_queue(netdev);
++
++ titan_ge_port_reset(titan_ge_eth->port_num);
++
++ titan_ge_free_tx_rings(netdev);
++ titan_ge_free_rx_rings(netdev);
++
++ /* Disable the Tx and Rx Interrupts for all channels */
++ TITAN_GE_WRITE(TITAN_GE_INTR_XDMA_IE, 0x0);
++}
++
++/*
++ * Update the MAC address. Note that we have to write the
++ * address in three station registers, 16 bits each. And this
++ * has to be done for TMAC and RMAC
++ */
++static void titan_ge_update_mac_address(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++ unsigned int port_num = titan_ge_eth->port_num;
++ u8 p_addr[6];
++
++ memcpy(titan_ge_eth->port_mac_addr, netdev->dev_addr, 6);
++ memcpy(p_addr, netdev->dev_addr, 6);
++
++ /* Update the Address Filtering Match tables */
++ titan_ge_update_afx(titan_ge_eth);
++
++ printk("Station MAC : %d %d %d %d %d %d \n",
++ p_addr[5], p_addr[4], p_addr[3],
++ p_addr[2], p_addr[1], p_addr[0]);
++
++ /* Set the MAC address here for TMAC and RMAC */
++ TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_HI + (port_num << 12)),
++ ((p_addr[5] << 8) | p_addr[4]));
++ TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_MID + (port_num << 12)),
++ ((p_addr[3] << 8) | p_addr[2]));
++ TITAN_GE_WRITE((TITAN_GE_TMAC_STATION_LOW + (port_num << 12)),
++ ((p_addr[1] << 8) | p_addr[0]));
++
++ TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_HI + (port_num << 12)),
++ ((p_addr[5] << 8) | p_addr[4]));
++ TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_MID + (port_num << 12)),
++ ((p_addr[3] << 8) | p_addr[2]));
++ TITAN_GE_WRITE((TITAN_GE_RMAC_STATION_LOW + (port_num << 12)),
++ ((p_addr[1] << 8) | p_addr[0]));
++}
++
++/*
++ * Set the MAC address of the Ethernet device
++ */
++static int titan_ge_set_mac_address(struct net_device *dev, void *addr)
++{
++ titan_ge_port_info *tp = netdev_priv(dev);
++ struct sockaddr *sa = addr;
++
++ memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
++
++ spin_lock_irq(&tp->lock);
++ titan_ge_update_mac_address(dev);
++ spin_unlock_irq(&tp->lock);
++
++ return 0;
++}
++
++/*
++ * Get the Ethernet device stats
++ */
++static struct net_device_stats *titan_ge_get_stats(struct net_device *netdev)
++{
++ titan_ge_port_info *titan_ge_eth = netdev_priv(netdev);
++
++ return &titan_ge_eth->stats;
++}
++
++/*
++ * Initialize the Rx descriptor ring for the Titan Ge
++ */
++static int titan_ge_init_rx_desc_ring(titan_ge_port_info * titan_eth_port,
++ int rx_desc_num,
++ int rx_buff_size,
++ unsigned long rx_desc_base_addr,
++ unsigned long rx_buff_base_addr,
++ unsigned long rx_dma)
++{
++ volatile titan_ge_rx_desc *rx_desc;
++ unsigned long buffer_addr;
++ int index;
++ unsigned long titan_ge_rx_desc_bus = rx_dma;
++
++ buffer_addr = rx_buff_base_addr;
++ rx_desc = (titan_ge_rx_desc *) rx_desc_base_addr;
++
++ /* Check alignment */
++ if (rx_buff_base_addr & 0xF)
++ return 0;
++
++ /* Check Rx buffer size */
++ if ((rx_buff_size < 8) || (rx_buff_size > TITAN_GE_MAX_RX_BUFFER))
++ return 0;
++
++ /* 64-bit alignment
++ if ((rx_buff_base_addr + rx_buff_size) & 0x7)
++ return 0; */
++
++ /* Initialize the Rx desc ring */
++ for (index = 0; index < rx_desc_num; index++) {
++ titan_ge_rx_desc_bus += sizeof(titan_ge_rx_desc);
++ rx_desc[index].cmd_sts = 0;
++ rx_desc[index].buffer_addr = buffer_addr;
++ titan_eth_port->rx_skb[index] = NULL;
++ buffer_addr += rx_buff_size;
++ }
++
++ titan_eth_port->rx_curr_desc_q = 0;
++ titan_eth_port->rx_used_desc_q = 0;
++
++ titan_eth_port->rx_desc_area = (titan_ge_rx_desc *) rx_desc_base_addr;
++ titan_eth_port->rx_desc_area_size =
++ rx_desc_num * sizeof(titan_ge_rx_desc);
++
++ titan_eth_port->rx_dma = rx_dma;
++
++ return TITAN_OK;
++}
++
++/*
++ * Initialize the Tx descriptor ring. Descriptors in the SRAM
++ */
++static int titan_ge_init_tx_desc_ring(titan_ge_port_info * titan_ge_port,
++ int tx_desc_num,
++ unsigned long tx_desc_base_addr,
++ unsigned long tx_dma)
++{
++ titan_ge_tx_desc *tx_desc;
++ int index;
++ unsigned long titan_ge_tx_desc_bus = tx_dma;
++
++ if (tx_desc_base_addr & 0xF)
++ return 0;
++
++ tx_desc = (titan_ge_tx_desc *) tx_desc_base_addr;
++
++ for (index = 0; index < tx_desc_num; index++) {
++ titan_ge_port->tx_dma_array[index] =
++ (dma_addr_t) titan_ge_tx_desc_bus;
++ titan_ge_tx_desc_bus += sizeof(titan_ge_tx_desc);
++ tx_desc[index].cmd_sts = 0x0000;
++ tx_desc[index].buffer_len = 0;
++ tx_desc[index].buffer_addr = 0x00000000;
++ titan_ge_port->tx_skb[index] = NULL;
++ }
++
++ titan_ge_port->tx_curr_desc_q = 0;
++ titan_ge_port->tx_used_desc_q = 0;
++
++ titan_ge_port->tx_desc_area = (titan_ge_tx_desc *) tx_desc_base_addr;
++ titan_ge_port->tx_desc_area_size =
++ tx_desc_num * sizeof(titan_ge_tx_desc);
++
++ titan_ge_port->tx_dma = tx_dma;
++ return TITAN_OK;
++}
++
++/*
++ * Initialize the device as an Ethernet device
++ */
++static int __init titan_ge_probe(struct device *device)
++{
++ titan_ge_port_info *titan_ge_eth;
++ struct net_device *netdev;
++ int port = to_platform_device(device)->id;
++ int err;
++
++ netdev = alloc_etherdev(sizeof(titan_ge_port_info));
++ if (!netdev) {
++ err = -ENODEV;
++ goto out;
++ }
++
++ netdev->open = titan_ge_open;
++ netdev->stop = titan_ge_stop;
++ netdev->hard_start_xmit = titan_ge_start_xmit;
++ netdev->get_stats = titan_ge_get_stats;
++ netdev->set_multicast_list = titan_ge_set_multi;
++ netdev->set_mac_address = titan_ge_set_mac_address;
++
++ /* Tx timeout */
++ netdev->tx_timeout = titan_ge_tx_timeout;
++ netdev->watchdog_timeo = 2 * HZ;
++
++ /* Set these to very high values */
++ netdev->poll = titan_ge_poll;
++ netdev->weight = 64;
++
++ netdev->tx_queue_len = TITAN_GE_TX_QUEUE;
++ netif_carrier_off(netdev);
++ netdev->base_addr = 0;
++
++ netdev->change_mtu = titan_ge_change_mtu;
++
++ titan_ge_eth = netdev_priv(netdev);
++ /* Allocation of memory for the driver structures */
++
++ titan_ge_eth->port_num = port;
++
++ /* Configure the Tx timeout handler */
++ INIT_WORK(&titan_ge_eth->tx_timeout_task,
++ (void (*)(void *)) titan_ge_tx_timeout_task, netdev);
++
++ spin_lock_init(&titan_ge_eth->lock);
++
++ /* set MAC addresses */
++ memcpy(netdev->dev_addr, titan_ge_mac_addr_base, 6);
++ netdev->dev_addr[5] += port;
++
++ err = register_netdev(netdev);
++
++ if (err)
++ goto out_free_netdev;
++
++ printk(KERN_NOTICE
++ "%s: port %d with MAC address %02x:%02x:%02x:%02x:%02x:%02x\n",
++ netdev->name, port, netdev->dev_addr[0],
++ netdev->dev_addr[1], netdev->dev_addr[2],
++ netdev->dev_addr[3], netdev->dev_addr[4],
++ netdev->dev_addr[5]);
++
++ printk(KERN_NOTICE "Rx NAPI supported, Tx Coalescing ON \n");
++
++ return 0;
++
++out_free_netdev:
++ kfree(netdev);
++
++out:
++ return err;
++}
++
++static void __devexit titan_device_remove(struct device *device)
++{
++}
++
++/*
++ * Reset the Ethernet port
++ */
++static void titan_ge_port_reset(unsigned int port_num)
++{
++ unsigned int reg_data;
++
++ /* Stop the Tx port activity */
++ reg_data = TITAN_GE_READ(TITAN_GE_TMAC_CONFIG_1 +
++ (port_num << 12));
++ reg_data &= ~(0x0001);
++ TITAN_GE_WRITE((TITAN_GE_TMAC_CONFIG_1 +
++ (port_num << 12)), reg_data);
++
++ /* Stop the Rx port activity */
++ reg_data = TITAN_GE_READ(TITAN_GE_RMAC_CONFIG_1 +
++ (port_num << 12));
++ reg_data &= ~(0x0001);
++ TITAN_GE_WRITE((TITAN_GE_RMAC_CONFIG_1 +
++ (port_num << 12)), reg_data);
++
++ return;
++}
++
++/*
++ * Return the Tx desc after use by the XDMA
++ */
++static int titan_ge_return_tx_desc(titan_ge_port_info * titan_ge_eth, int port)
++{
++ int tx_desc_used;
++ struct sk_buff *skb;
++
++ tx_desc_used = titan_ge_eth->tx_used_desc_q;
++
++ /* return right away */
++ if (tx_desc_used == titan_ge_eth->tx_curr_desc_q)
++ return TITAN_ERROR;
++
++ /* Now the critical stuff */
++ skb = titan_ge_eth->tx_skb[tx_desc_used];
++
++ dev_kfree_skb_any(skb);
++
++ titan_ge_eth->tx_skb[tx_desc_used] = NULL;
++ titan_ge_eth->tx_used_desc_q =
++ (tx_desc_used + 1) % TITAN_GE_TX_QUEUE;
++
++ return 0;
++}
++
++/*
++ * Coalescing for the Tx path
++ */
++static unsigned long titan_ge_tx_coal(unsigned long delay, int port)
++{
++ unsigned long rx_delay;
++
++ rx_delay = TITAN_GE_READ(TITAN_GE_INT_COALESCING);
++ delay = (delay << 16) | rx_delay;
++
++ TITAN_GE_WRITE(TITAN_GE_INT_COALESCING, delay);
++ TITAN_GE_WRITE(0x5038, delay);
++
++ return delay;
++}
++
++static struct device_driver titan_soc_driver = {
++ .name = titan_string,
++ .bus = &platform_bus_type,
++ .probe = titan_ge_probe,
++ .remove = __devexit_p(titan_device_remove),
++};
++
++static void titan_platform_release (struct device *device)
++{
++ struct platform_device *pldev;
++
++ /* free device */
++ pldev = to_platform_device (device);
++ kfree (pldev);
++}
++
++/*
++ * Register the Titan GE with the kernel
++ */
++static int __init titan_ge_init_module(void)
++{
++ struct platform_device *pldev;
++ unsigned int version, device;
++ int i;
++
++ printk(KERN_NOTICE
++ "PMC-Sierra TITAN 10/100/1000 Ethernet Driver \n");
++
++ titan_ge_base = (unsigned long) ioremap(TITAN_GE_BASE, TITAN_GE_SIZE);
++ if (!titan_ge_base) {
++ printk("Mapping Titan GE failed\n");
++ goto out;
++ }
++
++ device = TITAN_GE_READ(TITAN_GE_DEVICE_ID);
++ version = (device & 0x000f0000) >> 16;
++ device &= 0x0000ffff;
++
++ printk(KERN_NOTICE "Device Id : %x, Version : %x \n", device, version);
++
++#ifdef TITAN_RX_RING_IN_SRAM
++ titan_ge_sram = (unsigned long) ioremap(TITAN_SRAM_BASE,
++ TITAN_SRAM_SIZE);
++ if (!titan_ge_sram) {
++ printk("Mapping Titan SRAM failed\n");
++ goto out_unmap_ge;
++ }
++#endif
++
++ if (driver_register(&titan_soc_driver)) {
++ printk(KERN_ERR "Driver registration failed\n");
++ goto out_unmap_sram;
++ }
++
++ for (i = 0; i < 3; i++) {
++ titan_ge_device[i] = NULL;
++
++ if (!(pldev = kmalloc (sizeof (*pldev), GFP_KERNEL)))
++ continue;
++
++ memset (pldev, 0, sizeof (*pldev));
++ pldev->name = titan_string;
++ pldev->id = i;
++ pldev->dev.release = titan_platform_release;
++ titan_ge_device[i] = pldev;
++
++ if (platform_device_register (pldev)) {
++ kfree (pldev);
++ titan_ge_device[i] = NULL;
++ continue;
++ }
++
++ if (!pldev->dev.driver) {
++ /*
++ * The driver was not bound to this device, there was
++ * no hardware at this address. Unregister it, as the
++ * release fuction will take care of freeing the
++ * allocated structure
++ */
++ titan_ge_device[i] = NULL;
++ platform_device_unregister (pldev);
++ }
++ }
++
++ return 0;
++
++out_unmap_sram:
++ iounmap((void *)titan_ge_sram);
++
++out_unmap_ge:
++ iounmap((void *)titan_ge_base);
++
++out:
++ return -ENOMEM;
++}
++
++/*
++ * Unregister the Titan GE from the kernel
++ */
++static void __exit titan_ge_cleanup_module(void)
++{
++ int i;
++
++ driver_unregister(&titan_soc_driver);
++
++ for (i = 0; i < 3; i++) {
++ if (titan_ge_device[i]) {
++ platform_device_unregister (titan_ge_device[i]);
++ titan_ge_device[i] = NULL;
++ }
++ }
++
++ iounmap((void *)titan_ge_sram);
++ iounmap((void *)titan_ge_base);
++}
++
++MODULE_AUTHOR("Manish Lachwani <lachwani@pmc-sierra.com>");
++MODULE_DESCRIPTION("Titan GE Ethernet driver");
++MODULE_LICENSE("GPL");
++
++module_init(titan_ge_init_module);
++module_exit(titan_ge_cleanup_module);
+diff --git a/drivers/net/titan_ge.h b/drivers/net/titan_ge.h
+new file mode 100644
+index 0000000..3719f78
+--- /dev/null
++++ b/drivers/net/titan_ge.h
+@@ -0,0 +1,415 @@
++#ifndef _TITAN_GE_H_
++#define _TITAN_GE_H_
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/spinlock.h>
++#include <asm/byteorder.h>
++
++/*
++ * These functions should be later moved to a more generic location since there
++ * will be others accessing it also
++ */
++
++/*
++ * This is the way it works: LKB5 Base is at 0x0128. TITAN_BASE is defined in
++ * include/asm/titan_dep.h. TITAN_GE_BASE is the value in the TITAN_GE_LKB5
++ * register.
++ */
++
++#define TITAN_GE_BASE 0xfe000000UL
++#define TITAN_GE_SIZE 0x10000UL
++
++extern unsigned long titan_ge_base;
++
++#define TITAN_GE_WRITE(offset, data) \
++ *(volatile u32 *)(titan_ge_base + (offset)) = (data)
++
++#define TITAN_GE_READ(offset) *(volatile u32 *)(titan_ge_base + (offset))
++
++#ifndef msec_delay
++#define msec_delay(x) do { if(in_interrupt()) { \
++ /* Don't mdelay in interrupt context! */ \
++ BUG(); \
++ } else { \
++ set_current_state(TASK_UNINTERRUPTIBLE); \
++ schedule_timeout((x * HZ)/1000); \
++ } } while(0)
++#endif
++
++#define TITAN_GE_PORT_0
++
++#define TITAN_SRAM_BASE ((OCD_READ(RM9000x2_OCD_LKB13) & ~1) << 4)
++#define TITAN_SRAM_SIZE 0x2000UL
++
++/*
++ * We may need these constants
++ */
++#define TITAN_BIT0 0x00000001
++#define TITAN_BIT1 0x00000002
++#define TITAN_BIT2 0x00000004
++#define TITAN_BIT3 0x00000008
++#define TITAN_BIT4 0x00000010
++#define TITAN_BIT5 0x00000020
++#define TITAN_BIT6 0x00000040
++#define TITAN_BIT7 0x00000080
++#define TITAN_BIT8 0x00000100
++#define TITAN_BIT9 0x00000200
++#define TITAN_BIT10 0x00000400
++#define TITAN_BIT11 0x00000800
++#define TITAN_BIT12 0x00001000
++#define TITAN_BIT13 0x00002000
++#define TITAN_BIT14 0x00004000
++#define TITAN_BIT15 0x00008000
++#define TITAN_BIT16 0x00010000
++#define TITAN_BIT17 0x00020000
++#define TITAN_BIT18 0x00040000
++#define TITAN_BIT19 0x00080000
++#define TITAN_BIT20 0x00100000
++#define TITAN_BIT21 0x00200000
++#define TITAN_BIT22 0x00400000
++#define TITAN_BIT23 0x00800000
++#define TITAN_BIT24 0x01000000
++#define TITAN_BIT25 0x02000000
++#define TITAN_BIT26 0x04000000
++#define TITAN_BIT27 0x08000000
++#define TITAN_BIT28 0x10000000
++#define TITAN_BIT29 0x20000000
++#define TITAN_BIT30 0x40000000
++#define TITAN_BIT31 0x80000000
++
++/* Flow Control */
++#define TITAN_GE_FC_NONE 0x0
++#define TITAN_GE_FC_FULL 0x1
++#define TITAN_GE_FC_TX_PAUSE 0x2
++#define TITAN_GE_FC_RX_PAUSE 0x3
++
++/* Duplex Settings */
++#define TITAN_GE_FULL_DUPLEX 0x1
++#define TITAN_GE_HALF_DUPLEX 0x2
++
++/* Speed settings */
++#define TITAN_GE_SPEED_1000 0x1
++#define TITAN_GE_SPEED_100 0x2
++#define TITAN_GE_SPEED_10 0x3
++
++/* Debugging info only */
++#undef TITAN_DEBUG
++
++/* Keep the rings in the Titan's SSRAM */
++#define TITAN_RX_RING_IN_SRAM
++
++#ifdef CONFIG_64BIT
++#define TITAN_GE_IE_MASK 0xfffffffffb001b64
++#define TITAN_GE_IE_STATUS 0xfffffffffb001b60
++#else
++#define TITAN_GE_IE_MASK 0xfb001b64
++#define TITAN_GE_IE_STATUS 0xfb001b60
++#endif
++
++/* Support for Jumbo Frames */
++#undef TITAN_GE_JUMBO_FRAMES
++
++/* Rx buffer size */
++#ifdef TITAN_GE_JUMBO_FRAMES
++#define TITAN_GE_JUMBO_BUFSIZE 9080
++#else
++#define TITAN_GE_STD_BUFSIZE 1580
++#endif
++
++/*
++ * Tx and Rx Interrupt Coalescing parameter. These values are
++ * for 1 Ghz processor. Rx coalescing can be taken care of
++ * by NAPI. NAPI is adaptive and hence useful. Tx coalescing
++ * is not adaptive. Hence, these values need to be adjusted
++ * based on load, CPU speed etc.
++ */
++#define TITAN_GE_RX_COAL 150
++#define TITAN_GE_TX_COAL 300
++
++#if defined(__BIG_ENDIAN)
++
++/* Define the Rx descriptor */
++typedef struct eth_rx_desc {
++ u32 reserved; /* Unused */
++ u32 buffer_addr; /* CPU buffer address */
++ u32 cmd_sts; /* Command and Status */
++ u32 buffer; /* XDMA buffer address */
++} titan_ge_rx_desc;
++
++/* Define the Tx descriptor */
++typedef struct eth_tx_desc {
++ u16 cmd_sts; /* Command, Status and Buffer count */
++ u16 buffer_len; /* Length of the buffer */
++ u32 buffer_addr; /* Physical address of the buffer */
++} titan_ge_tx_desc;
++
++#elif defined(__LITTLE_ENDIAN)
++
++/* Define the Rx descriptor */
++typedef struct eth_rx_desc {
++ u32 buffer_addr; /* CPU buffer address */
++ u32 reserved; /* Unused */
++ u32 buffer; /* XDMA buffer address */
++ u32 cmd_sts; /* Command and Status */
++} titan_ge_rx_desc;
++
++/* Define the Tx descriptor */
++typedef struct eth_tx_desc {
++ u32 buffer_addr; /* Physical address of the buffer */
++ u16 buffer_len; /* Length of the buffer */
++ u16 cmd_sts; /* Command, Status and Buffer count */
++} titan_ge_tx_desc;
++#endif
++
++/* Default Tx Queue Size */
++#define TITAN_GE_TX_QUEUE 128
++#define TITAN_TX_RING_BYTES (TITAN_GE_TX_QUEUE * sizeof(struct eth_tx_desc))
++
++/* Default Rx Queue Size */
++#define TITAN_GE_RX_QUEUE 64
++#define TITAN_RX_RING_BYTES (TITAN_GE_RX_QUEUE * sizeof(struct eth_rx_desc))
++
++/* Packet Structure */
++typedef struct _pkt_info {
++ unsigned int len;
++ unsigned int cmd_sts;
++ unsigned int buffer;
++ struct sk_buff *skb;
++ unsigned int checksum;
++} titan_ge_packet;
++
++
++#define PHYS_CNT 3
++
++/* Titan Port specific data structure */
++typedef struct _eth_port_ctrl {
++ unsigned int port_num;
++ u8 port_mac_addr[6];
++
++ /* Rx descriptor pointers */
++ int rx_curr_desc_q, rx_used_desc_q;
++
++ /* Tx descriptor pointers */
++ int tx_curr_desc_q, tx_used_desc_q;
++
++ /* Rx descriptor area */
++ volatile titan_ge_rx_desc *rx_desc_area;
++ unsigned int rx_desc_area_size;
++ struct sk_buff* rx_skb[TITAN_GE_RX_QUEUE];
++
++ /* Tx Descriptor area */
++ volatile titan_ge_tx_desc *tx_desc_area;
++ unsigned int tx_desc_area_size;
++ struct sk_buff* tx_skb[TITAN_GE_TX_QUEUE];
++
++ /* Timeout task */
++ struct work_struct tx_timeout_task;
++
++ /* DMA structures and handles */
++ dma_addr_t tx_dma;
++ dma_addr_t rx_dma;
++ dma_addr_t tx_dma_array[TITAN_GE_TX_QUEUE];
++
++ /* Device lock */
++ spinlock_t lock;
++
++ unsigned int tx_ring_skbs;
++ unsigned int rx_ring_size;
++ unsigned int tx_ring_size;
++ unsigned int rx_ring_skbs;
++
++ struct net_device_stats stats;
++
++ /* Tx and Rx coalescing */
++ unsigned long rx_int_coal;
++ unsigned long tx_int_coal;
++
++ /* Threshold for replenishing the Rx and Tx rings */
++ unsigned int tx_threshold;
++ unsigned int rx_threshold;
++
++ /* NAPI work limit */
++ unsigned int rx_work_limit;
++} titan_ge_port_info;
++
++/* Titan specific constants */
++#define TITAN_ETH_PORT_IRQ 3
++
++/* Max Rx buffer */
++#define TITAN_GE_MAX_RX_BUFFER 65536
++
++/* Tx and Rx Error */
++#define TITAN_GE_ERROR
++
++/* Rx Descriptor Command and Status */
++
++#define TITAN_GE_RX_CRC_ERROR TITAN_BIT27 /* crc error */
++#define TITAN_GE_RX_OVERFLOW_ERROR TITAN_BIT15 /* overflow */
++#define TITAN_GE_RX_BUFFER_OWNED TITAN_BIT21 /* buffer ownership */
++#define TITAN_GE_RX_STP TITAN_BIT31 /* start of packet */
++#define TITAN_GE_RX_BAM TITAN_BIT30 /* broadcast address match */
++#define TITAN_GE_RX_PAM TITAN_BIT28 /* physical address match */
++#define TITAN_GE_RX_LAFM TITAN_BIT29 /* logical address filter match */
++#define TITAN_GE_RX_VLAN TITAN_BIT26 /* virtual lans */
++#define TITAN_GE_RX_PERR TITAN_BIT19 /* packet error */
++#define TITAN_GE_RX_TRUNC TITAN_BIT20 /* packet size greater than 32 buffers */
++
++/* Tx Descriptor Command */
++#define TITAN_GE_TX_BUFFER_OWNED TITAN_BIT5 /* buffer ownership */
++#define TITAN_GE_TX_ENABLE_INTERRUPT TITAN_BIT15 /* Interrupt Enable */
++
++/* Return Status */
++#define TITAN_OK 0x1 /* Good Status */
++#define TITAN_ERROR 0x2 /* Error Status */
++
++/* MIB specific register offset */
++#define TITAN_GE_MSTATX_STATS_BASE_LOW 0x0800 /* MSTATX COUNTL[15:0] */
++#define TITAN_GE_MSTATX_STATS_BASE_MID 0x0804 /* MSTATX COUNTM[15:0] */
++#define TITAN_GE_MSTATX_STATS_BASE_HI 0x0808 /* MSTATX COUNTH[7:0] */
++#define TITAN_GE_MSTATX_CONTROL 0x0828 /* MSTATX Control */
++#define TITAN_GE_MSTATX_VARIABLE_SELECT 0x082C /* MSTATX Variable Select */
++
++/* MIB counter offsets, add to the TITAN_GE_MSTATX_STATS_BASE_XXX */
++#define TITAN_GE_MSTATX_RXFRAMESOK 0x0040
++#define TITAN_GE_MSTATX_RXOCTETSOK 0x0050
++#define TITAN_GE_MSTATX_RXFRAMES 0x0060
++#define TITAN_GE_MSTATX_RXOCTETS 0x0070
++#define TITAN_GE_MSTATX_RXUNICASTFRAMESOK 0x0080
++#define TITAN_GE_MSTATX_RXBROADCASTFRAMESOK 0x0090
++#define TITAN_GE_MSTATX_RXMULTICASTFRAMESOK 0x00A0
++#define TITAN_GE_MSTATX_RXTAGGEDFRAMESOK 0x00B0
++#define TITAN_GE_MSTATX_RXMACPAUSECONTROLFRAMESOK 0x00C0
++#define TITAN_GE_MSTATX_RXMACCONTROLFRAMESOK 0x00D0
++#define TITAN_GE_MSTATX_RXFCSERROR 0x00E0
++#define TITAN_GE_MSTATX_RXALIGNMENTERROR 0x00F0
++#define TITAN_GE_MSTATX_RXSYMBOLERROR 0x0100
++#define TITAN_GE_MSTATX_RXLAYER1ERROR 0x0110
++#define TITAN_GE_MSTATX_RXINRANGELENGTHERROR 0x0120
++#define TITAN_GE_MSTATX_RXLONGLENGTHERROR 0x0130
++#define TITAN_GE_MSTATX_RXLONGLENGTHCRCERROR 0x0140
++#define TITAN_GE_MSTATX_RXSHORTLENGTHERROR 0x0150
++#define TITAN_GE_MSTATX_RXSHORTLLENGTHCRCERROR 0x0160
++#define TITAN_GE_MSTATX_RXFRAMES64OCTETS 0x0170
++#define TITAN_GE_MSTATX_RXFRAMES65TO127OCTETS 0x0180
++#define TITAN_GE_MSTATX_RXFRAMES128TO255OCTETS 0x0190
++#define TITAN_GE_MSTATX_RXFRAMES256TO511OCTETS 0x01A0
++#define TITAN_GE_MSTATX_RXFRAMES512TO1023OCTETS 0x01B0
++#define TITAN_GE_MSTATX_RXFRAMES1024TO1518OCTETS 0x01C0
++#define TITAN_GE_MSTATX_RXFRAMES1519TOMAXSIZE 0x01D0
++#define TITAN_GE_MSTATX_RXSTATIONADDRESSFILTERED 0x01E0
++#define TITAN_GE_MSTATX_RXVARIABLE 0x01F0
++#define TITAN_GE_MSTATX_GENERICADDRESSFILTERED 0x0200
++#define TITAN_GE_MSTATX_UNICASTFILTERED 0x0210
++#define TITAN_GE_MSTATX_MULTICASTFILTERED 0x0220
++#define TITAN_GE_MSTATX_BROADCASTFILTERED 0x0230
++#define TITAN_GE_MSTATX_HASHFILTERED 0x0240
++#define TITAN_GE_MSTATX_TXFRAMESOK 0x0250
++#define TITAN_GE_MSTATX_TXOCTETSOK 0x0260
++#define TITAN_GE_MSTATX_TXOCTETS 0x0270
++#define TITAN_GE_MSTATX_TXTAGGEDFRAMESOK 0x0280
++#define TITAN_GE_MSTATX_TXMACPAUSECONTROLFRAMESOK 0x0290
++#define TITAN_GE_MSTATX_TXFCSERROR 0x02A0
++#define TITAN_GE_MSTATX_TXSHORTLENGTHERROR 0x02B0
++#define TITAN_GE_MSTATX_TXLONGLENGTHERROR 0x02C0
++#define TITAN_GE_MSTATX_TXSYSTEMERROR 0x02D0
++#define TITAN_GE_MSTATX_TXMACERROR 0x02E0
++#define TITAN_GE_MSTATX_TXCARRIERSENSEERROR 0x02F0
++#define TITAN_GE_MSTATX_TXSQETESTERROR 0x0300
++#define TITAN_GE_MSTATX_TXUNICASTFRAMESOK 0x0310
++#define TITAN_GE_MSTATX_TXBROADCASTFRAMESOK 0x0320
++#define TITAN_GE_MSTATX_TXMULTICASTFRAMESOK 0x0330
++#define TITAN_GE_MSTATX_TXUNICASTFRAMESATTEMPTED 0x0340
++#define TITAN_GE_MSTATX_TXBROADCASTFRAMESATTEMPTED 0x0350
++#define TITAN_GE_MSTATX_TXMULTICASTFRAMESATTEMPTED 0x0360
++#define TITAN_GE_MSTATX_TXFRAMES64OCTETS 0x0370
++#define TITAN_GE_MSTATX_TXFRAMES65TO127OCTETS 0x0380
++#define TITAN_GE_MSTATX_TXFRAMES128TO255OCTETS 0x0390
++#define TITAN_GE_MSTATX_TXFRAMES256TO511OCTETS 0x03A0
++#define TITAN_GE_MSTATX_TXFRAMES512TO1023OCTETS 0x03B0
++#define TITAN_GE_MSTATX_TXFRAMES1024TO1518OCTETS 0x03C0
++#define TITAN_GE_MSTATX_TXFRAMES1519TOMAXSIZE 0x03D0
++#define TITAN_GE_MSTATX_TXVARIABLE 0x03E0
++#define TITAN_GE_MSTATX_RXSYSTEMERROR 0x03F0
++#define TITAN_GE_MSTATX_SINGLECOLLISION 0x0400
++#define TITAN_GE_MSTATX_MULTIPLECOLLISION 0x0410
++#define TITAN_GE_MSTATX_DEFERREDXMISSIONS 0x0420
++#define TITAN_GE_MSTATX_LATECOLLISIONS 0x0430
++#define TITAN_GE_MSTATX_ABORTEDDUETOXSCOLLS 0x0440
++
++/* Interrupt specific defines */
++#define TITAN_GE_DEVICE_ID 0x0000 /* Device ID */
++#define TITAN_GE_RESET 0x0004 /* Reset reg */
++#define TITAN_GE_TSB_CTRL_0 0x000C /* TSB Control reg 0 */
++#define TITAN_GE_TSB_CTRL_1 0x0010 /* TSB Control reg 1 */
++#define TITAN_GE_INTR_GRP0_STATUS 0x0040 /* General Interrupt Group 0 Status */
++#define TITAN_GE_INTR_XDMA_CORE_A 0x0048 /* XDMA Channel Interrupt Status, Core A*/
++#define TITAN_GE_INTR_XDMA_CORE_B 0x004C /* XDMA Channel Interrupt Status, Core B*/
++#define TITAN_GE_INTR_XDMA_IE 0x0058 /* XDMA Channel Interrupt Enable */
++#define TITAN_GE_SDQPF_ECC_INTR 0x480C /* SDQPF ECC Interrupt Status */
++#define TITAN_GE_SDQPF_RXFIFO_CTL 0x4828 /* SDQPF RxFifo Control and Interrupt Enb*/
++#define TITAN_GE_SDQPF_RXFIFO_INTR 0x482C /* SDQPF RxFifo Interrupt Status */
++#define TITAN_GE_SDQPF_TXFIFO_CTL 0x4928 /* SDQPF TxFifo Control and Interrupt Enb*/
++#define TITAN_GE_SDQPF_TXFIFO_INTR 0x492C /* SDQPF TxFifo Interrupt Status */
++#define TITAN_GE_SDQPF_RXFIFO_0 0x4840 /* SDQPF RxFIFO Enable */
++#define TITAN_GE_SDQPF_TXFIFO_0 0x4940 /* SDQPF TxFIFO Enable */
++#define TITAN_GE_XDMA_CONFIG 0x5000 /* XDMA Global Configuration */
++#define TITAN_GE_XDMA_INTR_SUMMARY 0x5010 /* XDMA Interrupt Summary */
++#define TITAN_GE_XDMA_BUFADDRPRE 0x5018 /* XDMA Buffer Address Prefix */
++#define TITAN_GE_XDMA_DESCADDRPRE 0x501C /* XDMA Descriptor Address Prefix */
++#define TITAN_GE_XDMA_PORTWEIGHT 0x502C /* XDMA Port Weight Configuration */
++
++/* Rx MAC defines */
++#define TITAN_GE_RMAC_CONFIG_1 0x1200 /* RMAC Configuration 1 */
++#define TITAN_GE_RMAC_CONFIG_2 0x1204 /* RMAC Configuration 2 */
++#define TITAN_GE_RMAC_MAX_FRAME_LEN 0x1208 /* RMAC Max Frame Length */
++#define TITAN_GE_RMAC_STATION_HI 0x120C /* Rx Station Address High */
++#define TITAN_GE_RMAC_STATION_MID 0x1210 /* Rx Station Address Middle */
++#define TITAN_GE_RMAC_STATION_LOW 0x1214 /* Rx Station Address Low */
++#define TITAN_GE_RMAC_LINK_CONFIG 0x1218 /* RMAC Link Configuration */
++
++/* Tx MAC defines */
++#define TITAN_GE_TMAC_CONFIG_1 0x1240 /* TMAC Configuration 1 */
++#define TITAN_GE_TMAC_CONFIG_2 0x1244 /* TMAC Configuration 2 */
++#define TITAN_GE_TMAC_IPG 0x1248 /* TMAC Inter-Packet Gap */
++#define TITAN_GE_TMAC_STATION_HI 0x124C /* Tx Station Address High */
++#define TITAN_GE_TMAC_STATION_MID 0x1250 /* Tx Station Address Middle */
++#define TITAN_GE_TMAC_STATION_LOW 0x1254 /* Tx Station Address Low */
++#define TITAN_GE_TMAC_MAX_FRAME_LEN 0x1258 /* TMAC Max Frame Length */
++#define TITAN_GE_TMAC_MIN_FRAME_LEN 0x125C /* TMAC Min Frame Length */
++#define TITAN_GE_TMAC_PAUSE_FRAME_TIME 0x1260 /* TMAC Pause Frame Time */
++#define TITAN_GE_TMAC_PAUSE_FRAME_INTERVAL 0x1264 /* TMAC Pause Frame Interval */
++
++/* GMII register */
++#define TITAN_GE_GMII_INTERRUPT_STATUS 0x1348 /* GMII Interrupt Status */
++#define TITAN_GE_GMII_CONFIG_GENERAL 0x134C /* GMII Configuration General */
++#define TITAN_GE_GMII_CONFIG_MODE 0x1350 /* GMII Configuration Mode */
++
++/* Tx and Rx XDMA defines */
++#define TITAN_GE_INT_COALESCING 0x5030 /* Interrupt Coalescing */
++#define TITAN_GE_CHANNEL0_CONFIG 0x5040 /* Channel 0 XDMA config */
++#define TITAN_GE_CHANNEL0_INTERRUPT 0x504c /* Channel 0 Interrupt Status */
++#define TITAN_GE_GDI_INTERRUPT_ENABLE 0x5050 /* IE for the GDI Errors */
++#define TITAN_GE_CHANNEL0_PACKET 0x5060 /* Channel 0 Packet count */
++#define TITAN_GE_CHANNEL0_BYTE 0x5064 /* Channel 0 Byte count */
++#define TITAN_GE_CHANNEL0_TX_DESC 0x5054 /* Channel 0 Tx first desc */
++#define TITAN_GE_CHANNEL0_RX_DESC 0x5058 /* Channel 0 Rx first desc */
++
++/* AFX (Address Filter Exact) register offsets for Slice 0 */
++#define TITAN_GE_AFX_EXACT_MATCH_LOW 0x1100 /* AFX Exact Match Address Low*/
++#define TITAN_GE_AFX_EXACT_MATCH_MID 0x1104 /* AFX Exact Match Address Mid*/
++#define TITAN_GE_AFX_EXACT_MATCH_HIGH 0x1108 /* AFX Exact Match Address Hi */
++#define TITAN_GE_AFX_EXACT_MATCH_VID 0x110C /* AFX Exact Match VID */
++#define TITAN_GE_AFX_MULTICAST_HASH_LOW 0x1110 /* AFX Multicast HASH Low */
++#define TITAN_GE_AFX_MULTICAST_HASH_MIDLOW 0x1114 /* AFX Multicast HASH MidLow */
++#define TITAN_GE_AFX_MULTICAST_HASH_MIDHI 0x1118 /* AFX Multicast HASH MidHi */
++#define TITAN_GE_AFX_MULTICAST_HASH_HI 0x111C /* AFX Multicast HASH Hi */
++#define TITAN_GE_AFX_ADDRS_FILTER_CTRL_0 0x1120 /* AFX Address Filter Ctrl 0 */
++#define TITAN_GE_AFX_ADDRS_FILTER_CTRL_1 0x1124 /* AFX Address Filter Ctrl 1 */
++#define TITAN_GE_AFX_ADDRS_FILTER_CTRL_2 0x1128 /* AFX Address Filter Ctrl 2 */
++
++/* Traffic Groomer block */
++#define TITAN_GE_TRTG_CONFIG 0x1000 /* TRTG Config */
++
++#endif /* _TITAN_GE_H_ */
++
+diff --git a/drivers/net/titan_mdio.c b/drivers/net/titan_mdio.c
+new file mode 100644
+index 0000000..8a8785b
+--- /dev/null
++++ b/drivers/net/titan_mdio.c
+@@ -0,0 +1,217 @@
++/*
++ * drivers/net/titan_mdio.c - Driver for Titan ethernet ports
++ *
++ * Copyright (C) 2003 PMC-Sierra Inc.
++ * Author : Manish Lachwani (lachwani@pmc-sierra.com)
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++ *
++ * Management Data IO (MDIO) driver for the Titan GMII. Interacts with the Marvel PHY
++ * on the Titan. No support for the TBI as yet.
++ *
++ */
++
++#include "titan_mdio.h"
++
++#define MDIO_DEBUG
++
++/*
++ * Local constants
++ */
++#define MAX_CLKA 1023
++#define MAX_PHY_DEV 31
++#define MAX_PHY_REG 31
++#define WRITEADDRS_OPCODE 0x0
++#define READ_OPCODE 0x2
++#define WRITE_OPCODE 0x1
++#define MAX_MDIO_POLL 100
++
++/*
++ * Titan MDIO and SCMB registers
++ */
++#define TITAN_GE_SCMB_CONTROL 0x01c0 /* SCMB Control */
++#define TITAN_GE_SCMB_CLKA 0x01c4 /* SCMB Clock A */
++#define TITAN_GE_MDIO_COMMAND 0x01d0 /* MDIO Command */
++#define TITAN_GE_MDIO_DEVICE_PORT_ADDRESS 0x01d4 /* MDIO Device and Port addrs */
++#define TITAN_GE_MDIO_DATA 0x01d8 /* MDIO Data */
++#define TITAN_GE_MDIO_INTERRUPTS 0x01dC /* MDIO Interrupts */
++
++/*
++ * Function to poll the MDIO
++ */
++static int titan_ge_mdio_poll(void)
++{
++ int i, val;
++
++ for (i = 0; i < MAX_MDIO_POLL; i++) {
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
++
++ if (!(val & 0x8000))
++ return TITAN_GE_MDIO_GOOD;
++ }
++
++ return TITAN_GE_MDIO_ERROR;
++}
++
++
++/*
++ * Initialize and configure the MDIO
++ */
++int titan_ge_mdio_setup(titan_ge_mdio_config *titan_mdio)
++{
++ unsigned long val;
++
++ /* Reset the SCMB and program into MDIO mode*/
++ TITAN_GE_MDIO_WRITE(TITAN_GE_SCMB_CONTROL, 0x9000);
++ TITAN_GE_MDIO_WRITE(TITAN_GE_SCMB_CONTROL, 0x1000);
++
++ /* CLK A */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_SCMB_CLKA);
++ val = ( (val & ~(0x03ff)) | (titan_mdio->clka & 0x03ff));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_SCMB_CLKA, val);
++
++ /* Preamble Suppresion */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
++ val = ( (val & ~(0x0001)) | (titan_mdio->mdio_spre & 0x0001));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
++
++ /* MDIO mode */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
++ val = ( (val & ~(0x4000)) | (titan_mdio->mdio_mode & 0x4000));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
++
++ return TITAN_GE_MDIO_GOOD;
++}
++
++/*
++ * Set the PHY address in indirect mode
++ */
++int titan_ge_mdio_inaddrs(int dev_addr, int reg_addr)
++{
++ volatile unsigned long val;
++
++ /* Setup the PHY device */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
++ val = ( (val & ~(0x1f00)) | ( (dev_addr << 8) & 0x1f00));
++ val = ( (val & ~(0x001f)) | ( reg_addr & 0x001f));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
++
++ /* Write the new address */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
++ val = ( (val & ~(0x0300)) | ( (WRITEADDRS_OPCODE << 8) & 0x0300));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
++
++ return TITAN_GE_MDIO_GOOD;
++}
++
++/*
++ * Read the MDIO register. This is what the individual parametes mean:
++ *
++ * dev_addr : PHY ID
++ * reg_addr : register offset
++ *
++ * See the spec for the Titan MAC. We operate in the Direct Mode.
++ */
++
++#define MAX_RETRIES 2
++
++int titan_ge_mdio_read(int dev_addr, int reg_addr, unsigned int *pdata)
++{
++ volatile unsigned long val;
++ int retries = 0;
++
++ /* Setup the PHY device */
++
++again:
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
++ val = ( (val & ~(0x1f00)) | ( (dev_addr << 8) & 0x1f00));
++ val = ( (val & ~(0x001f)) | ( reg_addr & 0x001f));
++ val |= 0x4000;
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
++
++ udelay(30);
++
++ /* Issue the read command */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
++ val = ( (val & ~(0x0300)) | ( (READ_OPCODE << 8) & 0x0300));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
++
++ udelay(30);
++
++ if (titan_ge_mdio_poll() != TITAN_GE_MDIO_GOOD)
++ return TITAN_GE_MDIO_ERROR;
++
++ *pdata = (unsigned int)TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DATA);
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_INTERRUPTS);
++
++ udelay(30);
++
++ if (val & 0x2) {
++ if (retries == MAX_RETRIES)
++ return TITAN_GE_MDIO_ERROR;
++ else {
++ retries++;
++ goto again;
++ }
++ }
++
++ return TITAN_GE_MDIO_GOOD;
++}
++
++/*
++ * Write to the MDIO register
++ *
++ * dev_addr : PHY ID
++ * reg_addr : register that needs to be written to
++ *
++ */
++int titan_ge_mdio_write(int dev_addr, int reg_addr, unsigned int data)
++{
++ volatile unsigned long val;
++
++ if (titan_ge_mdio_poll() != TITAN_GE_MDIO_GOOD)
++ return TITAN_GE_MDIO_ERROR;
++
++ /* Setup the PHY device */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS);
++ val = ( (val & ~(0x1f00)) | ( (dev_addr << 8) & 0x1f00));
++ val = ( (val & ~(0x001f)) | ( reg_addr & 0x001f));
++ val |= 0x4000;
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DEVICE_PORT_ADDRESS, val);
++
++ udelay(30);
++
++ /* Setup the data to write */
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_DATA, data);
++
++ udelay(30);
++
++ /* Issue the write command */
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_COMMAND);
++ val = ( (val & ~(0x0300)) | ( (WRITE_OPCODE << 8) & 0x0300));
++ TITAN_GE_MDIO_WRITE(TITAN_GE_MDIO_COMMAND, val);
++
++ udelay(30);
++
++ if (titan_ge_mdio_poll() != TITAN_GE_MDIO_GOOD)
++ return TITAN_GE_MDIO_ERROR;
++
++ val = TITAN_GE_MDIO_READ(TITAN_GE_MDIO_INTERRUPTS);
++ if (val & 0x2)
++ return TITAN_GE_MDIO_ERROR;
++
++ return TITAN_GE_MDIO_GOOD;
++}
++
+diff --git a/drivers/net/titan_mdio.h b/drivers/net/titan_mdio.h
+new file mode 100644
+index 0000000..5d23344
+--- /dev/null
++++ b/drivers/net/titan_mdio.h
+@@ -0,0 +1,56 @@
++/*
++ * MDIO used to interact with the PHY when using GMII/MII
++ */
++#ifndef _TITAN_MDIO_H
++#define _TITAN_MDIO_H
++
++#include <linux/netdevice.h>
++#include <linux/workqueue.h>
++#include <linux/delay.h>
++#include "titan_ge.h"
++
++
++#define TITAN_GE_MDIO_ERROR (-9000)
++#define TITAN_GE_MDIO_GOOD 0
++
++#define TITAN_GE_MDIO_BASE titan_ge_base
++
++#define TITAN_GE_MDIO_READ(offset) \
++ *(volatile u32 *)(titan_ge_base + (offset))
++
++#define TITAN_GE_MDIO_WRITE(offset, data) \
++ *(volatile u32 *)(titan_ge_base + (offset)) = (data)
++
++
++/* GMII specific registers */
++#define TITAN_GE_MARVEL_PHY_ID 0x00
++#define TITAN_PHY_AUTONEG_ADV 0x04
++#define TITAN_PHY_LP_ABILITY 0x05
++#define TITAN_GE_MDIO_MII_CTRL 0x09
++#define TITAN_GE_MDIO_MII_EXTENDED 0x0f
++#define TITAN_GE_MDIO_PHY_CTRL 0x10
++#define TITAN_GE_MDIO_PHY_STATUS 0x11
++#define TITAN_GE_MDIO_PHY_IE 0x12
++#define TITAN_GE_MDIO_PHY_IS 0x13
++#define TITAN_GE_MDIO_PHY_LED 0x18
++#define TITAN_GE_MDIO_PHY_LED_OVER 0x19
++#define PHY_ANEG_TIME_WAIT 45 /* 45 seconds wait time */
++
++/*
++ * MDIO Config Structure
++ */
++typedef struct {
++ unsigned int clka;
++ int mdio_spre;
++ int mdio_mode;
++} titan_ge_mdio_config;
++
++/*
++ * Function Prototypes
++ */
++int titan_ge_mdio_setup(titan_ge_mdio_config *);
++int titan_ge_mdio_inaddrs(int, int);
++int titan_ge_mdio_read(int, int, unsigned int *);
++int titan_ge_mdio_write(int, int, unsigned int);
++
++#endif /* _TITAN_MDIO_H */
+diff --git a/drivers/net/wireless/rtl818x/rtl8187/rfkill.c b/drivers/net/wireless/rtl818x/rtl8187/rfkill.c
+index 3411671..4d252c1 100644
+--- a/drivers/net/wireless/rtl818x/rtl8187/rfkill.c
++++ b/drivers/net/wireless/rtl818x/rtl8187/rfkill.c
+@@ -22,6 +22,10 @@
+
+ static bool rtl8187_is_radio_enabled(struct rtl8187_priv *priv)
+ {
++#ifdef CONFIG_LEMOTE_MACH2F
++ /* Allow users to activate rfkill through only the /sys interface */
++ return 1;
++#else
+ u8 gpio;
+
+ gpio = rtl818x_ioread8(priv, &priv->map->GPIO0);
+@@ -29,6 +33,7 @@ static bool rtl8187_is_radio_enabled(struct rtl8187_priv *priv)
+ gpio = rtl818x_ioread8(priv, &priv->map->GPIO1);
+
+ return gpio & priv->rfkill_mask;
++#endif
+ }
+
+ void rtl8187_rfkill_init(struct ieee80211_hw *hw)
+diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
+index 0adccbf..5b8cf5c 100644
+--- a/drivers/platform/Kconfig
++++ b/drivers/platform/Kconfig
+@@ -7,5 +7,8 @@ endif
+ if GOLDFISH
+ source "drivers/platform/goldfish/Kconfig"
+ endif
++if MIPS
++source "drivers/platform/mips/Kconfig"
++endif
+
+ source "drivers/platform/chrome/Kconfig"
+diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
+index 125e569..70a607d 100644
+--- a/drivers/platform/mips/Kconfig
++++ b/drivers/platform/mips/Kconfig
+@@ -15,6 +15,49 @@ 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 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 4341284..23b5760 100644
+--- a/drivers/platform/mips/Makefile
++++ b/drivers/platform/mips/Makefile
+@@ -1,2 +1,12 @@
++#
++# Makefile for MIPS Platform-Specific Drivers
++#
++
++obj-$(CONFIG_LEMOTE_YEELOONG2F) += yeeloong_laptop.o # yeeloong_ecrom.o
++CFLAGS_yeeloong_laptop.o = -I$(srctree)/arch/mips/loongson/lemote-2f
++
++obj-$(CONFIG_LEMOTE_LYNLOONG2F) += lynloong_pc.o
++obj-$(CONFIG_GDIUM_LAPTOP) += gdium_laptop.o
++
+ 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/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/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/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
+index f940056..a9bb1db 100644
+--- a/drivers/usb/host/pci-quirks.c
++++ b/drivers/usb/host/pci-quirks.c
+@@ -450,6 +450,26 @@ void usb_amd_dev_put(void)
+ }
+ EXPORT_SYMBOL_GPL(usb_amd_dev_put);
+
++#if defined(CONFIG_USB_UHCI_HCD) || defined(CONFIG_USB_UHCI_HCD_MODULE) \
++ || defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) \
++ || defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE) \
++ || defined(CONFIG_USB_XHCI_HCD) || defined(CONFIG_USB_XHCI_HCD_MODULE)
++static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
++{
++ u16 cmd;
++ return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
++}
++
++#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
++#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
++
++static int mmio_resource_enabled(struct pci_dev *pdev, int idx)
++{
++ return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
++}
++#endif
++
++#if defined(CONFIG_USB_UHCI_HCD) || defined(CONFIG_USB_UHCI_HCD_MODULE)
+ /*
+ * Make sure the controller is completely inactive, unable to
+ * generate interrupts or do DMA.
+@@ -531,15 +551,6 @@ reset_needed:
+ }
+ EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
+
+-static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
+-{
+- u16 cmd;
+- return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
+-}
+-
+-#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
+-#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
+-
+ static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
+ {
+ unsigned long base = 0;
+@@ -557,12 +568,11 @@ static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
+ if (base)
+ uhci_check_and_reset_hc(pdev, base);
+ }
++#else
++#define quirk_usb_handoff_uhci(x) do { } while (0)
++#endif /* CONFIG_USB_UHCI_HCD* */
+
+-static int mmio_resource_enabled(struct pci_dev *pdev, int idx)
+-{
+- return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
+-}
+-
++#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE)
+ static void quirk_usb_handoff_ohci(struct pci_dev *pdev)
+ {
+ void __iomem *base;
+@@ -641,7 +651,11 @@ static void quirk_usb_handoff_ohci(struct pci_dev *pdev)
+ /* Now the controller is safely in SUSPEND and nothing can wake it up */
+ iounmap(base);
+ }
++#else
++#define quirk_usb_handoff_ohci(x) do { } while(0)
++#endif /* CONFIG_USB_OHCI_HCD* */
+
++#if defined(CONFIG_USB_EHCI_HCD) || defined(CONFIG_USB_EHCI_HCD_MODULE)
+ static const struct dmi_system_id ehci_dmi_nohandoff_table[] = {
+ {
+ /* Pegatron Lucid (ExoPC) */
+@@ -816,6 +830,9 @@ static void quirk_usb_disable_ehci(struct pci_dev *pdev)
+
+ iounmap(base);
+ }
++#else
++#define quirk_usb_disable_ehci(x) do { } while (0)
++#endif /* CONFIG_USB_EHCI_HCD* */
+
+ /*
+ * handshake - spin reading a register until handshake completes
+@@ -956,6 +973,7 @@ void usb_disable_xhci_ports(struct pci_dev *xhci_pdev)
+ }
+ EXPORT_SYMBOL_GPL(usb_disable_xhci_ports);
+
++#if defined(CONFIG_USB_XHCI_HCD) || defined(CONFIG_USB_XHCI_HCD_MODULE)
+ /**
+ * PCI Quirks for xHCI.
+ *
+@@ -1064,6 +1082,9 @@ hc_init:
+
+ iounmap(base);
+ }
++#else
++#define quirk_usb_handoff_xhci(x) do { } while (0)
++#endif /* CONFIG_USB_UHCI_HCD* */
+
+ static void quirk_usb_early_handoff(struct pci_dev *pdev)
+ {
+diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
+index f0c0c53..84dff90 100644
+--- a/drivers/usb/serial/option.c
++++ b/drivers/usb/serial/option.c
+@@ -79,6 +79,9 @@ static void option_instat_callback(struct urb *urb);
+ #define OPTION_PRODUCT_ETNA_KOI_MODEM 0x7100
+ #define OPTION_PRODUCT_GTM380_MODEM 0x7201
+
++#define HUAWO_VENDOR_ID 0x21F5
++#define HUAWO_PRODUCT_E1621 0x2008
++
+ #define HUAWEI_VENDOR_ID 0x12D1
+ #define HUAWEI_PRODUCT_E173 0x140C
+ #define HUAWEI_PRODUCT_E1750 0x1406
+@@ -633,6 +636,7 @@ static const struct usb_device_id option_ids[] = {
+ { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) },
+ { USB_DEVICE(QUANTA_VENDOR_ID, 0xea42),
+ .driver_info = (kernel_ulong_t)&net_intf4_blacklist },
++ { USB_DEVICE(HUAWO_VENDOR_ID, HUAWO_PRODUCT_E1621) }, /* QUANTA 6500 chips, Unicom extensive use of this card */
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c05, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c1f, USB_CLASS_COMM, 0x02, 0xff) },
+ { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x1c23, USB_CLASS_COMM, 0x02, 0xff) },
+diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
+index 8bf495f..f6a15b6 100644
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -36,6 +36,12 @@ 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/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)
+
+diff --git a/include/linux/video_output.h b/include/linux/video_output.h
+new file mode 100644
+index 0000000..ed5cdeb
+--- /dev/null
++++ b/include/linux/video_output.h
+@@ -0,0 +1,57 @@
++/*
++ *
++ * Copyright (C) 2006 Luming Yu <luming.yu@intel.com>
++ *
++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or (at
++ * your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful, but
++ * WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along
++ * with this program; if not, write to the Free Software Foundation, Inc.,
++ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++ */
++#ifndef _LINUX_VIDEO_OUTPUT_H
++#define _LINUX_VIDEO_OUTPUT_H
++#include <linux/device.h>
++#include <linux/err.h>
++struct output_device;
++struct output_properties {
++ int (*set_state)(struct output_device *);
++ int (*get_status)(struct output_device *);
++};
++struct output_device {
++ int request_state;
++ struct output_properties *props;
++ struct device dev;
++};
++#define to_output_device(obj) container_of(obj, struct output_device, dev)
++#if defined(CONFIG_VIDEO_OUTPUT_CONTROL) || defined(CONFIG_VIDEO_OUTPUT_CONTROL_MODULE)
++struct output_device *video_output_register(const char *name,
++ struct device *dev,
++ void *devdata,
++ struct output_properties *op);
++void video_output_unregister(struct output_device *dev);
++#else
++static struct output_device *video_output_register(const char *name,
++ struct device *dev,
++ void *devdata,
++ struct output_properties *op)
++{
++ return ERR_PTR(-ENODEV);
++}
++static void video_output_unregister(struct output_device *dev)
++{
++ return;
++}
++#endif
++#endif
+diff --git a/init/calibrate.c b/init/calibrate.c
+index ce635dc..10e775d 100644
+--- a/init/calibrate.c
++++ b/init/calibrate.c
+@@ -21,6 +21,7 @@ static int __init lpj_setup(char *str)
+
+ __setup("lpj=", lpj_setup);
+
++#ifndef ARCH_HAS_PREPARED_LPJ
+ #ifdef ARCH_HAS_READ_CURRENT_TIMER
+
+ /* This routine uses the read_current_timer() routine and gets the
+@@ -171,6 +172,7 @@ static unsigned long calibrate_delay_direct(void)
+ return 0;
+ }
+ #endif
++#endif /* ARCH_HAS_PREPARED_LPJ */
+
+ /*
+ * This is the number of bits of precision for the loops_per_jiffy. Each
+@@ -291,6 +293,7 @@ void calibrate_delay(void)
+ lpj = lpj_fine;
+ pr_info("Calibrating delay loop (skipped), "
+ "value calculated using timer frequency.. ");
++#ifndef ARCH_HAS_PREPARED_LPJ
+ } else if ((lpj = calibrate_delay_is_known())) {
+ ;
+ } else if ((lpj = calibrate_delay_direct()) != 0) {
+@@ -301,6 +304,7 @@ void calibrate_delay(void)
+ if (!printed)
+ pr_info("Calibrating delay loop... ");
+ lpj = calibrate_delay_converge();
++#endif /* ARCH_HAS_PREPARED_LPJ */
+ }
+ per_cpu(cpu_loops_per_jiffy, this_cpu) = lpj;
+ if (!printed)
+diff --git a/net/rfkill/core.c b/net/rfkill/core.c
+index fa7cd79..616abb5 100644
+--- a/net/rfkill/core.c
++++ b/net/rfkill/core.c
+@@ -111,7 +111,7 @@ static LIST_HEAD(rfkill_list); /* list of registered rf switches */
+ static DEFINE_MUTEX(rfkill_global_mutex);
+ static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
+
+-static unsigned int rfkill_default_state = 1;
++static unsigned int rfkill_default_state; /* default: 0 = radio off */
+ module_param_named(default_state, rfkill_default_state, uint, 0444);
+ MODULE_PARM_DESC(default_state,
+ "Default initial state for all radio types, 0 = radio off");
+diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl
+index 826470d..9e6dc30 100755
+--- a/scripts/recordmcount.pl
++++ b/scripts/recordmcount.pl
+@@ -309,14 +309,33 @@ if ($arch eq "x86_64") {
+ $cc .= " -m64";
+ $objcopy .= " -O elf64-sparc";
+ } elsif ($arch eq "mips") {
+- # To enable module support, we need to enable the -mlong-calls option
+- # of gcc for module, after using this option, we can not get the real
+- # offset of the calling to _mcount, but the offset of the lui
+- # instruction or the addiu one. herein, we record the address of the
+- # first one, and then we can replace this instruction by a branch
+- # instruction to jump over the profiling function to filter the
+- # indicated functions, or swith back to the lui instruction to trace
+- # them, which means dynamic tracing.
++ # <For kernel>
++ # To disable tracing, just replace "jal _mcount" with nop;
++ # to enable tracing, replace back. so, the offset 14 is
++ # needed to be recorded.
++ #
++ # 10: 03e0082d move at,ra
++ # 14: 0c000000 jal 0
++ # 14: R_MIPS_26 _mcount
++ # 14: R_MIPS_NONE *ABS*
++ # 14: R_MIPS_NONE *ABS*
++ # 18: 00020021 nop
++ #
++ # <For module>
++ #
++ # If no long call(-mlong-calls), the same to kernel.
++ #
++ # If the module space differs from the kernel space, long
++ # call is needed, as a result, the address of _mcount is
++ # needed to be recorded in a register and then jump from
++ # module space to kernel space via "jalr <register>". To
++ # disable tracing, "jalr <register>" can be replaced by
++ # nop; to enable tracing, replace it back. Since the
++ # offset of "jalr <register>" is not easy to be matched,
++ # the offset of the 1st _mcount below is recorded and to
++ # disable tracing, "lui v1, 0x0" is substituted with "b
++ # label", which jumps over "jalr <register>"; to enable
++ # tracing, replace it back.
+ #
+ # c: 3c030000 lui v1,0x0
+ # c: R_MIPS_HI16 _mcount
+@@ -328,19 +347,12 @@ if ($arch eq "x86_64") {
+ # 10: R_MIPS_NONE *ABS*
+ # 14: 03e0082d move at,ra
+ # 18: 0060f809 jalr v1
++ # label:
+ #
+- # for the kernel:
+- #
+- # 10: 03e0082d move at,ra
+- # 14: 0c000000 jal 0 <loongson_halt>
+- # 14: R_MIPS_26 _mcount
+- # 14: R_MIPS_NONE *ABS*
+- # 14: R_MIPS_NONE *ABS*
+- # 18: 00020021 nop
+ if ($is_module eq "0") {
+ $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_26\\s+_mcount\$";
+ } else {
+- $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_HI16\\s+_mcount\$";
++ $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_(HI16|26)\\s+_mcount\$";
+ }
+ $objdump .= " -Melf-trad".$endian."mips ";
+
+diff --git a/scripts/sstrip.sh b/scripts/sstrip.sh
+new file mode 100755
+index 0000000..49b973a
+--- /dev/null
++++ b/scripts/sstrip.sh
+@@ -0,0 +1,59 @@
++#!/bin/bash
++# sstrip.sh -- strip the section table of an elf file
++#
++# Copyright (C) 2010 Wu Zhangjin, wuzhangjin@gmail.com
++# Licensed under the GPLv2
++#
++# Since the section table is useless for the embedded device, it can be
++# stripped out.
++#
++# Note: Some bootloader may check the section table but most of the time, it
++# may be not really used, If it really need the section table, it may need the
++# decompressed kernel image.
++
++# Usage
++
++function usage
++{
++cat <<EOF
++
++ # sstrip.sh -- strip the section table of an elf file
++
++ # Input: elf file
++ # Output: truncated elf file without the section table
++ # Usage: sstrip.sh /path/to/image
++
++EOF
++}
++
++# Do some necessary check
++IMAGE=$1
++
++[ -z "${IMAGE}" ] && echo "$0 : No indicated file to be stripped" && usage && exit -1
++[ ! -f "${IMAGE}" ] && echo "$0 : ${IMAGE} : No such file" && exit -1
++FILE_TYPE=`dd if=${IMAGE} bs=1 skip=1 count=3 2>/dev/null`
++[ "xELF" != "x${FILE_TYPE}" ] && echo "$0: ${IMAGE} is not an ELF file" && exit -1
++
++[ "x${V}" == "x1" ] && orig_filesz=`wc -c ${IMAGE} | cut -d' ' -f1`
++
++# Get the offset of the section table, here get the end of the program section
++filesz=$((`${OBJDUMP} -p ${IMAGE} | grep -m1 filesz | tr -s ' ' | cut -d' ' -f3`))
++
++# Truncate it via the dd tool
++dd if=/dev/null bs=1 of=${IMAGE} seek=${filesz} 2>/dev/null
++
++# Clear the section table information in the ELF header
++# The last 6 bytes of the ELF header are the section table information
++echo -ne "\x00\x00\x00\x00\x00\x00" | dd of=${IMAGE} bs=1 seek=46 count=6 conv=notrunc 2>/dev/null
++
++# Debug
++if [ "x${V}" == "x1" ]; then
++ echo "----------------------------------------------------------------"
++ echo "Strip the section table at ${filesz} of ${IMAGE}"
++ echo "----------------------------------------------------------------"
++ echo " sstrip: $0"
++ echo " objdump: ${OBJDUMP}"
++ echo "original size: ${orig_filesz}"
++ echo "current size: ${filesz}"
++ echo "reduced size: $((${orig_filesz} - ${filesz}))"
++fi
OpenPOWER on IntegriCloud