mirror of https://gitee.com/openkylin/linux.git
unicore32 additional architecture files: pm related files
This patch adds pm related files, including hibernate and sleep supports. Signed-off-by: Guan Xuetao <gxt@mprc.pku.edu.cn> Acked-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
parent
f864d2f830
commit
6490988286
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* linux/arch/unicore32/include/asm/suspend.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_SUSPEND_H__
|
||||
#define __UNICORE_SUSPEND_H__
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
static inline int arch_prepare_suspend(void) { return 0; }
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
|
||||
struct swsusp_arch_regs {
|
||||
struct cpu_context_save cpu_context; /* cpu context */
|
||||
#ifdef CONFIG_UNICORE_FPU_F64
|
||||
struct fp_state fpstate __attribute__((aligned(8)));
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* __UNICORE_SUSPEND_H__ */
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* linux/arch/unicore/include/mach/pm.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 __PUV3_PM_H__
|
||||
#define __PUV3_PM_H__
|
||||
|
||||
#include <linux/suspend.h>
|
||||
|
||||
struct puv3_cpu_pm_fns {
|
||||
int save_count;
|
||||
void (*save)(unsigned long *);
|
||||
void (*restore)(unsigned long *);
|
||||
int (*valid)(suspend_state_t state);
|
||||
void (*enter)(suspend_state_t state);
|
||||
int (*prepare)(void);
|
||||
void (*finish)(void);
|
||||
};
|
||||
|
||||
extern struct puv3_cpu_pm_fns *puv3_cpu_pm_fns;
|
||||
|
||||
/* sleep.S */
|
||||
extern void puv3_cpu_suspend(unsigned int);
|
||||
|
||||
extern void puv3_cpu_resume(void);
|
||||
|
||||
extern int puv3_pm_enter(suspend_state_t state);
|
||||
|
||||
/* Defined in hibernate_asm.S */
|
||||
extern int restore_image(pgd_t *resume_pg_dir, struct pbe *restore_pblist);
|
||||
|
||||
/* References to section boundaries */
|
||||
extern const void __nosave_begin, __nosave_end;
|
||||
|
||||
extern struct pbe *restore_pblist;
|
||||
#endif
|
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* 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 <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 (PM_PLLVGACFG == pll_vgacfg)
|
||||
return 0;
|
||||
|
||||
/* set pll vga cfg reg. */
|
||||
PM_PLLVGACFG = pll_vgacfg;
|
||||
|
||||
PM_PMCR = PM_PMCR_CFBVGA;
|
||||
while ((PM_PLLDFCDONE & PM_PLLDFCDONE_VGADFC)
|
||||
!= PM_PLLDFCDONE_VGADFC)
|
||||
udelay(100); /* about 1ms */
|
||||
|
||||
/* set div cfg reg. */
|
||||
PM_PCGR |= PM_PCGR_VGACLK;
|
||||
|
||||
PM_DIVCFG = (PM_DIVCFG & ~PM_DIVCFG_VGACLK_MASK)
|
||||
| PM_DIVCFG_VGACLK(pll_vgadiv);
|
||||
|
||||
PM_SWRESET |= PM_SWRESET_VGADIV;
|
||||
while ((PM_SWRESET & PM_SWRESET_VGADIV) == PM_SWRESET_VGADIV)
|
||||
udelay(100); /* 65536 bclk32, about 320us */
|
||||
|
||||
PM_PCGR &= ~PM_PCGR_VGACLK;
|
||||
}
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
if (clk == &clk_mclk_clk) {
|
||||
u32 pll_rate, divstatus = PM_DIVSTATUS;
|
||||
int ret, i;
|
||||
|
||||
/* lookup mclk_clk_table */
|
||||
ret = -EINVAL;
|
||||
for (i = 0; i < ARRAY_SIZE(mclk_clk_table); i++) {
|
||||
if (rate == mclk_clk_table[i].mrate) {
|
||||
pll_rate = mclk_clk_table[i].prate;
|
||||
clk_mclk_clk.rate = mclk_clk_table[i].mrate;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (clk_mclk_clk.rate)
|
||||
clk_bclk32_clk.rate = clk_mclk_clk.rate
|
||||
/ (((divstatus & 0x0000f000) >> 12) + 1);
|
||||
|
||||
/* set pll sys cfg reg. */
|
||||
PM_PLLSYSCFG = pll_rate;
|
||||
|
||||
PM_PMCR = PM_PMCR_CFBSYS;
|
||||
while ((PM_PLLDFCDONE & PM_PLLDFCDONE_SYSDFC)
|
||||
!= PM_PLLDFCDONE_SYSDFC)
|
||||
udelay(100);
|
||||
/* about 1ms */
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_set_rate);
|
||||
|
||||
int clk_register(struct clk *clk)
|
||||
{
|
||||
mutex_lock(&clocks_mutex);
|
||||
list_add(&clk->node, &clocks);
|
||||
mutex_unlock(&clocks_mutex);
|
||||
printk(KERN_DEFAULT "PKUnity PM: %s %lu.%02luM\n", clk->name,
|
||||
(clk->rate)/1000000, (clk->rate)/10000 % 100);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_register);
|
||||
|
||||
void clk_unregister(struct clk *clk)
|
||||
{
|
||||
mutex_lock(&clocks_mutex);
|
||||
list_del(&clk->node);
|
||||
mutex_unlock(&clocks_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(clk_unregister);
|
||||
|
||||
struct {
|
||||
unsigned long prate;
|
||||
unsigned long rate;
|
||||
} pllrate_table[] = {
|
||||
{.prate = 0x00002001, .rate = 250000000},
|
||||
{.prate = 0x00104801, .rate = 250000000},
|
||||
{.prate = 0x00104C01, .rate = 262500000},
|
||||
{.prate = 0x00002401, .rate = 275000000},
|
||||
{.prate = 0x00105001, .rate = 275000000},
|
||||
{.prate = 0x00105401, .rate = 287500000},
|
||||
{.prate = 0x00002801, .rate = 300000000},
|
||||
{.prate = 0x00105801, .rate = 300000000},
|
||||
{.prate = 0x00105C01, .rate = 312500000},
|
||||
{.prate = 0x00002C01, .rate = 325000000},
|
||||
{.prate = 0x00106001, .rate = 325000000},
|
||||
{.prate = 0x00106401, .rate = 337500000},
|
||||
{.prate = 0x00003001, .rate = 350000000},
|
||||
{.prate = 0x00106801, .rate = 350000000},
|
||||
{.prate = 0x00106C01, .rate = 362500000},
|
||||
{.prate = 0x00003401, .rate = 375000000},
|
||||
{.prate = 0x00107001, .rate = 375000000},
|
||||
{.prate = 0x00107401, .rate = 387500000},
|
||||
{.prate = 0x00003801, .rate = 400000000},
|
||||
{.prate = 0x00107801, .rate = 400000000},
|
||||
{.prate = 0x00107C01, .rate = 412500000},
|
||||
{.prate = 0x00003C01, .rate = 425000000},
|
||||
{.prate = 0x00108001, .rate = 425000000},
|
||||
{.prate = 0x00108401, .rate = 437500000},
|
||||
{.prate = 0x00004001, .rate = 450000000},
|
||||
{.prate = 0x00108801, .rate = 450000000},
|
||||
{.prate = 0x00108C01, .rate = 462500000},
|
||||
{.prate = 0x00004401, .rate = 475000000},
|
||||
{.prate = 0x00109001, .rate = 475000000},
|
||||
{.prate = 0x00109401, .rate = 487500000},
|
||||
{.prate = 0x00004801, .rate = 500000000},
|
||||
{.prate = 0x00109801, .rate = 500000000},
|
||||
{.prate = 0x00104C00, .rate = 525000000},
|
||||
{.prate = 0x00002400, .rate = 550000000},
|
||||
{.prate = 0x00105000, .rate = 550000000},
|
||||
{.prate = 0x00105400, .rate = 575000000},
|
||||
{.prate = 0x00002800, .rate = 600000000},
|
||||
{.prate = 0x00105800, .rate = 600000000},
|
||||
{.prate = 0x00105C00, .rate = 625000000},
|
||||
{.prate = 0x00002C00, .rate = 650000000},
|
||||
{.prate = 0x00106000, .rate = 650000000},
|
||||
{.prate = 0x00106400, .rate = 675000000},
|
||||
{.prate = 0x00003000, .rate = 700000000},
|
||||
{.prate = 0x00106800, .rate = 700000000},
|
||||
{.prate = 0x00106C00, .rate = 725000000},
|
||||
{.prate = 0x00003400, .rate = 750000000},
|
||||
{.prate = 0x00107000, .rate = 750000000},
|
||||
{.prate = 0x00107400, .rate = 775000000},
|
||||
{.prate = 0x00003800, .rate = 800000000},
|
||||
{.prate = 0x00107800, .rate = 800000000},
|
||||
{.prate = 0x00107C00, .rate = 825000000},
|
||||
{.prate = 0x00003C00, .rate = 850000000},
|
||||
{.prate = 0x00108000, .rate = 850000000},
|
||||
{.prate = 0x00108400, .rate = 875000000},
|
||||
{.prate = 0x00004000, .rate = 900000000},
|
||||
{.prate = 0x00108800, .rate = 900000000},
|
||||
{.prate = 0x00108C00, .rate = 925000000},
|
||||
{.prate = 0x00004400, .rate = 950000000},
|
||||
{.prate = 0x00109000, .rate = 950000000},
|
||||
{.prate = 0x00109400, .rate = 975000000},
|
||||
{.prate = 0x00004800, .rate = 1000000000},
|
||||
{.prate = 0x00109800, .rate = 1000000000},
|
||||
};
|
||||
|
||||
struct {
|
||||
unsigned long prate;
|
||||
unsigned long drate;
|
||||
} pddr_table[] = {
|
||||
{.prate = 0x00100800, .drate = 44236800},
|
||||
{.prate = 0x00100C00, .drate = 66355200},
|
||||
{.prate = 0x00101000, .drate = 88473600},
|
||||
{.prate = 0x00101400, .drate = 110592000},
|
||||
{.prate = 0x00101800, .drate = 132710400},
|
||||
{.prate = 0x00101C01, .drate = 154828800},
|
||||
{.prate = 0x00102001, .drate = 176947200},
|
||||
{.prate = 0x00102401, .drate = 199065600},
|
||||
{.prate = 0x00102801, .drate = 221184000},
|
||||
{.prate = 0x00102C01, .drate = 243302400},
|
||||
{.prate = 0x00103001, .drate = 265420800},
|
||||
{.prate = 0x00103401, .drate = 287539200},
|
||||
{.prate = 0x00103801, .drate = 309657600},
|
||||
{.prate = 0x00103C01, .drate = 331776000},
|
||||
{.prate = 0x00104001, .drate = 353894400},
|
||||
};
|
||||
|
||||
static int __init clk_init(void)
|
||||
{
|
||||
#ifdef CONFIG_PUV3_PM
|
||||
u32 pllrate, divstatus = PM_DIVSTATUS;
|
||||
u32 pcgr_val = PM_PCGR;
|
||||
int i;
|
||||
|
||||
pcgr_val |= PM_PCGR_BCLKMME | PM_PCGR_BCLKH264E | PM_PCGR_BCLKH264D
|
||||
| PM_PCGR_HECLK | PM_PCGR_HDCLK;
|
||||
PM_PCGR = pcgr_val;
|
||||
|
||||
pllrate = 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 = 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 = 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);
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* linux/arch/unicore32/kernel/cpu-ucv2.c: clock scaling for the UniCore-II
|
||||
*
|
||||
* Code specific to PKUnity SoC and UniCore ISA
|
||||
*
|
||||
* Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn>
|
||||
* Copyright (C) 2001-2010 Guan Xuetao
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/cpufreq.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
|
||||
static struct cpufreq_driver ucv2_driver;
|
||||
|
||||
/* make sure that only the "userspace" governor is run
|
||||
* -- anything else wouldn't make sense on this platform, anyway.
|
||||
*/
|
||||
int ucv2_verify_speed(struct cpufreq_policy *policy)
|
||||
{
|
||||
if (policy->cpu)
|
||||
return -EINVAL;
|
||||
|
||||
cpufreq_verify_within_limits(policy,
|
||||
policy->cpuinfo.min_freq, policy->cpuinfo.max_freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int ucv2_getspeed(unsigned int cpu)
|
||||
{
|
||||
struct clk *mclk = clk_get(NULL, "MAIN_CLK");
|
||||
|
||||
if (cpu)
|
||||
return 0;
|
||||
return clk_get_rate(mclk)/1000;
|
||||
}
|
||||
|
||||
static int ucv2_target(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq,
|
||||
unsigned int relation)
|
||||
{
|
||||
unsigned int cur = ucv2_getspeed(0);
|
||||
struct cpufreq_freqs freqs;
|
||||
struct clk *mclk = clk_get(NULL, "MAIN_CLK");
|
||||
|
||||
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
|
||||
|
||||
if (!clk_set_rate(mclk, target_freq * 1000)) {
|
||||
freqs.old = cur;
|
||||
freqs.new = target_freq;
|
||||
freqs.cpu = 0;
|
||||
}
|
||||
|
||||
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init ucv2_cpu_init(struct cpufreq_policy *policy)
|
||||
{
|
||||
if (policy->cpu != 0)
|
||||
return -EINVAL;
|
||||
policy->cur = ucv2_getspeed(0);
|
||||
policy->min = policy->cpuinfo.min_freq = 250000;
|
||||
policy->max = policy->cpuinfo.max_freq = 1000000;
|
||||
policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct cpufreq_driver ucv2_driver = {
|
||||
.flags = CPUFREQ_STICKY,
|
||||
.verify = ucv2_verify_speed,
|
||||
.target = ucv2_target,
|
||||
.get = ucv2_getspeed,
|
||||
.init = ucv2_cpu_init,
|
||||
.name = "UniCore-II",
|
||||
};
|
||||
|
||||
static int __init ucv2_cpufreq_init(void)
|
||||
{
|
||||
return cpufreq_register_driver(&ucv2_driver);
|
||||
}
|
||||
|
||||
arch_initcall(ucv2_cpufreq_init);
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* linux/arch/unicore32/kernel/hibernate.c
|
||||
*
|
||||
* Code specific to PKUnity SoC and UniCore ISA
|
||||
*
|
||||
* Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn>
|
||||
* Copyright (C) 2001-2010 Guan Xuetao
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/bootmem.h>
|
||||
|
||||
#include <asm/system.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/pgalloc.h>
|
||||
#include <asm/suspend.h>
|
||||
|
||||
#include "mach/pm.h"
|
||||
|
||||
/* Pointer to the temporary resume page tables */
|
||||
pgd_t *resume_pg_dir;
|
||||
|
||||
struct swsusp_arch_regs swsusp_arch_regs_cpu0;
|
||||
|
||||
/*
|
||||
* Create a middle page table on a resume-safe page and put a pointer to it in
|
||||
* the given global directory entry. This only returns the gd entry
|
||||
* in non-PAE compilation mode, since the middle layer is folded.
|
||||
*/
|
||||
static pmd_t *resume_one_md_table_init(pgd_t *pgd)
|
||||
{
|
||||
pud_t *pud;
|
||||
pmd_t *pmd_table;
|
||||
|
||||
pud = pud_offset(pgd, 0);
|
||||
pmd_table = pmd_offset(pud, 0);
|
||||
|
||||
return pmd_table;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a page table on a resume-safe page and place a pointer to it in
|
||||
* a middle page directory entry.
|
||||
*/
|
||||
static pte_t *resume_one_page_table_init(pmd_t *pmd)
|
||||
{
|
||||
if (pmd_none(*pmd)) {
|
||||
pte_t *page_table = (pte_t *)get_safe_page(GFP_ATOMIC);
|
||||
if (!page_table)
|
||||
return NULL;
|
||||
|
||||
set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_KERNEL_TABLE));
|
||||
|
||||
BUG_ON(page_table != pte_offset_kernel(pmd, 0));
|
||||
|
||||
return page_table;
|
||||
}
|
||||
|
||||
return pte_offset_kernel(pmd, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* This maps the physical memory to kernel virtual address space, a total
|
||||
* of max_low_pfn pages, by creating page tables starting from address
|
||||
* PAGE_OFFSET. The page tables are allocated out of resume-safe pages.
|
||||
*/
|
||||
static int resume_physical_mapping_init(pgd_t *pgd_base)
|
||||
{
|
||||
unsigned long pfn;
|
||||
pgd_t *pgd;
|
||||
pmd_t *pmd;
|
||||
pte_t *pte;
|
||||
int pgd_idx, pmd_idx;
|
||||
|
||||
pgd_idx = pgd_index(PAGE_OFFSET);
|
||||
pgd = pgd_base + pgd_idx;
|
||||
pfn = 0;
|
||||
|
||||
for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
|
||||
pmd = resume_one_md_table_init(pgd);
|
||||
if (!pmd)
|
||||
return -ENOMEM;
|
||||
|
||||
if (pfn >= max_low_pfn)
|
||||
continue;
|
||||
|
||||
for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD; pmd++, pmd_idx++) {
|
||||
pte_t *max_pte;
|
||||
|
||||
if (pfn >= max_low_pfn)
|
||||
break;
|
||||
|
||||
/* Map with normal page tables.
|
||||
* NOTE: We can mark everything as executable here
|
||||
*/
|
||||
pte = resume_one_page_table_init(pmd);
|
||||
if (!pte)
|
||||
return -ENOMEM;
|
||||
|
||||
max_pte = pte + PTRS_PER_PTE;
|
||||
for (; pte < max_pte; pte++, pfn++) {
|
||||
if (pfn >= max_low_pfn)
|
||||
break;
|
||||
|
||||
set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void resume_init_first_level_page_table(pgd_t *pg_dir)
|
||||
{
|
||||
}
|
||||
|
||||
int swsusp_arch_resume(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
resume_pg_dir = (pgd_t *)get_safe_page(GFP_ATOMIC);
|
||||
if (!resume_pg_dir)
|
||||
return -ENOMEM;
|
||||
|
||||
resume_init_first_level_page_table(resume_pg_dir);
|
||||
error = resume_physical_mapping_init(resume_pg_dir);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
/* We have got enough memory and from now on we cannot recover */
|
||||
restore_image(resume_pg_dir, restore_pblist);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* pfn_is_nosave - check if given pfn is in the 'nosave' section
|
||||
*/
|
||||
|
||||
int pfn_is_nosave(unsigned long pfn)
|
||||
{
|
||||
unsigned long begin_pfn = __pa(&__nosave_begin) >> PAGE_SHIFT;
|
||||
unsigned long end_pfn = PAGE_ALIGN(__pa(&__nosave_end)) >> PAGE_SHIFT;
|
||||
|
||||
return (pfn >= begin_pfn) && (pfn < end_pfn);
|
||||
}
|
||||
|
||||
void save_processor_state(void)
|
||||
{
|
||||
}
|
||||
|
||||
void restore_processor_state(void)
|
||||
{
|
||||
local_flush_tlb_all();
|
||||
}
|
|
@ -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
|
|
@ -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);
|
|
@ -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, =io_p2v(PKUNITY_DDR2CTRL_BASE)
|
||||
|
||||
@ PM BaseAddr
|
||||
ldw r1, =io_p2v(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
|
Loading…
Reference in New Issue