diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-17 10:11:25 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-17 10:11:25 -0700 |
commit | 7b7adc4a016a1decb806eb71ecab98721fa7f146 (patch) | |
tree | 0a6f9a6e5659faa94604fbc575382a18f143c657 /arch/unicore32/kernel | |
parent | 31598e8713ef501c8f6aad2e2ec8a9457e8877c1 (diff) | |
parent | 289d6b0e287e0acd85f3e6b7ea6c2cb5c234909a (diff) |
Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/epip/linux-2.6-unicore32
* 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/epip/linux-2.6-unicore32: (40 commits)
unicore32: rewrite arch-specific tlb.h to use asm-generic version
unicore32: modify io_p2v and io_v2p macros, and adjust PKUNITY_mmio_BASEs
unicore32: replace unicore32-specific iomap functions with generic lib implementation
unicore32 machine related: add frame buffer driver for pkunity-v3 soc
unicore32 machine related files: add i2c bus drivers for pkunity-v3 soc
unicore32 io: redefine __REG(x) and re-use readl/writel funcs
unicore32 i8042 upgrade and bugfix: adjust resource request region type
unicore32 upgrade to v2.6.38-rc5: add one more paramter for pte_alloc_map call
unicore32 i8042: adjust io funcs of i8042-unicore32io.h
unicore32: rename PKUNITY_IOSPACE_BASE to PKUNITY_MMIO_BASE
unicore32: modify function names and parameters for irq_chips
unicore32: remove unused lines in arch/unicore32/include/asm/irq.h
unicore32 time.c: change calculate method for clock_event_device
unicore32: ADD MAINTAINER for unicore32 architecture
unicore32 machine related files: ps2 driver
unicore32 machine related files: pci bus handling
unicore32 machine related files: hardware registers
unicore32 machine related files: core files
unicore32 additional architecture files: boot process
unicore32 additional architecture files: low-level lib: misc
...
Acked-by: Arnd Bergmann <arnd@arndb.de>
Diffstat (limited to 'arch/unicore32/kernel')
37 files changed, 7437 insertions, 0 deletions
diff --git a/arch/unicore32/kernel/Makefile b/arch/unicore32/kernel/Makefile new file mode 100644 index 00000000000..ec23a2fb2f5 --- /dev/null +++ b/arch/unicore32/kernel/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for the linux kernel. +# + +# Object file lists. +obj-y := dma.o elf.o entry.o process.o ptrace.o +obj-y += setup.o signal.o sys.o stacktrace.o traps.o + +obj-$(CONFIG_MODULES) += ksyms.o module.o +obj-$(CONFIG_EARLY_PRINTK) += early_printk.o + +obj-$(CONFIG_CPU_FREQ) += cpu-ucv2.o +obj-$(CONFIG_UNICORE_FPU_F64) += fpu-ucf64.o + +# obj-y for architecture PKUnity v3 +obj-$(CONFIG_ARCH_PUV3) += clock.o irq.o time.o + +obj-$(CONFIG_PUV3_GPIO) += gpio.o +obj-$(CONFIG_PUV3_RTC) += rtc.o +obj-$(CONFIG_PUV3_PWM) += pwm.o +obj-$(CONFIG_PUV3_PM) += pm.o sleep.o +obj-$(CONFIG_HIBERNATION) += hibernate.o hibernate_asm.o + +obj-$(CONFIG_PCI) += pci.o + +# obj-y for specific machines +obj-$(CONFIG_ARCH_PUV3) += puv3-core.o +obj-$(CONFIG_PUV3_NB0916) += puv3-nb0916.o + +head-y := head.o +obj-$(CONFIG_DEBUG_LL) += debug.o + +extra-y := $(head-y) init_task.o vmlinux.lds diff --git a/arch/unicore32/kernel/asm-offsets.c b/arch/unicore32/kernel/asm-offsets.c new file mode 100644 index 00000000000..ffcbe7536ca --- /dev/null +++ b/arch/unicore32/kernel/asm-offsets.c @@ -0,0 +1,112 @@ +/* + * linux/arch/unicore32/kernel/asm-offsets.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * Generate definitions needed by assembly language modules. + * This code generates raw asm output which is post-processed to extract + * and format the required data. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/kbuild.h> +#include <linux/suspend.h> +#include <linux/thread_info.h> +#include <asm/memory.h> +#include <asm/suspend.h> + +/* + * GCC 3.0, 3.1: general bad code generation. + * GCC 3.2.0: incorrect function argument offset calculation. + * GCC 3.2.x: miscompiles NEW_AUX_ENT in fs/binfmt_elf.c + * (http://gcc.gnu.org/PR8896) and incorrect structure + * initialisation in fs/jffs2/erase.c + */ +#if (__GNUC__ < 4) +#error Your compiler should upgrade to uc4 +#error Known good compilers: 4.2.2 +#endif + +int main(void) +{ + DEFINE(TSK_ACTIVE_MM, offsetof(struct task_struct, active_mm)); + BLANK(); + DEFINE(TI_FLAGS, offsetof(struct thread_info, flags)); + DEFINE(TI_PREEMPT, offsetof(struct thread_info, preempt_count)); + DEFINE(TI_ADDR_LIMIT, offsetof(struct thread_info, addr_limit)); + DEFINE(TI_TASK, offsetof(struct thread_info, task)); + DEFINE(TI_EXEC_DOMAIN, offsetof(struct thread_info, exec_domain)); + DEFINE(TI_CPU, offsetof(struct thread_info, cpu)); + DEFINE(TI_CPU_SAVE, offsetof(struct thread_info, cpu_context)); + DEFINE(TI_USED_CP, offsetof(struct thread_info, used_cp)); +#ifdef CONFIG_UNICORE_FPU_F64 + DEFINE(TI_FPSTATE, offsetof(struct thread_info, fpstate)); +#endif + BLANK(); + DEFINE(S_R0, offsetof(struct pt_regs, UCreg_00)); + DEFINE(S_R1, offsetof(struct pt_regs, UCreg_01)); + DEFINE(S_R2, offsetof(struct pt_regs, UCreg_02)); + DEFINE(S_R3, offsetof(struct pt_regs, UCreg_03)); + DEFINE(S_R4, offsetof(struct pt_regs, UCreg_04)); + DEFINE(S_R5, offsetof(struct pt_regs, UCreg_05)); + DEFINE(S_R6, offsetof(struct pt_regs, UCreg_06)); + DEFINE(S_R7, offsetof(struct pt_regs, UCreg_07)); + DEFINE(S_R8, offsetof(struct pt_regs, UCreg_08)); + DEFINE(S_R9, offsetof(struct pt_regs, UCreg_09)); + DEFINE(S_R10, offsetof(struct pt_regs, UCreg_10)); + DEFINE(S_R11, offsetof(struct pt_regs, UCreg_11)); + DEFINE(S_R12, offsetof(struct pt_regs, UCreg_12)); + DEFINE(S_R13, offsetof(struct pt_regs, UCreg_13)); + DEFINE(S_R14, offsetof(struct pt_regs, UCreg_14)); + DEFINE(S_R15, offsetof(struct pt_regs, UCreg_15)); + DEFINE(S_R16, offsetof(struct pt_regs, UCreg_16)); + DEFINE(S_R17, offsetof(struct pt_regs, UCreg_17)); + DEFINE(S_R18, offsetof(struct pt_regs, UCreg_18)); + DEFINE(S_R19, offsetof(struct pt_regs, UCreg_19)); + DEFINE(S_R20, offsetof(struct pt_regs, UCreg_20)); + DEFINE(S_R21, offsetof(struct pt_regs, UCreg_21)); + DEFINE(S_R22, offsetof(struct pt_regs, UCreg_22)); + DEFINE(S_R23, offsetof(struct pt_regs, UCreg_23)); + DEFINE(S_R24, offsetof(struct pt_regs, UCreg_24)); + DEFINE(S_R25, offsetof(struct pt_regs, UCreg_25)); + DEFINE(S_R26, offsetof(struct pt_regs, UCreg_26)); + DEFINE(S_FP, offsetof(struct pt_regs, UCreg_fp)); + DEFINE(S_IP, offsetof(struct pt_regs, UCreg_ip)); + DEFINE(S_SP, offsetof(struct pt_regs, UCreg_sp)); + DEFINE(S_LR, offsetof(struct pt_regs, UCreg_lr)); + DEFINE(S_PC, offsetof(struct pt_regs, UCreg_pc)); + DEFINE(S_PSR, offsetof(struct pt_regs, UCreg_asr)); + DEFINE(S_OLD_R0, offsetof(struct pt_regs, UCreg_ORIG_00)); + DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs)); + BLANK(); + DEFINE(VMA_VM_MM, offsetof(struct vm_area_struct, vm_mm)); + DEFINE(VMA_VM_FLAGS, offsetof(struct vm_area_struct, vm_flags)); + BLANK(); + DEFINE(VM_EXEC, VM_EXEC); + BLANK(); + DEFINE(PAGE_SZ, PAGE_SIZE); + BLANK(); + DEFINE(SYS_ERROR0, 0x9f0000); + BLANK(); + DEFINE(PBE_ADDRESS, offsetof(struct pbe, address)); + DEFINE(PBE_ORIN_ADDRESS, offsetof(struct pbe, orig_address)); + DEFINE(PBE_NEXT, offsetof(struct pbe, next)); + DEFINE(SWSUSP_CPU, offsetof(struct swsusp_arch_regs, \ + cpu_context)); +#ifdef CONFIG_UNICORE_FPU_F64 + DEFINE(SWSUSP_FPSTATE, offsetof(struct swsusp_arch_regs, \ + fpstate)); +#endif + BLANK(); + DEFINE(DMA_BIDIRECTIONAL, DMA_BIDIRECTIONAL); + DEFINE(DMA_TO_DEVICE, DMA_TO_DEVICE); + DEFINE(DMA_FROM_DEVICE, DMA_FROM_DEVICE); + return 0; +} diff --git a/arch/unicore32/kernel/clock.c b/arch/unicore32/kernel/clock.c new file mode 100644 index 00000000000..18d4563e6fa --- /dev/null +++ b/arch/unicore32/kernel/clock.c @@ -0,0 +1,390 @@ +/* + * linux/arch/unicore32/kernel/clock.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/string.h> +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <mach/hardware.h> + +/* + * Very simple clock implementation + */ +struct clk { + struct list_head node; + unsigned long rate; + const char *name; +}; + +static struct clk clk_ost_clk = { + .name = "OST_CLK", + .rate = CLOCK_TICK_RATE, +}; + +static struct clk clk_mclk_clk = { + .name = "MAIN_CLK", +}; + +static struct clk clk_bclk32_clk = { + .name = "BUS32_CLK", +}; + +static struct clk clk_ddr_clk = { + .name = "DDR_CLK", +}; + +static struct clk clk_vga_clk = { + .name = "VGA_CLK", +}; + +static LIST_HEAD(clocks); +static DEFINE_MUTEX(clocks_mutex); + +struct clk *clk_get(struct device *dev, const char *id) +{ + struct clk *p, *clk = ERR_PTR(-ENOENT); + + mutex_lock(&clocks_mutex); + list_for_each_entry(p, &clocks, node) { + if (strcmp(id, p->name) == 0) { + clk = p; + break; + } + } + mutex_unlock(&clocks_mutex); + + return clk; +} +EXPORT_SYMBOL(clk_get); + +void clk_put(struct clk *clk) +{ +} +EXPORT_SYMBOL(clk_put); + +int clk_enable(struct clk *clk) +{ + return 0; +} +EXPORT_SYMBOL(clk_enable); + +void clk_disable(struct clk *clk) +{ +} +EXPORT_SYMBOL(clk_disable); + +unsigned long clk_get_rate(struct clk *clk) +{ + return clk->rate; +} +EXPORT_SYMBOL(clk_get_rate); + +struct { + unsigned long rate; + unsigned long cfg; + unsigned long div; +} vga_clk_table[] = { + {.rate = 25175000, .cfg = 0x00002001, .div = 0x9}, + {.rate = 31500000, .cfg = 0x00002001, .div = 0x7}, + {.rate = 40000000, .cfg = 0x00003801, .div = 0x9}, + {.rate = 49500000, .cfg = 0x00003801, .div = 0x7}, + {.rate = 65000000, .cfg = 0x00002c01, .div = 0x4}, + {.rate = 78750000, .cfg = 0x00002400, .div = 0x7}, + {.rate = 108000000, .cfg = 0x00002c01, .div = 0x2}, + {.rate = 106500000, .cfg = 0x00003c01, .div = 0x3}, + {.rate = 50650000, .cfg = 0x00106400, .div = 0x9}, + {.rate = 61500000, .cfg = 0x00106400, .div = 0xa}, + {.rate = 85500000, .cfg = 0x00002800, .div = 0x6}, +}; + +struct { + unsigned long mrate; + unsigned long prate; +} mclk_clk_table[] = { + {.mrate = 500000000, .prate = 0x00109801}, + {.mrate = 525000000, .prate = 0x00104C00}, + {.mrate = 550000000, .prate = 0x00105000}, + {.mrate = 575000000, .prate = 0x00105400}, + {.mrate = 600000000, .prate = 0x00105800}, + {.mrate = 625000000, .prate = 0x00105C00}, + {.mrate = 650000000, .prate = 0x00106000}, + {.mrate = 675000000, .prate = 0x00106400}, + {.mrate = 700000000, .prate = 0x00106800}, + {.mrate = 725000000, .prate = 0x00106C00}, + {.mrate = 750000000, .prate = 0x00107000}, + {.mrate = 775000000, .prate = 0x00107400}, + {.mrate = 800000000, .prate = 0x00107800}, +}; + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + if (clk == &clk_vga_clk) { + unsigned long pll_vgacfg, pll_vgadiv; + int ret, i; + + /* lookup vga_clk_table */ + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(vga_clk_table); i++) { + if (rate == vga_clk_table[i].rate) { + pll_vgacfg = vga_clk_table[i].cfg; + pll_vgadiv = vga_clk_table[i].div; + ret = 0; + break; + } + } + + if (ret) + return ret; + + if (readl(PM_PLLVGACFG) == pll_vgacfg) + return 0; + + /* set pll vga cfg reg. */ + writel(pll_vgacfg, PM_PLLVGACFG); + + writel(PM_PMCR_CFBVGA, PM_PMCR); + while ((readl(PM_PLLDFCDONE) & PM_PLLDFCDONE_VGADFC) + != PM_PLLDFCDONE_VGADFC) + udelay(100); /* about 1ms */ + + /* set div cfg reg. */ + writel(readl(PM_PCGR) | PM_PCGR_VGACLK, PM_PCGR); + + writel((readl(PM_DIVCFG) & ~PM_DIVCFG_VGACLK_MASK) + | PM_DIVCFG_VGACLK(pll_vgadiv), PM_DIVCFG); + + writel(readl(PM_SWRESET) | PM_SWRESET_VGADIV, PM_SWRESET); + while ((readl(PM_SWRESET) & PM_SWRESET_VGADIV) + == PM_SWRESET_VGADIV) + udelay(100); /* 65536 bclk32, about 320us */ + + writel(readl(PM_PCGR) & ~PM_PCGR_VGACLK, PM_PCGR); + } +#ifdef CONFIG_CPU_FREQ + if (clk == &clk_mclk_clk) { + u32 pll_rate, divstatus = PM_DIVSTATUS; + int ret, i; + + /* lookup mclk_clk_table */ + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(mclk_clk_table); i++) { + if (rate == mclk_clk_table[i].mrate) { + pll_rate = mclk_clk_table[i].prate; + clk_mclk_clk.rate = mclk_clk_table[i].mrate; + ret = 0; + break; + } + } + + if (ret) + return ret; + + if (clk_mclk_clk.rate) + clk_bclk32_clk.rate = clk_mclk_clk.rate + / (((divstatus & 0x0000f000) >> 12) + 1); + + /* set pll sys cfg reg. */ + PM_PLLSYSCFG = pll_rate; + + PM_PMCR = PM_PMCR_CFBSYS; + while ((PM_PLLDFCDONE & PM_PLLDFCDONE_SYSDFC) + != PM_PLLDFCDONE_SYSDFC) + udelay(100); + /* about 1ms */ + } +#endif + return 0; +} +EXPORT_SYMBOL(clk_set_rate); + +int clk_register(struct clk *clk) +{ + mutex_lock(&clocks_mutex); + list_add(&clk->node, &clocks); + mutex_unlock(&clocks_mutex); + printk(KERN_DEFAULT "PKUnity PM: %s %lu.%02luM\n", clk->name, + (clk->rate)/1000000, (clk->rate)/10000 % 100); + return 0; +} +EXPORT_SYMBOL(clk_register); + +void clk_unregister(struct clk *clk) +{ + mutex_lock(&clocks_mutex); + list_del(&clk->node); + mutex_unlock(&clocks_mutex); +} +EXPORT_SYMBOL(clk_unregister); + +struct { + unsigned long prate; + unsigned long rate; +} pllrate_table[] = { + {.prate = 0x00002001, .rate = 250000000}, + {.prate = 0x00104801, .rate = 250000000}, + {.prate = 0x00104C01, .rate = 262500000}, + {.prate = 0x00002401, .rate = 275000000}, + {.prate = 0x00105001, .rate = 275000000}, + {.prate = 0x00105401, .rate = 287500000}, + {.prate = 0x00002801, .rate = 300000000}, + {.prate = 0x00105801, .rate = 300000000}, + {.prate = 0x00105C01, .rate = 312500000}, + {.prate = 0x00002C01, .rate = 325000000}, + {.prate = 0x00106001, .rate = 325000000}, + {.prate = 0x00106401, .rate = 337500000}, + {.prate = 0x00003001, .rate = 350000000}, + {.prate = 0x00106801, .rate = 350000000}, + {.prate = 0x00106C01, .rate = 362500000}, + {.prate = 0x00003401, .rate = 375000000}, + {.prate = 0x00107001, .rate = 375000000}, + {.prate = 0x00107401, .rate = 387500000}, + {.prate = 0x00003801, .rate = 400000000}, + {.prate = 0x00107801, .rate = 400000000}, + {.prate = 0x00107C01, .rate = 412500000}, + {.prate = 0x00003C01, .rate = 425000000}, + {.prate = 0x00108001, .rate = 425000000}, + {.prate = 0x00108401, .rate = 437500000}, + {.prate = 0x00004001, .rate = 450000000}, + {.prate = 0x00108801, .rate = 450000000}, + {.prate = 0x00108C01, .rate = 462500000}, + {.prate = 0x00004401, .rate = 475000000}, + {.prate = 0x00109001, .rate = 475000000}, + {.prate = 0x00109401, .rate = 487500000}, + {.prate = 0x00004801, .rate = 500000000}, + {.prate = 0x00109801, .rate = 500000000}, + {.prate = 0x00104C00, .rate = 525000000}, + {.prate = 0x00002400, .rate = 550000000}, + {.prate = 0x00105000, .rate = 550000000}, + {.prate = 0x00105400, .rate = 575000000}, + {.prate = 0x00002800, .rate = 600000000}, + {.prate = 0x00105800, .rate = 600000000}, + {.prate = 0x00105C00, .rate = 625000000}, + {.prate = 0x00002C00, .rate = 650000000}, + {.prate = 0x00106000, .rate = 650000000}, + {.prate = 0x00106400, .rate = 675000000}, + {.prate = 0x00003000, .rate = 700000000}, + {.prate = 0x00106800, .rate = 700000000}, + {.prate = 0x00106C00, .rate = 725000000}, + {.prate = 0x00003400, .rate = 750000000}, + {.prate = 0x00107000, .rate = 750000000}, + {.prate = 0x00107400, .rate = 775000000}, + {.prate = 0x00003800, .rate = 800000000}, + {.prate = 0x00107800, .rate = 800000000}, + {.prate = 0x00107C00, .rate = 825000000}, + {.prate = 0x00003C00, .rate = 850000000}, + {.prate = 0x00108000, .rate = 850000000}, + {.prate = 0x00108400, .rate = 875000000}, + {.prate = 0x00004000, .rate = 900000000}, + {.prate = 0x00108800, .rate = 900000000}, + {.prate = 0x00108C00, .rate = 925000000}, + {.prate = 0x00004400, .rate = 950000000}, + {.prate = 0x00109000, .rate = 950000000}, + {.prate = 0x00109400, .rate = 975000000}, + {.prate = 0x00004800, .rate = 1000000000}, + {.prate = 0x00109800, .rate = 1000000000}, +}; + +struct { + unsigned long prate; + unsigned long drate; +} pddr_table[] = { + {.prate = 0x00100800, .drate = 44236800}, + {.prate = 0x00100C00, .drate = 66355200}, + {.prate = 0x00101000, .drate = 88473600}, + {.prate = 0x00101400, .drate = 110592000}, + {.prate = 0x00101800, .drate = 132710400}, + {.prate = 0x00101C01, .drate = 154828800}, + {.prate = 0x00102001, .drate = 176947200}, + {.prate = 0x00102401, .drate = 199065600}, + {.prate = 0x00102801, .drate = 221184000}, + {.prate = 0x00102C01, .drate = 243302400}, + {.prate = 0x00103001, .drate = 265420800}, + {.prate = 0x00103401, .drate = 287539200}, + {.prate = 0x00103801, .drate = 309657600}, + {.prate = 0x00103C01, .drate = 331776000}, + {.prate = 0x00104001, .drate = 353894400}, +}; + +static int __init clk_init(void) +{ +#ifdef CONFIG_PUV3_PM + u32 pllrate, divstatus = readl(PM_DIVSTATUS); + u32 pcgr_val = readl(PM_PCGR); + int i; + + pcgr_val |= PM_PCGR_BCLKMME | PM_PCGR_BCLKH264E | PM_PCGR_BCLKH264D + | PM_PCGR_HECLK | PM_PCGR_HDCLK; + writel(pcgr_val, PM_PCGR); + + pllrate = readl(PM_PLLSYSSTATUS); + + /* lookup pmclk_table */ + clk_mclk_clk.rate = 0; + for (i = 0; i < ARRAY_SIZE(pllrate_table); i++) { + if (pllrate == pllrate_table[i].prate) { + clk_mclk_clk.rate = pllrate_table[i].rate; + break; + } + } + + if (clk_mclk_clk.rate) + clk_bclk32_clk.rate = clk_mclk_clk.rate / + (((divstatus & 0x0000f000) >> 12) + 1); + + pllrate = readl(PM_PLLDDRSTATUS); + + /* lookup pddr_table */ + clk_ddr_clk.rate = 0; + for (i = 0; i < ARRAY_SIZE(pddr_table); i++) { + if (pllrate == pddr_table[i].prate) { + clk_ddr_clk.rate = pddr_table[i].drate; + break; + } + } + + pllrate = readl(PM_PLLVGASTATUS); + + /* lookup pvga_table */ + clk_vga_clk.rate = 0; + for (i = 0; i < ARRAY_SIZE(pllrate_table); i++) { + if (pllrate == pllrate_table[i].prate) { + clk_vga_clk.rate = pllrate_table[i].rate; + break; + } + } + + if (clk_vga_clk.rate) + clk_vga_clk.rate = clk_vga_clk.rate / + (((divstatus & 0x00f00000) >> 20) + 1); + + clk_register(&clk_vga_clk); +#endif +#ifdef CONFIG_ARCH_FPGA + clk_ddr_clk.rate = 33000000; + clk_mclk_clk.rate = 33000000; + clk_bclk32_clk.rate = 33000000; +#endif + clk_register(&clk_ddr_clk); + clk_register(&clk_mclk_clk); + clk_register(&clk_bclk32_clk); + clk_register(&clk_ost_clk); + return 0; +} +core_initcall(clk_init); diff --git a/arch/unicore32/kernel/cpu-ucv2.c b/arch/unicore32/kernel/cpu-ucv2.c new file mode 100644 index 00000000000..4a99f62584c --- /dev/null +++ b/arch/unicore32/kernel/cpu-ucv2.c @@ -0,0 +1,93 @@ +/* + * linux/arch/unicore32/kernel/cpu-ucv2.c: clock scaling for the UniCore-II + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> + +#include <mach/hardware.h> + +static struct cpufreq_driver ucv2_driver; + +/* make sure that only the "userspace" governor is run + * -- anything else wouldn't make sense on this platform, anyway. + */ +int ucv2_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu) + return -EINVAL; + + cpufreq_verify_within_limits(policy, + policy->cpuinfo.min_freq, policy->cpuinfo.max_freq); + + return 0; +} + +static unsigned int ucv2_getspeed(unsigned int cpu) +{ + struct clk *mclk = clk_get(NULL, "MAIN_CLK"); + + if (cpu) + return 0; + return clk_get_rate(mclk)/1000; +} + +static int ucv2_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int cur = ucv2_getspeed(0); + struct cpufreq_freqs freqs; + struct clk *mclk = clk_get(NULL, "MAIN_CLK"); + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + if (!clk_set_rate(mclk, target_freq * 1000)) { + freqs.old = cur; + freqs.new = target_freq; + freqs.cpu = 0; + } + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int __init ucv2_cpu_init(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + policy->cur = ucv2_getspeed(0); + policy->min = policy->cpuinfo.min_freq = 250000; + policy->max = policy->cpuinfo.max_freq = 1000000; + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + return 0; +} + +static struct cpufreq_driver ucv2_driver = { + .flags = CPUFREQ_STICKY, + .verify = ucv2_verify_speed, + .target = ucv2_target, + .get = ucv2_getspeed, + .init = ucv2_cpu_init, + .name = "UniCore-II", +}; + +static int __init ucv2_cpufreq_init(void) +{ + return cpufreq_register_driver(&ucv2_driver); +} + +arch_initcall(ucv2_cpufreq_init); diff --git a/arch/unicore32/kernel/debug-macro.S b/arch/unicore32/kernel/debug-macro.S new file mode 100644 index 00000000000..2711d6d87d8 --- /dev/null +++ b/arch/unicore32/kernel/debug-macro.S @@ -0,0 +1,89 @@ +/* + * linux/arch/unicore32/kernel/debug-macro.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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. + * + * Debugging macro include header + */ +#include <generated/asm-offsets.h> +#include <mach/hardware.h> + + .macro put_word_ocd, rd, rx=r16 +1001: movc \rx, p1.c0, #0 + cand.a \rx, #2 + bne 1001b + movc p1.c1, \rd, #1 + .endm + +#ifdef CONFIG_DEBUG_OCD + /* debug using UniCore On-Chip-Debugger */ + .macro addruart, rx + .endm + + .macro senduart, rd, rx + put_word_ocd \rd, \rx + .endm + + .macro busyuart, rd, rx + .endm + + .macro waituart, rd, rx + .endm +#else +#define UART_CLK_DEFAULT 3686400 * 20 + /* Uartclk = MCLK/ 2, The MCLK on my board is 3686400 * 40 */ +#define BAUD_RATE_DEFAULT 115200 + /* The baud rate of the serial port */ + +#define UART_DIVISOR_DEFAULT (UART_CLK_DEFAULT \ + / (16 * BAUD_RATE_DEFAULT) - 1) + + .macro addruart,rx + mrc p0, #0, \rx, c1, c0 + tst \rx, #1 @ MMU enabled? + moveq \rx, #0xee000000 @ physical base address + movne \rx, #0x6e000000 @ virtual address + + @ We probe for the active serial port here + @ However, now we assume UART0 is active: epip4d + @ We assume r1 and r2 can be clobbered. + + movl r2, #UART_DIVISOR_DEFAULT + mov r1, #0x80 + str r1, [\rx, #UART_LCR_OFFSET] + and r1, r2, #0xff00 + mov r1, r1, lsr #8 + str r1, [\rx, #UART_DLH_OFFSET] + and r1, r2, #0xff + str r1, [\rx, #UART_DLL_OFFSET] + mov r1, #0x7 + str r1, [\rx, #UART_FCR_OFFSET] + mov r1, #0x3 + str r1, [\rx, #UART_LCR_OFFSET] + mov r1, #0x0 + str r1, [\rx, #UART_IER_OFFSET] + .endm + + .macro senduart,rd,rx + str \rd, [\rx, #UART_THR_OFFSET] + .endm + + .macro waituart,rd,rx +1001: ldr \rd, [\rx, #UART_LSR_OFFSET] + tst \rd, #UART_LSR_THRE + beq 1001b + .endm + + .macro busyuart,rd,rx +1001: ldr \rd, [\rx, #UART_LSR_OFFSET] + tst \rd, #UART_LSR_TEMT + bne 1001b + .endm +#endif + diff --git a/arch/unicore32/kernel/debug.S b/arch/unicore32/kernel/debug.S new file mode 100644 index 00000000000..029fd12f6ab --- /dev/null +++ b/arch/unicore32/kernel/debug.S @@ -0,0 +1,85 @@ +/* + * linux/arch/unicore32/kernel/debug.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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. + * + * 32-bit debugging code + */ +#include <linux/linkage.h> +#include <asm/assembler.h> + + .text + +/* + * Some debugging routines (useful if you've got MM problems and + * printk isn't working). For DEBUGGING ONLY!!! Do not leave + * references to these in a production kernel! + */ +#include "debug-macro.S" + +/* + * Useful debugging routines + */ +ENTRY(printhex8) + mov r1, #8 + b printhex +ENDPROC(printhex8) + +ENTRY(printhex4) + mov r1, #4 + b printhex +ENDPROC(printhex4) + +ENTRY(printhex2) + mov r1, #2 +printhex: adr r2, hexbuf + add r3, r2, r1 + mov r1, #0 + stb r1, [r3] +1: and r1, r0, #15 + mov r0, r0 >> #4 + csub.a r1, #10 + beg 2f + add r1, r1, #'0' - 'a' + 10 +2: add r1, r1, #'a' - 10 + stb.w r1, [r3+], #-1 + cxor.a r3, r2 + bne 1b + mov r0, r2 + b printascii +ENDPROC(printhex2) + + .ltorg + +ENTRY(printascii) + addruart r3 + b 2f +1: waituart r2, r3 + senduart r1, r3 + busyuart r2, r3 + cxor.a r1, #'\n' + cmoveq r1, #'\r' + beq 1b +2: cxor.a r0, #0 + beq 3f + ldb.w r1, [r0]+, #1 + cxor.a r1, #0 + bne 1b +3: mov pc, lr +ENDPROC(printascii) + +ENTRY(printch) + addruart r3 + mov r1, r0 + mov r0, #0 + b 1b +ENDPROC(printch) + +hexbuf: .space 16 + diff --git a/arch/unicore32/kernel/dma.c b/arch/unicore32/kernel/dma.c new file mode 100644 index 00000000000..ae441bc3122 --- /dev/null +++ b/arch/unicore32/kernel/dma.c @@ -0,0 +1,183 @@ +/* + * linux/arch/unicore32/kernel/dma.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/io.h> + +#include <asm/system.h> +#include <asm/irq.h> +#include <mach/hardware.h> +#include <mach/dma.h> + +struct dma_channel { + char *name; + puv3_dma_prio prio; + void (*irq_handler)(int, void *); + void (*err_handler)(int, void *); + void *data; +}; + +static struct dma_channel dma_channels[MAX_DMA_CHANNELS]; + +int puv3_request_dma(char *name, puv3_dma_prio prio, + void (*irq_handler)(int, void *), + void (*err_handler)(int, void *), + void *data) +{ + unsigned long flags; + int i, found = 0; + + /* basic sanity checks */ + if (!name) + return -EINVAL; + + local_irq_save(flags); + + do { + /* try grabbing a DMA channel with the requested priority */ + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + if ((dma_channels[i].prio == prio) && + !dma_channels[i].name) { + found = 1; + break; + } + } + /* if requested prio group is full, try a hier priority */ + } while (!found && prio--); + + if (found) { + dma_channels[i].name = name; + dma_channels[i].irq_handler = irq_handler; + dma_channels[i].err_handler = err_handler; + dma_channels[i].data = data; + } else { + printk(KERN_WARNING "No more available DMA channels for %s\n", + name); + i = -ENODEV; + } + + local_irq_restore(flags); + return i; +} +EXPORT_SYMBOL(puv3_request_dma); + +void puv3_free_dma(int dma_ch) +{ + unsigned long flags; + + if (!dma_channels[dma_ch].name) { + printk(KERN_CRIT + "%s: trying to free channel %d which is already freed\n", + __func__, dma_ch); + return; + } + + local_irq_save(flags); + dma_channels[dma_ch].name = NULL; + dma_channels[dma_ch].err_handler = NULL; + local_irq_restore(flags); +} +EXPORT_SYMBOL(puv3_free_dma); + +static irqreturn_t dma_irq_handler(int irq, void *dev_id) +{ + int i, dint; + + dint = readl(DMAC_ITCSR); + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + if (dint & DMAC_CHANNEL(i)) { + struct dma_channel *channel = &dma_channels[i]; + + /* Clear TC interrupt of channel i */ + writel(DMAC_CHANNEL(i), DMAC_ITCCR); + writel(0, DMAC_ITCCR); + + if (channel->name && channel->irq_handler) { + channel->irq_handler(i, channel->data); + } else { + /* + * IRQ for an unregistered DMA channel: + * let's clear the interrupts and disable it. + */ + printk(KERN_WARNING "spurious IRQ for" + " DMA channel %d\n", i); + } + } + } + return IRQ_HANDLED; +} + +static irqreturn_t dma_err_handler(int irq, void *dev_id) +{ + int i, dint; + + dint = readl(DMAC_IESR); + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + if (dint & DMAC_CHANNEL(i)) { + struct dma_channel *channel = &dma_channels[i]; + + /* Clear Err interrupt of channel i */ + writel(DMAC_CHANNEL(i), DMAC_IECR); + writel(0, DMAC_IECR); + + if (channel->name && channel->err_handler) { + channel->err_handler(i, channel->data); + } else { + /* + * IRQ for an unregistered DMA channel: + * let's clear the interrupts and disable it. + */ + printk(KERN_WARNING "spurious IRQ for" + " DMA channel %d\n", i); + } + } + } + return IRQ_HANDLED; +} + +int __init puv3_init_dma(void) +{ + int i, ret; + + /* dma channel priorities on v8 processors: + * ch 0 - 1 <--> (0) DMA_PRIO_HIGH + * ch 2 - 3 <--> (1) DMA_PRIO_MEDIUM + * ch 4 - 5 <--> (2) DMA_PRIO_LOW + */ + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + puv3_stop_dma(i); + dma_channels[i].name = NULL; + dma_channels[i].prio = min((i & 0x7) >> 1, DMA_PRIO_LOW); + } + + ret = request_irq(IRQ_DMA, dma_irq_handler, 0, "DMA", NULL); + if (ret) { + printk(KERN_CRIT "Can't register IRQ for DMA\n"); + return ret; + } + + ret = request_irq(IRQ_DMAERR, dma_err_handler, 0, "DMAERR", NULL); + if (ret) { + printk(KERN_CRIT "Can't register IRQ for DMAERR\n"); + free_irq(IRQ_DMA, "DMA"); + return ret; + } + + return 0; +} + +postcore_initcall(puv3_init_dma); diff --git a/arch/unicore32/kernel/early_printk.c b/arch/unicore32/kernel/early_printk.c new file mode 100644 index 00000000000..3922255f1fa --- /dev/null +++ b/arch/unicore32/kernel/early_printk.c @@ -0,0 +1,59 @@ +/* + * linux/arch/unicore32/kernel/early_printk.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/console.h> +#include <linux/init.h> +#include <linux/string.h> +#include <mach/ocd.h> + +/* On-Chip-Debugger functions */ + +static void early_ocd_write(struct console *con, const char *s, unsigned n) +{ + while (*s && n-- > 0) { + if (*s == '\n') + ocd_putc((int)'\r'); + ocd_putc((int)*s); + s++; + } +} + +static struct console early_ocd_console = { + .name = "earlyocd", + .write = early_ocd_write, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +/* Direct interface for emergencies */ +static struct console *early_console = &early_ocd_console; + +static int __initdata keep_early; + +static int __init setup_early_printk(char *buf) +{ + if (!buf) + return 0; + + if (strstr(buf, "keep")) + keep_early = 1; + + if (!strncmp(buf, "ocd", 3)) + early_console = &early_ocd_console; + + if (keep_early) + early_console->flags &= ~CON_BOOT; + else + early_console->flags |= CON_BOOT; + register_console(early_console); + return 0; +} +early_param("earlyprintk", setup_early_printk); diff --git a/arch/unicore32/kernel/elf.c b/arch/unicore32/kernel/elf.c new file mode 100644 index 00000000000..0a176734fef --- /dev/null +++ b/arch/unicore32/kernel/elf.c @@ -0,0 +1,38 @@ +/* + * linux/arch/unicore32/kernel/elf.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/personality.h> +#include <linux/binfmts.h> +#include <linux/elf.h> + +int elf_check_arch(const struct elf32_hdr *x) +{ + /* Make sure it's an UniCore executable */ + if (x->e_machine != EM_UNICORE) + return 0; + + /* Make sure the entry address is reasonable */ + if (x->e_entry & 3) + return 0; + + return 1; +} +EXPORT_SYMBOL(elf_check_arch); + +void elf_set_personality(const struct elf32_hdr *x) +{ + unsigned int personality = PER_LINUX; + + set_personality(personality); +} +EXPORT_SYMBOL(elf_set_personality); diff --git a/arch/unicore32/kernel/entry.S b/arch/unicore32/kernel/entry.S new file mode 100644 index 00000000000..00a259f9819 --- /dev/null +++ b/arch/unicore32/kernel/entry.S @@ -0,0 +1,824 @@ +/* + * linux/arch/unicore32/kernel/entry.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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. + * + * Low-level vector interface routines + */ +#include <linux/init.h> +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/errno.h> +#include <asm/thread_info.h> +#include <asm/memory.h> +#include <asm/unistd.h> +#include <generated/asm-offsets.h> +#include "debug-macro.S" + +@ +@ Most of the stack format comes from struct pt_regs, but with +@ the addition of 8 bytes for storing syscall args 5 and 6. +@ +#define S_OFF 8 + +/* + * The SWI code relies on the fact that R0 is at the bottom of the stack + * (due to slow/fast restore user regs). + */ +#if S_R0 != 0 +#error "Please fix" +#endif + + .macro zero_fp +#ifdef CONFIG_FRAME_POINTER + mov fp, #0 +#endif + .endm + + .macro alignment_trap, rtemp +#ifdef CONFIG_ALIGNMENT_TRAP + ldw \rtemp, .LCcralign + ldw \rtemp, [\rtemp] + movc p0.c1, \rtemp, #0 +#endif + .endm + + .macro load_user_sp_lr, rd, rtemp, offset = 0 + mov \rtemp, asr + xor \rtemp, \rtemp, #(PRIV_MODE ^ SUSR_MODE) + mov.a asr, \rtemp @ switch to the SUSR mode + + ldw sp, [\rd+], #\offset @ load sp_user + ldw lr, [\rd+], #\offset + 4 @ load lr_user + + xor \rtemp, \rtemp, #(PRIV_MODE ^ SUSR_MODE) + mov.a asr, \rtemp @ switch back to the PRIV mode + .endm + + .macro priv_exit, rpsr + mov.a bsr, \rpsr + ldm.w (r0 - r15), [sp]+ + ldm.b (r16 - pc), [sp]+ @ load r0 - pc, asr + .endm + + .macro restore_user_regs, fast = 0, offset = 0 + ldw r1, [sp+], #\offset + S_PSR @ get calling asr + ldw lr, [sp+], #\offset + S_PC @ get pc + mov.a bsr, r1 @ save in bsr_priv + .if \fast + add sp, sp, #\offset + S_R1 @ r0 is syscall return value + ldm.w (r1 - r15), [sp]+ @ get calling r1 - r15 + ldur (r16 - lr), [sp]+ @ get calling r16 - lr + .else + ldm.w (r0 - r15), [sp]+ @ get calling r0 - r15 + ldur (r16 - lr), [sp]+ @ get calling r16 - lr + .endif + nop + add sp, sp, #S_FRAME_SIZE - S_R16 + mov.a pc, lr @ return + @ and move bsr_priv into asr + .endm + + .macro get_thread_info, rd + mov \rd, sp >> #13 + mov \rd, \rd << #13 + .endm + + .macro get_irqnr_and_base, irqnr, irqstat, base, tmp + ldw \base, =(PKUNITY_INTC_BASE) + ldw \irqstat, [\base+], #0xC @ INTC_ICIP + ldw \tmp, [\base+], #0x4 @ INTC_ICMR + and.a \irqstat, \irqstat, \tmp + beq 1001f + cntlz \irqnr, \irqstat + rsub \irqnr, \irqnr, #31 +1001: /* EQ will be set if no irqs pending */ + .endm + +#ifdef CONFIG_DEBUG_LL + .macro printreg, reg, temp + adr \temp, 901f + stm (r0-r3), [\temp]+ + stw lr, [\temp+], #0x10 + mov r0, \reg + b.l printhex8 + mov r0, #':' + b.l printch + mov r0, pc + b.l printhex8 + adr r0, 902f + b.l printascii + adr \temp, 901f + ldm (r0-r3), [\temp]+ + ldw lr, [\temp+], #0x10 + b 903f +901: .word 0, 0, 0, 0, 0 @ r0-r3, lr +902: .asciz ": epip4d\n" + .align +903: + .endm +#endif + +/* + * These are the registers used in the syscall handler, and allow us to + * have in theory up to 7 arguments to a function - r0 to r6. + * + * Note that tbl == why is intentional. + * + * We must set at least "tsk" and "why" when calling ret_with_reschedule. + */ +scno .req r21 @ syscall number +tbl .req r22 @ syscall table pointer +why .req r22 @ Linux syscall (!= 0) +tsk .req r23 @ current thread_info + +/* + * Interrupt handling. Preserves r17, r18, r19 + */ + .macro intr_handler +1: get_irqnr_and_base r0, r6, r5, lr + beq 2f + mov r1, sp + @ + @ routine called with r0 = irq number, r1 = struct pt_regs * + @ + adr lr, 1b + b asm_do_IRQ +2: + .endm + +/* + * PRIV mode handlers + */ + .macro priv_entry + sub sp, sp, #(S_FRAME_SIZE - 4) + stm (r1 - r15), [sp]+ + add r5, sp, #S_R15 + stm (r16 - r28), [r5]+ + + ldm (r1 - r3), [r0]+ + add r5, sp, #S_SP - 4 @ here for interlock avoidance + mov r4, #-1 @ "" "" "" "" + add r0, sp, #(S_FRAME_SIZE - 4) + stw.w r1, [sp+], #-4 @ save the "real" r0 copied + @ from the exception stack + + mov r1, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r0 - sp_priv + @ r1 - lr_priv + @ r2 - lr_<exception>, already fixed up for correct return/restart + @ r3 - bsr_<exception> + @ r4 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stm (r0 - r4), [r5]+ + .endm + +/* + * User mode handlers + * + */ + .macro user_entry + sub sp, sp, #S_FRAME_SIZE + stm (r1 - r15), [sp+] + add r4, sp, #S_R16 + stm (r16 - r28), [r4]+ + + ldm (r1 - r3), [r0]+ + add r0, sp, #S_PC @ here for interlock avoidance + mov r4, #-1 @ "" "" "" "" + + stw r1, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - lr_<exception>, already fixed up for correct return/restart + @ r3 - bsr_<exception> + @ r4 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_user and lr_user + @ + stm (r2 - r4), [r0]+ + stur (sp, lr), [r0-] + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0 + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + .endm + + .text + +@ +@ __invalid - generic code for failed exception +@ (re-entrant version of handlers) +@ +__invalid: + sub sp, sp, #S_FRAME_SIZE + stm (r1 - r15), [sp+] + add r1, sp, #S_R16 + stm (r16 - r28, sp, lr), [r1]+ + + zero_fp + + ldm (r4 - r6), [r0]+ + add r0, sp, #S_PC @ here for interlock avoidance + mov r7, #-1 @ "" "" "" "" + stw r4, [sp] @ save preserved r0 + stm (r5 - r7), [r0]+ @ lr_<exception>, + @ asr_<exception>, "old_r0" + + mov r0, sp + mov r1, asr + b bad_mode +ENDPROC(__invalid) + + .align 5 +__dabt_priv: + priv_entry + + @ + @ get ready to re-enable interrupts if appropriate + @ + mov r17, asr + cand.a r3, #PSR_I_BIT + bne 1f + andn r17, r17, #PSR_I_BIT +1: + + @ + @ Call the processor-specific abort handler: + @ + @ r2 - aborted context pc + @ r3 - aborted context asr + @ + @ The abort handler must return the aborted address in r0, and + @ the fault status register in r1. + @ + movc r1, p0.c3, #0 @ get FSR + movc r0, p0.c4, #0 @ get FAR + + @ + @ set desired INTR state, then call main handler + @ + mov.a asr, r17 + mov r2, sp + b.l do_DataAbort + + @ + @ INTRs off again before pulling preserved data off the stack + @ + disable_irq r0 + + @ + @ restore BSR and restart the instruction + @ + ldw r2, [sp+], #S_PSR + priv_exit r2 @ return from exception +ENDPROC(__dabt_priv) + + .align 5 +__intr_priv: + priv_entry + + intr_handler + + mov r0, #0 @ epip4d + movc p0.c5, r0, #14 + nop; nop; nop; nop; nop; nop; nop; nop + + ldw r4, [sp+], #S_PSR @ irqs are already disabled + + priv_exit r4 @ return from exception +ENDPROC(__intr_priv) + + .ltorg + + .align 5 +__extn_priv: + priv_entry + + mov r0, sp @ struct pt_regs *regs + mov r1, asr + b bad_mode @ not supported +ENDPROC(__extn_priv) + + .align 5 +__pabt_priv: + priv_entry + + @ + @ re-enable interrupts if appropriate + @ + mov r17, asr + cand.a r3, #PSR_I_BIT + bne 1f + andn r17, r17, #PSR_I_BIT +1: + + @ + @ set args, then call main handler + @ + @ r0 - address of faulting instruction + @ r1 - pointer to registers on stack + @ + mov r0, r2 @ pass address of aborted instruction + mov r1, #5 + mov.a asr, r17 + mov r2, sp @ regs + b.l do_PrefetchAbort @ call abort handler + + @ + @ INTRs off again before pulling preserved data off the stack + @ + disable_irq r0 + + @ + @ restore BSR and restart the instruction + @ + ldw r2, [sp+], #S_PSR + priv_exit r2 @ return from exception +ENDPROC(__pabt_priv) + + .align 5 +.LCcralign: + .word cr_alignment + + .align 5 +__dabt_user: + user_entry + +#ifdef CONFIG_UNICORE_FPU_F64 + cff ip, s31 + cand.a ip, #0x08000000 @ FPU execption traps? + beq 209f + + ldw ip, [sp+], #S_PC + add ip, ip, #4 + stw ip, [sp+], #S_PC + @ + @ fall through to the emulation code, which returns using r19 if + @ it has emulated the instruction, or the more conventional lr + @ if we are to treat this as a real extended instruction + @ + @ r0 - instruction + @ +1: ldw.u r0, [r2] + adr r19, ret_from_exception + adr lr, 209f + @ + @ fallthrough to call do_uc_f64 + @ +/* + * Check whether the instruction is a co-processor instruction. + * If yes, we need to call the relevant co-processor handler. + * + * Note that we don't do a full check here for the co-processor + * instructions; all instructions with bit 27 set are well + * defined. The only instructions that should fault are the + * co-processor instructions. + * + * Emulators may wish to make use of the following registers: + * r0 = instruction opcode. + * r2 = PC + * r19 = normal "successful" return address + * r20 = this threads thread_info structure. + * lr = unrecognised instruction return address + */ + get_thread_info r20 @ get current thread + and r8, r0, #0x00003c00 @ mask out CP number + mov r7, #1 + stb r7, [r20+], #TI_USED_CP + 2 @ set appropriate used_cp[] + + @ F64 hardware support entry point. + @ r0 = faulted instruction + @ r19 = return address + @ r20 = fp_state + enable_irq r4 + add r20, r20, #TI_FPSTATE @ r20 = workspace + cff r1, s31 @ get fpu FPSCR + andn r2, r1, #0x08000000 + ctf r2, s31 @ clear 27 bit + mov r2, sp @ nothing stacked - regdump is at TOS + mov lr, r19 @ setup for a return to the user code + + @ Now call the C code to package up the bounce to the support code + @ r0 holds the trigger instruction + @ r1 holds the FPSCR value + @ r2 pointer to register dump + b ucf64_exchandler +209: +#endif + @ + @ Call the processor-specific abort handler: + @ + @ r2 - aborted context pc + @ r3 - aborted context asr + @ + @ The abort handler must return the aborted address in r0, and + @ the fault status register in r1. + @ + movc r1, p0.c3, #0 @ get FSR + movc r0, p0.c4, #0 @ get FAR + + @ + @ INTRs on, then call the main handler + @ + enable_irq r2 + mov r2, sp + adr lr, ret_from_exception + b do_DataAbort +ENDPROC(__dabt_user) + + .align 5 +__intr_user: + user_entry + + get_thread_info tsk + + intr_handler + + mov why, #0 + b ret_to_user +ENDPROC(__intr_user) + + .ltorg + + .align 5 +__extn_user: + user_entry + + mov r0, sp + mov r1, asr + b bad_mode +ENDPROC(__extn_user) + + .align 5 +__pabt_user: + user_entry + + mov r0, r2 @ pass address of aborted instruction. + mov r1, #5 + enable_irq r1 @ Enable interrupts + mov r2, sp @ regs + b.l do_PrefetchAbort @ call abort handler + /* fall through */ +/* + * This is the return code to user mode for abort handlers + */ +ENTRY(ret_from_exception) + get_thread_info tsk + mov why, #0 + b ret_to_user +ENDPROC(__pabt_user) +ENDPROC(ret_from_exception) + +/* + * Register switch for UniCore V2 processors + * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info + * previous and next are guaranteed not to be the same. + */ +ENTRY(__switch_to) + add ip, r1, #TI_CPU_SAVE + stm.w (r4 - r15), [ip]+ + stm.w (r16 - r27, sp, lr), [ip]+ + +#ifdef CONFIG_UNICORE_FPU_F64 + add ip, r1, #TI_FPSTATE + sfm.w (f0 - f7 ), [ip]+ + sfm.w (f8 - f15), [ip]+ + sfm.w (f16 - f23), [ip]+ + sfm.w (f24 - f31), [ip]+ + cff r4, s31 + stw r4, [ip] + + add ip, r2, #TI_FPSTATE + lfm.w (f0 - f7 ), [ip]+ + lfm.w (f8 - f15), [ip]+ + lfm.w (f16 - f23), [ip]+ + lfm.w (f24 - f31), [ip]+ + ldw r4, [ip] + ctf r4, s31 +#endif + add ip, r2, #TI_CPU_SAVE + ldm.w (r4 - r15), [ip]+ + ldm (r16 - r27, sp, pc), [ip]+ @ Load all regs saved previously +ENDPROC(__switch_to) + + .align 5 +/* + * This is the fast syscall return path. We do as little as + * possible here, and this includes saving r0 back into the PRIV + * stack. + */ +ret_fast_syscall: + disable_irq r1 @ disable interrupts + ldw r1, [tsk+], #TI_FLAGS + cand.a r1, #_TIF_WORK_MASK + bne fast_work_pending + + @ fast_restore_user_regs + restore_user_regs fast = 1, offset = S_OFF + +/* + * Ok, we need to do extra processing, enter the slow path. + */ +fast_work_pending: + stw.w r0, [sp+], #S_R0+S_OFF @ returned r0 +work_pending: + cand.a r1, #_TIF_NEED_RESCHED + bne work_resched + cand.a r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME + beq no_work_pending + mov r0, sp @ 'regs' + mov r2, why @ 'syscall' + cand.a r1, #_TIF_SIGPENDING @ delivering a signal? + cmovne why, #0 @ prevent further restarts + b.l do_notify_resume + b ret_slow_syscall @ Check work again + +work_resched: + b.l schedule +/* + * "slow" syscall return path. "why" tells us if this was a real syscall. + */ +ENTRY(ret_to_user) +ret_slow_syscall: + disable_irq r1 @ disable interrupts + get_thread_info tsk @ epip4d, one path error?! + ldw r1, [tsk+], #TI_FLAGS + cand.a r1, #_TIF_WORK_MASK + bne work_pending +no_work_pending: + @ slow_restore_user_regs + restore_user_regs fast = 0, offset = 0 +ENDPROC(ret_to_user) + +/* + * This is how we return from a fork. + */ +ENTRY(ret_from_fork) + b.l schedule_tail + get_thread_info tsk + ldw r1, [tsk+], #TI_FLAGS @ check for syscall tracing + mov why, #1 + cand.a r1, #_TIF_SYSCALL_TRACE @ are we tracing syscalls? + beq ret_slow_syscall + mov r1, sp + mov r0, #1 @ trace exit [IP = 1] + b.l syscall_trace + b ret_slow_syscall +ENDPROC(ret_from_fork) + +/*============================================================================= + * SWI handler + *----------------------------------------------------------------------------- + */ + .align 5 +ENTRY(vector_swi) + sub sp, sp, #S_FRAME_SIZE + stm (r0 - r15), [sp]+ @ Calling r0 - r15 + add r8, sp, #S_R16 + stm (r16 - r28), [r8]+ @ Calling r16 - r28 + add r8, sp, #S_PC + stur (sp, lr), [r8-] @ Calling sp, lr + mov r8, bsr @ called from non-REAL mode + stw lr, [sp+], #S_PC @ Save calling PC + stw r8, [sp+], #S_PSR @ Save ASR + stw r0, [sp+], #S_OLD_R0 @ Save OLD_R0 + zero_fp + + /* + * Get the system call number. + */ + sub ip, lr, #4 + ldw.u scno, [ip] @ get SWI instruction + +#ifdef CONFIG_ALIGNMENT_TRAP + ldw ip, __cr_alignment + ldw ip, [ip] + movc p0.c1, ip, #0 @ update control register +#endif + enable_irq ip + + get_thread_info tsk + ldw tbl, =sys_call_table @ load syscall table pointer + + andn scno, scno, #0xff000000 @ mask off SWI op-code + andn scno, scno, #0x00ff0000 @ mask off SWI op-code + + stm.w (r4, r5), [sp-] @ push fifth and sixth args + ldw ip, [tsk+], #TI_FLAGS @ check for syscall tracing + cand.a ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls? + bne __sys_trace + + csub.a scno, #__NR_syscalls @ check upper syscall limit + adr lr, ret_fast_syscall @ return address + bea 1f + ldw pc, [tbl+], scno << #2 @ call sys_* routine +1: + add r1, sp, #S_OFF +2: mov why, #0 @ no longer a real syscall + b sys_ni_syscall @ not private func + + /* + * This is the really slow path. We're going to be doing + * context switches, and waiting for our parent to respond. + */ +__sys_trace: + mov r2, scno + add r1, sp, #S_OFF + mov r0, #0 @ trace entry [IP = 0] + b.l syscall_trace + + adr lr, __sys_trace_return @ return address + mov scno, r0 @ syscall number (possibly new) + add r1, sp, #S_R0 + S_OFF @ pointer to regs + csub.a scno, #__NR_syscalls @ check upper syscall limit + bea 2b + ldm (r0 - r3), [r1]+ @ have to reload r0 - r3 + ldw pc, [tbl+], scno << #2 @ call sys_* routine + +__sys_trace_return: + stw.w r0, [sp+], #S_R0 + S_OFF @ save returned r0 + mov r2, scno + mov r1, sp + mov r0, #1 @ trace exit [IP = 1] + b.l syscall_trace + b ret_slow_syscall + + .align 5 +#ifdef CONFIG_ALIGNMENT_TRAP + .type __cr_alignment, #object +__cr_alignment: + .word cr_alignment +#endif + .ltorg + +ENTRY(sys_execve) + add r3, sp, #S_OFF + b __sys_execve +ENDPROC(sys_execve) + +ENTRY(sys_clone) + add ip, sp, #S_OFF + stw ip, [sp+], #4 + b __sys_clone +ENDPROC(sys_clone) + +ENTRY(sys_rt_sigreturn) + add r0, sp, #S_OFF + mov why, #0 @ prevent syscall restart handling + b __sys_rt_sigreturn +ENDPROC(sys_rt_sigreturn) + +ENTRY(sys_sigaltstack) + ldw r2, [sp+], #S_OFF + S_SP + b do_sigaltstack +ENDPROC(sys_sigaltstack) + + __INIT + +/* + * Vector stubs. + * + * This code is copied to 0xffff0200 so we can use branches in the + * vectors, rather than ldr's. Note that this code must not + * exceed 0x300 bytes. + * + * Common stub entry macro: + * Enter in INTR mode, bsr = PRIV/USER ASR, lr = PRIV/USER PC + * + * SP points to a minimal amount of processor-private memory, the address + * of which is copied into r0 for the mode specific abort handler. + */ + .macro vector_stub, name, mode + .align 5 + +vector_\name: + @ + @ Save r0, lr_<exception> (parent PC) and bsr_<exception> + @ (parent ASR) + @ + stw r0, [sp] + stw lr, [sp+], #4 @ save r0, lr + mov lr, bsr + stw lr, [sp+], #8 @ save bsr + + @ + @ Prepare for PRIV mode. INTRs remain disabled. + @ + mov r0, asr + xor r0, r0, #(\mode ^ PRIV_MODE) + mov.a bsr, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x03 + add lr, lr, #1 + mov r0, sp + ldw lr, [pc+], lr << #2 + mov.a pc, lr @ branch to handler in PRIV mode +ENDPROC(vector_\name) + .align 2 + @ handler addresses follow this label + .endm + + .globl __stubs_start +__stubs_start: +/* + * Interrupt dispatcher + */ + vector_stub intr, INTR_MODE + + .long __intr_user @ 0 (USER) + .long __invalid @ 1 + .long __invalid @ 2 + .long __intr_priv @ 3 (PRIV) + +/* + * Data abort dispatcher + * Enter in ABT mode, bsr = USER ASR, lr = USER PC + */ + vector_stub dabt, ABRT_MODE + + .long __dabt_user @ 0 (USER) + .long __invalid @ 1 + .long __invalid @ 2 (INTR) + .long __dabt_priv @ 3 (PRIV) + +/* + * Prefetch abort dispatcher + * Enter in ABT mode, bsr = USER ASR, lr = USER PC + */ + vector_stub pabt, ABRT_MODE + + .long __pabt_user @ 0 (USER) + .long __invalid @ 1 + .long __invalid @ 2 (INTR) + .long __pabt_priv @ 3 (PRIV) + +/* + * Undef instr entry dispatcher + * Enter in EXTN mode, bsr = PRIV/USER ASR, lr = PRIV/USER PC + */ + vector_stub extn, EXTN_MODE + + .long __extn_user @ 0 (USER) + .long __invalid @ 1 + .long __invalid @ 2 (INTR) + .long __extn_priv @ 3 (PRIV) + +/* + * We group all the following data together to optimise + * for CPUs with separate I & D caches. + */ + .align 5 + +.LCvswi: + .word vector_swi + + .globl __stubs_end +__stubs_end: + + .equ stubs_offset, __vectors_start + 0x200 - __stubs_start + + .globl __vectors_start +__vectors_start: + jepriv SYS_ERROR0 + b vector_extn + stubs_offset + ldw pc, .LCvswi + stubs_offset + b vector_pabt + stubs_offset + b vector_dabt + stubs_offset + jepriv SYS_ERROR0 + b vector_intr + stubs_offset + jepriv SYS_ERROR0 + + .globl __vectors_end +__vectors_end: + + .data + + .globl cr_alignment + .globl cr_no_alignment +cr_alignment: + .space 4 +cr_no_alignment: + .space 4 diff --git a/arch/unicore32/kernel/fpu-ucf64.c b/arch/unicore32/kernel/fpu-ucf64.c new file mode 100644 index 00000000000..282a60ac82b --- /dev/null +++ b/arch/unicore32/kernel/fpu-ucf64.c @@ -0,0 +1,126 @@ +/* + * linux/arch/unicore32/kernel/fpu-ucf64.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/init.h> + +#include <asm/fpu-ucf64.h> + +/* + * A special flag to tell the normalisation code not to normalise. + */ +#define F64_NAN_FLAG 0x100 + +/* + * A bit pattern used to indicate the initial (unset) value of the + * exception mask, in case nothing handles an instruction. This + * doesn't include the NAN flag, which get masked out before + * we check for an error. + */ +#define F64_EXCEPTION_ERROR ((u32)-1 & ~F64_NAN_FLAG) + +/* + * Since we aren't building with -mfpu=f64, we need to code + * these instructions using their MRC/MCR equivalents. + */ +#define f64reg(_f64_) #_f64_ + +#define cff(_f64_) ({ \ + u32 __v; \ + asm("cff %0, " f64reg(_f64_) "@ fmrx %0, " #_f64_ \ + : "=r" (__v) : : "cc"); \ + __v; \ + }) + +#define ctf(_f64_, _var_) \ + asm("ctf %0, " f64reg(_f64_) "@ fmxr " #_f64_ ", %0" \ + : : "r" (_var_) : "cc") + +/* + * Raise a SIGFPE for the current process. + * sicode describes the signal being raised. + */ +void ucf64_raise_sigfpe(unsigned int sicode, struct pt_regs *regs) +{ + siginfo_t info; + + memset(&info, 0, sizeof(info)); + + info.si_signo = SIGFPE; + info.si_code = sicode; + info.si_addr = (void __user *)(instruction_pointer(regs) - 4); + + /* + * This is the same as NWFPE, because it's not clear what + * this is used for + */ + current->thread.error_code = 0; + current->thread.trap_no = 6; + + send_sig_info(SIGFPE, &info, current); +} + +/* + * Handle exceptions of UniCore-F64. + */ +void ucf64_exchandler(u32 inst, u32 fpexc, struct pt_regs *regs) +{ + u32 tmp = fpexc; + u32 exc = F64_EXCEPTION_ERROR & fpexc; + + pr_debug("UniCore-F64: instruction %08x fpscr %08x\n", + inst, fpexc); + + if (exc & FPSCR_CMPINSTR_BIT) { + if (exc & FPSCR_CON) + tmp |= FPSCR_CON; + else + tmp &= ~(FPSCR_CON); + exc &= ~(FPSCR_CMPINSTR_BIT | FPSCR_CON); + } else { + pr_debug(KERN_ERR "UniCore-F64 Error: unhandled exceptions\n"); + pr_debug(KERN_ERR "UniCore-F64 FPSCR 0x%08x INST 0x%08x\n", + cff(FPSCR), inst); + + ucf64_raise_sigfpe(0, regs); + return; + } + + /* + * Update the FPSCR with the additional exception flags. + * Comparison instructions always return at least one of + * these flags set. + */ + tmp &= ~(FPSCR_TRAP | FPSCR_IOS | FPSCR_OFS | FPSCR_UFS | + FPSCR_IXS | FPSCR_HIS | FPSCR_IOC | FPSCR_OFC | + FPSCR_UFC | FPSCR_IXC | FPSCR_HIC); + + tmp |= exc; + ctf(FPSCR, tmp); +} + +/* + * F64 support code initialisation. + */ +static int __init ucf64_init(void) +{ + ctf(FPSCR, 0x0); /* FPSCR_UFE | FPSCR_NDE perhaps better */ + + printk(KERN_INFO "Enable UniCore-F64 support.\n"); + + return 0; +} + +late_initcall(ucf64_init); diff --git a/arch/unicore32/kernel/gpio.c b/arch/unicore32/kernel/gpio.c new file mode 100644 index 00000000000..cb12ec39552 --- /dev/null +++ b/arch/unicore32/kernel/gpio.c @@ -0,0 +1,122 @@ +/* + * linux/arch/unicore32/kernel/gpio.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * 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. + */ +/* in FPGA, no GPIO support */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <mach/hardware.h> + +#ifdef CONFIG_LEDS +#include <linux/leds.h> +#include <linux/platform_device.h> + +static const struct gpio_led puv3_gpio_leds[] = { + { .name = "cpuhealth", .gpio = GPO_CPU_HEALTH, .active_low = 0, + .default_trigger = "heartbeat", }, + { .name = "hdd_led", .gpio = GPO_HDD_LED, .active_low = 1, + .default_trigger = "ide-disk", }, +}; + +static const struct gpio_led_platform_data puv3_gpio_led_data = { + .num_leds = ARRAY_SIZE(puv3_gpio_leds), + .leds = (void *) puv3_gpio_leds, +}; + +static struct platform_device puv3_gpio_gpio_leds = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = (void *) &puv3_gpio_led_data, + } +}; + +static int __init puv3_gpio_leds_init(void) +{ + platform_device_register(&puv3_gpio_gpio_leds); + return 0; +} + +device_initcall(puv3_gpio_leds_init); +#endif + +static int puv3_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + return readl(GPIO_GPLR) & GPIO_GPIO(offset); +} + +static void puv3_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (value) + writel(GPIO_GPIO(offset), GPIO_GPSR); + else + writel(GPIO_GPIO(offset), GPIO_GPCR); +} + +static int puv3_direction_input(struct gpio_chip *chip, unsigned offset) +{ + unsigned long flags; + + local_irq_save(flags); + writel(readl(GPIO_GPDR) & ~GPIO_GPIO(offset), GPIO_GPDR); + local_irq_restore(flags); + return 0; +} + +static int puv3_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + unsigned long flags; + + local_irq_save(flags); + puv3_gpio_set(chip, offset, value); + writel(readl(GPIO_GPDR) | GPIO_GPIO(offset), GPIO_GPDR); + local_irq_restore(flags); + return 0; +} + +static struct gpio_chip puv3_gpio_chip = { + .label = "gpio", + .direction_input = puv3_direction_input, + .direction_output = puv3_direction_output, + .set = puv3_gpio_set, + .get = puv3_gpio_get, + .base = 0, + .ngpio = GPIO_MAX + 1, +}; + +void __init puv3_init_gpio(void) +{ + writel(GPIO_DIR, GPIO_GPDR); +#if defined(CONFIG_PUV3_NB0916) || defined(CONFIG_PUV3_SMW0919) \ + || defined(CONFIG_PUV3_DB0913) + gpio_set_value(GPO_WIFI_EN, 1); + gpio_set_value(GPO_HDD_LED, 1); + gpio_set_value(GPO_VGA_EN, 1); + gpio_set_value(GPO_LCD_EN, 1); + gpio_set_value(GPO_CAM_PWR_EN, 0); + gpio_set_value(GPO_LCD_VCC_EN, 1); + gpio_set_value(GPO_SOFT_OFF, 1); + gpio_set_value(GPO_BT_EN, 1); + gpio_set_value(GPO_FAN_ON, 0); + gpio_set_value(GPO_SPKR, 0); + gpio_set_value(GPO_CPU_HEALTH, 1); + gpio_set_value(GPO_LAN_SEL, 1); +/* + * DO NOT modify the GPO_SET_V1 and GPO_SET_V2 in kernel + * gpio_set_value(GPO_SET_V1, 1); + * gpio_set_value(GPO_SET_V2, 1); + */ +#endif + gpiochip_add(&puv3_gpio_chip); +} diff --git a/arch/unicore32/kernel/head.S b/arch/unicore32/kernel/head.S new file mode 100644 index 00000000000..92255f3ab6a --- /dev/null +++ b/arch/unicore32/kernel/head.S @@ -0,0 +1,252 @@ +/* + * linux/arch/unicore32/kernel/head.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/linkage.h> +#include <linux/init.h> + +#include <asm/assembler.h> +#include <asm/ptrace.h> +#include <generated/asm-offsets.h> +#include <asm/memory.h> +#include <asm/thread_info.h> +#include <asm/system.h> +#include <asm/pgtable-hwdef.h> + +#if (PHYS_OFFSET & 0x003fffff) +#error "PHYS_OFFSET must be at an even 4MiB boundary!" +#endif + +#define KERNEL_RAM_VADDR (PAGE_OFFSET + KERNEL_IMAGE_START) +#define KERNEL_RAM_PADDR (PHYS_OFFSET + KERNEL_IMAGE_START) + +#define KERNEL_PGD_PADDR (KERNEL_RAM_PADDR - 0x1000) +#define KERNEL_PGD_VADDR (KERNEL_RAM_VADDR - 0x1000) + +#define KERNEL_START KERNEL_RAM_VADDR +#define KERNEL_END _end + +/* + * swapper_pg_dir is the virtual address of the initial page table. + * We place the page tables 4K below KERNEL_RAM_VADDR. Therefore, we must + * make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect + * the least significant 16 bits to be 0x8000, but we could probably + * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x1000. + */ +#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000 +#error KERNEL_RAM_VADDR must start at 0xXXXX8000 +#endif + + .globl swapper_pg_dir + .equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x1000 + +/* + * Kernel startup entry point. + * --------------------------- + * + * This is normally called from the decompressor code. The requirements + * are: MMU = off, D-cache = off, I-cache = dont care + * + * This code is mostly position independent, so if you link the kernel at + * 0xc0008000, you call this at __pa(0xc0008000). + */ + __HEAD +ENTRY(stext) + @ set asr + mov r0, #PRIV_MODE @ ensure priv mode + or r0, #PSR_R_BIT | PSR_I_BIT @ disable irqs + mov.a asr, r0 + + @ process identify + movc r0, p0.c0, #0 @ cpuid + movl r1, 0xff00ffff @ mask + movl r2, 0x4d000863 @ value + and r0, r1, r0 + cxor.a r0, r2 + bne __error_p @ invalid processor id + + /* + * Clear the 4K level 1 swapper page table + */ + movl r0, #KERNEL_PGD_PADDR @ page table address + mov r1, #0 + add r2, r0, #0x1000 +101: stw.w r1, [r0]+, #4 + stw.w r1, [r0]+, #4 + stw.w r1, [r0]+, #4 + stw.w r1, [r0]+, #4 + cxor.a r0, r2 + bne 101b + + movl r4, #KERNEL_PGD_PADDR @ page table address + mov r7, #PMD_TYPE_SECT | PMD_PRESENT @ page size: section + or r7, r7, #PMD_SECT_CACHEABLE @ cacheable + or r7, r7, #PMD_SECT_READ | PMD_SECT_WRITE | PMD_SECT_EXEC + + /* + * Create identity mapping for first 4MB of kernel to + * cater for the MMU enable. This identity mapping + * will be removed by paging_init(). We use our current program + * counter to determine corresponding section base address. + */ + mov r6, pc + mov r6, r6 >> #22 @ start of kernel section + or r1, r7, r6 << #22 @ flags + kernel base + stw r1, [r4+], r6 << #2 @ identity mapping + + /* + * Now setup the pagetables for our kernel direct + * mapped region. + */ + add r0, r4, #(KERNEL_START & 0xff000000) >> 20 + stw.w r1, [r0+], #(KERNEL_START & 0x00c00000) >> 20 + movl r6, #(KERNEL_END - 1) + add r0, r0, #4 + add r6, r4, r6 >> #20 +102: csub.a r0, r6 + add r1, r1, #1 << 22 + bua 103f + stw.w r1, [r0]+, #4 + b 102b +103: + /* + * Then map first 4MB of ram in case it contains our boot params. + */ + add r0, r4, #PAGE_OFFSET >> 20 + or r6, r7, #(PHYS_OFFSET & 0xffc00000) + stw r6, [r0] + + ldw r15, __switch_data @ address to jump to after + + /* + * Initialise TLB, Caches, and MMU state ready to switch the MMU + * on. + */ + mov r0, #0 + movc p0.c5, r0, #28 @ cache invalidate all + nop8 + movc p0.c6, r0, #6 @ TLB invalidate all + nop8 + + /* + * ..V. .... ..TB IDAM + * ..1. .... ..01 1111 + */ + movl r0, #0x201f @ control register setting + + /* + * Setup common bits before finally enabling the MMU. Essentially + * this is just loading the page table pointer and domain access + * registers. + */ + #ifndef CONFIG_ALIGNMENT_TRAP + andn r0, r0, #CR_A + #endif + #ifdef CONFIG_CPU_DCACHE_DISABLE + andn r0, r0, #CR_D + #endif + #ifdef CONFIG_CPU_DCACHE_WRITETHROUGH + andn r0, r0, #CR_B + #endif + #ifdef CONFIG_CPU_ICACHE_DISABLE + andn r0, r0, #CR_I + #endif + + movc p0.c2, r4, #0 @ set pgd + b __turn_mmu_on +ENDPROC(stext) + +/* + * Enable the MMU. This completely changes the stucture of the visible + * memory space. You will not be able to trace execution through this. + * + * r0 = cp#0 control register + * r15 = *virtual* address to jump to upon completion + */ + .align 5 +__turn_mmu_on: + mov r0, r0 + movc p0.c1, r0, #0 @ write control reg + nop @ fetch inst by phys addr + mov pc, r15 + nop8 @ fetch inst by phys addr +ENDPROC(__turn_mmu_on) + +/* + * Setup the initial page tables. We only setup the barest + * amount which are required to get the kernel running, which + * generally means mapping in the kernel code. + * + * r9 = cpuid + * r10 = procinfo + * + * Returns: + * r0, r3, r6, r7 corrupted + * r4 = physical page table address + */ + .ltorg + + .align 2 + .type __switch_data, %object +__switch_data: + .long __mmap_switched + .long __bss_start @ r6 + .long _end @ r7 + .long cr_alignment @ r8 + .long init_thread_union + THREAD_START_SP @ sp + +/* + * The following fragment of code is executed with the MMU on in MMU mode, + * and uses absolute addresses; this is not position independent. + * + * r0 = cp#0 control register + */ +__mmap_switched: + adr r3, __switch_data + 4 + + ldm.w (r6, r7, r8), [r3]+ + ldw sp, [r3] + + mov fp, #0 @ Clear BSS (and zero fp) +203: csub.a r6, r7 + bea 204f + stw.w fp, [r6]+,#4 + b 203b +204: + andn r1, r0, #CR_A @ Clear 'A' bit + stm (r0, r1), [r8]+ @ Save control register values + b start_kernel +ENDPROC(__mmap_switched) + +/* + * Exception handling. Something went wrong and we can't proceed. We + * ought to tell the user, but since we don't have any guarantee that + * we're even running on the right architecture, we do virtually nothing. + * + * If CONFIG_DEBUG_LL is set we try to print out something about the error + * and hope for the best (useful if bootloader fails to pass a proper + * machine ID for example). + */ +__error_p: +#ifdef CONFIG_DEBUG_LL + adr r0, str_p1 + b.l printascii + mov r0, r9 + b.l printhex8 + adr r0, str_p2 + b.l printascii +901: nop8 + b 901b +str_p1: .asciz "\nError: unrecognized processor variant (0x" +str_p2: .asciz ").\n" + .align +#endif +ENDPROC(__error_p) + diff --git a/arch/unicore32/kernel/hibernate.c b/arch/unicore32/kernel/hibernate.c new file mode 100644 index 00000000000..7d0f0b7983a --- /dev/null +++ b/arch/unicore32/kernel/hibernate.c @@ -0,0 +1,160 @@ +/* + * linux/arch/unicore32/kernel/hibernate.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/gfp.h> +#include <linux/suspend.h> +#include <linux/bootmem.h> + +#include <asm/system.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/pgalloc.h> +#include <asm/suspend.h> + +#include "mach/pm.h" + +/* Pointer to the temporary resume page tables */ +pgd_t *resume_pg_dir; + +struct swsusp_arch_regs swsusp_arch_regs_cpu0; + +/* + * Create a middle page table on a resume-safe page and put a pointer to it in + * the given global directory entry. This only returns the gd entry + * in non-PAE compilation mode, since the middle layer is folded. + */ +static pmd_t *resume_one_md_table_init(pgd_t *pgd) +{ + pud_t *pud; + pmd_t *pmd_table; + + pud = pud_offset(pgd, 0); + pmd_table = pmd_offset(pud, 0); + + return pmd_table; +} + +/* + * Create a page table on a resume-safe page and place a pointer to it in + * a middle page directory entry. + */ +static pte_t *resume_one_page_table_init(pmd_t *pmd) +{ + if (pmd_none(*pmd)) { + pte_t *page_table = (pte_t *)get_safe_page(GFP_ATOMIC); + if (!page_table) + return NULL; + + set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_KERNEL_TABLE)); + + BUG_ON(page_table != pte_offset_kernel(pmd, 0)); + + return page_table; + } + + return pte_offset_kernel(pmd, 0); +} + +/* + * This maps the physical memory to kernel virtual address space, a total + * of max_low_pfn pages, by creating page tables starting from address + * PAGE_OFFSET. The page tables are allocated out of resume-safe pages. + */ +static int resume_physical_mapping_init(pgd_t *pgd_base) +{ + unsigned long pfn; + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + int pgd_idx, pmd_idx; + + pgd_idx = pgd_index(PAGE_OFFSET); + pgd = pgd_base + pgd_idx; + pfn = 0; + + for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) { + pmd = resume_one_md_table_init(pgd); + if (!pmd) + return -ENOMEM; + + if (pfn >= max_low_pfn) + continue; + + for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD; pmd++, pmd_idx++) { + pte_t *max_pte; + + if (pfn >= max_low_pfn) + break; + + /* Map with normal page tables. + * NOTE: We can mark everything as executable here + */ + pte = resume_one_page_table_init(pmd); + if (!pte) + return -ENOMEM; + + max_pte = pte + PTRS_PER_PTE; + for (; pte < max_pte; pte++, pfn++) { + if (pfn >= max_low_pfn) + break; + + set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC)); + } + } + } + + return 0; +} + +static inline void resume_init_first_level_page_table(pgd_t *pg_dir) +{ +} + +int swsusp_arch_resume(void) +{ + int error; + + resume_pg_dir = (pgd_t *)get_safe_page(GFP_ATOMIC); + if (!resume_pg_dir) + return -ENOMEM; + + resume_init_first_level_page_table(resume_pg_dir); + error = resume_physical_mapping_init(resume_pg_dir); + if (error) + return error; + + /* We have got enough memory and from now on we cannot recover */ + restore_image(resume_pg_dir, restore_pblist); + return 0; +} + +/* + * pfn_is_nosave - check if given pfn is in the 'nosave' section + */ + +int pfn_is_nosave(unsigned long pfn) +{ + unsigned long begin_pfn = __pa(&__nosave_begin) >> PAGE_SHIFT; + unsigned long end_pfn = PAGE_ALIGN(__pa(&__nosave_end)) >> PAGE_SHIFT; + + return (pfn >= begin_pfn) && (pfn < end_pfn); +} + +void save_processor_state(void) +{ +} + +void restore_processor_state(void) +{ + local_flush_tlb_all(); +} diff --git a/arch/unicore32/kernel/hibernate_asm.S b/arch/unicore32/kernel/hibernate_asm.S new file mode 100644 index 00000000000..cc3c65253c8 --- /dev/null +++ b/arch/unicore32/kernel/hibernate_asm.S @@ -0,0 +1,117 @@ +/* + * linux/arch/unicore32/kernel/hibernate_asm.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/sys.h> +#include <linux/errno.h> +#include <linux/linkage.h> +#include <generated/asm-offsets.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/assembler.h> + +@ restore_image(pgd_t *resume_pg_dir, struct pbe *restore_pblist) +@ r0: resume_pg_dir +@ r1: restore_pblist +@ copy restore_pblist pages +@ restore registers from swsusp_arch_regs_cpu0 +@ +ENTRY(restore_image) + sub r0, r0, #PAGE_OFFSET + mov r5, #0 + movc p0.c6, r5, #6 @invalidate ITLB & DTLB + movc p0.c2, r0, #0 + nop + nop + nop + nop + nop + nop + nop + + .p2align 4,,7 +101: + csub.a r1, #0 + beq 109f + + ldw r6, [r1+], #PBE_ADDRESS + ldw r7, [r1+], #PBE_ORIN_ADDRESS + + movl ip, #128 +102: ldm.w (r8 - r15), [r6]+ + stm.w (r8 - r15), [r7]+ + sub.a ip, ip, #1 + bne 102b + + ldw r1, [r1+], #PBE_NEXT + b 101b + + .p2align 4,,7 +109: + /* go back to the original page tables */ + ldw r0, =swapper_pg_dir + sub r0, r0, #PAGE_OFFSET + mov r5, #0 + movc p0.c6, r5, #6 + movc p0.c2, r0, #0 + nop + nop + nop + nop + nop + nop + nop + +#ifdef CONFIG_UNICORE_FPU_F64 + ldw ip, 1f + add ip, ip, #SWSUSP_FPSTATE + lfm.w (f0 - f7 ), [ip]+ + lfm.w (f8 - f15), [ip]+ + lfm.w (f16 - f23), [ip]+ + lfm.w (f24 - f31), [ip]+ + ldw r4, [ip] + ctf r4, s31 +#endif + mov r0, #0x0 + ldw ip, 1f + add ip, ip, #SWSUSP_CPU + ldm.w (r4 - r15), [ip]+ + ldm (r16 - r27, sp, pc), [ip]+ @ Load all regs saved previously + + .align 2 +1: .long swsusp_arch_regs_cpu0 + + +@ swsusp_arch_suspend() +@ - prepare pc for resume, return from function without swsusp_save on resume +@ - save registers in swsusp_arch_regs_cpu0 +@ - call swsusp_save write suspend image + +ENTRY(swsusp_arch_suspend) + ldw ip, 1f + add ip, ip, #SWSUSP_CPU + stm.w (r4 - r15), [ip]+ + stm.w (r16 - r27, sp, lr), [ip]+ + +#ifdef CONFIG_UNICORE_FPU_F64 + ldw ip, 1f + add ip, ip, #SWSUSP_FPSTATE + sfm.w (f0 - f7 ), [ip]+ + sfm.w (f8 - f15), [ip]+ + sfm.w (f16 - f23), [ip]+ + sfm.w (f24 - f31), [ip]+ + cff r4, s31 + stw r4, [ip] +#endif + b swsusp_save @ no return + +1: .long swsusp_arch_regs_cpu0 diff --git a/arch/unicore32/kernel/init_task.c b/arch/unicore32/kernel/init_task.c new file mode 100644 index 00000000000..a35a1e50e4f --- /dev/null +++ b/arch/unicore32/kernel/init_task.c @@ -0,0 +1,44 @@ +/* + * linux/arch/unicore32/kernel/init_task.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/init_task.h> +#include <linux/mqueue.h> +#include <linux/uaccess.h> + +#include <asm/pgtable.h> + +static struct signal_struct init_signals = INIT_SIGNALS(init_signals); +static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); +/* + * Initial thread structure. + * + * We need to make sure that this is 8192-byte aligned due to the + * way process stacks are handled. This is done by making sure + * the linker maps this in the .text segment right after head.S, + * and making head.S ensure the proper alignment. + * + * The things we do for performance.. + */ +union thread_union init_thread_union __init_task_data = { + INIT_THREAD_INFO(init_task) }; + +/* + * Initial task structure. + * + * All other task structs will be allocated on slabs in fork.c + */ +struct task_struct init_task = INIT_TASK(init_task); +EXPORT_SYMBOL(init_task); diff --git a/arch/unicore32/kernel/irq.c b/arch/unicore32/kernel/irq.c new file mode 100644 index 00000000000..b23624cf306 --- /dev/null +++ b/arch/unicore32/kernel/irq.c @@ -0,0 +1,426 @@ +/* + * linux/arch/unicore32/kernel/irq.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel_stat.h> +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/random.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/kallsyms.h> +#include <linux/proc_fs.h> +#include <linux/sysdev.h> +#include <linux/gpio.h> + +#include <asm/system.h> +#include <mach/hardware.h> + +#include "setup.h" + +/* + * PKUnity GPIO edge detection for IRQs: + * IRQs are generated on Falling-Edge, Rising-Edge, or both. + * Use this instead of directly setting GRER/GFER. + */ +static int GPIO_IRQ_rising_edge; +static int GPIO_IRQ_falling_edge; +static int GPIO_IRQ_mask = 0; + +#define GPIO_MASK(irq) (1 << (irq - IRQ_GPIO0)) + +static int puv3_gpio_type(struct irq_data *d, unsigned int type) +{ + unsigned int mask; + + if (d->irq < IRQ_GPIOHIGH) + mask = 1 << d->irq; + else + mask = GPIO_MASK(d->irq); + + if (type == IRQ_TYPE_PROBE) { + if ((GPIO_IRQ_rising_edge | GPIO_IRQ_falling_edge) & mask) + return 0; + type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + } + + if (type & IRQ_TYPE_EDGE_RISING) + GPIO_IRQ_rising_edge |= mask; + else + GPIO_IRQ_rising_edge &= ~mask; + if (type & IRQ_TYPE_EDGE_FALLING) + GPIO_IRQ_falling_edge |= mask; + else + GPIO_IRQ_falling_edge &= ~mask; + + writel(GPIO_IRQ_rising_edge & GPIO_IRQ_mask, GPIO_GRER); + writel(GPIO_IRQ_falling_edge & GPIO_IRQ_mask, GPIO_GFER); + + return 0; +} + +/* + * GPIO IRQs must be acknowledged. This is for IRQs from 0 to 7. + */ +static void puv3_low_gpio_ack(struct irq_data *d) +{ + writel((1 << d->irq), GPIO_GEDR); +} + +static void puv3_low_gpio_mask(struct irq_data *d) +{ + writel(readl(INTC_ICMR) & ~(1 << d->irq), INTC_ICMR); +} + +static void puv3_low_gpio_unmask(struct irq_data *d) +{ + writel(readl(INTC_ICMR) | (1 << d->irq), INTC_ICMR); +} + +static int puv3_low_gpio_wake(struct irq_data *d, unsigned int on) +{ + if (on) + writel(readl(PM_PWER) | (1 << d->irq), PM_PWER); + else + writel(readl(PM_PWER) & ~(1 << d->irq), PM_PWER); + return 0; +} + +static struct irq_chip puv3_low_gpio_chip = { + .name = "GPIO-low", + .irq_ack = puv3_low_gpio_ack, + .irq_mask = puv3_low_gpio_mask, + .irq_unmask = puv3_low_gpio_unmask, + .irq_set_type = puv3_gpio_type, + .irq_set_wake = puv3_low_gpio_wake, +}; + +/* + * IRQ8 (GPIO0 through 27) handler. We enter here with the + * irq_controller_lock held, and IRQs disabled. Decode the IRQ + * and call the handler. + */ +static void +puv3_gpio_handler(unsigned int irq, struct irq_desc *desc) +{ + unsigned int mask; + + mask = readl(GPIO_GEDR); + do { + /* + * clear down all currently active IRQ sources. + * We will be processing them all. + */ + writel(mask, GPIO_GEDR); + + irq = IRQ_GPIO0; + do { + if (mask & 1) + generic_handle_irq(irq); + mask >>= 1; + irq++; + } while (mask); + mask = readl(GPIO_GEDR); + } while (mask); +} + +/* + * GPIO0-27 edge IRQs need to be handled specially. + * In addition, the IRQs are all collected up into one bit in the + * interrupt controller registers. + */ +static void puv3_high_gpio_ack(struct irq_data *d) +{ + unsigned int mask = GPIO_MASK(d->irq); + + writel(mask, GPIO_GEDR); +} + +static void puv3_high_gpio_mask(struct irq_data *d) +{ + unsigned int mask = GPIO_MASK(d->irq); + + GPIO_IRQ_mask &= ~mask; + + writel(readl(GPIO_GRER) & ~mask, GPIO_GRER); + writel(readl(GPIO_GFER) & ~mask, GPIO_GFER); +} + +static void puv3_high_gpio_unmask(struct irq_data *d) +{ + unsigned int mask = GPIO_MASK(d->irq); + + GPIO_IRQ_mask |= mask; + + writel(GPIO_IRQ_rising_edge & GPIO_IRQ_mask, GPIO_GRER); + writel(GPIO_IRQ_falling_edge & GPIO_IRQ_mask, GPIO_GFER); +} + +static int puv3_high_gpio_wake(struct irq_data *d, unsigned int on) +{ + if (on) + writel(readl(PM_PWER) | PM_PWER_GPIOHIGH, PM_PWER); + else + writel(readl(PM_PWER) & ~PM_PWER_GPIOHIGH, PM_PWER); + return 0; +} + +static struct irq_chip puv3_high_gpio_chip = { + .name = "GPIO-high", + .irq_ack = puv3_high_gpio_ack, + .irq_mask = puv3_high_gpio_mask, + .irq_unmask = puv3_high_gpio_unmask, + .irq_set_type = puv3_gpio_type, + .irq_set_wake = puv3_high_gpio_wake, +}; + +/* + * We don't need to ACK IRQs on the PKUnity unless they're GPIOs + * this is for internal IRQs i.e. from 8 to 31. + */ +static void puv3_mask_irq(struct irq_data *d) +{ + writel(readl(INTC_ICMR) & ~(1 << d->irq), INTC_ICMR); +} + +static void puv3_unmask_irq(struct irq_data *d) +{ + writel(readl(INTC_ICMR) | (1 << d->irq), INTC_ICMR); +} + +/* + * Apart form GPIOs, only the RTC alarm can be a wakeup event. + */ +static int puv3_set_wake(struct irq_data *d, unsigned int on) +{ + if (d->irq == IRQ_RTCAlarm) { + if (on) + writel(readl(PM_PWER) | PM_PWER_RTC, PM_PWER); + else + writel(readl(PM_PWER) & ~PM_PWER_RTC, PM_PWER); + return 0; + } + return -EINVAL; +} + +static struct irq_chip puv3_normal_chip = { + .name = "PKUnity-v3", + .irq_ack = puv3_mask_irq, + .irq_mask = puv3_mask_irq, + .irq_unmask = puv3_unmask_irq, + .irq_set_wake = puv3_set_wake, +}; + +static struct resource irq_resource = { + .name = "irqs", + .start = io_v2p(PKUNITY_INTC_BASE), + .end = io_v2p(PKUNITY_INTC_BASE) + 0xFFFFF, +}; + +static struct puv3_irq_state { + unsigned int saved; + unsigned int icmr; + unsigned int iclr; + unsigned int iccr; +} puv3_irq_state; + +static int puv3_irq_suspend(struct sys_device *dev, pm_message_t state) +{ + struct puv3_irq_state *st = &puv3_irq_state; + + st->saved = 1; + st->icmr = readl(INTC_ICMR); + st->iclr = readl(INTC_ICLR); + st->iccr = readl(INTC_ICCR); + + /* + * Disable all GPIO-based interrupts. + */ + writel(readl(INTC_ICMR) & ~(0x1ff), INTC_ICMR); + + /* + * Set the appropriate edges for wakeup. + */ + writel(readl(PM_PWER) & GPIO_IRQ_rising_edge, GPIO_GRER); + writel(readl(PM_PWER) & GPIO_IRQ_falling_edge, GPIO_GFER); + + /* + * Clear any pending GPIO interrupts. + */ + writel(readl(GPIO_GEDR), GPIO_GEDR); + + return 0; +} + +static int puv3_irq_resume(struct sys_device *dev) +{ + struct puv3_irq_state *st = &puv3_irq_state; + + if (st->saved) { + writel(st->iccr, INTC_ICCR); + writel(st->iclr, INTC_ICLR); + + writel(GPIO_IRQ_rising_edge & GPIO_IRQ_mask, GPIO_GRER); + writel(GPIO_IRQ_falling_edge & GPIO_IRQ_mask, GPIO_GFER); + + writel(st->icmr, INTC_ICMR); + } + return 0; +} + +static struct sysdev_class puv3_irq_sysclass = { + .name = "pkunity-irq", + .suspend = puv3_irq_suspend, + .resume = puv3_irq_resume, +}; + +static struct sys_device puv3_irq_device = { + .id = 0, + .cls = &puv3_irq_sysclass, +}; + +static int __init puv3_irq_init_devicefs(void) +{ + sysdev_class_register(&puv3_irq_sysclass); + return sysdev_register(&puv3_irq_device); +} + +device_initcall(puv3_irq_init_devicefs); + +void __init init_IRQ(void) +{ + unsigned int irq; + + request_resource(&iomem_resource, &irq_resource); + + /* disable all IRQs */ + writel(0, INTC_ICMR); + + /* all IRQs are IRQ, not REAL */ + writel(0, INTC_ICLR); + + /* clear all GPIO edge detects */ + writel(FMASK(8, 0) & ~FIELD(1, 1, GPI_SOFF_REQ), GPIO_GPIR); + writel(0, GPIO_GFER); + writel(0, GPIO_GRER); + writel(0x0FFFFFFF, GPIO_GEDR); + + writel(1, INTC_ICCR); + + for (irq = 0; irq < IRQ_GPIOHIGH; irq++) { + set_irq_chip(irq, &puv3_low_gpio_chip); + set_irq_handler(irq, handle_edge_irq); + irq_modify_status(irq, + IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, + 0); + } + + for (irq = IRQ_GPIOHIGH + 1; irq < IRQ_GPIO0; irq++) { + set_irq_chip(irq, &puv3_normal_chip); + set_irq_handler(irq, handle_level_irq); + irq_modify_status(irq, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + + for (irq = IRQ_GPIO0; irq <= IRQ_GPIO27; irq++) { + set_irq_chip(irq, &puv3_high_gpio_chip); + set_irq_handler(irq, handle_edge_irq); + irq_modify_status(irq, + IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, + 0); + } + + /* + * Install handler for GPIO 0-27 edge detect interrupts + */ + set_irq_chip(IRQ_GPIOHIGH, &puv3_normal_chip); + set_irq_chained_handler(IRQ_GPIOHIGH, puv3_gpio_handler); + +#ifdef CONFIG_PUV3_GPIO + puv3_init_gpio(); +#endif +} + +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v, cpu; + struct irq_desc *desc; + struct irqaction *action; + unsigned long flags; + + if (i == 0) { + char cpuname[12]; + + seq_printf(p, " "); + for_each_present_cpu(cpu) { + sprintf(cpuname, "CPU%d", cpu); + seq_printf(p, " %10s", cpuname); + } + seq_putc(p, '\n'); + } + + if (i < nr_irqs) { + desc = irq_to_desc(i); + raw_spin_lock_irqsave(&desc->lock, flags); + action = desc->action; + if (!action) + goto unlock; + + seq_printf(p, "%3d: ", i); + for_each_present_cpu(cpu) + seq_printf(p, "%10u ", kstat_irqs_cpu(i, cpu)); + seq_printf(p, " %10s", desc->irq_data.chip->name ? : "-"); + seq_printf(p, " %s", action->name); + for (action = action->next; action; action = action->next) + seq_printf(p, ", %s", action->name); + + seq_putc(p, '\n'); +unlock: + raw_spin_unlock_irqrestore(&desc->lock, flags); + } else if (i == nr_irqs) { + seq_printf(p, "Error in interrupt!\n"); + } + return 0; +} + +/* + * do_IRQ handles all hardware IRQ's. Decoded IRQs should not + * come via this function. Instead, they should provide their + * own 'handler' + */ +asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + irq_enter(); + + /* + * Some hardware gives randomly wrong interrupts. Rather + * than crashing, do something sensible. + */ + if (unlikely(irq >= nr_irqs)) { + if (printk_ratelimit()) + printk(KERN_WARNING "Bad IRQ%u\n", irq); + ack_bad_irq(irq); + } else { + generic_handle_irq(irq); + } + + irq_exit(); + set_irq_regs(old_regs); +} + diff --git a/arch/unicore32/kernel/ksyms.c b/arch/unicore32/kernel/ksyms.c new file mode 100644 index 00000000000..a8970809428 --- /dev/null +++ b/arch/unicore32/kernel/ksyms.c @@ -0,0 +1,99 @@ +/* + * linux/arch/unicore32/kernel/ksyms.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/cryptohash.h> +#include <linux/delay.h> +#include <linux/in6.h> +#include <linux/syscalls.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <asm/checksum.h> +#include <asm/system.h> + +#include "ksyms.h" + +EXPORT_SYMBOL(__uc32_find_next_zero_bit); +EXPORT_SYMBOL(__uc32_find_next_bit); + +EXPORT_SYMBOL(__backtrace); + + /* platform dependent support */ +EXPORT_SYMBOL(__udelay); +EXPORT_SYMBOL(__const_udelay); + + /* networking */ +EXPORT_SYMBOL(csum_partial); +EXPORT_SYMBOL(csum_partial_copy_from_user); +EXPORT_SYMBOL(csum_partial_copy_nocheck); +EXPORT_SYMBOL(__csum_ipv6_magic); + + /* io */ +#ifndef __raw_readsb +EXPORT_SYMBOL(__raw_readsb); +#endif +#ifndef __raw_readsw +EXPORT_SYMBOL(__raw_readsw); +#endif +#ifndef __raw_readsl +EXPORT_SYMBOL(__raw_readsl); +#endif +#ifndef __raw_writesb +EXPORT_SYMBOL(__raw_writesb); +#endif +#ifndef __raw_writesw +EXPORT_SYMBOL(__raw_writesw); +#endif +#ifndef __raw_writesl +EXPORT_SYMBOL(__raw_writesl); +#endif + + /* string / mem functions */ +EXPORT_SYMBOL(strchr); +EXPORT_SYMBOL(strrchr); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memmove); +EXPORT_SYMBOL(memchr); + + /* user mem (segment) */ +EXPORT_SYMBOL(__strnlen_user); +EXPORT_SYMBOL(__strncpy_from_user); + +EXPORT_SYMBOL(copy_page); + +EXPORT_SYMBOL(__copy_from_user); +EXPORT_SYMBOL(__copy_to_user); +EXPORT_SYMBOL(__clear_user); + +EXPORT_SYMBOL(__get_user_1); +EXPORT_SYMBOL(__get_user_2); +EXPORT_SYMBOL(__get_user_4); + +EXPORT_SYMBOL(__put_user_1); +EXPORT_SYMBOL(__put_user_2); +EXPORT_SYMBOL(__put_user_4); +EXPORT_SYMBOL(__put_user_8); + +EXPORT_SYMBOL(__ashldi3); +EXPORT_SYMBOL(__ashrdi3); +EXPORT_SYMBOL(__divsi3); +EXPORT_SYMBOL(__lshrdi3); +EXPORT_SYMBOL(__modsi3); +EXPORT_SYMBOL(__muldi3); +EXPORT_SYMBOL(__ucmpdi2); +EXPORT_SYMBOL(__udivsi3); +EXPORT_SYMBOL(__umodsi3); +EXPORT_SYMBOL(__bswapsi2); + diff --git a/arch/unicore32/kernel/ksyms.h b/arch/unicore32/kernel/ksyms.h new file mode 100644 index 00000000000..185cdc712d0 --- /dev/null +++ b/arch/unicore32/kernel/ksyms.h @@ -0,0 +1,15 @@ +/* + * libgcc functions - functions that are used internally by the + * compiler... (prototypes are not correct though, but that + * doesn't really matter since they're not versioned). + */ +extern void __ashldi3(void); +extern void __ashrdi3(void); +extern void __divsi3(void); +extern void __lshrdi3(void); +extern void __modsi3(void); +extern void __muldi3(void); +extern void __ucmpdi2(void); +extern void __udivsi3(void); +extern void __umodsi3(void); +extern void __bswapsi2(void); diff --git a/arch/unicore32/kernel/module.c b/arch/unicore32/kernel/module.c new file mode 100644 index 00000000000..3e5a38d71a1 --- /dev/null +++ b/arch/unicore32/kernel/module.c @@ -0,0 +1,152 @@ +/* + * linux/arch/unicore32/kernel/module.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/moduleloader.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/elf.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/gfp.h> + +#include <asm/pgtable.h> +#include <asm/sections.h> + +void *module_alloc(unsigned long size) +{ + struct vm_struct *area; + + size = PAGE_ALIGN(size); + if (!size) + return NULL; + + area = __get_vm_area(size, VM_ALLOC, MODULES_VADDR, MODULES_END); + if (!area) + return NULL; + + return __vmalloc_area(area, GFP_KERNEL, PAGE_KERNEL_EXEC); +} + +void module_free(struct module *module, void *region) +{ + vfree(region); +} + +int module_frob_arch_sections(Elf_Ehdr *hdr, + Elf_Shdr *sechdrs, + char *secstrings, + struct module *mod) +{ + return 0; +} + +int +apply_relocate(Elf32_Shdr *sechdrs, const char *strtab, unsigned int symindex, + unsigned int relindex, struct module *module) +{ + Elf32_Shdr *symsec = sechdrs + symindex; + Elf32_Shdr *relsec = sechdrs + relindex; + Elf32_Shdr *dstsec = sechdrs + relsec->sh_info; + Elf32_Rel *rel = (void *)relsec->sh_addr; + unsigned int i; + + for (i = 0; i < relsec->sh_size / sizeof(Elf32_Rel); i++, rel++) { + unsigned long loc; + Elf32_Sym *sym; + s32 offset; + + offset = ELF32_R_SYM(rel->r_info); + if (offset < 0 || offset > + (symsec->sh_size / sizeof(Elf32_Sym))) { + printk(KERN_ERR "%s: bad relocation, " + "section %d reloc %d\n", + module->name, relindex, i); + return -ENOEXEC; + } + + sym = ((Elf32_Sym *)symsec->sh_addr) + offset; + + if (rel->r_offset < 0 || rel->r_offset > + dstsec->sh_size - sizeof(u32)) { + printk(KERN_ERR "%s: out of bounds relocation, " + "section %d reloc %d offset %d size %d\n", + module->name, relindex, i, rel->r_offset, + dstsec->sh_size); + return -ENOEXEC; + } + + loc = dstsec->sh_addr + rel->r_offset; + + switch (ELF32_R_TYPE(rel->r_info)) { + case R_UNICORE_NONE: + /* ignore */ + break; + + case R_UNICORE_ABS32: + *(u32 *)loc += sym->st_value; + break; + + case R_UNICORE_PC24: + case R_UNICORE_CALL: + case R_UNICORE_JUMP24: + offset = (*(u32 *)loc & 0x00ffffff) << 2; + if (offset & 0x02000000) + offset -= 0x04000000; + + offset += sym->st_value - loc; + if (offset & 3 || + offset <= (s32)0xfe000000 || + offset >= (s32)0x02000000) { + printk(KERN_ERR + "%s: relocation out of range, section " + "%d reloc %d sym '%s'\n", module->name, + relindex, i, strtab + sym->st_name); + return -ENOEXEC; + } + + offset >>= 2; + + *(u32 *)loc &= 0xff000000; + *(u32 *)loc |= offset & 0x00ffffff; + break; + + default: + printk(KERN_ERR "%s: unknown relocation: %u\n", + module->name, ELF32_R_TYPE(rel->r_info)); + return -ENOEXEC; + } + } + return 0; +} + +int +apply_relocate_add(Elf32_Shdr *sechdrs, const char *strtab, + unsigned int symindex, unsigned int relsec, + struct module *module) +{ + printk(KERN_ERR "module %s: ADD RELOCATION unsupported\n", + module->name); + return -ENOEXEC; +} + +int +module_finalize(const Elf32_Ehdr *hdr, const Elf_Shdr *sechdrs, + struct module *module) +{ + return 0; +} + +void +module_arch_cleanup(struct module *mod) +{ +} diff --git a/arch/unicore32/kernel/pci.c b/arch/unicore32/kernel/pci.c new file mode 100644 index 00000000000..100eab842e6 --- /dev/null +++ b/arch/unicore32/kernel/pci.c @@ -0,0 +1,404 @@ +/* + * linux/arch/unicore32/kernel/pci.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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. + * + * PCI bios-type initialisation for PCI machines + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/io.h> + +static int debug_pci; +static int use_firmware; + +#define CONFIG_CMD(bus, devfn, where) \ + (0x80000000 | (bus->number << 16) | (devfn << 8) | (where & ~3)) + +static int +puv3_read_config(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 *value) +{ + writel(CONFIG_CMD(bus, devfn, where), PCICFG_ADDR); + switch (size) { + case 1: + *value = (readl(PCICFG_DATA) >> ((where & 3) * 8)) & 0xFF; + break; + case 2: + *value = (readl(PCICFG_DATA) >> ((where & 2) * 8)) & 0xFFFF; + break; + case 4: + *value = readl(PCICFG_DATA); + break; + } + return PCIBIOS_SUCCESSFUL; +} + +static int +puv3_write_config(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 value) +{ + writel(CONFIG_CMD(bus, devfn, where), PCICFG_ADDR); + switch (size) { + case 1: + writel((readl(PCICFG_DATA) & ~FMASK(8, (where&3)*8)) + | FIELD(value, 8, (where&3)*8), PCICFG_DATA); + break; + case 2: + writel((readl(PCICFG_DATA) & ~FMASK(16, (where&2)*8)) + | FIELD(value, 16, (where&2)*8), PCICFG_DATA); + break; + case 4: + writel(value, PCICFG_DATA); + break; + } + return PCIBIOS_SUCCESSFUL; +} + +struct pci_ops pci_puv3_ops = { + .read = puv3_read_config, + .write = puv3_write_config, +}; + +void pci_puv3_preinit(void) +{ + printk(KERN_DEBUG "PCI: PKUnity PCI Controller Initializing ...\n"); + /* config PCI bridge base */ + writel(io_v2p(PKUNITY_PCIBRI_BASE), PCICFG_BRIBASE); + + writel(0, PCIBRI_AHBCTL0); + writel(io_v2p(PKUNITY_PCIBRI_BASE) | PCIBRI_BARx_MEM, PCIBRI_AHBBAR0); + writel(0xFFFF0000, PCIBRI_AHBAMR0); + writel(0, PCIBRI_AHBTAR0); + + writel(PCIBRI_CTLx_AT, PCIBRI_AHBCTL1); + writel(io_v2p(PKUNITY_PCILIO_BASE) | PCIBRI_BARx_IO, PCIBRI_AHBBAR1); + writel(0xFFFF0000, PCIBRI_AHBAMR1); + writel(0x00000000, PCIBRI_AHBTAR1); + + writel(PCIBRI_CTLx_PREF, PCIBRI_AHBCTL2); + writel(io_v2p(PKUNITY_PCIMEM_BASE) | PCIBRI_BARx_MEM, PCIBRI_AHBBAR2); + writel(0xF8000000, PCIBRI_AHBAMR2); + writel(0, PCIBRI_AHBTAR2); + + writel(io_v2p(PKUNITY_PCIAHB_BASE) | PCIBRI_BARx_MEM, PCIBRI_BAR1); + + writel(PCIBRI_CTLx_AT | PCIBRI_CTLx_PREF, PCIBRI_PCICTL0); + writel(io_v2p(PKUNITY_PCIAHB_BASE) | PCIBRI_BARx_MEM, PCIBRI_PCIBAR0); + writel(0xF8000000, PCIBRI_PCIAMR0); + writel(PKUNITY_SDRAM_BASE, PCIBRI_PCITAR0); + + writel(readl(PCIBRI_CMD) | PCIBRI_CMD_IO | PCIBRI_CMD_MEM, PCIBRI_CMD); +} + +static int __init pci_puv3_map_irq(struct pci_dev *dev, u8 slot, u8 pin) +{ + if (dev->bus->number == 0) { +#ifdef CONFIG_ARCH_FPGA /* 4 pci slots */ + if (dev->devfn == 0x00) + return IRQ_PCIINTA; + else if (dev->devfn == 0x08) + return IRQ_PCIINTB; + else if (dev->devfn == 0x10) + return IRQ_PCIINTC; + else if (dev->devfn == 0x18) + return IRQ_PCIINTD; +#endif +#ifdef CONFIG_PUV3_DB0913 /* 3 pci slots */ + if (dev->devfn == 0x30) + return IRQ_PCIINTB; + else if (dev->devfn == 0x60) + return IRQ_PCIINTC; + else if (dev->devfn == 0x58) + return IRQ_PCIINTD; +#endif +#if defined(CONFIG_PUV3_NB0916) || defined(CONFIG_PUV3_SMW0919) + /* only support 2 pci devices */ + if (dev->devfn == 0x00) + return IRQ_PCIINTC; /* sata */ +#endif + } + return -1; +} + +/* + * Only first 128MB of memory can be accessed via PCI. + * We use GFP_DMA to allocate safe buffers to do map/unmap. + * This is really ugly and we need a better way of specifying + * DMA-capable regions of memory. + */ +void __init puv3_pci_adjust_zones(unsigned long *zone_size, + unsigned long *zhole_size) +{ + unsigned int sz = SZ_128M >> PAGE_SHIFT; + + /* + * Only adjust if > 128M on current system + */ + if (zone_size[0] <= sz) + return; + + zone_size[1] = zone_size[0] - sz; + zone_size[0] = sz; + zhole_size[1] = zhole_size[0]; + zhole_size[0] = 0; +} + +void __devinit pcibios_update_irq(struct pci_dev *dev, int irq) +{ + if (debug_pci) + printk(KERN_DEBUG "PCI: Assigning IRQ %02d to %s\n", + irq, pci_name(dev)); + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); +} + +/* + * If the bus contains any of these devices, then we must not turn on + * parity checking of any kind. + */ +static inline int pdev_bad_for_parity(struct pci_dev *dev) +{ + return 0; +} + +/* + * pcibios_fixup_bus - Called after each bus is probed, + * but before its children are examined. + */ +void __devinit pcibios_fixup_bus(struct pci_bus *bus) +{ + struct pci_dev *dev; + u16 features = PCI_COMMAND_SERR + | PCI_COMMAND_PARITY + | PCI_COMMAND_FAST_BACK; + + bus->resource[0] = &ioport_resource; + bus->resource[1] = &iomem_resource; + + /* + * Walk the devices on this bus, working out what we can + * and can't support. + */ + list_for_each_entry(dev, &bus->devices, bus_list) { + u16 status; + + pci_read_config_word(dev, PCI_STATUS, &status); + + /* + * If any device on this bus does not support fast back + * to back transfers, then the bus as a whole is not able + * to support them. Having fast back to back transfers + * on saves us one PCI cycle per transaction. + */ + if (!(status & PCI_STATUS_FAST_BACK)) + features &= ~PCI_COMMAND_FAST_BACK; + + if (pdev_bad_for_parity(dev)) + features &= ~(PCI_COMMAND_SERR + | PCI_COMMAND_PARITY); + + switch (dev->class >> 8) { + case PCI_CLASS_BRIDGE_PCI: + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &status); + status |= PCI_BRIDGE_CTL_PARITY + | PCI_BRIDGE_CTL_MASTER_ABORT; + status &= ~(PCI_BRIDGE_CTL_BUS_RESET + | PCI_BRIDGE_CTL_FAST_BACK); + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, status); + break; + + case PCI_CLASS_BRIDGE_CARDBUS: + pci_read_config_word(dev, PCI_CB_BRIDGE_CONTROL, + &status); + status |= PCI_CB_BRIDGE_CTL_PARITY + | PCI_CB_BRIDGE_CTL_MASTER_ABORT; + pci_write_config_word(dev, PCI_CB_BRIDGE_CONTROL, + status); + break; + } + } + + /* + * Now walk the devices again, this time setting them up. + */ + list_for_each_entry(dev, &bus->devices, bus_list) { + u16 cmd; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + cmd |= features; + pci_write_config_word(dev, PCI_COMMAND, cmd); + + pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, + L1_CACHE_BYTES >> 2); + } + + /* + * Propagate the flags to the PCI bridge. + */ + if (bus->self && bus->self->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + if (features & PCI_COMMAND_FAST_BACK) + bus->bridge_ctl |= PCI_BRIDGE_CTL_FAST_BACK; + if (features & PCI_COMMAND_PARITY) + bus->bridge_ctl |= PCI_BRIDGE_CTL_PARITY; + } + + /* + * Report what we did for this bus + */ + printk(KERN_INFO "PCI: bus%d: Fast back to back transfers %sabled\n", + bus->number, (features & PCI_COMMAND_FAST_BACK) ? "en" : "dis"); +} +#ifdef CONFIG_HOTPLUG +EXPORT_SYMBOL(pcibios_fixup_bus); +#endif + +static int __init pci_common_init(void) +{ + struct pci_bus *puv3_bus; + + pci_puv3_preinit(); + + puv3_bus = pci_scan_bus(0, &pci_puv3_ops, NULL); + + if (!puv3_bus) + panic("PCI: unable to scan bus!"); + + pci_fixup_irqs(pci_common_swizzle, pci_puv3_map_irq); + + if (!use_firmware) { + /* + * Size the bridge windows. + */ + pci_bus_size_bridges(puv3_bus); + + /* + * Assign resources. + */ + pci_bus_assign_resources(puv3_bus); + } + + /* + * Tell drivers about devices found. + */ + pci_bus_add_devices(puv3_bus); + + return 0; +} +subsys_initcall(pci_common_init); + +char * __devinit pcibios_setup(char *str) +{ + if (!strcmp(str, "debug")) { + debug_pci = 1; + return NULL; + } else if (!strcmp(str, "firmware")) { + use_firmware = 1; + return NULL; + } + return str; +} + +/* + * From arch/i386/kernel/pci-i386.c: + * + * We need to avoid collisions with `mirrored' VGA ports + * and other strange ISA hardware, so we always want the + * addresses to be allocated in the 0x000-0x0ff region + * modulo 0x400. + * + * Why? Because some silly external IO cards only decode + * the low 10 bits of the IO address. The 0x00-0xff region + * is reserved for motherboard devices that decode all 16 + * bits, so it's ok to allocate at, say, 0x2800-0x28ff, + * but we want to try to avoid allocating at 0x2900-0x2bff + * which might be mirrored at 0x0100-0x03ff.. + */ +resource_size_t pcibios_align_resource(void *data, const struct resource *res, + resource_size_t size, resource_size_t align) +{ + resource_size_t start = res->start; + + if (res->flags & IORESOURCE_IO && start & 0x300) + start = (start + 0x3ff) & ~0x3ff; + + start = (start + align - 1) & ~(align - 1); + + return start; +} + +/** + * pcibios_enable_device - Enable I/O and memory. + * @dev: PCI device to be enabled + */ +int pcibios_enable_device(struct pci_dev *dev, int mask) +{ + u16 cmd, old_cmd; + int idx; + struct resource *r; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + old_cmd = cmd; + for (idx = 0; idx < 6; idx++) { + /* Only set up the requested stuff */ + if (!(mask & (1 << idx))) + continue; + + r = dev->resource + idx; + if (!r->start && r->end) { + printk(KERN_ERR "PCI: Device %s not available because" + " of resource collisions\n", pci_name(dev)); + return -EINVAL; + } + if (r->flags & IORESOURCE_IO) + cmd |= PCI_COMMAND_IO; + if (r->flags & IORESOURCE_MEM) + cmd |= PCI_COMMAND_MEMORY; + } + + /* + * Bridges (eg, cardbus bridges) need to be fully enabled + */ + if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) + cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + + if (cmd != old_cmd) { + printk("PCI: enabling device %s (%04x -> %04x)\n", + pci_name(dev), old_cmd, cmd); + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + return 0; +} + +int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma, + enum pci_mmap_state mmap_state, int write_combine) +{ + unsigned long phys; + + if (mmap_state == pci_mmap_io) + return -EINVAL; + + phys = vma->vm_pgoff; + + /* + * Mark this as IO + */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, phys, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} diff --git a/arch/unicore32/kernel/pm.c b/arch/unicore32/kernel/pm.c new file mode 100644 index 00000000000..784bc2db3b2 --- /dev/null +++ b/arch/unicore32/kernel/pm.c @@ -0,0 +1,123 @@ +/* + * linux/arch/unicore32/kernel/pm.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/suspend.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/pm.h> + +#include "setup.h" + +struct puv3_cpu_pm_fns *puv3_cpu_pm_fns; +static unsigned long *sleep_save; + +int puv3_pm_enter(suspend_state_t state) +{ + unsigned long sleep_save_checksum = 0, checksum = 0; + int i; + + /* skip registers saving for standby */ + if (state != PM_SUSPEND_STANDBY) { + puv3_cpu_pm_fns->save(sleep_save); + /* before sleeping, calculate and save a checksum */ + for (i = 0; i < puv3_cpu_pm_fns->save_count - 1; i++) + sleep_save_checksum += sleep_save[i]; + } + + /* *** go zzz *** */ + puv3_cpu_pm_fns->enter(state); + cpu_init(); +#ifdef CONFIG_INPUT_KEYBOARD + puv3_ps2_init(); +#endif +#ifdef CONFIG_PCI + pci_puv3_preinit(); +#endif + if (state != PM_SUSPEND_STANDBY) { + /* after sleeping, validate the checksum */ + for (i = 0; i < puv3_cpu_pm_fns->save_count - 1; i++) + checksum += sleep_save[i]; + + /* if invalid, display message and wait for a hardware reset */ + if (checksum != sleep_save_checksum) { + while (1) + puv3_cpu_pm_fns->enter(state); + } + puv3_cpu_pm_fns->restore(sleep_save); + } + + pr_debug("*** made it back from resume\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(puv3_pm_enter); + +unsigned long sleep_phys_sp(void *sp) +{ + return virt_to_phys(sp); +} + +static int puv3_pm_valid(suspend_state_t state) +{ + if (puv3_cpu_pm_fns) + return puv3_cpu_pm_fns->valid(state); + + return -EINVAL; +} + +static int puv3_pm_prepare(void) +{ + int ret = 0; + + if (puv3_cpu_pm_fns && puv3_cpu_pm_fns->prepare) + ret = puv3_cpu_pm_fns->prepare(); + + return ret; +} + +static void puv3_pm_finish(void) +{ + if (puv3_cpu_pm_fns && puv3_cpu_pm_fns->finish) + puv3_cpu_pm_fns->finish(); +} + +static struct platform_suspend_ops puv3_pm_ops = { + .valid = puv3_pm_valid, + .enter = puv3_pm_enter, + .prepare = puv3_pm_prepare, + .finish = puv3_pm_finish, +}; + +static int __init puv3_pm_init(void) +{ + if (!puv3_cpu_pm_fns) { + printk(KERN_ERR "no valid puv3_cpu_pm_fns defined\n"); + return -EINVAL; + } + + sleep_save = kmalloc(puv3_cpu_pm_fns->save_count + * sizeof(unsigned long), GFP_KERNEL); + if (!sleep_save) { + printk(KERN_ERR "failed to alloc memory for pm save\n"); + return -ENOMEM; + } + + suspend_set_ops(&puv3_pm_ops); + return 0; +} + +device_initcall(puv3_pm_init); diff --git a/arch/unicore32/kernel/process.c b/arch/unicore32/kernel/process.c new file mode 100644 index 00000000000..ba401df971e --- /dev/null +++ b/arch/unicore32/kernel/process.c @@ -0,0 +1,389 @@ +/* + * linux/arch/unicore32/kernel/process.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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 <stdarg.h> + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/delay.h> +#include <linux/reboot.h> +#include <linux/interrupt.h> +#include <linux/kallsyms.h> +#include <linux/init.h> +#include <linux/cpu.h> +#include <linux/elfcore.h> +#include <linux/pm.h> +#include <linux/tick.h> +#include <linux/utsname.h> +#include <linux/uaccess.h> +#include <linux/random.h> +#include <linux/gpio.h> +#include <linux/stacktrace.h> + +#include <asm/cacheflush.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/stacktrace.h> + +#include "setup.h" + +static const char * const processor_modes[] = { + "UK00", "UK01", "UK02", "UK03", "UK04", "UK05", "UK06", "UK07", + "UK08", "UK09", "UK0A", "UK0B", "UK0C", "UK0D", "UK0E", "UK0F", + "USER", "REAL", "INTR", "PRIV", "UK14", "UK15", "UK16", "ABRT", + "UK18", "UK19", "UK1A", "EXTN", "UK1C", "UK1D", "UK1E", "SUSR" +}; + +/* + * The idle thread, has rather strange semantics for calling pm_idle, + * but this is what x86 does and we need to do the same, so that + * things like cpuidle get called in the same way. + */ +void cpu_idle(void) +{ + /* endless idle loop with no priority at all */ + while (1) { + tick_nohz_stop_sched_tick(1); + while (!need_resched()) { + local_irq_disable(); + stop_critical_timings(); + cpu_do_idle(); + local_irq_enable(); + start_critical_timings(); + } + tick_nohz_restart_sched_tick(); + preempt_enable_no_resched(); + schedule(); + preempt_disable(); + } +} + +static char reboot_mode = 'h'; + +int __init reboot_setup(char *str) +{ + reboot_mode = str[0]; + return 1; +} + +__setup("reboot=", reboot_setup); + +void machine_halt(void) +{ + gpio_set_value(GPO_SOFT_OFF, 0); +} + +/* + * Function pointers to optional machine specific functions + */ +void (*pm_power_off)(void) = NULL; + +void machine_power_off(void) +{ + if (pm_power_off) + pm_power_off(); + machine_halt(); +} + +void machine_restart(char *cmd) +{ + /* Disable interrupts first */ + local_irq_disable(); + + /* + * Tell the mm system that we are going to reboot - + * we may need it to insert some 1:1 mappings so that + * soft boot works. + */ + setup_mm_for_reboot(reboot_mode); + + /* Clean and invalidate caches */ + flush_cache_all(); + + /* Turn off caching */ + cpu_proc_fin(); + + /* Push out any further dirty data, and ensure cache is empty */ + flush_cache_all(); + + /* + * Now handle reboot code. + */ + if (reboot_mode == 's') { + /* Jump into ROM at address 0xffff0000 */ + cpu_reset(VECTORS_BASE); + } else { + writel(0x00002001, PM_PLLSYSCFG); /* cpu clk = 250M */ + writel(0x00100800, PM_PLLDDRCFG); /* ddr clk = 44M */ + writel(0x00002001, PM_PLLVGACFG); /* vga clk = 250M */ + + /* Use on-chip reset capability */ + /* following instructions must be in one icache line */ + __asm__ __volatile__( + " .align 5\n\t" + " stw %1, [%0]\n\t" + "201: ldw r0, [%0]\n\t" + " cmpsub.a r0, #0\n\t" + " bne 201b\n\t" + " stw %3, [%2]\n\t" + " nop; nop; nop\n\t" + /* prefetch 3 instructions at most */ + : + : "r" (PM_PMCR), + "r" (PM_PMCR_CFBSYS | PM_PMCR_CFBDDR + | PM_PMCR_CFBVGA), + "r" (RESETC_SWRR), + "r" (RESETC_SWRR_SRB) + : "r0", "memory"); + } + + /* + * Whoops - the architecture was unable to reboot. + * Tell the user! + */ + mdelay(1000); + printk(KERN_EMERG "Reboot failed -- System halted\n"); + do { } while (1); +} + +void __show_regs(struct pt_regs *regs) +{ + unsigned long flags; + char buf[64]; + + printk(KERN_DEFAULT "CPU: %d %s (%s %.*s)\n", + raw_smp_processor_id(), print_tainted(), + init_utsname()->release, + (int)strcspn(init_utsname()->version, " "), + init_utsname()->version); + print_symbol("PC is at %s\n", instruction_pointer(regs)); + print_symbol("LR is at %s\n", regs->UCreg_lr); + printk(KERN_DEFAULT "pc : [<%08lx>] lr : [<%08lx>] psr: %08lx\n" + "sp : %08lx ip : %08lx fp : %08lx\n", + regs->UCreg_pc, regs->UCreg_lr, regs->UCreg_asr, + regs->UCreg_sp, regs->UCreg_ip, regs->UCreg_fp); + printk(KERN_DEFAULT "r26: %08lx r25: %08lx r24: %08lx\n", + regs->UCreg_26, regs->UCreg_25, + regs->UCreg_24); + printk(KERN_DEFAULT "r23: %08lx r22: %08lx r21: %08lx r20: %08lx\n", + regs->UCreg_23, regs->UCreg_22, + regs->UCreg_21, regs->UCreg_20); + printk(KERN_DEFAULT "r19: %08lx r18: %08lx r17: %08lx r16: %08lx\n", + regs->UCreg_19, regs->UCreg_18, + regs->UCreg_17, regs->UCreg_16); + printk(KERN_DEFAULT "r15: %08lx r14: %08lx r13: %08lx r12: %08lx\n", + regs->UCreg_15, regs->UCreg_14, + regs->UCreg_13, regs->UCreg_12); + printk(KERN_DEFAULT "r11: %08lx r10: %08lx r9 : %08lx r8 : %08lx\n", + regs->UCreg_11, regs->UCreg_10, + regs->UCreg_09, regs->UCreg_08); + printk(KERN_DEFAULT "r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx\n", + regs->UCreg_07, regs->UCreg_06, + regs->UCreg_05, regs->UCreg_04); + printk(KERN_DEFAULT "r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx\n", + regs->UCreg_03, regs->UCreg_02, + regs->UCreg_01, regs->UCreg_00); + + flags = regs->UCreg_asr; + buf[0] = flags & PSR_S_BIT ? 'S' : 's'; + buf[1] = flags & PSR_Z_BIT ? 'Z' : 'z'; + buf[2] = flags & PSR_C_BIT ? 'C' : 'c'; + buf[3] = flags & PSR_V_BIT ? 'V' : 'v'; + buf[4] = '\0'; + + printk(KERN_DEFAULT "Flags: %s INTR o%s REAL o%s Mode %s Segment %s\n", + buf, interrupts_enabled(regs) ? "n" : "ff", + fast_interrupts_enabled(regs) ? "n" : "ff", + processor_modes[processor_mode(regs)], + segment_eq(get_fs(), get_ds()) ? "kernel" : "user"); + { + unsigned int ctrl; + + buf[0] = '\0'; + { + unsigned int transbase; + asm("movc %0, p0.c2, #0\n" + : "=r" (transbase)); + snprintf(buf, sizeof(buf), " Table: %08x", transbase); + } + asm("movc %0, p0.c1, #0\n" : "=r" (ctrl)); + + printk(KERN_DEFAULT "Control: %08x%s\n", ctrl, buf); + } +} + +void show_regs(struct pt_regs *regs) +{ + printk(KERN_DEFAULT "\n"); + printk(KERN_DEFAULT "Pid: %d, comm: %20s\n", + task_pid_nr(current), current->comm); + __show_regs(regs); + __backtrace(); +} + +/* + * Free current thread data structures etc.. + */ +void exit_thread(void) +{ +} + +void flush_thread(void) +{ + struct thread_info *thread = current_thread_info(); + struct task_struct *tsk = current; + + memset(thread->used_cp, 0, sizeof(thread->used_cp)); + memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); +#ifdef CONFIG_UNICORE_FPU_F64 + memset(&thread->fpstate, 0, sizeof(struct fp_state)); +#endif +} + +void release_thread(struct task_struct *dead_task) +{ +} + +asmlinkage void ret_from_fork(void) __asm__("ret_from_fork"); + +int +copy_thread(unsigned long clone_flags, unsigned long stack_start, + unsigned long stk_sz, struct task_struct *p, struct pt_regs *regs) +{ + struct thread_info *thread = task_thread_info(p); + struct pt_regs *childregs = task_pt_regs(p); + + *childregs = *regs; + childregs->UCreg_00 = 0; + childregs->UCreg_sp = stack_start; + + memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save)); + thread->cpu_context.sp = (unsigned long)childregs; + thread->cpu_context.pc = (unsigned long)ret_from_fork; + + if (clone_flags & CLONE_SETTLS) + childregs->UCreg_16 = regs->UCreg_03; + + return 0; +} + +/* + * Fill in the task's elfregs structure for a core dump. + */ +int dump_task_regs(struct task_struct *t, elf_gregset_t *elfregs) +{ + elf_core_copy_regs(elfregs, task_pt_regs(t)); + return 1; +} + +/* + * fill in the fpe structure for a core dump... + */ +int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fp) +{ + struct thread_info *thread = current_thread_info(); + int used_math = thread->used_cp[1] | thread->used_cp[2]; + +#ifdef CONFIG_UNICORE_FPU_F64 + if (used_math) + memcpy(fp, &thread->fpstate, sizeof(*fp)); +#endif + return used_math != 0; +} +EXPORT_SYMBOL(dump_fpu); + +/* + * Shuffle the argument into the correct register before calling the + * thread function. r1 is the thread argument, r2 is the pointer to + * the thread function, and r3 points to the exit function. + */ +asm(".pushsection .text\n" +" .align\n" +" .type kernel_thread_helper, #function\n" +"kernel_thread_helper:\n" +" mov.a asr, r7\n" +" mov r0, r4\n" +" mov lr, r6\n" +" mov pc, r5\n" +" .size kernel_thread_helper, . - kernel_thread_helper\n" +" .popsection"); + +/* + * Create a kernel thread. + */ +pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + + regs.UCreg_04 = (unsigned long)arg; + regs.UCreg_05 = (unsigned long)fn; + regs.UCreg_06 = (unsigned long)do_exit; + regs.UCreg_07 = PRIV_MODE; + regs.UCreg_pc = (unsigned long)kernel_thread_helper; + regs.UCreg_asr = regs.UCreg_07 | PSR_I_BIT; + + return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); +} +EXPORT_SYMBOL(kernel_thread); + +unsigned long get_wchan(struct task_struct *p) +{ + struct stackframe frame; + int count = 0; + if (!p || p == current || p->state == TASK_RUNNING) + return 0; + + frame.fp = thread_saved_fp(p); + frame.sp = thread_saved_sp(p); + frame.lr = 0; /* recovered from the stack */ + frame.pc = thread_saved_pc(p); + do { + int ret = unwind_frame(&frame); + if (ret < 0) + return 0; + if (!in_sched_functions(frame.pc)) + return frame.pc; + } while ((count++) < 16); + return 0; +} + +unsigned long arch_randomize_brk(struct mm_struct *mm) +{ + unsigned long range_end = mm->brk + 0x02000000; + return randomize_range(mm->brk, range_end, 0) ? : mm->brk; +} + +/* + * The vectors page is always readable from user space for the + * atomic helpers and the signal restart code. Let's declare a mapping + * for it so it is visible through ptrace and /proc/<pid>/mem. + */ + +int vectors_user_mapping(void) +{ + struct mm_struct *mm = current->mm; + return install_special_mapping(mm, 0xffff0000, PAGE_SIZE, + VM_READ | VM_EXEC | + VM_MAYREAD | VM_MAYEXEC | + VM_ALWAYSDUMP | VM_RESERVED, + NULL); +} + +const char *arch_vma_name(struct vm_area_struct *vma) +{ + return (vma->vm_start == 0xffff0000) ? "[vectors]" : NULL; +} diff --git a/arch/unicore32/kernel/ptrace.c b/arch/unicore32/kernel/ptrace.c new file mode 100644 index 00000000000..9f07c08da05 --- /dev/null +++ b/arch/unicore32/kernel/ptrace.c @@ -0,0 +1,149 @@ +/* + * linux/arch/unicore32/kernel/ptrace.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * By Ross Biro 1/23/92 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/signal.h> +#include <linux/uaccess.h> + +/* + * this routine will get a word off of the processes privileged stack. + * the offset is how far from the base addr as stored in the THREAD. + * this routine assumes that all the privileged stacks are in our + * data space. + */ +static inline long get_user_reg(struct task_struct *task, int offset) +{ + return task_pt_regs(task)->uregs[offset]; +} + +/* + * this routine will put a word on the processes privileged stack. + * the offset is how far from the base addr as stored in the THREAD. + * this routine assumes that all the privileged stacks are in our + * data space. + */ +static inline int +put_user_reg(struct task_struct *task, int offset, long data) +{ + struct pt_regs newregs, *regs = task_pt_regs(task); + int ret = -EINVAL; + + newregs = *regs; + newregs.uregs[offset] = data; + + if (valid_user_regs(&newregs)) { + regs->uregs[offset] = data; + ret = 0; + } + + return ret; +} + +/* + * Called by kernel/ptrace.c when detaching.. + */ +void ptrace_disable(struct task_struct *child) +{ +} + +/* + * We actually access the pt_regs stored on the kernel stack. + */ +static int ptrace_read_user(struct task_struct *tsk, unsigned long off, + unsigned long __user *ret) +{ + unsigned long tmp; + + tmp = 0; + if (off < sizeof(struct pt_regs)) + tmp = get_user_reg(tsk, off >> 2); + + return put_user(tmp, ret); +} + +/* + * We actually access the pt_regs stored on the kernel stack. + */ +static int ptrace_write_user(struct task_struct *tsk, unsigned long off, + unsigned long val) +{ + if (off >= sizeof(struct pt_regs)) + return 0; + + return put_user_reg(tsk, off >> 2, val); +} + +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret; + unsigned long __user *datap = (unsigned long __user *) data; + + switch (request) { + case PTRACE_PEEKUSR: + ret = ptrace_read_user(child, addr, datap); + break; + + case PTRACE_POKEUSR: + ret = ptrace_write_user(child, addr, data); + break; + + case PTRACE_GET_THREAD_AREA: + ret = put_user(task_pt_regs(child)->UCreg_16, + datap); + break; + + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +asmlinkage int syscall_trace(int why, struct pt_regs *regs, int scno) +{ + unsigned long ip; + + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return scno; + if (!(current->ptrace & PT_PTRACED)) + return scno; + + /* + * Save IP. IP is used to denote syscall entry/exit: + * IP = 0 -> entry, = 1 -> exit + */ + ip = regs->UCreg_ip; + regs->UCreg_ip = why; + + current_thread_info()->syscall = scno; + + /* the 0x80 provides a way for the tracing parent to distinguish + between a syscall stop and SIGTRAP delivery */ + ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) + ? 0x80 : 0)); + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. strace only continues with a signal if the + * stopping signal is not SIGTRAP. -brl + */ + if (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } + regs->UCreg_ip = ip; + + return current_thread_info()->syscall; +} diff --git a/arch/unicore32/kernel/puv3-core.c b/arch/unicore32/kernel/puv3-core.c new file mode 100644 index 00000000000..8b1b6beb858 --- /dev/null +++ b/arch/unicore32/kernel/puv3-core.c @@ -0,0 +1,285 @@ +/* + * linux/arch/unicore32/kernel/puv3-core.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/amba/bus.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/cnt32_to_63.h> +#include <linux/usb/musb.h> + +#include <asm/irq.h> +#include <mach/hardware.h> +#include <mach/pm.h> + +/* + * This is the PKUnity sched_clock implementation. This has + * a resolution of 271ns, and a maximum value of 32025597s (370 days). + * + * The return value is guaranteed to be monotonic in that range as + * long as there is always less than 582 seconds between successive + * calls to this function. + * + * ( * 1E9 / CLOCK_TICK_RATE ) -> about 2235/32 + */ +unsigned long long sched_clock(void) +{ + unsigned long long v = cnt32_to_63(readl(OST_OSCR)); + + /* original conservative method, but overflow frequently + * v *= NSEC_PER_SEC >> 12; + * do_div(v, CLOCK_TICK_RATE >> 12); + */ + v = ((v & 0x7fffffffffffffffULL) * 2235) >> 5; + + return v; +} + +static struct resource puv3_usb_resources[] = { + /* order is significant! */ + { + .start = io_v2p(PKUNITY_USB_BASE), + .end = io_v2p(PKUNITY_USB_BASE) + 0x3ff, + .flags = IORESOURCE_MEM, + }, { + .start = IRQ_USB, + .flags = IORESOURCE_IRQ, + }, { + .start = IRQ_USB, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct musb_hdrc_config puv3_usb_config[] = { + { + .num_eps = 16, + .multipoint = 1, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma = 1, + .dma_channels = 8, +#endif + }, +}; + +static struct musb_hdrc_platform_data puv3_usb_plat = { + .mode = MUSB_HOST, + .min_power = 100, + .clock = 0, + .config = puv3_usb_config, +}; + +static struct resource puv3_mmc_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_SDC_BASE), + .end = io_v2p(PKUNITY_SDC_BASE) + 0xfff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_SDC, + .end = IRQ_SDC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource puv3_unigfx_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_UNIGFX_BASE), + .end = io_v2p(PKUNITY_UNIGFX_BASE) + 0xfff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = PKUNITY_UNIGFX_MMAP_BASE, + .end = PKUNITY_UNIGFX_MMAP_BASE + PKUNITY_UNIGFX_MMAP_SIZE, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource puv3_rtc_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_RTC_BASE), + .end = io_v2p(PKUNITY_RTC_BASE) + 0xff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_RTCAlarm, + .end = IRQ_RTCAlarm, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IRQ_RTC, + .end = IRQ_RTC, + .flags = IORESOURCE_IRQ + } +}; + +static struct resource puv3_pwm_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_OST_BASE) + 0x80, + .end = io_v2p(PKUNITY_OST_BASE) + 0xff, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource puv3_uart0_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_UART0_BASE), + .end = io_v2p(PKUNITY_UART0_BASE) + 0xff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_UART0, + .end = IRQ_UART0, + .flags = IORESOURCE_IRQ + } +}; + +static struct resource puv3_uart1_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_UART1_BASE), + .end = io_v2p(PKUNITY_UART1_BASE) + 0xff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_UART1, + .end = IRQ_UART1, + .flags = IORESOURCE_IRQ + } +}; + +static struct resource puv3_umal_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_UMAL_BASE), + .end = io_v2p(PKUNITY_UMAL_BASE) + 0x1fff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_UMAL, + .end = IRQ_UMAL, + .flags = IORESOURCE_IRQ + } +}; + +#ifdef CONFIG_PUV3_PM + +#define SAVE(x) sleep_save[SLEEP_SAVE_##x] = x +#define RESTORE(x) x = sleep_save[SLEEP_SAVE_##x] + +/* + * List of global PXA peripheral registers to preserve. + * More ones like CP and general purpose register values are preserved + * with the stack pointer in sleep.S. + */ +enum { + SLEEP_SAVE_PM_PLLDDRCFG, + SLEEP_SAVE_COUNT +}; + + +static void puv3_cpu_pm_save(unsigned long *sleep_save) +{ +/* SAVE(PM_PLLDDRCFG); */ +} + +static void puv3_cpu_pm_restore(unsigned long *sleep_save) +{ +/* RESTORE(PM_PLLDDRCFG); */ +} + +static int puv3_cpu_pm_prepare(void) +{ + /* set resume return address */ + writel(virt_to_phys(puv3_cpu_resume), PM_DIVCFG); + return 0; +} + +static void puv3_cpu_pm_enter(suspend_state_t state) +{ + /* Clear reset status */ + writel(RESETC_RSSR_HWR | RESETC_RSSR_WDR + | RESETC_RSSR_SMR | RESETC_RSSR_SWR, RESETC_RSSR); + + switch (state) { +/* case PM_SUSPEND_ON: + puv3_cpu_idle(); + break; */ + case PM_SUSPEND_MEM: + puv3_cpu_pm_prepare(); + puv3_cpu_suspend(PM_PMCR_SFB); + break; + } +} + +static int puv3_cpu_pm_valid(suspend_state_t state) +{ + return state == PM_SUSPEND_MEM; +} + +static void puv3_cpu_pm_finish(void) +{ + /* ensure not to come back here if it wasn't intended */ + /* PSPR = 0; */ +} + +static struct puv3_cpu_pm_fns puv3_cpu_pm_fnss = { + .save_count = SLEEP_SAVE_COUNT, + .valid = puv3_cpu_pm_valid, + .save = puv3_cpu_pm_save, + .restore = puv3_cpu_pm_restore, + .enter = puv3_cpu_pm_enter, + .prepare = puv3_cpu_pm_prepare, + .finish = puv3_cpu_pm_finish, +}; + +static void __init puv3_init_pm(void) +{ + puv3_cpu_pm_fns = &puv3_cpu_pm_fnss; +} +#else +static inline void puv3_init_pm(void) {} +#endif + +void puv3_ps2_init(void) +{ + struct clk *bclk32; + + bclk32 = clk_get(NULL, "BUS32_CLK"); + writel(clk_get_rate(bclk32) / 200000, PS2_CNT); /* should > 5us */ +} + +void __init puv3_core_init(void) +{ + puv3_init_pm(); + puv3_ps2_init(); + + platform_device_register_simple("PKUnity-v3-RTC", -1, + puv3_rtc_resources, ARRAY_SIZE(puv3_rtc_resources)); + platform_device_register_simple("PKUnity-v3-UMAL", -1, + puv3_umal_resources, ARRAY_SIZE(puv3_umal_resources)); + platform_device_register_simple("PKUnity-v3-MMC", -1, + puv3_mmc_resources, ARRAY_SIZE(puv3_mmc_resources)); + platform_device_register_simple("PKUnity-v3-UNIGFX", -1, + puv3_unigfx_resources, ARRAY_SIZE(puv3_unigfx_resources)); + platform_device_register_simple("PKUnity-v3-PWM", -1, + puv3_pwm_resources, ARRAY_SIZE(puv3_pwm_resources)); + platform_device_register_simple("PKUnity-v3-UART", 0, + puv3_uart0_resources, ARRAY_SIZE(puv3_uart0_resources)); + platform_device_register_simple("PKUnity-v3-UART", 1, + puv3_uart1_resources, ARRAY_SIZE(puv3_uart1_resources)); + platform_device_register_simple("PKUnity-v3-AC97", -1, NULL, 0); + platform_device_register_resndata(&platform_bus, "musb_hdrc", -1, + puv3_usb_resources, ARRAY_SIZE(puv3_usb_resources), + &puv3_usb_plat, sizeof(puv3_usb_plat)); +} + diff --git a/arch/unicore32/kernel/puv3-nb0916.c b/arch/unicore32/kernel/puv3-nb0916.c new file mode 100644 index 00000000000..e731c561ed4 --- /dev/null +++ b/arch/unicore32/kernel/puv3-nb0916.c @@ -0,0 +1,145 @@ +/* + * linux/arch/unicore32/kernel/puv3-nb0916.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/platform_device.h> +#include <linux/mtd/physmap.h> +#include <linux/io.h> +#include <linux/reboot.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/pwm_backlight.h> +#include <linux/gpio.h> +#include <linux/gpio_keys.h> +#include <linux/input.h> + +#include <mach/hardware.h> + +static struct physmap_flash_data physmap_flash_data = { + .width = 1, +}; + +static struct resource physmap_flash_resource = { + .start = 0xFFF80000, + .end = 0xFFFFFFFF, + .flags = IORESOURCE_MEM, +}; + +static struct resource puv3_i2c_resources[] = { + [0] = { + .start = io_v2p(PKUNITY_I2C_BASE), + .end = io_v2p(PKUNITY_I2C_BASE) + 0xff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_I2C, + .end = IRQ_I2C, + .flags = IORESOURCE_IRQ, + } +}; + +static struct platform_pwm_backlight_data nb0916_backlight_data = { + .pwm_id = 0, + .max_brightness = 100, + .dft_brightness = 100, + .pwm_period_ns = 70 * 1024, +}; + +static struct gpio_keys_button nb0916_gpio_keys[] = { + { + .type = EV_KEY, + .code = KEY_POWER, + .gpio = GPI_SOFF_REQ, + .desc = "Power Button", + .wakeup = 1, + .active_low = 1, + }, + { + .type = EV_KEY, + .code = BTN_TOUCH, + .gpio = GPI_BTN_TOUCH, + .desc = "Touchpad Button", + .wakeup = 1, + .active_low = 1, + }, +}; + +static struct gpio_keys_platform_data nb0916_gpio_button_data = { + .buttons = nb0916_gpio_keys, + .nbuttons = ARRAY_SIZE(nb0916_gpio_keys), +}; + +static irqreturn_t nb0916_lcdcaseoff_handler(int irq, void *dev_id) +{ + if (gpio_get_value(GPI_LCD_CASE_OFF)) + gpio_set_value(GPO_LCD_EN, 1); + else + gpio_set_value(GPO_LCD_EN, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t nb0916_overheat_handler(int irq, void *dev_id) +{ + machine_halt(); + /* SYSTEM HALT, NO RETURN */ + return IRQ_HANDLED; +} + +static struct i2c_board_info __initdata puv3_i2c_devices[] = { + { I2C_BOARD_INFO("lm75", I2C_TAR_THERMAL), }, + { I2C_BOARD_INFO("bq27200", I2C_TAR_PWIC), }, + { I2C_BOARD_INFO("24c02", I2C_TAR_EEPROM), }, +}; + +int __init mach_nb0916_init(void) +{ + i2c_register_board_info(0, puv3_i2c_devices, + ARRAY_SIZE(puv3_i2c_devices)); + + platform_device_register_simple("PKUnity-v3-I2C", -1, + puv3_i2c_resources, ARRAY_SIZE(puv3_i2c_resources)); + + platform_device_register_data(&platform_bus, "pwm-backlight", -1, + &nb0916_backlight_data, sizeof(nb0916_backlight_data)); + + platform_device_register_data(&platform_bus, "gpio-keys", -1, + &nb0916_gpio_button_data, sizeof(nb0916_gpio_button_data)); + + platform_device_register_resndata(&platform_bus, "physmap-flash", -1, + &physmap_flash_resource, 1, + &physmap_flash_data, sizeof(physmap_flash_data)); + + if (request_irq(gpio_to_irq(GPI_LCD_CASE_OFF), + &nb0916_lcdcaseoff_handler, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "NB0916 lcd case off", NULL) < 0) { + + printk(KERN_DEBUG "LCD-Case-OFF IRQ %d not available\n", + gpio_to_irq(GPI_LCD_CASE_OFF)); + } + + if (request_irq(gpio_to_irq(GPI_OTP_INT), &nb0916_overheat_handler, + IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "NB0916 overheating protection", NULL) < 0) { + + printk(KERN_DEBUG "Overheating Protection IRQ %d not available\n", + gpio_to_irq(GPI_OTP_INT)); + } + + return 0; +} + +subsys_initcall_sync(mach_nb0916_init); diff --git a/arch/unicore32/kernel/pwm.c b/arch/unicore32/kernel/pwm.c new file mode 100644 index 00000000000..4615d51e3ba --- /dev/null +++ b/arch/unicore32/kernel/pwm.c @@ -0,0 +1,263 @@ +/* + * linux/arch/unicore32/kernel/pwm.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> + +#include <asm/div64.h> +#include <mach/hardware.h> + +struct pwm_device { + struct list_head node; + struct platform_device *pdev; + + const char *label; + struct clk *clk; + int clk_enabled; + + unsigned int use_count; + unsigned int pwm_id; +}; + +/* + * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + */ +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +{ + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + + if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) + return -EINVAL; + + c = clk_get_rate(pwm->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 1024; + pv = period_cycles / (prescale + 1) - 1; + + if (prescale > 63) + return -EINVAL; + + if (duty_ns == period_ns) + dc = OST_PWMDCCR_FDCYCLE; + else + dc = (pv + 1) * duty_ns / period_ns; + + /* NOTE: the clock to PWM has to be enabled first + * before writing to the registers + */ + clk_enable(pwm->clk); + OST_PWMPWCR = prescale; + OST_PWMDCCR = pv - dc; + OST_PWMPCR = pv; + clk_disable(pwm->clk); + + return 0; +} +EXPORT_SYMBOL(pwm_config); + +int pwm_enable(struct pwm_device *pwm) +{ + int rc = 0; + + if (!pwm->clk_enabled) { + rc = clk_enable(pwm->clk); + if (!rc) + pwm->clk_enabled = 1; + } + return rc; +} +EXPORT_SYMBOL(pwm_enable); + +void pwm_disable(struct pwm_device *pwm) +{ + if (pwm->clk_enabled) { + clk_disable(pwm->clk); + pwm->clk_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) { + found = 1; + break; + } + } + + if (found) { + if (pwm->use_count == 0) { + pwm->use_count++; + pwm->label = label; + } else + pwm = ERR_PTR(-EBUSY); + } else + pwm = ERR_PTR(-ENOENT); + + mutex_unlock(&pwm_lock); + return pwm; +} +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 + pr_warning("PWM device already freed\n"); + + mutex_unlock(&pwm_lock); +} +EXPORT_SYMBOL(pwm_free); + +static inline void __add_pwm(struct pwm_device *pwm) +{ + mutex_lock(&pwm_lock); + list_add_tail(&pwm->node, &pwm_list); + mutex_unlock(&pwm_lock); +} + +static struct pwm_device *pwm_probe(struct platform_device *pdev, + unsigned int pwm_id, struct pwm_device *parent_pwm) +{ + struct pwm_device *pwm; + struct resource *r; + int ret = 0; + + pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); + if (pwm == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return ERR_PTR(-ENOMEM); + } + + pwm->clk = clk_get(NULL, "OST_CLK"); + if (IS_ERR(pwm->clk)) { + ret = PTR_ERR(pwm->clk); + goto err_free; + } + pwm->clk_enabled = 0; + + pwm->use_count = 0; + pwm->pwm_id = pwm_id; + pwm->pdev = pdev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + __add_pwm(pwm); + platform_set_drvdata(pdev, pwm); + return pwm; + +err_free_clk: + clk_put(pwm->clk); +err_free: + kfree(pwm); + return ERR_PTR(ret); +} + +static int __devinit puv3_pwm_probe(struct platform_device *pdev) +{ + struct pwm_device *pwm = pwm_probe(pdev, pdev->id, NULL); + + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + return 0; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct pwm_device *pwm; + struct resource *r; + + pwm = platform_get_drvdata(pdev); + if (pwm == NULL) + return -ENODEV; + + mutex_lock(&pwm_lock); + list_del(&pwm->node); + mutex_unlock(&pwm_lock); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_put(pwm->clk); + kfree(pwm); + return 0; +} + +static struct platform_driver puv3_pwm_driver = { + .driver = { + .name = "PKUnity-v3-PWM", + }, + .probe = puv3_pwm_probe, + .remove = __devexit_p(pwm_remove), +}; + +static int __init pwm_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&puv3_pwm_driver); + if (ret) { + printk(KERN_ERR "failed to register puv3_pwm_driver\n"); + return ret; + } + + return ret; +} +arch_initcall(pwm_init); + +static void __exit pwm_exit(void) +{ + platform_driver_unregister(&puv3_pwm_driver); +} +module_exit(pwm_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/arch/unicore32/kernel/rtc.c b/arch/unicore32/kernel/rtc.c new file mode 100644 index 00000000000..c5f068295b5 --- /dev/null +++ b/arch/unicore32/kernel/rtc.c @@ -0,0 +1,380 @@ +/* + * linux/arch/unicore32/kernel/rtc.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/clk.h> +#include <linux/log2.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <asm/irq.h> +#include <mach/hardware.h> + +static struct resource *puv3_rtc_mem; + +static int puv3_rtc_alarmno = IRQ_RTCAlarm; +static int puv3_rtc_tickno = IRQ_RTC; + +static DEFINE_SPINLOCK(puv3_rtc_pie_lock); + +/* IRQ Handlers */ + +static irqreturn_t puv3_rtc_alarmirq(int irq, void *id) +{ + struct rtc_device *rdev = id; + + writel(readl(RTC_RTSR) | RTC_RTSR_AL, RTC_RTSR); + rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF); + return IRQ_HANDLED; +} + +static irqreturn_t puv3_rtc_tickirq(int irq, void *id) +{ + struct rtc_device *rdev = id; + + writel(readl(RTC_RTSR) | RTC_RTSR_HZ, RTC_RTSR); + rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF); + return IRQ_HANDLED; +} + +/* Update control registers */ +static void puv3_rtc_setaie(int to) +{ + unsigned int tmp; + + pr_debug("%s: aie=%d\n", __func__, to); + + tmp = readl(RTC_RTSR) & ~RTC_RTSR_ALE; + + if (to) + tmp |= RTC_RTSR_ALE; + + writel(tmp, RTC_RTSR); +} + +static int puv3_rtc_setpie(struct device *dev, int enabled) +{ + unsigned int tmp; + + pr_debug("%s: pie=%d\n", __func__, enabled); + + spin_lock_irq(&puv3_rtc_pie_lock); + tmp = readl(RTC_RTSR) & ~RTC_RTSR_HZE; + + if (enabled) + tmp |= RTC_RTSR_HZE; + + writel(tmp, RTC_RTSR); + spin_unlock_irq(&puv3_rtc_pie_lock); + + return 0; +} + +static int puv3_rtc_setfreq(struct device *dev, int freq) +{ + return 0; +} + +/* Time read/write */ + +static int puv3_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) +{ + rtc_time_to_tm(readl(RTC_RCNR), rtc_tm); + + pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n", + rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday, + rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec); + + return 0; +} + +static int puv3_rtc_settime(struct device *dev, struct rtc_time *tm) +{ + unsigned long rtc_count = 0; + + pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\n", + tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + rtc_tm_to_time(tm, &rtc_count); + writel(rtc_count, RTC_RCNR); + + return 0; +} + +static int puv3_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_time *alm_tm = &alrm->time; + + rtc_time_to_tm(readl(RTC_RTAR), alm_tm); + + alrm->enabled = readl(RTC_RTSR) & RTC_RTSR_ALE; + + pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n", + alrm->enabled, + alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday, + alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec); + + return 0; +} + +static int puv3_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_time *tm = &alrm->time; + unsigned long rtcalarm_count = 0; + + pr_debug("puv3_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n", + alrm->enabled, + tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff, + tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec); + + rtc_tm_to_time(tm, &rtcalarm_count); + writel(rtcalarm_count, RTC_RTAR); + + puv3_rtc_setaie(alrm->enabled); + + if (alrm->enabled) + enable_irq_wake(puv3_rtc_alarmno); + else + disable_irq_wake(puv3_rtc_alarmno); + + return 0; +} + +static int puv3_rtc_proc(struct device *dev, struct seq_file *seq) +{ + seq_printf(seq, "periodic_IRQ\t: %s\n", + (readl(RTC_RTSR) & RTC_RTSR_HZE) ? "yes" : "no"); + return 0; +} + +static int puv3_rtc_open(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc_dev = platform_get_drvdata(pdev); + int ret; + + ret = request_irq(puv3_rtc_alarmno, puv3_rtc_alarmirq, + IRQF_DISABLED, "pkunity-rtc alarm", rtc_dev); + + if (ret) { + dev_err(dev, "IRQ%d error %d\n", puv3_rtc_alarmno, ret); + return ret; + } + + ret = request_irq(puv3_rtc_tickno, puv3_rtc_tickirq, + IRQF_DISABLED, "pkunity-rtc tick", rtc_dev); + + if (ret) { + dev_err(dev, "IRQ%d error %d\n", puv3_rtc_tickno, ret); + goto tick_err; + } + + return ret; + + tick_err: + free_irq(puv3_rtc_alarmno, rtc_dev); + return ret; +} + +static void puv3_rtc_release(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc_dev = platform_get_drvdata(pdev); + + /* do not clear AIE here, it may be needed for wake */ + + puv3_rtc_setpie(dev, 0); + free_irq(puv3_rtc_alarmno, rtc_dev); + free_irq(puv3_rtc_tickno, rtc_dev); +} + +static const struct rtc_class_ops puv3_rtcops = { + .open = puv3_rtc_open, + .release = puv3_rtc_release, + .read_time = puv3_rtc_gettime, + .set_time = puv3_rtc_settime, + .read_alarm = puv3_rtc_getalarm, + .set_alarm = puv3_rtc_setalarm, + .irq_set_freq = puv3_rtc_setfreq, + .irq_set_state = puv3_rtc_setpie, + .proc = puv3_rtc_proc, +}; + +static void puv3_rtc_enable(struct platform_device *pdev, int en) +{ + if (!en) { + writel(readl(RTC_RTSR) & ~RTC_RTSR_HZE, RTC_RTSR); + } else { + /* re-enable the device, and check it is ok */ + + if ((readl(RTC_RTSR) & RTC_RTSR_HZE) == 0) { + dev_info(&pdev->dev, "rtc disabled, re-enabling\n"); + writel(readl(RTC_RTSR) | RTC_RTSR_HZE, RTC_RTSR); + } + } +} + +static int puv3_rtc_remove(struct platform_device *dev) +{ + struct rtc_device *rtc = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + rtc_device_unregister(rtc); + + puv3_rtc_setpie(&dev->dev, 0); + puv3_rtc_setaie(0); + + release_resource(puv3_rtc_mem); + kfree(puv3_rtc_mem); + + return 0; +} + +static int puv3_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + struct resource *res; + int ret; + + pr_debug("%s: probe=%p\n", __func__, pdev); + + /* find the IRQs */ + + puv3_rtc_tickno = platform_get_irq(pdev, 1); + if (puv3_rtc_tickno < 0) { + dev_err(&pdev->dev, "no irq for rtc tick\n"); + return -ENOENT; + } + + puv3_rtc_alarmno = platform_get_irq(pdev, 0); + if (puv3_rtc_alarmno < 0) { + dev_err(&pdev->dev, "no irq for alarm\n"); + return -ENOENT; + } + + pr_debug("PKUnity_rtc: tick irq %d, alarm irq %d\n", + puv3_rtc_tickno, puv3_rtc_alarmno); + + /* get the memory region */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory region resource\n"); + return -ENOENT; + } + + puv3_rtc_mem = request_mem_region(res->start, + res->end-res->start+1, + pdev->name); + + if (puv3_rtc_mem == NULL) { + dev_err(&pdev->dev, "failed to reserve memory region\n"); + ret = -ENOENT; + goto err_nores; + } + + puv3_rtc_enable(pdev, 1); + + puv3_rtc_setfreq(&pdev->dev, 1); + + /* register RTC and exit */ + + rtc = rtc_device_register("pkunity", &pdev->dev, &puv3_rtcops, + THIS_MODULE); + + if (IS_ERR(rtc)) { + dev_err(&pdev->dev, "cannot attach rtc\n"); + ret = PTR_ERR(rtc); + goto err_nortc; + } + + /* platform setup code should have handled this; sigh */ + if (!device_can_wakeup(&pdev->dev)) + device_init_wakeup(&pdev->dev, 1); + + platform_set_drvdata(pdev, rtc); + return 0; + + err_nortc: + puv3_rtc_enable(pdev, 0); + release_resource(puv3_rtc_mem); + + err_nores: + return ret; +} + +#ifdef CONFIG_PM + +/* RTC Power management control */ + +static int ticnt_save; + +static int puv3_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* save RTAR for anyone using periodic interrupts */ + ticnt_save = readl(RTC_RTAR); + puv3_rtc_enable(pdev, 0); + return 0; +} + +static int puv3_rtc_resume(struct platform_device *pdev) +{ + puv3_rtc_enable(pdev, 1); + writel(ticnt_save, RTC_RTAR); + return 0; +} +#else +#define puv3_rtc_suspend NULL +#define puv3_rtc_resume NULL +#endif + +static struct platform_driver puv3_rtcdrv = { + .probe = puv3_rtc_probe, + .remove = __devexit_p(puv3_rtc_remove), + .suspend = puv3_rtc_suspend, + .resume = puv3_rtc_resume, + .driver = { + .name = "PKUnity-v3-RTC", + .owner = THIS_MODULE, + } +}; + +static char __initdata banner[] = "PKUnity-v3 RTC, (c) 2009 PKUnity Co.\n"; + +static int __init puv3_rtc_init(void) +{ + printk(banner); + return platform_driver_register(&puv3_rtcdrv); +} + +static void __exit puv3_rtc_exit(void) +{ + platform_driver_unregister(&puv3_rtcdrv); +} + +module_init(puv3_rtc_init); +module_exit(puv3_rtc_exit); + +MODULE_DESCRIPTION("RTC Driver for the PKUnity v3 chip"); +MODULE_AUTHOR("Hu Dongliang"); +MODULE_LICENSE("GPL v2"); + diff --git a/arch/unicore32/kernel/setup.c b/arch/unicore32/kernel/setup.c new file mode 100644 index 00000000000..1e175a82844 --- /dev/null +++ b/arch/unicore32/kernel/setup.c @@ -0,0 +1,360 @@ +/* + * linux/arch/unicore32/kernel/setup.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/utsname.h> +#include <linux/initrd.h> +#include <linux/console.h> +#include <linux/bootmem.h> +#include <linux/seq_file.h> +#include <linux/screen_info.h> +#include <linux/init.h> +#include <linux/root_dev.h> +#include <linux/cpu.h> +#include <linux/interrupt.h> +#include <linux/smp.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/memblock.h> +#include <linux/elf.h> +#include <linux/io.h> + +#include <asm/cputype.h> +#include <asm/sections.h> +#include <asm/setup.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/traps.h> + +#include "setup.h" + +#ifndef MEM_SIZE +#define MEM_SIZE (16*1024*1024) +#endif + +struct stack { + u32 irq[3]; + u32 abt[3]; + u32 und[3]; +} ____cacheline_aligned; + +static struct stack stacks[NR_CPUS]; + +char elf_platform[ELF_PLATFORM_SIZE]; +EXPORT_SYMBOL(elf_platform); + +static char __initdata cmd_line[COMMAND_LINE_SIZE]; + +static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; + +/* + * Standard memory resources + */ +static struct resource mem_res[] = { + { + .name = "Video RAM", + .start = 0, + .end = 0, + .flags = IORESOURCE_MEM + }, + { + .name = "Kernel text", + .start = 0, + .end = 0, + .flags = IORESOURCE_MEM + }, + { + .name = "Kernel data", + .start = 0, + .end = 0, + .flags = IORESOURCE_MEM + } +}; + +#define video_ram mem_res[0] +#define kernel_code mem_res[1] +#define kernel_data mem_res[2] + +/* + * These functions re-use the assembly code in head.S, which + * already provide the required functionality. + */ +static void __init setup_processor(void) +{ + printk(KERN_DEFAULT "CPU: UniCore-II [%08x] revision %d, cr=%08lx\n", + uc32_cpuid, (int)(uc32_cpuid >> 16) & 15, cr_alignment); + + sprintf(init_utsname()->machine, "puv3"); + sprintf(elf_platform, "ucv2"); +} + +/* + * cpu_init - initialise one CPU. + * + * cpu_init sets up the per-CPU stacks. + */ +void cpu_init(void) +{ + unsigned int cpu = smp_processor_id(); + struct stack *stk = &stacks[cpu]; + + /* + * setup stacks for re-entrant exception handlers + */ + __asm__ ( + "mov.a asr, %1\n\t" + "add sp, %0, %2\n\t" + "mov.a asr, %3\n\t" + "add sp, %0, %4\n\t" + "mov.a asr, %5\n\t" + "add sp, %0, %6\n\t" + "mov.a asr, %7" + : + : "r" (stk), + "r" (PSR_R_BIT | PSR_I_BIT | INTR_MODE), + "I" (offsetof(struct stack, irq[0])), + "r" (PSR_R_BIT | PSR_I_BIT | ABRT_MODE), + "I" (offsetof(struct stack, abt[0])), + "r" (PSR_R_BIT | PSR_I_BIT | EXTN_MODE), + "I" (offsetof(struct stack, und[0])), + "r" (PSR_R_BIT | PSR_I_BIT | PRIV_MODE) + : "r30", "cc"); +} + +static int __init uc32_add_memory(unsigned long start, unsigned long size) +{ + struct membank *bank = &meminfo.bank[meminfo.nr_banks]; + + if (meminfo.nr_banks >= NR_BANKS) { + printk(KERN_CRIT "NR_BANKS too low, " + "ignoring memory at %#lx\n", start); + return -EINVAL; + } + + /* + * Ensure that start/size are aligned to a page boundary. + * Size is appropriately rounded down, start is rounded up. + */ + size -= start & ~PAGE_MASK; + + bank->start = PAGE_ALIGN(start); + bank->size = size & PAGE_MASK; + + /* + * Check whether this memory region has non-zero size or + * invalid node number. + */ + if (bank->size == 0) + return -EINVAL; + + meminfo.nr_banks++; + return 0; +} + +/* + * Pick out the memory size. We look for mem=size@start, + * where start and size are "size[KkMm]" + */ +static int __init early_mem(char *p) +{ + static int usermem __initdata = 1; + unsigned long size, start; + char *endp; + + /* + * If the user specifies memory size, we + * blow away any automatically generated + * size. + */ + if (usermem) { + usermem = 0; + meminfo.nr_banks = 0; + } + + start = PHYS_OFFSET; + size = memparse(p, &endp); + if (*endp == '@') + start = memparse(endp + 1, NULL); + + uc32_add_memory(start, size); + + return 0; +} +early_param("mem", early_mem); + +static void __init +request_standard_resources(struct meminfo *mi) +{ + struct resource *res; + int i; + + kernel_code.start = virt_to_phys(_stext); + kernel_code.end = virt_to_phys(_etext - 1); + kernel_data.start = virt_to_phys(_sdata); + kernel_data.end = virt_to_phys(_end - 1); + + for (i = 0; i < mi->nr_banks; i++) { + if (mi->bank[i].size == 0) + continue; + + res = alloc_bootmem_low(sizeof(*res)); + res->name = "System RAM"; + res->start = mi->bank[i].start; + res->end = mi->bank[i].start + mi->bank[i].size - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + + request_resource(&iomem_resource, res); + + if (kernel_code.start >= res->start && + kernel_code.end <= res->end) + request_resource(res, &kernel_code); + if (kernel_data.start >= res->start && + kernel_data.end <= res->end) + request_resource(res, &kernel_data); + } + + video_ram.start = PKUNITY_UNIGFX_MMAP_BASE; + video_ram.end = PKUNITY_UNIGFX_MMAP_BASE + PKUNITY_UNIGFX_MMAP_SIZE; + request_resource(&iomem_resource, &video_ram); +} + +static void (*init_machine)(void) __initdata; + +static int __init customize_machine(void) +{ + /* customizes platform devices, or adds new ones */ + if (init_machine) + init_machine(); + return 0; +} +arch_initcall(customize_machine); + +void __init setup_arch(char **cmdline_p) +{ + char *from = default_command_line; + + setup_processor(); + + init_mm.start_code = (unsigned long) _stext; + init_mm.end_code = (unsigned long) _etext; + init_mm.end_data = (unsigned long) _edata; + init_mm.brk = (unsigned long) _end; + + /* parse_early_param needs a boot_command_line */ + strlcpy(boot_command_line, from, COMMAND_LINE_SIZE); + + /* populate cmd_line too for later use, preserving boot_command_line */ + strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE); + *cmdline_p = cmd_line; + + parse_early_param(); + + uc32_memblock_init(&meminfo); + + paging_init(); + request_standard_resources(&meminfo); + + cpu_init(); + + /* + * Set up various architecture-specific pointers + */ + init_machine = puv3_core_init; + +#ifdef CONFIG_VT +#if defined(CONFIG_VGA_CONSOLE) + conswitchp = &vga_con; +#elif defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif +#endif + early_trap_init(); +} + +static struct cpu cpuinfo_unicore; + +static int __init topology_init(void) +{ + int i; + + for_each_possible_cpu(i) + register_cpu(&cpuinfo_unicore, i); + + return 0; +} +subsys_initcall(topology_init); + +#ifdef CONFIG_HAVE_PROC_CPU +static int __init proc_cpu_init(void) +{ + struct proc_dir_entry *res; + + res = proc_mkdir("cpu", NULL); + if (!res) + return -ENOMEM; + return 0; +} +fs_initcall(proc_cpu_init); +#endif + +static int c_show(struct seq_file *m, void *v) +{ + seq_printf(m, "Processor\t: UniCore-II rev %d (%s)\n", + (int)(uc32_cpuid >> 16) & 15, elf_platform); + + seq_printf(m, "BogoMIPS\t: %lu.%02lu\n", + loops_per_jiffy / (500000/HZ), + (loops_per_jiffy / (5000/HZ)) % 100); + + /* dump out the processor features */ + seq_puts(m, "Features\t: CMOV UC-F64"); + + seq_printf(m, "\nCPU implementer\t: 0x%02x\n", uc32_cpuid >> 24); + seq_printf(m, "CPU architecture: 2\n"); + seq_printf(m, "CPU revision\t: %d\n", (uc32_cpuid >> 16) & 15); + + seq_printf(m, "Cache type\t: write-back\n" + "Cache clean\t: cp0 c5 ops\n" + "Cache lockdown\t: not support\n" + "Cache format\t: Harvard\n"); + + seq_puts(m, "\n"); + + seq_printf(m, "Hardware\t: PKUnity v3\n"); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +static void c_stop(struct seq_file *m, void *v) +{ +} + +const struct seq_operations cpuinfo_op = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_show +}; diff --git a/arch/unicore32/kernel/setup.h b/arch/unicore32/kernel/setup.h new file mode 100644 index 00000000000..dcd1306eb5c --- /dev/null +++ b/arch/unicore32/kernel/setup.h @@ -0,0 +1,30 @@ +/* + * linux/arch/unicore32/kernel/setup.h + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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. + */ +#ifndef __UNICORE_KERNEL_SETUP_H__ +#define __UNICORE_KERNEL_SETUP_H__ + +extern void paging_init(void); +extern void puv3_core_init(void); + +extern void puv3_ps2_init(void); +extern void pci_puv3_preinit(void); +extern void __init puv3_init_gpio(void); + +extern void setup_mm_for_reboot(char mode); + +extern char __stubs_start[], __stubs_end[]; +extern char __vectors_start[], __vectors_end[]; + +extern void kernel_thread_helper(void); + +extern void __init early_signal_init(void); +#endif diff --git a/arch/unicore32/kernel/signal.c b/arch/unicore32/kernel/signal.c new file mode 100644 index 00000000000..b163fca5678 --- /dev/null +++ b/arch/unicore32/kernel/signal.c @@ -0,0 +1,494 @@ +/* + * linux/arch/unicore32/kernel/signal.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/personality.h> +#include <linux/freezer.h> +#include <linux/uaccess.h> +#include <linux/tracehook.h> +#include <linux/elf.h> +#include <linux/unistd.h> + +#include <asm/cacheflush.h> +#include <asm/ucontext.h> + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* + * For UniCore syscalls, we encode the syscall number into the instruction. + */ +#define SWI_SYS_SIGRETURN (0xff000000) /* error number for new abi */ +#define SWI_SYS_RT_SIGRETURN (0xff000000 | (__NR_rt_sigreturn)) +#define SWI_SYS_RESTART (0xff000000 | (__NR_restart_syscall)) + +#define KERN_SIGRETURN_CODE (KUSER_VECPAGE_BASE + 0x00000500) +#define KERN_RESTART_CODE (KERN_SIGRETURN_CODE + sizeof(sigreturn_codes)) + +const unsigned long sigreturn_codes[3] = { + SWI_SYS_SIGRETURN, SWI_SYS_RT_SIGRETURN, +}; + +const unsigned long syscall_restart_code[2] = { + SWI_SYS_RESTART, /* swi __NR_restart_syscall */ + 0x69efc004, /* ldr pc, [sp], #4 */ +}; + +/* + * Do a signal return; undo the signal stack. These are aligned to 64-bit. + */ +struct sigframe { + struct ucontext uc; + unsigned long retcode[2]; +}; + +struct rt_sigframe { + struct siginfo info; + struct sigframe sig; +}; + +static int restore_sigframe(struct pt_regs *regs, struct sigframe __user *sf) +{ + sigset_t set; + int err; + + err = __copy_from_user(&set, &sf->uc.uc_sigmask, sizeof(set)); + if (err == 0) { + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } + + err |= __get_user(regs->UCreg_00, &sf->uc.uc_mcontext.regs.UCreg_00); + err |= __get_user(regs->UCreg_01, &sf->uc.uc_mcontext.regs.UCreg_01); + err |= __get_user(regs->UCreg_02, &sf->uc.uc_mcontext.regs.UCreg_02); + err |= __get_user(regs->UCreg_03, &sf->uc.uc_mcontext.regs.UCreg_03); + err |= __get_user(regs->UCreg_04, &sf->uc.uc_mcontext.regs.UCreg_04); + err |= __get_user(regs->UCreg_05, &sf->uc.uc_mcontext.regs.UCreg_05); + err |= __get_user(regs->UCreg_06, &sf->uc.uc_mcontext.regs.UCreg_06); + err |= __get_user(regs->UCreg_07, &sf->uc.uc_mcontext.regs.UCreg_07); + err |= __get_user(regs->UCreg_08, &sf->uc.uc_mcontext.regs.UCreg_08); + err |= __get_user(regs->UCreg_09, &sf->uc.uc_mcontext.regs.UCreg_09); + err |= __get_user(regs->UCreg_10, &sf->uc.uc_mcontext.regs.UCreg_10); + err |= __get_user(regs->UCreg_11, &sf->uc.uc_mcontext.regs.UCreg_11); + err |= __get_user(regs->UCreg_12, &sf->uc.uc_mcontext.regs.UCreg_12); + err |= __get_user(regs->UCreg_13, &sf->uc.uc_mcontext.regs.UCreg_13); + err |= __get_user(regs->UCreg_14, &sf->uc.uc_mcontext.regs.UCreg_14); + err |= __get_user(regs->UCreg_15, &sf->uc.uc_mcontext.regs.UCreg_15); + err |= __get_user(regs->UCreg_16, &sf->uc.uc_mcontext.regs.UCreg_16); + err |= __get_user(regs->UCreg_17, &sf->uc.uc_mcontext.regs.UCreg_17); + err |= __get_user(regs->UCreg_18, &sf->uc.uc_mcontext.regs.UCreg_18); + err |= __get_user(regs->UCreg_19, &sf->uc.uc_mcontext.regs.UCreg_19); + err |= __get_user(regs->UCreg_20, &sf->uc.uc_mcontext.regs.UCreg_20); + err |= __get_user(regs->UCreg_21, &sf->uc.uc_mcontext.regs.UCreg_21); + err |= __get_user(regs->UCreg_22, &sf->uc.uc_mcontext.regs.UCreg_22); + err |= __get_user(regs->UCreg_23, &sf->uc.uc_mcontext.regs.UCreg_23); + err |= __get_user(regs->UCreg_24, &sf->uc.uc_mcontext.regs.UCreg_24); + err |= __get_user(regs->UCreg_25, &sf->uc.uc_mcontext.regs.UCreg_25); + err |= __get_user(regs->UCreg_26, &sf->uc.uc_mcontext.regs.UCreg_26); + err |= __get_user(regs->UCreg_fp, &sf->uc.uc_mcontext.regs.UCreg_fp); + err |= __get_user(regs->UCreg_ip, &sf->uc.uc_mcontext.regs.UCreg_ip); + err |= __get_user(regs->UCreg_sp, &sf->uc.uc_mcontext.regs.UCreg_sp); + err |= __get_user(regs->UCreg_lr, &sf->uc.uc_mcontext.regs.UCreg_lr); + err |= __get_user(regs->UCreg_pc, &sf->uc.uc_mcontext.regs.UCreg_pc); + err |= __get_user(regs->UCreg_asr, &sf->uc.uc_mcontext.regs.UCreg_asr); + + err |= !valid_user_regs(regs); + + return err; +} + +asmlinkage int __sys_rt_sigreturn(struct pt_regs *regs) +{ + struct rt_sigframe __user *frame; + + /* Always make any pending restarted system calls return -EINTR */ + current_thread_info()->restart_block.fn = do_no_restart_syscall; + + /* + * Since we stacked the signal on a 64-bit boundary, + * then 'sp' should be word aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (regs->UCreg_sp & 7) + goto badframe; + + frame = (struct rt_sigframe __user *)regs->UCreg_sp; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (restore_sigframe(regs, &frame->sig)) + goto badframe; + + if (do_sigaltstack(&frame->sig.uc.uc_stack, NULL, regs->UCreg_sp) + == -EFAULT) + goto badframe; + + return regs->UCreg_00; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +static int setup_sigframe(struct sigframe __user *sf, struct pt_regs *regs, + sigset_t *set) +{ + int err = 0; + + err |= __put_user(regs->UCreg_00, &sf->uc.uc_mcontext.regs.UCreg_00); + err |= __put_user(regs->UCreg_01, &sf->uc.uc_mcontext.regs.UCreg_01); + err |= __put_user(regs->UCreg_02, &sf->uc.uc_mcontext.regs.UCreg_02); + err |= __put_user(regs->UCreg_03, &sf->uc.uc_mcontext.regs.UCreg_03); + err |= __put_user(regs->UCreg_04, &sf->uc.uc_mcontext.regs.UCreg_04); + err |= __put_user(regs->UCreg_05, &sf->uc.uc_mcontext.regs.UCreg_05); + err |= __put_user(regs->UCreg_06, &sf->uc.uc_mcontext.regs.UCreg_06); + err |= __put_user(regs->UCreg_07, &sf->uc.uc_mcontext.regs.UCreg_07); + err |= __put_user(regs->UCreg_08, &sf->uc.uc_mcontext.regs.UCreg_08); + err |= __put_user(regs->UCreg_09, &sf->uc.uc_mcontext.regs.UCreg_09); + err |= __put_user(regs->UCreg_10, &sf->uc.uc_mcontext.regs.UCreg_10); + err |= __put_user(regs->UCreg_11, &sf->uc.uc_mcontext.regs.UCreg_11); + err |= __put_user(regs->UCreg_12, &sf->uc.uc_mcontext.regs.UCreg_12); + err |= __put_user(regs->UCreg_13, &sf->uc.uc_mcontext.regs.UCreg_13); + err |= __put_user(regs->UCreg_14, &sf->uc.uc_mcontext.regs.UCreg_14); + err |= __put_user(regs->UCreg_15, &sf->uc.uc_mcontext.regs.UCreg_15); + err |= __put_user(regs->UCreg_16, &sf->uc.uc_mcontext.regs.UCreg_16); + err |= __put_user(regs->UCreg_17, &sf->uc.uc_mcontext.regs.UCreg_17); + err |= __put_user(regs->UCreg_18, &sf->uc.uc_mcontext.regs.UCreg_18); + err |= __put_user(regs->UCreg_19, &sf->uc.uc_mcontext.regs.UCreg_19); + err |= __put_user(regs->UCreg_20, &sf->uc.uc_mcontext.regs.UCreg_20); + err |= __put_user(regs->UCreg_21, &sf->uc.uc_mcontext.regs.UCreg_21); + err |= __put_user(regs->UCreg_22, &sf->uc.uc_mcontext.regs.UCreg_22); + err |= __put_user(regs->UCreg_23, &sf->uc.uc_mcontext.regs.UCreg_23); + err |= __put_user(regs->UCreg_24, &sf->uc.uc_mcontext.regs.UCreg_24); + err |= __put_user(regs->UCreg_25, &sf->uc.uc_mcontext.regs.UCreg_25); + err |= __put_user(regs->UCreg_26, &sf->uc.uc_mcontext.regs.UCreg_26); + err |= __put_user(regs->UCreg_fp, &sf->uc.uc_mcontext.regs.UCreg_fp); + err |= __put_user(regs->UCreg_ip, &sf->uc.uc_mcontext.regs.UCreg_ip); + err |= __put_user(regs->UCreg_sp, &sf->uc.uc_mcontext.regs.UCreg_sp); + err |= __put_user(regs->UCreg_lr, &sf->uc.uc_mcontext.regs.UCreg_lr); + err |= __put_user(regs->UCreg_pc, &sf->uc.uc_mcontext.regs.UCreg_pc); + err |= __put_user(regs->UCreg_asr, &sf->uc.uc_mcontext.regs.UCreg_asr); + + err |= __put_user(current->thread.trap_no, + &sf->uc.uc_mcontext.trap_no); + err |= __put_user(current->thread.error_code, + &sf->uc.uc_mcontext.error_code); + err |= __put_user(current->thread.address, + &sf->uc.uc_mcontext.fault_address); + err |= __put_user(set->sig[0], &sf->uc.uc_mcontext.oldmask); + + err |= __copy_to_user(&sf->uc.uc_sigmask, set, sizeof(*set)); + + return err; +} + +static inline void __user *get_sigframe(struct k_sigaction *ka, + struct pt_regs *regs, int framesize) +{ + unsigned long sp = regs->UCreg_sp; + void __user *frame; + + /* + * This is the X/Open sanctioned signal stack switching. + */ + if ((ka->sa.sa_flags & SA_ONSTACK) && !sas_ss_flags(sp)) + sp = current->sas_ss_sp + current->sas_ss_size; + + /* + * ATPCS B01 mandates 8-byte alignment + */ + frame = (void __user *)((sp - framesize) & ~7); + + /* + * Check that we can actually write to the signal frame. + */ + if (!access_ok(VERIFY_WRITE, frame, framesize)) + frame = NULL; + + return frame; +} + +static int setup_return(struct pt_regs *regs, struct k_sigaction *ka, + unsigned long __user *rc, void __user *frame, int usig) +{ + unsigned long handler = (unsigned long)ka->sa.sa_handler; + unsigned long retcode; + unsigned long asr = regs->UCreg_asr & ~PSR_f; + + unsigned int idx = 0; + + if (ka->sa.sa_flags & SA_SIGINFO) + idx += 1; + + if (__put_user(sigreturn_codes[idx], rc) || + __put_user(sigreturn_codes[idx+1], rc+1)) + return 1; + + retcode = KERN_SIGRETURN_CODE + (idx << 2); + + regs->UCreg_00 = usig; + regs->UCreg_sp = (unsigned long)frame; + regs->UCreg_lr = retcode; + regs->UCreg_pc = handler; + regs->UCreg_asr = asr; + + return 0; +} + +static int setup_frame(int usig, struct k_sigaction *ka, + sigset_t *set, struct pt_regs *regs) +{ + struct sigframe __user *frame = get_sigframe(ka, regs, sizeof(*frame)); + int err = 0; + + if (!frame) + return 1; + + /* + * Set uc.uc_flags to a value which sc.trap_no would never have. + */ + err |= __put_user(0x5ac3c35a, &frame->uc.uc_flags); + + err |= setup_sigframe(frame, regs, set); + if (err == 0) + err |= setup_return(regs, ka, frame->retcode, frame, usig); + + return err; +} + +static int setup_rt_frame(int usig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs *regs) +{ + struct rt_sigframe __user *frame = + get_sigframe(ka, regs, sizeof(*frame)); + stack_t stack; + int err = 0; + + if (!frame) + return 1; + + err |= copy_siginfo_to_user(&frame->info, info); + + err |= __put_user(0, &frame->sig.uc.uc_flags); + err |= __put_user(NULL, &frame->sig.uc.uc_link); + + memset(&stack, 0, sizeof(stack)); + stack.ss_sp = (void __user *)current->sas_ss_sp; + stack.ss_flags = sas_ss_flags(regs->UCreg_sp); + stack.ss_size = current->sas_ss_size; + err |= __copy_to_user(&frame->sig.uc.uc_stack, &stack, sizeof(stack)); + + err |= setup_sigframe(&frame->sig, regs, set); + if (err == 0) + err |= setup_return(regs, ka, frame->sig.retcode, frame, usig); + + if (err == 0) { + /* + * For realtime signals we must also set the second and third + * arguments for the signal handler. + */ + regs->UCreg_01 = (unsigned long)&frame->info; + regs->UCreg_02 = (unsigned long)&frame->sig.uc; + } + + return err; +} + +static inline void setup_syscall_restart(struct pt_regs *regs) +{ + regs->UCreg_00 = regs->UCreg_ORIG_00; + regs->UCreg_pc -= 4; +} + +/* + * OK, we're invoking a handler + */ +static int handle_signal(unsigned long sig, struct k_sigaction *ka, + siginfo_t *info, sigset_t *oldset, + struct pt_regs *regs, int syscall) +{ + struct thread_info *thread = current_thread_info(); + struct task_struct *tsk = current; + int usig = sig; + int ret; + + /* + * If we were from a system call, check for system call restarting... + */ + if (syscall) { + switch (regs->UCreg_00) { + case -ERESTART_RESTARTBLOCK: + case -ERESTARTNOHAND: + regs->UCreg_00 = -EINTR; + break; + case -ERESTARTSYS: + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->UCreg_00 = -EINTR; + break; + } + /* fallthrough */ + case -ERESTARTNOINTR: + setup_syscall_restart(regs); + } + } + + /* + * translate the signal + */ + if (usig < 32 && thread->exec_domain + && thread->exec_domain->signal_invmap) + usig = thread->exec_domain->signal_invmap[usig]; + + /* + * Set up the stack frame + */ + if (ka->sa.sa_flags & SA_SIGINFO) + ret = setup_rt_frame(usig, ka, info, oldset, regs); + else + ret = setup_frame(usig, ka, oldset, regs); + + /* + * Check that the resulting registers are actually sane. + */ + ret |= !valid_user_regs(regs); + + if (ret != 0) { + force_sigsegv(sig, tsk); + return ret; + } + + /* + * Block the signal if we were successful. + */ + spin_lock_irq(&tsk->sighand->siglock); + sigorsets(&tsk->blocked, &tsk->blocked, + &ka->sa.sa_mask); + if (!(ka->sa.sa_flags & SA_NODEFER)) + sigaddset(&tsk->blocked, sig); + recalc_sigpending(); + spin_unlock_irq(&tsk->sighand->siglock); + + return 0; +} + +/* + * Note that 'init' is a special process: it doesn't get signals it doesn't + * want to handle. Thus you cannot kill init even with a SIGKILL even by + * mistake. + * + * Note that we go through the signals twice: once to check the signals that + * the kernel can handle, and then we build all the user-level signal handling + * stack-frames in one go after that. + */ +static void do_signal(struct pt_regs *regs, int syscall) +{ + struct k_sigaction ka; + siginfo_t info; + int signr; + + /* + * We want the common case to go fast, which + * is why we may in certain cases get here from + * kernel mode. Just return without doing anything + * if so. + */ + if (!user_mode(regs)) + return; + + if (try_to_freeze()) + goto no_signal; + + signr = get_signal_to_deliver(&info, &ka, regs, NULL); + if (signr > 0) { + sigset_t *oldset; + + if (test_thread_flag(TIF_RESTORE_SIGMASK)) + oldset = ¤t->saved_sigmask; + else + oldset = ¤t->blocked; + if (handle_signal(signr, &ka, &info, oldset, regs, syscall) + == 0) { + /* + * A signal was successfully delivered; the saved + * sigmask will have been stored in the signal frame, + * and will be restored by sigreturn, so we can simply + * clear the TIF_RESTORE_SIGMASK flag. + */ + if (test_thread_flag(TIF_RESTORE_SIGMASK)) + clear_thread_flag(TIF_RESTORE_SIGMASK); + } + return; + } + + no_signal: + /* + * No signal to deliver to the process - restart the syscall. + */ + if (syscall) { + if (regs->UCreg_00 == -ERESTART_RESTARTBLOCK) { + u32 __user *usp; + + regs->UCreg_sp -= 4; + usp = (u32 __user *)regs->UCreg_sp; + + if (put_user(regs->UCreg_pc, usp) == 0) { + regs->UCreg_pc = KERN_RESTART_CODE; + } else { + regs->UCreg_sp += 4; + force_sigsegv(0, current); + } + } + if (regs->UCreg_00 == -ERESTARTNOHAND || + regs->UCreg_00 == -ERESTARTSYS || + regs->UCreg_00 == -ERESTARTNOINTR) { + setup_syscall_restart(regs); + } + + /* If there's no signal to deliver, we just put the saved + * sigmask back. + */ + if (test_thread_flag(TIF_RESTORE_SIGMASK)) { + clear_thread_flag(TIF_RESTORE_SIGMASK); + sigprocmask(SIG_SETMASK, ¤t->saved_sigmask, NULL); + } + } +} + +asmlinkage void do_notify_resume(struct pt_regs *regs, + unsigned int thread_flags, int syscall) +{ + if (thread_flags & _TIF_SIGPENDING) + do_signal(regs, syscall); + + if (thread_flags & _TIF_NOTIFY_RESUME) { + clear_thread_flag(TIF_NOTIFY_RESUME); + tracehook_notify_resume(regs); + if (current->replacement_session_keyring) + key_replace_session_keyring(); + } +} + +/* + * Copy signal return handlers into the vector page, and + * set sigreturn to be a pointer to these. + */ +void __init early_signal_init(void) +{ + memcpy((void *)kuser_vecpage_to_vectors(KERN_SIGRETURN_CODE), + sigreturn_codes, sizeof(sigreturn_codes)); + memcpy((void *)kuser_vecpage_to_vectors(KERN_RESTART_CODE), + syscall_restart_code, sizeof(syscall_restart_code)); + /* Need not to flush icache, since early_trap_init will do it last. */ +} diff --git a/arch/unicore32/kernel/sleep.S b/arch/unicore32/kernel/sleep.S new file mode 100644 index 00000000000..607a104aec5 --- /dev/null +++ b/arch/unicore32/kernel/sleep.S @@ -0,0 +1,202 @@ +/* + * linux/arch/unicore32/kernel/sleep.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <mach/hardware.h> + + .text + +pkunity_cpu_save_cp: + + @ get coprocessor registers + + movc r3, p0.c7, #0 @ PID + movc r4, p0.c2, #0 @ translation table base addr + movc r5, p0.c1, #0 @ control reg + + + @ store them plus current virtual stack ptr on stack + mov r6, sp + stm.w (r3 - r6), [sp-] + + mov pc, lr + +pkunity_cpu_save_sp: + @ preserve phys address of stack + mov r0, sp + stw.w lr, [sp+], #-4 + b.l sleep_phys_sp + ldw r1, =sleep_save_sp + stw r0, [r1] + ldw.w pc, [sp]+, #4 + +/* + * puv3_cpu_suspend() + * + * Forces CPU into sleep state. + * + * r0 = value for PWRMODE M field for desired sleep state + */ + +ENTRY(puv3_cpu_suspend) + stm.w (r16 - r27, lr), [sp-] @ save registers on stack + stm.w (r4 - r15), [sp-] @ save registers on stack + +#ifdef CONFIG_UNICORE_FPU_F64 + sfm.w (f0 - f7 ), [sp-] + sfm.w (f8 - f15), [sp-] + sfm.w (f16 - f23), [sp-] + sfm.w (f24 - f31), [sp-] + cff r4, s31 + stm.w (r4), [sp-] +#endif + b.l pkunity_cpu_save_cp + + b.l pkunity_cpu_save_sp + + @ clean data cache + mov r1, #0 + movc p0.c5, r1, #14 + nop + nop + nop + nop + + + + @ DDR2 BaseAddr + ldw r0, =(PKUNITY_DDR2CTRL_BASE) + + @ PM BaseAddr + ldw r1, =(PKUNITY_PM_BASE) + + @ set PLL_SYS_CFG reg, 275 + movl r6, #0x00002401 + stw r6, [r1+], #0x18 + @ set PLL_DDR_CFG reg, 66MHz + movl r6, #0x00100c00 + stw r6, [r1+], #0x1c + + @ set wake up source + movl r8, #0x800001ff @ epip4d + stw r8, [r1+], #0xc + + @ set PGSR + movl r5, #0x40000 + stw r5, [r1+], #0x10 + + @ prepare DDR2 refresh settings + ldw r5, [r0+], #0x24 + or r5, r5, #0x00000001 + + @ prepare PMCR for PLL changing + movl r6, #0xc + + @ prepare for closing PLL + movl r7, #0x1 + + @ prepare sleep mode + mov r8, #0x1 + +@ movl r0, 0x11111111 +@ put_word_ocd r0 + b pkunity_cpu_do_suspend + + .ltorg + .align 5 +pkunity_cpu_do_suspend: + b 101f + @ put DDR2 into self-refresh +100: stw r5, [r0+], #0x24 + @ change PLL + stw r6, [r1] + b 1f + + .ltorg + .align 5 +101: b 102f + @ wait for PLL changing complete +1: ldw r6, [r1+], #0x44 + csub.a r6, #0x1 + bne 1b + b 2f + + .ltorg + .align 5 +102: b 100b + @ close PLL +2: stw r7, [r1+], #0x4 + @ enter sleep mode + stw r8, [r1] +3: b 3b + + + + +/* + * puv3_cpu_resume() + * + * entry point from bootloader into kernel during resume + * + * Note: Yes, part of the following code is located into the .data section. + * This is to allow sleep_save_sp to be accessed with a relative load + * while we can't rely on any MMU translation. We could have put + * sleep_save_sp in the .text section as well, but some setups might + * insist on it to be truly read-only. + */ + + .data + .align 5 +ENTRY(puv3_cpu_resume) +@ movl r0, 0x20202020 +@ put_word_ocd r0 + + ldw r0, sleep_save_sp @ stack phys addr + ldw r2, =resume_after_mmu @ its absolute virtual address + ldm (r3 - r6), [r0]+ @ CP regs + virt stack ptr + mov sp, r6 @ CP regs + virt stack ptr + + mov r1, #0 + movc p0.c6, r1, #6 @ invalidate I & D TLBs + movc p0.c5, r1, #28 @ invalidate I & D caches, BTB + + movc p0.c7, r3, #0 @ PID + movc p0.c2, r4, #0 @ translation table base addr + movc p0.c1, r5, #0 @ control reg, turn on mmu + nop + jump r2 + nop + nop + nop + nop + nop + +sleep_save_sp: + .word 0 @ preserve stack phys ptr here + + .text +resume_after_mmu: +@ movl r0, 0x30303030 +@ put_word_ocd r0 + +#ifdef CONFIG_UNICORE_FPU_F64 + lfm.w (f0 - f7 ), [sp]+ + lfm.w (f8 - f15), [sp]+ + lfm.w (f16 - f23), [sp]+ + lfm.w (f24 - f31), [sp]+ + ldm.w (r4), [sp]+ + ctf r4, s31 +#endif + ldm.w (r4 - r15), [sp]+ @ restore registers from stack + ldm.w (r16 - r27, pc), [sp]+ @ return to caller diff --git a/arch/unicore32/kernel/stacktrace.c b/arch/unicore32/kernel/stacktrace.c new file mode 100644 index 00000000000..b34030bdabe --- /dev/null +++ b/arch/unicore32/kernel/stacktrace.c @@ -0,0 +1,131 @@ +/* + * linux/arch/unicore32/kernel/stacktrace.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/stacktrace.h> + +#include <asm/stacktrace.h> + +#if defined(CONFIG_FRAME_POINTER) +/* + * Unwind the current stack frame and store the new register values in the + * structure passed as argument. Unwinding is equivalent to a function return, + * hence the new PC value rather than LR should be used for backtrace. + * + * With framepointer enabled, a simple function prologue looks like this: + * mov ip, sp + * stmdb sp!, {fp, ip, lr, pc} + * sub fp, ip, #4 + * + * A simple function epilogue looks like this: + * ldm sp, {fp, sp, pc} + * + * Note that with framepointer enabled, even the leaf functions have the same + * prologue and epilogue, therefore we can ignore the LR value in this case. + */ +int notrace unwind_frame(struct stackframe *frame) +{ + unsigned long high, low; + unsigned long fp = frame->fp; + + /* only go to a higher address on the stack */ + low = frame->sp; + high = ALIGN(low, THREAD_SIZE); + + /* check current frame pointer is within bounds */ + if (fp < (low + 12) || fp + 4 >= high) + return -EINVAL; + + /* restore the registers from the stack frame */ + frame->fp = *(unsigned long *)(fp - 12); + frame->sp = *(unsigned long *)(fp - 8); + frame->pc = *(unsigned long *)(fp - 4); + + return 0; +} +#endif + +void notrace walk_stackframe(struct stackframe *frame, + int (*fn)(struct stackframe *, void *), void *data) +{ + while (1) { + int ret; + + if (fn(frame, data)) + break; + ret = unwind_frame(frame); + if (ret < 0) + break; + } +} +EXPORT_SYMBOL(walk_stackframe); + +#ifdef CONFIG_STACKTRACE +struct stack_trace_data { + struct stack_trace *trace; + unsigned int no_sched_functions; + unsigned int skip; +}; + +static int save_trace(struct stackframe *frame, void *d) +{ + struct stack_trace_data *data = d; + struct stack_trace *trace = data->trace; + unsigned long addr = frame->pc; + + if (data->no_sched_functions && in_sched_functions(addr)) + return 0; + if (data->skip) { + data->skip--; + return 0; + } + + trace->entries[trace->nr_entries++] = addr; + + return trace->nr_entries >= trace->max_entries; +} + +void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) +{ + struct stack_trace_data data; + struct stackframe frame; + + data.trace = trace; + data.skip = trace->skip; + + if (tsk != current) { + data.no_sched_functions = 1; + frame.fp = thread_saved_fp(tsk); + frame.sp = thread_saved_sp(tsk); + frame.lr = 0; /* recovered from the stack */ + frame.pc = thread_saved_pc(tsk); + } else { + register unsigned long current_sp asm("sp"); + + data.no_sched_functions = 0; + frame.fp = (unsigned long)__builtin_frame_address(0); + frame.sp = current_sp; + frame.lr = (unsigned long)__builtin_return_address(0); + frame.pc = (unsigned long)save_stack_trace_tsk; + } + + walk_stackframe(&frame, save_trace, &data); + if (trace->nr_entries < trace->max_entries) + trace->entries[trace->nr_entries++] = ULONG_MAX; +} + +void save_stack_trace(struct stack_trace *trace) +{ + save_stack_trace_tsk(current, trace); +} +EXPORT_SYMBOL_GPL(save_stack_trace); +#endif diff --git a/arch/unicore32/kernel/sys.c b/arch/unicore32/kernel/sys.c new file mode 100644 index 00000000000..3afe60a39ac --- /dev/null +++ b/arch/unicore32/kernel/sys.c @@ -0,0 +1,126 @@ +/* + * linux/arch/unicore32/kernel/sys.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sem.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/stat.h> +#include <linux/syscalls.h> +#include <linux/mman.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/ipc.h> +#include <linux/uaccess.h> + +#include <asm/syscalls.h> +#include <asm/cacheflush.h> + +/* Clone a task - this clones the calling program thread. + * This is called indirectly via a small wrapper + */ +asmlinkage long __sys_clone(unsigned long clone_flags, unsigned long newsp, + void __user *parent_tid, void __user *child_tid, + struct pt_regs *regs) +{ + if (!newsp) + newsp = regs->UCreg_sp; + + return do_fork(clone_flags, newsp, regs, 0, + parent_tid, child_tid); +} + +/* sys_execve() executes a new program. + * This is called indirectly via a small wrapper + */ +asmlinkage long __sys_execve(const char __user *filename, + const char __user *const __user *argv, + const char __user *const __user *envp, + struct pt_regs *regs) +{ + int error; + char *fn; + + fn = getname(filename); + error = PTR_ERR(fn); + if (IS_ERR(fn)) + goto out; + error = do_execve(fn, argv, envp, regs); + putname(fn); +out: + return error; +} + +int kernel_execve(const char *filename, + const char *const argv[], + const char *const envp[]) +{ + struct pt_regs regs; + int ret; + + memset(®s, 0, sizeof(struct pt_regs)); + ret = do_execve(filename, + (const char __user *const __user *)argv, + (const char __user *const __user *)envp, ®s); + if (ret < 0) + goto out; + + /* + * Save argc to the register structure for userspace. + */ + regs.UCreg_00 = ret; + + /* + * We were successful. We won't be returning to our caller, but + * instead to user space by manipulating the kernel stack. + */ + asm("add r0, %0, %1\n\t" + "mov r1, %2\n\t" + "mov r2, %3\n\t" + "mov r22, #0\n\t" /* not a syscall */ + "mov r23, %0\n\t" /* thread structure */ + "b.l memmove\n\t" /* copy regs to top of stack */ + "mov sp, r0\n\t" /* reposition stack pointer */ + "b ret_to_user" + : + : "r" (current_thread_info()), + "Ir" (THREAD_START_SP - sizeof(regs)), + "r" (®s), + "Ir" (sizeof(regs)) + : "r0", "r1", "r2", "r3", "ip", "lr", "memory"); + + out: + return ret; +} +EXPORT_SYMBOL(kernel_execve); + +/* Note: used by the compat code even in 64-bit Linux. */ +SYSCALL_DEFINE6(mmap2, unsigned long, addr, unsigned long, len, + unsigned long, prot, unsigned long, flags, + unsigned long, fd, unsigned long, off_4k) +{ + return sys_mmap_pgoff(addr, len, prot, flags, fd, + off_4k); +} + +/* Provide the actual syscall number to call mapping. */ +#undef __SYSCALL +#define __SYSCALL(nr, call) [nr] = (call), + +/* Note that we don't include <linux/unistd.h> but <asm/unistd.h> */ +void *sys_call_table[__NR_syscalls] = { + [0 ... __NR_syscalls-1] = sys_ni_syscall, +#include <asm/unistd.h> +}; diff --git a/arch/unicore32/kernel/time.c b/arch/unicore32/kernel/time.c new file mode 100644 index 00000000000..080710c0924 --- /dev/null +++ b/arch/unicore32/kernel/time.c @@ -0,0 +1,143 @@ +/* + * linux/arch/unicore32/kernel/time.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/timex.h> +#include <linux/clockchips.h> + +#include <mach/hardware.h> + +#define MIN_OSCR_DELTA 2 + +static irqreturn_t puv3_ost0_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *c = dev_id; + + /* Disarm the compare/match, signal the event. */ + writel(readl(OST_OIER) & ~OST_OIER_E0, OST_OIER); + writel(readl(OST_OSSR) & ~OST_OSSR_M0, OST_OSSR); + c->event_handler(c); + + return IRQ_HANDLED; +} + +static int +puv3_osmr0_set_next_event(unsigned long delta, struct clock_event_device *c) +{ + unsigned long next, oscr; + + writel(readl(OST_OIER) | OST_OIER_E0, OST_OIER); + next = readl(OST_OSCR) + delta; + writel(next, OST_OSMR0); + oscr = readl(OST_OSCR); + + return (signed)(next - oscr) <= MIN_OSCR_DELTA ? -ETIME : 0; +} + +static void +puv3_osmr0_set_mode(enum clock_event_mode mode, struct clock_event_device *c) +{ + switch (mode) { + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + writel(readl(OST_OIER) & ~OST_OIER_E0, OST_OIER); + writel(readl(OST_OSSR) & ~OST_OSSR_M0, OST_OSSR); + break; + + case CLOCK_EVT_MODE_RESUME: + case CLOCK_EVT_MODE_PERIODIC: + break; + } +} + +static struct clock_event_device ckevt_puv3_osmr0 = { + .name = "osmr0", + .features = CLOCK_EVT_FEAT_ONESHOT, + .rating = 200, + .set_next_event = puv3_osmr0_set_next_event, + .set_mode = puv3_osmr0_set_mode, +}; + +static cycle_t puv3_read_oscr(struct clocksource *cs) +{ + return readl(OST_OSCR); +} + +static struct clocksource cksrc_puv3_oscr = { + .name = "oscr", + .rating = 200, + .read = puv3_read_oscr, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static struct irqaction puv3_timer_irq = { + .name = "ost0", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = puv3_ost0_interrupt, + .dev_id = &ckevt_puv3_osmr0, +}; + +void __init time_init(void) +{ + writel(0, OST_OIER); /* disable any timer interrupts */ + writel(0, OST_OSSR); /* clear status on all timers */ + + clockevents_calc_mult_shift(&ckevt_puv3_osmr0, CLOCK_TICK_RATE, 5); + + ckevt_puv3_osmr0.max_delta_ns = + clockevent_delta2ns(0x7fffffff, &ckevt_puv3_osmr0); + ckevt_puv3_osmr0.min_delta_ns = + clockevent_delta2ns(MIN_OSCR_DELTA * 2, &ckevt_puv3_osmr0) + 1; + ckevt_puv3_osmr0.cpumask = cpumask_of(0); + + setup_irq(IRQ_TIMER0, &puv3_timer_irq); + + clocksource_register_hz(&cksrc_puv3_oscr, CLOCK_TICK_RATE); + clockevents_register_device(&ckevt_puv3_osmr0); +} + +#ifdef CONFIG_PM +unsigned long osmr[4], oier; + +void puv3_timer_suspend(void) +{ + osmr[0] = readl(OST_OSMR0); + osmr[1] = readl(OST_OSMR1); + osmr[2] = readl(OST_OSMR2); + osmr[3] = readl(OST_OSMR3); + oier = readl(OST_OIER); +} + +void puv3_timer_resume(void) +{ + writel(0, OST_OSSR); + writel(osmr[0], OST_OSMR0); + writel(osmr[1], OST_OSMR1); + writel(osmr[2], OST_OSMR2); + writel(osmr[3], OST_OSMR3); + writel(oier, OST_OIER); + + /* + * OSMR0 is the system timer: make sure OSCR is sufficiently behind + */ + writel(readl(OST_OSMR0) - LATCH, OST_OSCR); +} +#else +void puv3_timer_suspend(void) { }; +void puv3_timer_resume(void) { }; +#endif + diff --git a/arch/unicore32/kernel/traps.c b/arch/unicore32/kernel/traps.c new file mode 100644 index 00000000000..25abbb10172 --- /dev/null +++ b/arch/unicore32/kernel/traps.c @@ -0,0 +1,333 @@ +/* + * linux/arch/unicore32/kernel/traps.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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. + * + * 'traps.c' handles hardware exceptions after we have saved some state. + * Mostly a debugging aid, but will probably kill the offending process. + */ +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/spinlock.h> +#include <linux/personality.h> +#include <linux/kallsyms.h> +#include <linux/kdebug.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/hardirq.h> +#include <linux/init.h> +#include <linux/uaccess.h> +#include <linux/atomic.h> +#include <linux/unistd.h> + +#include <asm/cacheflush.h> +#include <asm/system.h> +#include <asm/traps.h> + +#include "setup.h" + +static void dump_mem(const char *, const char *, unsigned long, unsigned long); + +void dump_backtrace_entry(unsigned long where, + unsigned long from, unsigned long frame) +{ +#ifdef CONFIG_KALLSYMS + printk(KERN_DEFAULT "[<%08lx>] (%pS) from [<%08lx>] (%pS)\n", + where, (void *)where, from, (void *)from); +#else + printk(KERN_DEFAULT "Function entered at [<%08lx>] from [<%08lx>]\n", + where, from); +#endif +} + +/* + * Stack pointers should always be within the kernels view of + * physical memory. If it is not there, then we can't dump + * out any information relating to the stack. + */ +static int verify_stack(unsigned long sp) +{ + if (sp < PAGE_OFFSET || + (sp > (unsigned long)high_memory && high_memory != NULL)) + return -EFAULT; + + return 0; +} + +/* + * Dump out the contents of some memory nicely... + */ +static void dump_mem(const char *lvl, const char *str, unsigned long bottom, + unsigned long top) +{ + unsigned long first; + mm_segment_t fs; + int i; + + /* + * We need to switch to kernel mode so that we can use __get_user + * to safely read from kernel space. Note that we now dump the + * code first, just in case the backtrace kills us. + */ + fs = get_fs(); + set_fs(KERNEL_DS); + + printk(KERN_DEFAULT "%s%s(0x%08lx to 0x%08lx)\n", + lvl, str, bottom, top); + + for (first = bottom & ~31; first < top; first += 32) { + unsigned long p; + char str[sizeof(" 12345678") * 8 + 1]; + + memset(str, ' ', sizeof(str)); + str[sizeof(str) - 1] = '\0'; + + for (p = first, i = 0; i < 8 && p < top; i++, p += 4) { + if (p >= bottom && p < top) { + unsigned long val; + if (__get_user(val, (unsigned long *)p) == 0) + sprintf(str + i * 9, " %08lx", val); + else + sprintf(str + i * 9, " ????????"); + } + } + printk(KERN_DEFAULT "%s%04lx:%s\n", lvl, first & 0xffff, str); + } + + set_fs(fs); +} + +static void dump_instr(const char *lvl, struct pt_regs *regs) +{ + unsigned long addr = instruction_pointer(regs); + const int width = 8; + mm_segment_t fs; + char str[sizeof("00000000 ") * 5 + 2 + 1], *p = str; + int i; + + /* + * We need to switch to kernel mode so that we can use __get_user + * to safely read from kernel space. Note that we now dump the + * code first, just in case the backtrace kills us. + */ + fs = get_fs(); + set_fs(KERNEL_DS); + + for (i = -4; i < 1; i++) { + unsigned int val, bad; + + bad = __get_user(val, &((u32 *)addr)[i]); + + if (!bad) + p += sprintf(p, i == 0 ? "(%0*x) " : "%0*x ", + width, val); + else { + p += sprintf(p, "bad PC value"); + break; + } + } + printk(KERN_DEFAULT "%sCode: %s\n", lvl, str); + + set_fs(fs); +} + +static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk) +{ + unsigned int fp, mode; + int ok = 1; + + printk(KERN_DEFAULT "Backtrace: "); + + if (!tsk) + tsk = current; + + if (regs) { + fp = regs->UCreg_fp; + mode = processor_mode(regs); + } else if (tsk != current) { + fp = thread_saved_fp(tsk); + mode = 0x10; + } else { + asm("mov %0, fp" : "=r" (fp) : : "cc"); + mode = 0x10; + } + + if (!fp) { + printk("no frame pointer"); + ok = 0; + } else if (verify_stack(fp)) { + printk("invalid frame pointer 0x%08x", fp); + ok = 0; + } else if (fp < (unsigned long)end_of_stack(tsk)) + printk("frame pointer underflow"); + printk("\n"); + + if (ok) + c_backtrace(fp, mode); +} + +void dump_stack(void) +{ + dump_backtrace(NULL, NULL); +} +EXPORT_SYMBOL(dump_stack); + +void show_stack(struct task_struct *tsk, unsigned long *sp) +{ + dump_backtrace(NULL, tsk); + barrier(); +} + +static int __die(const char *str, int err, struct thread_info *thread, + struct pt_regs *regs) +{ + struct task_struct *tsk = thread->task; + static int die_counter; + int ret; + + printk(KERN_EMERG "Internal error: %s: %x [#%d]\n", + str, err, ++die_counter); + sysfs_printk_last_file(); + + /* trap and error numbers are mostly meaningless on UniCore */ + ret = notify_die(DIE_OOPS, str, regs, err, tsk->thread.trap_no, \ + SIGSEGV); + if (ret == NOTIFY_STOP) + return ret; + + print_modules(); + __show_regs(regs); + printk(KERN_EMERG "Process %.*s (pid: %d, stack limit = 0x%p)\n", + TASK_COMM_LEN, tsk->comm, task_pid_nr(tsk), thread + 1); + + if (!user_mode(regs) || in_interrupt()) { + dump_mem(KERN_EMERG, "Stack: ", regs->UCreg_sp, + THREAD_SIZE + (unsigned long)task_stack_page(tsk)); + dump_backtrace(regs, tsk); + dump_instr(KERN_EMERG, regs); + } + + return ret; +} + +DEFINE_SPINLOCK(die_lock); + +/* + * This function is protected against re-entrancy. + */ +void die(const char *str, struct pt_regs *regs, int err) +{ + struct thread_info *thread = current_thread_info(); + int ret; + + oops_enter(); + + spin_lock_irq(&die_lock); + console_verbose(); + bust_spinlocks(1); + ret = __die(str, err, thread, regs); + + bust_spinlocks(0); + add_taint(TAINT_DIE); + spin_unlock_irq(&die_lock); + oops_exit(); + + if (in_interrupt()) + panic("Fatal exception in interrupt"); + if (panic_on_oops) + panic("Fatal exception"); + if (ret != NOTIFY_STOP) + do_exit(SIGSEGV); +} + +void uc32_notify_die(const char *str, struct pt_regs *regs, + struct siginfo *info, unsigned long err, unsigned long trap) +{ + if (user_mode(regs)) { + current->thread.error_code = err; + current->thread.trap_no = trap; + + force_sig_info(info->si_signo, info, current); + } else + die(str, regs, err); +} + +/* + * bad_mode handles the impossible case in the vectors. If you see one of + * these, then it's extremely serious, and could mean you have buggy hardware. + * It never returns, and never tries to sync. We hope that we can at least + * dump out some state information... + */ +asmlinkage void bad_mode(struct pt_regs *regs, unsigned int reason) +{ + console_verbose(); + + printk(KERN_CRIT "Bad mode detected with reason 0x%x\n", reason); + + die("Oops - bad mode", regs, 0); + local_irq_disable(); + panic("bad mode"); +} + +void __pte_error(const char *file, int line, unsigned long val) +{ + printk(KERN_DEFAULT "%s:%d: bad pte %08lx.\n", file, line, val); +} + +void __pmd_error(const char *file, int line, unsigned long val) +{ + printk(KERN_DEFAULT "%s:%d: bad pmd %08lx.\n", file, line, val); +} + +void __pgd_error(const char *file, int line, unsigned long val) +{ + printk(KERN_DEFAULT "%s:%d: bad pgd %08lx.\n", file, line, val); +} + +asmlinkage void __div0(void) +{ + printk(KERN_DEFAULT "Division by zero in kernel.\n"); + dump_stack(); +} +EXPORT_SYMBOL(__div0); + +void abort(void) +{ + BUG(); + + /* if that doesn't kill us, halt */ + panic("Oops failed to kill thread"); +} +EXPORT_SYMBOL(abort); + +void __init trap_init(void) +{ + return; +} + +void __init early_trap_init(void) +{ + unsigned long vectors = VECTORS_BASE; + + /* + * Copy the vectors, stubs (in entry-unicore.S) + * into the vector page, mapped at 0xffff0000, and ensure these + * are visible to the instruction stream. + */ + memcpy((void *)vectors, + __vectors_start, + __vectors_end - __vectors_start); + memcpy((void *)vectors + 0x200, + __stubs_start, + __stubs_end - __stubs_start); + + early_signal_init(); + + flush_icache_range(vectors, vectors + PAGE_SIZE); +} diff --git a/arch/unicore32/kernel/vmlinux.lds.S b/arch/unicore32/kernel/vmlinux.lds.S new file mode 100644 index 00000000000..0b4eb89729e --- /dev/null +++ b/arch/unicore32/kernel/vmlinux.lds.S @@ -0,0 +1,61 @@ +/* + * linux/arch/unicore32/kernel/vmlinux.lds.S + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Copyright (C) 2001-2010 GUAN Xue-tao + * + * 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 <asm-generic/vmlinux.lds.h> +#include <asm/thread_info.h> +#include <asm/memory.h> +#include <asm/page.h> + +OUTPUT_ARCH(unicore32) +ENTRY(stext) + +jiffies = jiffies_64; + +SECTIONS +{ + . = PAGE_OFFSET + KERNEL_IMAGE_START; + + _text = .; + __init_begin = .; + HEAD_TEXT_SECTION + INIT_TEXT_SECTION(PAGE_SIZE) + INIT_DATA_SECTION(16) + PERCPU(PAGE_SIZE) + __init_end = .; + + _stext = .; + .text : { /* Real text segment */ + TEXT_TEXT + SCHED_TEXT + LOCK_TEXT + + *(.fixup) + *(.gnu.warning) + } + _etext = .; + + _sdata = .; + RO_DATA_SECTION(PAGE_SIZE) + RW_DATA_SECTION(32, PAGE_SIZE, THREAD_SIZE) + _edata = .; + + EXCEPTION_TABLE(32) + NOTES + + BSS_SECTION(0, 0, 0) + _end = .; + + STABS_DEBUG + DWARF_DEBUG + + DISCARDS /* Exit code and data */ +} |