diff options
Diffstat (limited to 'arch/unicore32/kernel')
33 files changed, 6268 insertions, 0 deletions
diff --git a/arch/unicore32/kernel/Makefile b/arch/unicore32/kernel/Makefile new file mode 100644 index 00000000000..607a72f2ae3 --- /dev/null +++ b/arch/unicore32/kernel/Makefile @@ -0,0 +1,30 @@ +# +# 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_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_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) 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..b1ca775f6f6 --- /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 = readl(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. */ +		writel(pll_rate, PM_PLLSYSCFG); + +		writel(PM_PMCR_CFBSYS, PM_PMCR); +		while ((readl(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/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..ed2d4d78d9c --- /dev/null +++ b/arch/unicore32/kernel/dma.c @@ -0,0 +1,182 @@ +/* + * 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/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..f2f6323c8d6 --- /dev/null +++ b/arch/unicore32/kernel/early_printk.c @@ -0,0 +1,49 @@ +/* + * 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, +}; + +static int __init setup_early_printk(char *buf) +{ +	if (!buf || early_console) +		return 0; + +	early_console = &early_ocd_console; +	if (strstr(buf, "keep")) +		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..bcdedd80890 --- /dev/null +++ b/arch/unicore32/kernel/entry.S @@ -0,0 +1,805 @@ +/* + * 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 +	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 +	b	ret_slow_syscall +ENDPROC(ret_from_fork) + +ENTRY(ret_from_kernel_thread) +	b.l	schedule_tail +	mov	r0, r5 +	adr	lr, ret_slow_syscall +	mov	pc, r4 +ENDPROC(ret_from_kernel_thread) + +/*============================================================================= + * 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_rt_sigreturn) +		add	r0, sp, #S_OFF +		mov	why, #0		@ prevent syscall restart handling +		b	__sys_rt_sigreturn +ENDPROC(sys_rt_sigreturn) + +	__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..e8f0b98c02e --- /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/hwdef-copro.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 structure 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..d75ef8b6cb5 --- /dev/null +++ b/arch/unicore32/kernel/hibernate.c @@ -0,0 +1,159 @@ +/* + *  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/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/irq.c b/arch/unicore32/kernel/irq.c new file mode 100644 index 00000000000..0be5ccd7ccd --- /dev/null +++ b/arch/unicore32/kernel/irq.c @@ -0,0 +1,376 @@ +/* + * 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/syscore_ops.h> +#include <linux/gpio.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(void) +{ +	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 void puv3_irq_resume(void) +{ +	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); +	} +} + +static struct syscore_ops puv3_irq_syscore_ops = { +	.suspend	= puv3_irq_suspend, +	.resume		= puv3_irq_resume, +}; + +static int __init puv3_irq_init_syscore(void) +{ +	register_syscore_ops(&puv3_irq_syscore_ops); +	return 0; +} + +device_initcall(puv3_irq_init_syscore); + +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++) { +		irq_set_chip(irq, &puv3_low_gpio_chip); +		irq_set_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++) { +		irq_set_chip(irq, &puv3_normal_chip); +		irq_set_handler(irq, handle_level_irq); +		irq_modify_status(irq, +			IRQ_NOREQUEST | IRQ_NOAUTOEN, +			IRQ_NOPROBE); +	} + +	for (irq = IRQ_GPIO0; irq <= IRQ_GPIO27; irq++) { +		irq_set_chip(irq, &puv3_high_gpio_chip); +		irq_set_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 +	 */ +	irq_set_chip(IRQ_GPIOHIGH, &puv3_normal_chip); +	irq_set_chained_handler(IRQ_GPIOHIGH, puv3_gpio_handler); + +#ifdef CONFIG_PUV3_GPIO +	puv3_init_gpio(); +#endif +} + +/* + * 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..0323528a80f --- /dev/null +++ b/arch/unicore32/kernel/ksyms.c @@ -0,0 +1,61 @@ +/* + * 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 "ksyms.h" + +EXPORT_SYMBOL(find_first_bit); +EXPORT_SYMBOL(find_first_zero_bit); +EXPORT_SYMBOL(find_next_zero_bit); +EXPORT_SYMBOL(find_next_bit); + +	/* platform dependent support */ +EXPORT_SYMBOL(__udelay); +EXPORT_SYMBOL(__const_udelay); + +	/* 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(__ashldi3); +EXPORT_SYMBOL(__ashrdi3); +EXPORT_SYMBOL(__divsi3); +EXPORT_SYMBOL(__lshrdi3); +EXPORT_SYMBOL(__modsi3); +EXPORT_SYMBOL(__ucmpdi2); +EXPORT_SYMBOL(__udivsi3); +EXPORT_SYMBOL(__umodsi3); + diff --git a/arch/unicore32/kernel/ksyms.h b/arch/unicore32/kernel/ksyms.h new file mode 100644 index 00000000000..31472ad9467 --- /dev/null +++ b/arch/unicore32/kernel/ksyms.h @@ -0,0 +1,13 @@ +/* + * 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 __ucmpdi2(void); +extern void __udivsi3(void); +extern void __umodsi3(void); diff --git a/arch/unicore32/kernel/module.c b/arch/unicore32/kernel/module.c new file mode 100644 index 00000000000..dc41f6dfedb --- /dev/null +++ b/arch/unicore32/kernel/module.c @@ -0,0 +1,109 @@ +/* + * 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) +{ +	return __vmalloc_node_range(size, 1, MODULES_VADDR, MODULES_END, +				GFP_KERNEL, PAGE_KERNEL_EXEC, NUMA_NO_NODE, +				__builtin_return_address(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; +} diff --git a/arch/unicore32/kernel/pci.c b/arch/unicore32/kernel/pci.c new file mode 100644 index 00000000000..374a055a8e6 --- /dev/null +++ b/arch/unicore32/kernel/pci.c @@ -0,0 +1,393 @@ +/* + * 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; + +#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(const 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; +} + +/* + * 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 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"); +} +EXPORT_SYMBOL(pcibios_fixup_bus); + +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 (!pci_has_flag(PCI_PROBE_ONLY)) { +		/* +		 * Size the bridge windows. +		 */ +		pci_bus_size_bridges(puv3_bus); + +		/* +		 * Assign resources. +		 */ +		pci_bus_assign_resources(puv3_bus); +	} + +	return 0; +} +subsys_initcall(pci_common_init); + +char * __init pcibios_setup(char *str) +{ +	if (!strcmp(str, "debug")) { +		debug_pci = 1; +		return NULL; +	} else if (!strcmp(str, "firmware")) { +		pci_add_flags(PCI_PROBE_ONLY); +		return NULL; +	} +	return str; +} + +void pcibios_set_master(struct pci_dev *dev) +{ +	/* No special bus mastering setup handling */ +} + +/* + * 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..b008e996146 --- /dev/null +++ b/arch/unicore32/kernel/process.c @@ -0,0 +1,328 @@ +/* + * 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/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" +}; + +void arch_cpu_idle(void) +{ +	cpu_do_idle(); +	local_irq_enable(); +} + +void machine_halt(void) +{ +	gpio_set_value(GPO_SOFT_OFF, 0); +} + +/* + * Function pointers to optional machine specific functions + */ +void (*pm_power_off)(void) = NULL; +EXPORT_SYMBOL(pm_power_off); + +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(); + +	/* 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 == REBOOT_SOFT) { +		/* 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]; + +	show_regs_print_info(KERN_DEFAULT); +	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"); +asmlinkage void ret_from_kernel_thread(void) __asm__("ret_from_kernel_thread"); + +int +copy_thread(unsigned long clone_flags, unsigned long stack_start, +	    unsigned long stk_sz, struct task_struct *p) +{ +	struct thread_info *thread = task_thread_info(p); +	struct pt_regs *childregs = task_pt_regs(p); + +	memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save)); +	thread->cpu_context.sp = (unsigned long)childregs; +	if (unlikely(p->flags & PF_KTHREAD)) { +		thread->cpu_context.pc = (unsigned long)ret_from_kernel_thread; +		thread->cpu_context.r4 = stack_start; +		thread->cpu_context.r5 = stk_sz; +		memset(childregs, 0, sizeof(struct pt_regs)); +	} else { +		thread->cpu_context.pc = (unsigned long)ret_from_fork; +		*childregs = *current_pt_regs(); +		childregs->UCreg_00 = 0; +		if (stack_start) +			childregs->UCreg_sp = stack_start; + +		if (clone_flags & CLONE_SETTLS) +			childregs->UCreg_16 = childregs->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); + +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_DONTEXPAND | VM_DONTDUMP, +				       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..254adeecc61 --- /dev/null +++ b/arch/unicore32/kernel/puv3-core.c @@ -0,0 +1,279 @@ +/* + *  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/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, +	}, +}; + +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..0c6618e7189 --- /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/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, +	.enable_gpio	= -1, +}; + +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_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_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/setup.c b/arch/unicore32/kernel/setup.c new file mode 100644 index 00000000000..3fa317f9612 --- /dev/null +++ b/arch/unicore32/kernel/setup.c @@ -0,0 +1,354 @@ +/* + * 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 <asm/memblock.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]; + +#ifdef CONFIG_VGA_CONSOLE +struct screen_info screen_info; +#endif + +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 = "Kernel code", +		.start = 0, +		.end = 0, +		.flags = IORESOURCE_MEM +	}, +	{ +		.name = "Kernel data", +		.start = 0, +		.end = 0, +		.flags = IORESOURCE_MEM +	} +}; + +#define kernel_code mem_res[0] +#define kernel_data mem_res[1] + +/* + * 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); +	} +} + +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..f5c51b85ad2 --- /dev/null +++ b/arch/unicore32/kernel/setup.h @@ -0,0 +1,39 @@ +/* + * 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__ + +#include <asm/hwdef-copro.h> + +extern void paging_init(void); +extern void puv3_core_init(void); +extern void cpu_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(void); + +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); + +extern asmlinkage void __backtrace(void); +extern asmlinkage void c_backtrace(unsigned long fp, int pmode); + +extern void __show_regs(struct pt_regs *); + +#endif diff --git a/arch/unicore32/kernel/signal.c b/arch/unicore32/kernel/signal.c new file mode 100644 index 00000000000..6905f0ebdc7 --- /dev/null +++ b/arch/unicore32/kernel/signal.c @@ -0,0 +1,441 @@ +/* + * 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/uaccess.h> +#include <linux/tracehook.h> +#include <linux/elf.h> +#include <linux/unistd.h> + +#include <asm/cacheflush.h> +#include <asm/ucontext.h> + +/* + * 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) +		set_current_blocked(&set); + +	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 (restore_altstack(&frame->sig.uc.uc_stack)) +		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)); +	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); +	err |= __save_altstack(&frame->sig.uc.uc_stack, regs->UCreg_sp); +	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 void handle_signal(unsigned long sig, struct k_sigaction *ka, +	      siginfo_t *info, struct pt_regs *regs, int syscall) +{ +	struct thread_info *thread = current_thread_info(); +	struct task_struct *tsk = current; +	sigset_t *oldset = sigmask_to_save(); +	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; +	} + +	signal_delivered(sig, info, ka, regs, 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; + +	signr = get_signal_to_deliver(&info, &ka, regs, NULL); +	if (signr > 0) { +		handle_signal(signr, &ka, &info, regs, syscall); +		return; +	} + +	/* +	 * 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. +	 */ +	restore_saved_sigmask(); +} + +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); +	} +} + +/* + * 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..f9e86253931 --- /dev/null +++ b/arch/unicore32/kernel/sys.c @@ -0,0 +1,40 @@ +/* + * 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> + +/* Provide the actual syscall number to call mapping. */ +#undef __SYSCALL +#define __SYSCALL(nr, call)	[nr] = (call), + +#define sys_mmap2 sys_mmap_pgoff +/* 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..d3824b2ff64 --- /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_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..c54e32410ea --- /dev/null +++ b/arch/unicore32/kernel/traps.c @@ -0,0 +1,324 @@ +/* + * 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/atomic.h> +#include <linux/unistd.h> + +#include <asm/cacheflush.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 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); + +	/* 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, LOCKDEP_NOW_UNRELIABLE); +	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..77e407e49a6 --- /dev/null +++ b/arch/unicore32/kernel/vmlinux.lds.S @@ -0,0 +1,62 @@ +/* + * 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> +#include <asm/cache.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_SECTION(L1_CACHE_BYTES) +	__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(L1_CACHE_BYTES, PAGE_SIZE, THREAD_SIZE) +	_edata = .; + +	EXCEPTION_TABLE(L1_CACHE_BYTES) +	NOTES + +	BSS_SECTION(0, 0, 0) +	_end = .; + +	STABS_DEBUG +	DWARF_DEBUG + +	DISCARDS		/* Exit code and data */ +}  | 
