diff --git a/Makefile b/Makefile index 8e223e0..ba792fc 100644 --- a/Makefile +++ b/Makefile @@ -301,8 +301,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 \ @@ -639,9 +639,9 @@ ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE KBUILD_CFLAGS += -Os $(call cc-disable-warning,maybe-uninitialized,) else ifdef CONFIG_PROFILE_ALL_BRANCHES -KBUILD_CFLAGS += -O2 $(call cc-disable-warning,maybe-uninitialized,) +KBUILD_CFLAGS += -O3 $(call cc-disable-warning,maybe-uninitialized,) else -KBUILD_CFLAGS += -O2 +KBUILD_CFLAGS += -O3 endif endif diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index b3c5bde..9983a9b 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -1829,6 +1829,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 @@ -2540,7 +2549,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 90aca95..4b695ee 100644 --- a/arch/mips/boot/compressed/Makefile +++ b/arch/mips/boot/compressed/Makefile @@ -91,9 +91,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 @@ -106,14 +115,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 $@ @@ -122,11 +131,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 2ed08fb..a848aa5 100644 --- a/arch/mips/boot/compressed/ld.script +++ b/arch/mips/boot/compressed/ld.script @@ -53,5 +53,6 @@ SECTIONS *(.reginfo) *(.comment) *(.note) + *(.gnu.attributes) } } 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 +#include +#include +#include + +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 + * + * 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/mach-loongson64/cs5536/cs5536.h b/arch/mips/include/asm/mach-loongson64/cs5536/cs5536.h index a0ee0cb..4e18add 100644 --- a/arch/mips/include/asm/mach-loongson64/cs5536/cs5536.h +++ b/arch/mips/include/asm/mach-loongson64/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-loongson64/cs5536/cs5536_mfgpt.h b/arch/mips/include/asm/mach-loongson64/cs5536/cs5536_mfgpt.h index 021d017..50aafca 100644 --- a/arch/mips/include/asm/mach-loongson64/cs5536/cs5536_mfgpt.h +++ b/arch/mips/include/asm/mach-loongson64/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-loongson64/loongson.h b/arch/mips/include/asm/mach-loongson64/loongson.h index c68c0cc..1edf3a4 100644 --- a/arch/mips/include/asm/mach-loongson64/loongson.h +++ b/arch/mips/include/asm/mach-loongson64/loongson.h @@ -45,6 +45,12 @@ static inline void prom_init_uart_base(void) #endif } +/* + * Copy kernel command line from arcs_cmdline + */ +#include +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-loongson64/machine.h b/arch/mips/include/asm/mach-loongson64/machine.h index c52549b..bdde74e 100644 --- a/arch/mips/include/asm/mach-loongson64/machine.h +++ b/arch/mips/include/asm/mach-loongson64/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/sparsemem.h b/arch/mips/include/asm/sparsemem.h index b1071c1..4a0d1e7 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_LOONGSON64) || !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 #include diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h index 77429d1..1518c08 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 @@ -196,7 +198,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 }; /* @@ -231,7 +233,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 5a47042..c6a037c 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 a7f8126..6b67e6c 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 0344e57..b6a54d7 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/loongson64/Kconfig b/arch/mips/loongson64/Kconfig index 8e6e292..d7f9db5 100644 --- a/arch/mips/loongson64/Kconfig +++ b/arch/mips/loongson64/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 @@ -50,9 +50,9 @@ config LEMOTE_MACH2F 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 +60,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 @@ -147,6 +172,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/loongson64/Makefile b/arch/mips/loongson64/Makefile index 4fe3d88..fe2c285 100644 --- a/arch/mips/loongson64/Makefile +++ b/arch/mips/loongson64/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/loongson64/Platform b/arch/mips/loongson64/Platform index 0fce460..67516f3 100644 --- a/arch/mips/loongson64/Platform +++ b/arch/mips/loongson64/Platform @@ -51,4 +51,5 @@ platform-$(CONFIG_MACH_LOONGSON64) += loongson64/ cflags-$(CONFIG_MACH_LOONGSON64) += -I$(srctree)/arch/mips/include/asm/mach-loongson64 -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/loongson64/common/cmdline.c b/arch/mips/loongson64/common/cmdline.c index 01fbed1..1621cb3 100644 --- a/arch/mips/loongson64/common/cmdline.c +++ b/arch/mips/loongson64/common/cmdline.c @@ -17,10 +17,15 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ +#include #include #include +/* 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/loongson64/common/env.c b/arch/mips/loongson64/common/env.c index 57d590a..14ee254 100644 --- a/arch/mips/loongson64/common/env.c +++ b/arch/mips/loongson64/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/loongson64/gdium/Makefile b/arch/mips/loongson64/gdium/Makefile new file mode 100644 index 0000000..f3f4f51 --- /dev/null +++ b/arch/mips/loongson64/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/loongson64/gdium/gdium-clock.c b/arch/mips/loongson64/gdium/gdium-clock.c new file mode 100644 index 0000000..fdbf42a --- /dev/null +++ b/arch/mips/loongson64/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +MODULE_DESCRIPTION("Gdium PWM clock driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gdium-pwmclk"); diff --git a/arch/mips/loongson64/gdium/irq.c b/arch/mips/loongson64/gdium/irq.c new file mode 100644 index 0000000..2415d20 --- /dev/null +++ b/arch/mips/loongson64/gdium/irq.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 Lemote Inc. + * Author: Fuxin Zhang, zhangfx@lemote.com + * + * Copyright (c) 2010 yajin + * + * 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 +#include + +#include +#include + +#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/loongson64/gdium/platform.c b/arch/mips/loongson64/gdium/platform.c new file mode 100644 index 0000000..ffafba4 --- /dev/null +++ b/arch/mips/loongson64/gdium/platform.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009 Philippe Vachon + * + * 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 +#include +#include +#include +#include +#include + +#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/loongson64/gdium/reset.c b/arch/mips/loongson64/gdium/reset.c new file mode 100644 index 0000000..8289f95 --- /dev/null +++ b/arch/mips/loongson64/gdium/reset.c @@ -0,0 +1,22 @@ +/* Board-specific reboot/shutdown routines + * + * Copyright (C) 2010 yajin + * + * 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 + +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/loongson64/gdium/sm501-pwm.c b/arch/mips/loongson64/gdium/sm501-pwm.c new file mode 100644 index 0000000..5af3b23 --- /dev/null +++ b/arch/mips/loongson64/gdium/sm501-pwm.c @@ -0,0 +1,465 @@ +/* + * SM501 PWM clock + * Copyright (C) 2009-2010 Arnaud Patard + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("SM501 PWM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sm501-pwm"); diff --git a/arch/mips/loongson64/lemote-2f/Makefile b/arch/mips/loongson64/lemote-2f/Makefile index 08b8abc..31c9073 100644 --- a/arch/mips/loongson64/lemote-2f/Makefile +++ b/arch/mips/loongson64/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/loongson64/lemote-2f/platform.c b/arch/mips/loongson64/lemote-2f/platform.c new file mode 100644 index 0000000..5316360 --- /dev/null +++ b/arch/mips/loongson64/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 +#include + +#include + +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 a298ac9..51d646f 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 + * * 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. @@ -60,6 +63,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_LOONGSON64 +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 */ @@ -844,6 +852,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. */ @@ -1373,6 +1389,16 @@ branch_common: xcp->regs[MIPSInst_RD(ir)] = xcp->regs[MIPSInst_RS(ir)]; break; + +#ifdef CONFIG_MACH_LOONGSON64 + case spec2_op:{ + int sig = loongson_spec2_emu(xcp, ctx, ir, fault_addr); + if (sig) + return sig; + break; + } +#endif + default: sigill: return SIGILL; @@ -1458,6 +1484,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_LOONGSON64 +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) { @@ -1559,7 +1751,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, break; default: - return SIGILL; + goto SIGILL_unless_prefx_op; } break; } @@ -1629,7 +1821,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; } @@ -1642,6 +1834,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; } @@ -1663,7 +1860,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_LOONGSON64 + union ieee754sp s2; /* for Loongson paired singles */ +#endif + }; int w; s64 l; } rv; /* resulting value */ @@ -1880,7 +2082,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: @@ -2209,7 +2411,7 @@ copcsr: case fmov_op: /* an easy one */ DPFROMREG(rv.d, MIPSInst_FS(ir)); - goto copcsr; + break; /* binary op on handler */ dcopbop: @@ -2395,6 +2597,83 @@ dcopuop: } } +#ifdef CONFIG_MACH_LOONGSON64 + 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) @@ -2515,6 +2794,11 @@ dcopuop: DITOREG(rv.l, MIPSInst_FD(ir)); break; +#ifdef CONFIG_MACH_LOONGSON64 + 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 4b82148..33d9955 100644 --- a/arch/mips/pci/Makefile +++ b/arch/mips/pci/Makefile @@ -32,6 +32,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 + * + * 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 +#include + +#include +/* + * 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 6bbdac1..ffeba42 100644 --- a/drivers/cpufreq/loongson2_cpufreq.c +++ b/drivers/cpufreq/loongson2_cpufreq.c @@ -164,20 +164,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("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 4070b73..46c1e37 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -943,6 +943,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 4d111f2..cdf4733 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -106,6 +106,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 + * + * 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 +#include +#include +#include + +#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 350accf..10e1d58 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1104,6 +1104,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 210a088..5b564ec 100644 --- a/drivers/ide/ide-iops.c +++ b/drivers/ide/ide-iops.c @@ -27,6 +27,10 @@ #include #include +#ifdef CONFIG_LEMOTE_MACH2F +#include +#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 4053435..0785329 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; @@ -1119,6 +1119,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) { @@ -1138,7 +1154,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) @@ -1193,6 +1215,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 @@ -1351,6 +1387,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) { @@ -1537,10 +1575,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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* For MII specifc registers, titan_mdio.h should be included */ +#include + +#include +#include +#include +#include +#include +#include + +#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, ®_data); + + if (reg_data & 0x0400) { + /* Link status change */ + titan_ge_mdio_read(port_num, + TITAN_GE_MDIO_PHY_STATUS, ®_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 "); +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 +#include +#include +#include + +/* + * 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 +#include +#include +#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/realtek/rtl818x/rtl8187/rfkill.c b/drivers/net/wireless/realtek/rtl818x/rtl8187/rfkill.c index 3411671..4d252c1 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8187/rfkill.c +++ b/drivers/net/wireless/realtek/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/mips/Kconfig b/drivers/platform/mips/Kconfig index b3ae30a..f4fc851 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 CPU_HWMON tristate "Loongson CPU HWMon Driver" depends on LOONGSON_MACH3X diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile index 8dfd039..9ce6b6f 100644 --- a/drivers/platform/mips/Makefile +++ b/drivers/platform/mips/Makefile @@ -1 +1,11 @@ +# +# Makefile for MIPS Platform-Specific Drivers +# + +obj-$(CONFIG_LEMOTE_YEELOONG2F) += yeeloong_laptop.o # yeeloong_ecrom.o +CFLAGS_yeeloong_laptop.o = -I$(srctree)/arch/mips/loongson64/lemote-2f + +obj-$(CONFIG_LEMOTE_LYNLOONG2F) += lynloong_pc.o +obj-$(CONFIG_GDIUM_LAPTOP) += gdium_laptop.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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +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 , Xiang Yu + * + * 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 +#include +#include +#include /* for backlight subdriver */ +#include +#include /* for video output subdriver */ +#include /* for suspend support */ + +#include +#include + +#include + +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 ; Xiang Yu "); +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 + * + * NOTE : + * The EC resources accessing and programming are supported. + */ + +#include +#include +#include +#include +#include + +#include + +#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 "); +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 , Liu Junliang + * + * 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 +#include +#include +#include /* for backlight subdriver */ +#include +#include /* for hwmon subdriver */ +#include +#include /* for video output subdriver */ +#include /* for lcd output subdriver */ +#include /* for hotkey subdriver */ +#include +#include +#include +#include /* for AC & Battery subdriver */ +#include /* for register_reboot_notifier */ +#include /* for register_pm_notifier */ + +#include + +#include /* for loongson_cmdline */ +#include + +#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 ; Liu Junliang "); +MODULE_DESCRIPTION("YeeLoong laptop driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 5dc673d..7567c2e 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -829,6 +829,7 @@ comment "Platform RTC drivers" config RTC_DRV_CMOS tristate "PC-style 'CMOS'" depends on X86 || ARM || M32R || PPC || MIPS || SPARC64 || MN10300 + depends on !DEXXON_GDIUM default y if X86 select RTC_MC146818_LIB help diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index a9a1e4c..5dafdde 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -449,6 +449,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. @@ -530,15 +550,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; @@ -556,12 +567,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; @@ -640,7 +650,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) */ @@ -815,6 +829,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 @@ -955,6 +972,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. * @@ -1065,6 +1083,9 @@ hc_init: iounmap: 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 42cc72e..f8832e5 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -78,6 +78,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 @@ -694,6 +697,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 3c20af9..d972c37 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -30,6 +30,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 + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 +#include +#include +#include +#include + + +MODULE_DESCRIPTION("Display Output Switcher Lowlevel Control Abstraction"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luming Yu "); + +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 + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 +#include +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 884027f..b5b298e 100644 --- a/net/rfkill/core.c +++ b/net/rfkill/core.c @@ -113,7 +113,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/ld-version.sh b/scripts/ld-version.sh index d135882..f47b21c 100755 --- a/scripts/ld-version.sh +++ b/scripts/ld-version.sh @@ -1,7 +1,7 @@ #!/usr/bin/awk -f # extract linker version number from stdin and turn into single number { - gsub(".*\\)", ""); + gsub(".*[)]", ""); gsub(".*version ", ""); gsub("-.*", ""); split($1,a, "."); diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl index faac4b1..b1fc800 100755 --- a/scripts/recordmcount.pl +++ b/scripts/recordmcount.pl @@ -312,14 +312,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. + # + # 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 + # + # + # + # 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 ". To + # disable tracing, "jalr " can be replaced by + # nop; to enable tracing, replace it back. Since the + # offset of "jalr " 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 "; to enable + # tracing, replace it back. # # c: 3c030000 lui v1,0x0 # c: R_MIPS_HI16 _mcount @@ -331,19 +350,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 - # 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 </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