Merge branch 'pm-domains' into pm-for-linus
* pm-domains: PM / shmobile: Allow the A4R domain to be turned off at run time PM / input / touchscreen: Make st1232 use device PM QoS constraints PM / QoS: Introduce dev_pm_qos_add_ancestor_request() PM / shmobile: Remove the stay_on flag from SH7372's PM domains PM / shmobile: Don't include SH7372's INTCS in syscore suspend/resume PM / shmobile: Add support for the sh7372 A4S power domain / sleep mode ARM: S3C64XX: Implement basic power domain support PM / shmobile: Use common always on power domain governor PM / Domains: Provide an always on power domain governor PM / Domains: Fix default system suspend/resume operations PM / Domains: Make it possible to assign names to generic PM domains PM / Domains: fix compilation failure for CONFIG_PM_GENERIC_DOMAINS unset PM / Domains: Automatically update overoptimistic latency information PM / Domains: Add default power off governor function (v4) PM / Domains: Add device stop governor function (v4) PM / Domains: Rework system suspend callback routines (v2) PM / Domains: Introduce "save/restore state" device callbacks PM / Domains: Make it possible to use per-device domain callbacks
This commit is contained in:
commit
6d10463b2f
@ -8,6 +8,7 @@ config PLAT_S3C64XX
|
||||
bool
|
||||
depends on ARCH_S3C64XX
|
||||
select SAMSUNG_WAKEMASK
|
||||
select PM_GENERIC_DOMAINS
|
||||
default y
|
||||
help
|
||||
Base platform code for any Samsung S3C64XX device
|
||||
|
@ -704,7 +704,7 @@ static void __init crag6410_machine_init(void)
|
||||
|
||||
regulator_has_full_constraints();
|
||||
|
||||
s3c_pm_init();
|
||||
s3c64xx_pm_init();
|
||||
}
|
||||
|
||||
MACHINE_START(WLF_CRAGG_6410, "Wolfson Cragganmore 6410")
|
||||
|
@ -17,10 +17,12 @@
|
||||
#include <linux/serial_core.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/pm_domain.h>
|
||||
|
||||
#include <mach/map.h>
|
||||
#include <mach/irqs.h>
|
||||
|
||||
#include <plat/devs.h>
|
||||
#include <plat/pm.h>
|
||||
#include <plat/wakeup-mask.h>
|
||||
|
||||
@ -31,6 +33,148 @@
|
||||
#include <mach/regs-gpio-memport.h>
|
||||
#include <mach/regs-modem.h>
|
||||
|
||||
struct s3c64xx_pm_domain {
|
||||
char *const name;
|
||||
u32 ena;
|
||||
u32 pwr_stat;
|
||||
struct generic_pm_domain pd;
|
||||
};
|
||||
|
||||
static int s3c64xx_pd_off(struct generic_pm_domain *domain)
|
||||
{
|
||||
struct s3c64xx_pm_domain *pd;
|
||||
u32 val;
|
||||
|
||||
pd = container_of(domain, struct s3c64xx_pm_domain, pd);
|
||||
|
||||
val = __raw_readl(S3C64XX_NORMAL_CFG);
|
||||
val &= ~(pd->ena);
|
||||
__raw_writel(val, S3C64XX_NORMAL_CFG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c64xx_pd_on(struct generic_pm_domain *domain)
|
||||
{
|
||||
struct s3c64xx_pm_domain *pd;
|
||||
u32 val;
|
||||
long retry = 1000000L;
|
||||
|
||||
pd = container_of(domain, struct s3c64xx_pm_domain, pd);
|
||||
|
||||
val = __raw_readl(S3C64XX_NORMAL_CFG);
|
||||
val |= pd->ena;
|
||||
__raw_writel(val, S3C64XX_NORMAL_CFG);
|
||||
|
||||
/* Not all domains provide power status readback */
|
||||
if (pd->pwr_stat) {
|
||||
do {
|
||||
cpu_relax();
|
||||
if (__raw_readl(S3C64XX_BLK_PWR_STAT) & pd->pwr_stat)
|
||||
break;
|
||||
} while (retry--);
|
||||
|
||||
if (!retry) {
|
||||
pr_err("Failed to start domain %s\n", pd->name);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_irom = {
|
||||
.name = "IROM",
|
||||
.ena = S3C64XX_NORMALCFG_IROM_ON,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_etm = {
|
||||
.name = "ETM",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_ETM_ON,
|
||||
.pwr_stat = S3C64XX_BLKPWRSTAT_ETM,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_s = {
|
||||
.name = "S",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_S_ON,
|
||||
.pwr_stat = S3C64XX_BLKPWRSTAT_S,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_f = {
|
||||
.name = "F",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_F_ON,
|
||||
.pwr_stat = S3C64XX_BLKPWRSTAT_F,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_p = {
|
||||
.name = "P",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_P_ON,
|
||||
.pwr_stat = S3C64XX_BLKPWRSTAT_P,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_i = {
|
||||
.name = "I",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_I_ON,
|
||||
.pwr_stat = S3C64XX_BLKPWRSTAT_I,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_g = {
|
||||
.name = "G",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_G_ON,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain s3c64xx_pm_v = {
|
||||
.name = "V",
|
||||
.ena = S3C64XX_NORMALCFG_DOMAIN_V_ON,
|
||||
.pwr_stat = S3C64XX_BLKPWRSTAT_V,
|
||||
.pd = {
|
||||
.power_off = s3c64xx_pd_off,
|
||||
.power_on = s3c64xx_pd_on,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain *s3c64xx_always_on_pm_domains[] = {
|
||||
&s3c64xx_pm_irom,
|
||||
};
|
||||
|
||||
static struct s3c64xx_pm_domain *s3c64xx_pm_domains[] = {
|
||||
&s3c64xx_pm_etm,
|
||||
&s3c64xx_pm_g,
|
||||
&s3c64xx_pm_v,
|
||||
&s3c64xx_pm_i,
|
||||
&s3c64xx_pm_p,
|
||||
&s3c64xx_pm_s,
|
||||
&s3c64xx_pm_f,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_S3C_PM_DEBUG_LED_SMDK
|
||||
void s3c_pm_debug_smdkled(u32 set, u32 clear)
|
||||
{
|
||||
@ -89,6 +233,8 @@ static struct sleep_save misc_save[] = {
|
||||
|
||||
SAVE_ITEM(S3C64XX_SDMA_SEL),
|
||||
SAVE_ITEM(S3C64XX_MODEM_MIFPCON),
|
||||
|
||||
SAVE_ITEM(S3C64XX_NORMAL_CFG),
|
||||
};
|
||||
|
||||
void s3c_pm_configure_extint(void)
|
||||
@ -179,7 +325,26 @@ static void s3c64xx_pm_prepare(void)
|
||||
__raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT);
|
||||
}
|
||||
|
||||
static int s3c64xx_pm_init(void)
|
||||
int __init s3c64xx_pm_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
s3c_pm_init();
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s3c64xx_always_on_pm_domains); i++)
|
||||
pm_genpd_init(&s3c64xx_always_on_pm_domains[i]->pd,
|
||||
&pm_domain_always_on_gov, false);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s3c64xx_pm_domains); i++)
|
||||
pm_genpd_init(&s3c64xx_pm_domains[i]->pd, NULL, false);
|
||||
|
||||
if (dev_get_platdata(&s3c_device_fb.dev))
|
||||
pm_genpd_add_device(&s3c64xx_pm_f.pd, &s3c_device_fb.dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __init int s3c64xx_pm_initcall(void)
|
||||
{
|
||||
pm_cpu_prep = s3c64xx_pm_prepare;
|
||||
pm_cpu_sleep = s3c64xx_cpu_suspend;
|
||||
@ -198,5 +363,12 @@ static int s3c64xx_pm_init(void)
|
||||
|
||||
return 0;
|
||||
}
|
||||
arch_initcall(s3c64xx_pm_initcall);
|
||||
|
||||
arch_initcall(s3c64xx_pm_init);
|
||||
static __init int s3c64xx_pm_late_initcall(void)
|
||||
{
|
||||
pm_genpd_poweroff_unused();
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(s3c64xx_pm_late_initcall);
|
||||
|
@ -35,8 +35,8 @@ extern void sh7372_add_standard_devices(void);
|
||||
extern void sh7372_clock_init(void);
|
||||
extern void sh7372_pinmux_init(void);
|
||||
extern void sh7372_pm_init(void);
|
||||
extern void sh7372_resume_core_standby_a3sm(void);
|
||||
extern int sh7372_do_idle_a3sm(unsigned long unused);
|
||||
extern void sh7372_resume_core_standby_sysc(void);
|
||||
extern int sh7372_do_idle_sysc(unsigned long sleep_mode);
|
||||
extern struct clk sh7372_extal1_clk;
|
||||
extern struct clk sh7372_extal2_clk;
|
||||
|
||||
|
@ -480,11 +480,10 @@ struct platform_device;
|
||||
struct sh7372_pm_domain {
|
||||
struct generic_pm_domain genpd;
|
||||
struct dev_power_governor *gov;
|
||||
void (*suspend)(void);
|
||||
int (*suspend)(void);
|
||||
void (*resume)(void);
|
||||
unsigned int bit_shift;
|
||||
bool no_debug;
|
||||
bool stay_on;
|
||||
};
|
||||
|
||||
static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d)
|
||||
@ -499,6 +498,7 @@ extern struct sh7372_pm_domain sh7372_d4;
|
||||
extern struct sh7372_pm_domain sh7372_a4r;
|
||||
extern struct sh7372_pm_domain sh7372_a3rv;
|
||||
extern struct sh7372_pm_domain sh7372_a3ri;
|
||||
extern struct sh7372_pm_domain sh7372_a4s;
|
||||
extern struct sh7372_pm_domain sh7372_a3sp;
|
||||
extern struct sh7372_pm_domain sh7372_a3sg;
|
||||
|
||||
@ -515,5 +515,7 @@ extern void sh7372_pm_add_subdomain(struct sh7372_pm_domain *sh7372_pd,
|
||||
|
||||
extern void sh7372_intcs_suspend(void);
|
||||
extern void sh7372_intcs_resume(void);
|
||||
extern void sh7372_intca_suspend(void);
|
||||
extern void sh7372_intca_resume(void);
|
||||
|
||||
#endif /* __ASM_SH7372_H__ */
|
||||
|
@ -535,6 +535,7 @@ static struct resource intcs_resources[] __initdata = {
|
||||
static struct intc_desc intcs_desc __initdata = {
|
||||
.name = "sh7372-intcs",
|
||||
.force_enable = ENABLED_INTCS,
|
||||
.skip_syscore_suspend = true,
|
||||
.resource = intcs_resources,
|
||||
.num_resources = ARRAY_SIZE(intcs_resources),
|
||||
.hw = INTC_HW_DESC(intcs_vectors, intcs_groups, intcs_mask_registers,
|
||||
@ -611,3 +612,52 @@ void sh7372_intcs_resume(void)
|
||||
for (k = 0x80; k <= 0x9c; k += 4)
|
||||
__raw_writeb(ffd5[k], intcs_ffd5 + k);
|
||||
}
|
||||
|
||||
static unsigned short e694[0x200];
|
||||
static unsigned short e695[0x200];
|
||||
|
||||
void sh7372_intca_suspend(void)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0x00; k <= 0x38; k += 4)
|
||||
e694[k] = __raw_readw(0xe6940000 + k);
|
||||
|
||||
for (k = 0x80; k <= 0xb4; k += 4)
|
||||
e694[k] = __raw_readb(0xe6940000 + k);
|
||||
|
||||
for (k = 0x180; k <= 0x1b4; k += 4)
|
||||
e694[k] = __raw_readb(0xe6940000 + k);
|
||||
|
||||
for (k = 0x00; k <= 0x50; k += 4)
|
||||
e695[k] = __raw_readw(0xe6950000 + k);
|
||||
|
||||
for (k = 0x80; k <= 0xa8; k += 4)
|
||||
e695[k] = __raw_readb(0xe6950000 + k);
|
||||
|
||||
for (k = 0x180; k <= 0x1a8; k += 4)
|
||||
e695[k] = __raw_readb(0xe6950000 + k);
|
||||
}
|
||||
|
||||
void sh7372_intca_resume(void)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0x00; k <= 0x38; k += 4)
|
||||
__raw_writew(e694[k], 0xe6940000 + k);
|
||||
|
||||
for (k = 0x80; k <= 0xb4; k += 4)
|
||||
__raw_writeb(e694[k], 0xe6940000 + k);
|
||||
|
||||
for (k = 0x180; k <= 0x1b4; k += 4)
|
||||
__raw_writeb(e694[k], 0xe6940000 + k);
|
||||
|
||||
for (k = 0x00; k <= 0x50; k += 4)
|
||||
__raw_writew(e695[k], 0xe6950000 + k);
|
||||
|
||||
for (k = 0x80; k <= 0xa8; k += 4)
|
||||
__raw_writeb(e695[k], 0xe6950000 + k);
|
||||
|
||||
for (k = 0x180; k <= 0x1a8; k += 4)
|
||||
__raw_writeb(e695[k], 0xe6950000 + k);
|
||||
}
|
||||
|
@ -82,11 +82,12 @@ static int pd_power_down(struct generic_pm_domain *genpd)
|
||||
struct sh7372_pm_domain *sh7372_pd = to_sh7372_pd(genpd);
|
||||
unsigned int mask = 1 << sh7372_pd->bit_shift;
|
||||
|
||||
if (sh7372_pd->suspend)
|
||||
sh7372_pd->suspend();
|
||||
if (sh7372_pd->suspend) {
|
||||
int ret = sh7372_pd->suspend();
|
||||
|
||||
if (sh7372_pd->stay_on)
|
||||
return 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (__raw_readl(PSTR) & mask) {
|
||||
unsigned int retry_count;
|
||||
@ -101,8 +102,8 @@ static int pd_power_down(struct generic_pm_domain *genpd)
|
||||
}
|
||||
|
||||
if (!sh7372_pd->no_debug)
|
||||
pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
|
||||
mask, __raw_readl(PSTR));
|
||||
pr_debug("%s: Power off, 0x%08x -> PSTR = 0x%08x\n",
|
||||
genpd->name, mask, __raw_readl(PSTR));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -113,9 +114,6 @@ static int __pd_power_up(struct sh7372_pm_domain *sh7372_pd, bool do_resume)
|
||||
unsigned int retry_count;
|
||||
int ret = 0;
|
||||
|
||||
if (sh7372_pd->stay_on)
|
||||
goto out;
|
||||
|
||||
if (__raw_readl(PSTR) & mask)
|
||||
goto out;
|
||||
|
||||
@ -133,8 +131,8 @@ static int __pd_power_up(struct sh7372_pm_domain *sh7372_pd, bool do_resume)
|
||||
ret = -EIO;
|
||||
|
||||
if (!sh7372_pd->no_debug)
|
||||
pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
|
||||
mask, __raw_readl(PSTR));
|
||||
pr_debug("%s: Power on, 0x%08x -> PSTR = 0x%08x\n",
|
||||
sh7372_pd->genpd.name, mask, __raw_readl(PSTR));
|
||||
|
||||
out:
|
||||
if (ret == 0 && sh7372_pd->resume && do_resume)
|
||||
@ -148,35 +146,60 @@ static int pd_power_up(struct generic_pm_domain *genpd)
|
||||
return __pd_power_up(to_sh7372_pd(genpd), true);
|
||||
}
|
||||
|
||||
static void sh7372_a4r_suspend(void)
|
||||
static int sh7372_a4r_suspend(void)
|
||||
{
|
||||
sh7372_intcs_suspend();
|
||||
__raw_writel(0x300fffff, WUPRMSK); /* avoid wakeup */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool pd_active_wakeup(struct device *dev)
|
||||
{
|
||||
return true;
|
||||
bool (*active_wakeup)(struct device *dev);
|
||||
|
||||
active_wakeup = dev_gpd_data(dev)->ops.active_wakeup;
|
||||
return active_wakeup ? active_wakeup(dev) : true;
|
||||
}
|
||||
|
||||
static bool sh7372_power_down_forbidden(struct dev_pm_domain *domain)
|
||||
static int sh7372_stop_dev(struct device *dev)
|
||||
{
|
||||
return false;
|
||||
int (*stop)(struct device *dev);
|
||||
|
||||
stop = dev_gpd_data(dev)->ops.stop;
|
||||
if (stop) {
|
||||
int ret = stop(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return pm_clk_suspend(dev);
|
||||
}
|
||||
|
||||
struct dev_power_governor sh7372_always_on_gov = {
|
||||
.power_down_ok = sh7372_power_down_forbidden,
|
||||
};
|
||||
static int sh7372_start_dev(struct device *dev)
|
||||
{
|
||||
int (*start)(struct device *dev);
|
||||
int ret;
|
||||
|
||||
ret = pm_clk_resume(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
start = dev_gpd_data(dev)->ops.start;
|
||||
if (start)
|
||||
ret = start(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd)
|
||||
{
|
||||
struct generic_pm_domain *genpd = &sh7372_pd->genpd;
|
||||
struct dev_power_governor *gov = sh7372_pd->gov;
|
||||
|
||||
pm_genpd_init(genpd, sh7372_pd->gov, false);
|
||||
genpd->stop_device = pm_clk_suspend;
|
||||
genpd->start_device = pm_clk_resume;
|
||||
pm_genpd_init(genpd, gov ? : &simple_qos_governor, false);
|
||||
genpd->dev_ops.stop = sh7372_stop_dev;
|
||||
genpd->dev_ops.start = sh7372_start_dev;
|
||||
genpd->dev_ops.active_wakeup = pd_active_wakeup;
|
||||
genpd->dev_irq_safe = true;
|
||||
genpd->active_wakeup = pd_active_wakeup;
|
||||
genpd->power_off = pd_power_down;
|
||||
genpd->power_on = pd_power_up;
|
||||
__pd_power_up(sh7372_pd, false);
|
||||
@ -199,48 +222,73 @@ void sh7372_pm_add_subdomain(struct sh7372_pm_domain *sh7372_pd,
|
||||
}
|
||||
|
||||
struct sh7372_pm_domain sh7372_a4lc = {
|
||||
.genpd.name = "A4LC",
|
||||
.bit_shift = 1,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_a4mp = {
|
||||
.genpd.name = "A4MP",
|
||||
.bit_shift = 2,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_d4 = {
|
||||
.genpd.name = "D4",
|
||||
.bit_shift = 3,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_a4r = {
|
||||
.genpd.name = "A4R",
|
||||
.bit_shift = 5,
|
||||
.gov = &sh7372_always_on_gov,
|
||||
.suspend = sh7372_a4r_suspend,
|
||||
.resume = sh7372_intcs_resume,
|
||||
.stay_on = true,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_a3rv = {
|
||||
.genpd.name = "A3RV",
|
||||
.bit_shift = 6,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_a3ri = {
|
||||
.genpd.name = "A3RI",
|
||||
.bit_shift = 8,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_a3sp = {
|
||||
.bit_shift = 11,
|
||||
.gov = &sh7372_always_on_gov,
|
||||
.no_debug = true,
|
||||
};
|
||||
|
||||
static void sh7372_a3sp_init(void)
|
||||
static int sh7372_a4s_suspend(void)
|
||||
{
|
||||
/* serial consoles make use of SCIF hardware located in A3SP,
|
||||
* keep such power domain on if "no_console_suspend" is set.
|
||||
/*
|
||||
* The A4S domain contains the CPU core and therefore it should
|
||||
* only be turned off if the CPU is in use.
|
||||
*/
|
||||
sh7372_a3sp.stay_on = !console_suspend_enabled;
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
struct sh7372_pm_domain sh7372_a4s = {
|
||||
.genpd.name = "A4S",
|
||||
.bit_shift = 10,
|
||||
.gov = &pm_domain_always_on_gov,
|
||||
.no_debug = true,
|
||||
.suspend = sh7372_a4s_suspend,
|
||||
};
|
||||
|
||||
static int sh7372_a3sp_suspend(void)
|
||||
{
|
||||
/*
|
||||
* Serial consoles make use of SCIF hardware located in A3SP,
|
||||
* keep such power domain on if "no_console_suspend" is set.
|
||||
*/
|
||||
return console_suspend_enabled ? -EBUSY : 0;
|
||||
}
|
||||
|
||||
struct sh7372_pm_domain sh7372_a3sp = {
|
||||
.genpd.name = "A3SP",
|
||||
.bit_shift = 11,
|
||||
.gov = &pm_domain_always_on_gov,
|
||||
.no_debug = true,
|
||||
.suspend = sh7372_a3sp_suspend,
|
||||
};
|
||||
|
||||
struct sh7372_pm_domain sh7372_a3sg = {
|
||||
.genpd.name = "A3SG",
|
||||
.bit_shift = 13,
|
||||
};
|
||||
|
||||
@ -257,11 +305,16 @@ static int sh7372_do_idle_core_standby(unsigned long unused)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sh7372_enter_core_standby(void)
|
||||
static void sh7372_set_reset_vector(unsigned long address)
|
||||
{
|
||||
/* set reset vector, translate 4k */
|
||||
__raw_writel(__pa(sh7372_resume_core_standby_a3sm), SBAR);
|
||||
__raw_writel(address, SBAR);
|
||||
__raw_writel(0, APARMBAREA);
|
||||
}
|
||||
|
||||
static void sh7372_enter_core_standby(void)
|
||||
{
|
||||
sh7372_set_reset_vector(__pa(sh7372_resume_core_standby_sysc));
|
||||
|
||||
/* enter sleep mode with SYSTBCR to 0x10 */
|
||||
__raw_writel(0x10, SYSTBCR);
|
||||
@ -274,27 +327,22 @@ static void sh7372_enter_core_standby(void)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SUSPEND
|
||||
static void sh7372_enter_a3sm_common(int pllc0_on)
|
||||
static void sh7372_enter_sysc(int pllc0_on, unsigned long sleep_mode)
|
||||
{
|
||||
/* set reset vector, translate 4k */
|
||||
__raw_writel(__pa(sh7372_resume_core_standby_a3sm), SBAR);
|
||||
__raw_writel(0, APARMBAREA);
|
||||
|
||||
if (pllc0_on)
|
||||
__raw_writel(0, PLLC01STPCR);
|
||||
else
|
||||
__raw_writel(1 << 28, PLLC01STPCR);
|
||||
|
||||
__raw_writel(0, PDNSEL); /* power-down A3SM only, not A4S */
|
||||
__raw_readl(WUPSFAC); /* read wakeup int. factor before sleep */
|
||||
cpu_suspend(0, sh7372_do_idle_a3sm);
|
||||
cpu_suspend(sleep_mode, sh7372_do_idle_sysc);
|
||||
__raw_readl(WUPSFAC); /* read wakeup int. factor after wakeup */
|
||||
|
||||
/* disable reset vector translation */
|
||||
__raw_writel(0, SBAR);
|
||||
}
|
||||
|
||||
static int sh7372_a3sm_valid(unsigned long *mskp, unsigned long *msk2p)
|
||||
static int sh7372_sysc_valid(unsigned long *mskp, unsigned long *msk2p)
|
||||
{
|
||||
unsigned long mstpsr0, mstpsr1, mstpsr2, mstpsr3, mstpsr4;
|
||||
unsigned long msk, msk2;
|
||||
@ -382,7 +430,7 @@ static void sh7372_icr_to_irqcr(unsigned long icr, u16 *irqcr1p, u16 *irqcr2p)
|
||||
*irqcr2p = irqcr2;
|
||||
}
|
||||
|
||||
static void sh7372_setup_a3sm(unsigned long msk, unsigned long msk2)
|
||||
static void sh7372_setup_sysc(unsigned long msk, unsigned long msk2)
|
||||
{
|
||||
u16 irqcrx_low, irqcrx_high, irqcry_low, irqcry_high;
|
||||
unsigned long tmp;
|
||||
@ -415,6 +463,22 @@ static void sh7372_setup_a3sm(unsigned long msk, unsigned long msk2)
|
||||
__raw_writel((irqcrx_high << 16) | irqcrx_low, IRQCR3);
|
||||
__raw_writel((irqcry_high << 16) | irqcry_low, IRQCR4);
|
||||
}
|
||||
|
||||
static void sh7372_enter_a3sm_common(int pllc0_on)
|
||||
{
|
||||
sh7372_set_reset_vector(__pa(sh7372_resume_core_standby_sysc));
|
||||
sh7372_enter_sysc(pllc0_on, 1 << 12);
|
||||
}
|
||||
|
||||
static void sh7372_enter_a4s_common(int pllc0_on)
|
||||
{
|
||||
sh7372_intca_suspend();
|
||||
memcpy((void *)SMFRAM, sh7372_resume_core_standby_sysc, 0x100);
|
||||
sh7372_set_reset_vector(SMFRAM);
|
||||
sh7372_enter_sysc(pllc0_on, 1 << 10);
|
||||
sh7372_intca_resume();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CPU_IDLE
|
||||
@ -448,14 +512,20 @@ static int sh7372_enter_suspend(suspend_state_t suspend_state)
|
||||
unsigned long msk, msk2;
|
||||
|
||||
/* check active clocks to determine potential wakeup sources */
|
||||
if (sh7372_a3sm_valid(&msk, &msk2)) {
|
||||
|
||||
if (sh7372_sysc_valid(&msk, &msk2)) {
|
||||
/* convert INTC mask and sense to SYSC mask and sense */
|
||||
sh7372_setup_a3sm(msk, msk2);
|
||||
sh7372_setup_sysc(msk, msk2);
|
||||
|
||||
/* enter A3SM sleep with PLLC0 off */
|
||||
pr_debug("entering A3SM\n");
|
||||
sh7372_enter_a3sm_common(0);
|
||||
if (!console_suspend_enabled &&
|
||||
sh7372_a4s.genpd.status == GPD_STATE_POWER_OFF) {
|
||||
/* enter A4S sleep with PLLC0 off */
|
||||
pr_debug("entering A4S\n");
|
||||
sh7372_enter_a4s_common(0);
|
||||
} else {
|
||||
/* enter A3SM sleep with PLLC0 off */
|
||||
pr_debug("entering A3SM\n");
|
||||
sh7372_enter_a3sm_common(0);
|
||||
}
|
||||
} else {
|
||||
/* default to Core Standby that supports all wakeup sources */
|
||||
pr_debug("entering Core Standby\n");
|
||||
@ -464,9 +534,37 @@ static int sh7372_enter_suspend(suspend_state_t suspend_state)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sh7372_pm_notifier_fn - SH7372 PM notifier routine.
|
||||
* @notifier: Unused.
|
||||
* @pm_event: Event being handled.
|
||||
* @unused: Unused.
|
||||
*/
|
||||
static int sh7372_pm_notifier_fn(struct notifier_block *notifier,
|
||||
unsigned long pm_event, void *unused)
|
||||
{
|
||||
switch (pm_event) {
|
||||
case PM_SUSPEND_PREPARE:
|
||||
/*
|
||||
* This is necessary, because the A4R domain has to be "on"
|
||||
* when suspend_device_irqs() and resume_device_irqs() are
|
||||
* executed during system suspend and resume, respectively, so
|
||||
* that those functions don't crash while accessing the INTCS.
|
||||
*/
|
||||
pm_genpd_poweron(&sh7372_a4r.genpd);
|
||||
break;
|
||||
case PM_POST_SUSPEND:
|
||||
pm_genpd_poweroff_unused();
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static void sh7372_suspend_init(void)
|
||||
{
|
||||
shmobile_suspend_ops.enter = sh7372_enter_suspend;
|
||||
pm_notifier(sh7372_pm_notifier_fn, 0);
|
||||
}
|
||||
#else
|
||||
static void sh7372_suspend_init(void) {}
|
||||
@ -482,8 +580,6 @@ void __init sh7372_pm_init(void)
|
||||
/* do not convert A3SM, A3SP, A3SG, A4R power down into A4S */
|
||||
__raw_writel(0, PDNSEL);
|
||||
|
||||
sh7372_a3sp_init();
|
||||
|
||||
sh7372_suspend_init();
|
||||
sh7372_cpuidle_init();
|
||||
}
|
||||
|
@ -994,12 +994,16 @@ void __init sh7372_add_standard_devices(void)
|
||||
sh7372_init_pm_domain(&sh7372_a4r);
|
||||
sh7372_init_pm_domain(&sh7372_a3rv);
|
||||
sh7372_init_pm_domain(&sh7372_a3ri);
|
||||
sh7372_init_pm_domain(&sh7372_a3sg);
|
||||
sh7372_init_pm_domain(&sh7372_a4s);
|
||||
sh7372_init_pm_domain(&sh7372_a3sp);
|
||||
sh7372_init_pm_domain(&sh7372_a3sg);
|
||||
|
||||
sh7372_pm_add_subdomain(&sh7372_a4lc, &sh7372_a3rv);
|
||||
sh7372_pm_add_subdomain(&sh7372_a4r, &sh7372_a4lc);
|
||||
|
||||
sh7372_pm_add_subdomain(&sh7372_a4s, &sh7372_a3sg);
|
||||
sh7372_pm_add_subdomain(&sh7372_a4s, &sh7372_a3sp);
|
||||
|
||||
platform_add_devices(sh7372_early_devices,
|
||||
ARRAY_SIZE(sh7372_early_devices));
|
||||
|
||||
|
@ -37,13 +37,18 @@
|
||||
#if defined(CONFIG_SUSPEND) || defined(CONFIG_CPU_IDLE)
|
||||
.align 12
|
||||
.text
|
||||
.global sh7372_resume_core_standby_a3sm
|
||||
sh7372_resume_core_standby_a3sm:
|
||||
.global sh7372_resume_core_standby_sysc
|
||||
sh7372_resume_core_standby_sysc:
|
||||
ldr pc, 1f
|
||||
1: .long cpu_resume - PAGE_OFFSET + PLAT_PHYS_OFFSET
|
||||
|
||||
.global sh7372_do_idle_a3sm
|
||||
sh7372_do_idle_a3sm:
|
||||
#define SPDCR 0xe6180008
|
||||
|
||||
/* A3SM & A4S power down */
|
||||
.global sh7372_do_idle_sysc
|
||||
sh7372_do_idle_sysc:
|
||||
mov r8, r0 /* sleep mode passed in r0 */
|
||||
|
||||
/*
|
||||
* Clear the SCTLR.C bit to prevent further data cache
|
||||
* allocation. Clearing SCTLR.C would make all the data accesses
|
||||
@ -80,13 +85,9 @@ sh7372_do_idle_a3sm:
|
||||
dsb
|
||||
dmb
|
||||
|
||||
#define SPDCR 0xe6180008
|
||||
#define A3SM (1 << 12)
|
||||
|
||||
/* A3SM power down */
|
||||
/* SYSC power down */
|
||||
ldr r0, =SPDCR
|
||||
ldr r1, =A3SM
|
||||
str r1, [r0]
|
||||
str r8, [r0]
|
||||
1:
|
||||
b 1b
|
||||
|
||||
|
@ -22,6 +22,7 @@ struct sys_device;
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
extern __init int s3c_pm_init(void);
|
||||
extern __init int s3c64xx_pm_init(void);
|
||||
|
||||
#else
|
||||
|
||||
@ -29,6 +30,11 @@ static inline int s3c_pm_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int s3c64xx_pm_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* configuration for the IRQ mask over sleep */
|
||||
|
@ -3,7 +3,7 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
|
||||
obj-$(CONFIG_PM_RUNTIME) += runtime.o
|
||||
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
|
||||
obj-$(CONFIG_PM_OPP) += opp.o
|
||||
obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o
|
||||
obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o
|
||||
obj-$(CONFIG_HAVE_CLK) += clock_ops.o
|
||||
|
||||
ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
|
||||
|
@ -15,13 +15,44 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \
|
||||
({ \
|
||||
type (*__routine)(struct device *__d); \
|
||||
type __ret = (type)0; \
|
||||
\
|
||||
__routine = genpd->dev_ops.callback; \
|
||||
if (__routine) { \
|
||||
__ret = __routine(dev); \
|
||||
} else { \
|
||||
__routine = dev_gpd_data(dev)->ops.callback; \
|
||||
if (__routine) \
|
||||
__ret = __routine(dev); \
|
||||
} \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
#define GENPD_DEV_TIMED_CALLBACK(genpd, type, callback, dev, field, name) \
|
||||
({ \
|
||||
ktime_t __start = ktime_get(); \
|
||||
type __retval = GENPD_DEV_CALLBACK(genpd, type, callback, dev); \
|
||||
s64 __elapsed = ktime_to_ns(ktime_sub(ktime_get(), __start)); \
|
||||
struct generic_pm_domain_data *__gpd_data = dev_gpd_data(dev); \
|
||||
if (__elapsed > __gpd_data->td.field) { \
|
||||
__gpd_data->td.field = __elapsed; \
|
||||
dev_warn(dev, name " latency exceeded, new value %lld ns\n", \
|
||||
__elapsed); \
|
||||
} \
|
||||
__retval; \
|
||||
})
|
||||
|
||||
static LIST_HEAD(gpd_list);
|
||||
static DEFINE_MUTEX(gpd_list_lock);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static struct generic_pm_domain *dev_to_genpd(struct device *dev)
|
||||
struct generic_pm_domain *dev_to_genpd(struct device *dev)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(dev->pm_domain))
|
||||
return ERR_PTR(-EINVAL);
|
||||
@ -29,6 +60,31 @@ static struct generic_pm_domain *dev_to_genpd(struct device *dev)
|
||||
return pd_to_genpd(dev->pm_domain);
|
||||
}
|
||||
|
||||
static int genpd_stop_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_TIMED_CALLBACK(genpd, int, stop, dev,
|
||||
stop_latency_ns, "stop");
|
||||
}
|
||||
|
||||
static int genpd_start_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_TIMED_CALLBACK(genpd, int, start, dev,
|
||||
start_latency_ns, "start");
|
||||
}
|
||||
|
||||
static int genpd_save_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_TIMED_CALLBACK(genpd, int, save_state, dev,
|
||||
save_state_latency_ns, "state save");
|
||||
}
|
||||
|
||||
static int genpd_restore_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_TIMED_CALLBACK(genpd, int, restore_state, dev,
|
||||
restore_state_latency_ns,
|
||||
"state restore");
|
||||
}
|
||||
|
||||
static bool genpd_sd_counter_dec(struct generic_pm_domain *genpd)
|
||||
{
|
||||
bool ret = false;
|
||||
@ -145,9 +201,21 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd)
|
||||
}
|
||||
|
||||
if (genpd->power_on) {
|
||||
ktime_t time_start = ktime_get();
|
||||
s64 elapsed_ns;
|
||||
|
||||
ret = genpd->power_on(genpd);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
|
||||
if (elapsed_ns > genpd->power_on_latency_ns) {
|
||||
genpd->power_on_latency_ns = elapsed_ns;
|
||||
if (genpd->name)
|
||||
pr_warning("%s: Power-on latency exceeded, "
|
||||
"new value %lld ns\n", genpd->name,
|
||||
elapsed_ns);
|
||||
}
|
||||
}
|
||||
|
||||
genpd_set_active(genpd);
|
||||
@ -190,7 +258,6 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd,
|
||||
{
|
||||
struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
|
||||
struct device *dev = pdd->dev;
|
||||
struct device_driver *drv = dev->driver;
|
||||
int ret = 0;
|
||||
|
||||
if (gpd_data->need_restore)
|
||||
@ -198,15 +265,9 @@ static int __pm_genpd_save_device(struct pm_domain_data *pdd,
|
||||
|
||||
mutex_unlock(&genpd->lock);
|
||||
|
||||
if (drv && drv->pm && drv->pm->runtime_suspend) {
|
||||
if (genpd->start_device)
|
||||
genpd->start_device(dev);
|
||||
|
||||
ret = drv->pm->runtime_suspend(dev);
|
||||
|
||||
if (genpd->stop_device)
|
||||
genpd->stop_device(dev);
|
||||
}
|
||||
genpd_start_dev(genpd, dev);
|
||||
ret = genpd_save_dev(genpd, dev);
|
||||
genpd_stop_dev(genpd, dev);
|
||||
|
||||
mutex_lock(&genpd->lock);
|
||||
|
||||
@ -227,22 +288,15 @@ static void __pm_genpd_restore_device(struct pm_domain_data *pdd,
|
||||
{
|
||||
struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
|
||||
struct device *dev = pdd->dev;
|
||||
struct device_driver *drv = dev->driver;
|
||||
|
||||
if (!gpd_data->need_restore)
|
||||
return;
|
||||
|
||||
mutex_unlock(&genpd->lock);
|
||||
|
||||
if (drv && drv->pm && drv->pm->runtime_resume) {
|
||||
if (genpd->start_device)
|
||||
genpd->start_device(dev);
|
||||
|
||||
drv->pm->runtime_resume(dev);
|
||||
|
||||
if (genpd->stop_device)
|
||||
genpd->stop_device(dev);
|
||||
}
|
||||
genpd_start_dev(genpd, dev);
|
||||
genpd_restore_dev(genpd, dev);
|
||||
genpd_stop_dev(genpd, dev);
|
||||
|
||||
mutex_lock(&genpd->lock);
|
||||
|
||||
@ -354,11 +408,16 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd)
|
||||
}
|
||||
|
||||
if (genpd->power_off) {
|
||||
ktime_t time_start;
|
||||
s64 elapsed_ns;
|
||||
|
||||
if (atomic_read(&genpd->sd_count) > 0) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
time_start = ktime_get();
|
||||
|
||||
/*
|
||||
* If sd_count > 0 at this point, one of the subdomains hasn't
|
||||
* managed to call pm_genpd_poweron() for the master yet after
|
||||
@ -372,9 +431,29 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd)
|
||||
genpd_set_active(genpd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
|
||||
if (elapsed_ns > genpd->power_off_latency_ns) {
|
||||
genpd->power_off_latency_ns = elapsed_ns;
|
||||
if (genpd->name)
|
||||
pr_warning("%s: Power-off latency exceeded, "
|
||||
"new value %lld ns\n", genpd->name,
|
||||
elapsed_ns);
|
||||
}
|
||||
}
|
||||
|
||||
genpd->status = GPD_STATE_POWER_OFF;
|
||||
genpd->power_off_time = ktime_get();
|
||||
|
||||
/* Update PM QoS information for devices in the domain. */
|
||||
list_for_each_entry_reverse(pdd, &genpd->dev_list, list_node) {
|
||||
struct gpd_timing_data *td = &to_gpd_data(pdd)->td;
|
||||
|
||||
pm_runtime_update_max_time_suspended(pdd->dev,
|
||||
td->start_latency_ns +
|
||||
td->restore_state_latency_ns +
|
||||
genpd->power_on_latency_ns);
|
||||
}
|
||||
|
||||
list_for_each_entry(link, &genpd->slave_links, slave_node) {
|
||||
genpd_sd_counter_dec(link->master);
|
||||
@ -413,6 +492,8 @@ static void genpd_power_off_work_fn(struct work_struct *work)
|
||||
static int pm_genpd_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
bool (*stop_ok)(struct device *__dev);
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
@ -422,11 +503,16 @@ static int pm_genpd_runtime_suspend(struct device *dev)
|
||||
|
||||
might_sleep_if(!genpd->dev_irq_safe);
|
||||
|
||||
if (genpd->stop_device) {
|
||||
int ret = genpd->stop_device(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL;
|
||||
if (stop_ok && !stop_ok(dev))
|
||||
return -EBUSY;
|
||||
|
||||
ret = genpd_stop_dev(genpd, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_runtime_update_max_time_suspended(dev,
|
||||
dev_gpd_data(dev)->td.start_latency_ns);
|
||||
|
||||
/*
|
||||
* If power.irq_safe is set, this routine will be run with interrupts
|
||||
@ -502,8 +588,7 @@ static int pm_genpd_runtime_resume(struct device *dev)
|
||||
mutex_unlock(&genpd->lock);
|
||||
|
||||
out:
|
||||
if (genpd->start_device)
|
||||
genpd->start_device(dev);
|
||||
genpd_start_dev(genpd, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -534,6 +619,52 @@ static inline void genpd_power_off_work_fn(struct work_struct *work) {}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static bool genpd_dev_active_wakeup(struct generic_pm_domain *genpd,
|
||||
struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, bool, active_wakeup, dev);
|
||||
}
|
||||
|
||||
static int genpd_suspend_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, suspend, dev);
|
||||
}
|
||||
|
||||
static int genpd_suspend_late(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, suspend_late, dev);
|
||||
}
|
||||
|
||||
static int genpd_resume_early(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, resume_early, dev);
|
||||
}
|
||||
|
||||
static int genpd_resume_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, resume, dev);
|
||||
}
|
||||
|
||||
static int genpd_freeze_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, freeze, dev);
|
||||
}
|
||||
|
||||
static int genpd_freeze_late(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, freeze_late, dev);
|
||||
}
|
||||
|
||||
static int genpd_thaw_early(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, thaw_early, dev);
|
||||
}
|
||||
|
||||
static int genpd_thaw_dev(struct generic_pm_domain *genpd, struct device *dev)
|
||||
{
|
||||
return GENPD_DEV_CALLBACK(genpd, int, thaw, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_sync_poweroff - Synchronously power off a PM domain and its masters.
|
||||
* @genpd: PM domain to power off, if possible.
|
||||
@ -590,7 +721,7 @@ static bool resume_needed(struct device *dev, struct generic_pm_domain *genpd)
|
||||
if (!device_can_wakeup(dev))
|
||||
return false;
|
||||
|
||||
active_wakeup = genpd->active_wakeup && genpd->active_wakeup(dev);
|
||||
active_wakeup = genpd_dev_active_wakeup(genpd, dev);
|
||||
return device_may_wakeup(dev) ? active_wakeup : !active_wakeup;
|
||||
}
|
||||
|
||||
@ -646,7 +777,7 @@ static int pm_genpd_prepare(struct device *dev)
|
||||
/*
|
||||
* The PM domain must be in the GPD_STATE_ACTIVE state at this point,
|
||||
* so pm_genpd_poweron() will return immediately, but if the device
|
||||
* is suspended (e.g. it's been stopped by .stop_device()), we need
|
||||
* is suspended (e.g. it's been stopped by genpd_stop_dev()), we need
|
||||
* to make it operational.
|
||||
*/
|
||||
pm_runtime_resume(dev);
|
||||
@ -685,7 +816,7 @@ static int pm_genpd_suspend(struct device *dev)
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : pm_generic_suspend(dev);
|
||||
return genpd->suspend_power_off ? 0 : genpd_suspend_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -710,16 +841,14 @@ static int pm_genpd_suspend_noirq(struct device *dev)
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
|
||||
ret = pm_generic_suspend_noirq(dev);
|
||||
ret = genpd_suspend_late(genpd, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dev->power.wakeup_path
|
||||
&& genpd->active_wakeup && genpd->active_wakeup(dev))
|
||||
if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))
|
||||
return 0;
|
||||
|
||||
if (genpd->stop_device)
|
||||
genpd->stop_device(dev);
|
||||
genpd_stop_dev(genpd, dev);
|
||||
|
||||
/*
|
||||
* Since all of the "noirq" callbacks are executed sequentially, it is
|
||||
@ -761,10 +890,9 @@ static int pm_genpd_resume_noirq(struct device *dev)
|
||||
*/
|
||||
pm_genpd_poweron(genpd);
|
||||
genpd->suspended_count--;
|
||||
if (genpd->start_device)
|
||||
genpd->start_device(dev);
|
||||
genpd_start_dev(genpd, dev);
|
||||
|
||||
return pm_generic_resume_noirq(dev);
|
||||
return genpd_resume_early(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -785,7 +913,7 @@ static int pm_genpd_resume(struct device *dev)
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : pm_generic_resume(dev);
|
||||
return genpd->suspend_power_off ? 0 : genpd_resume_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -806,7 +934,7 @@ static int pm_genpd_freeze(struct device *dev)
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : pm_generic_freeze(dev);
|
||||
return genpd->suspend_power_off ? 0 : genpd_freeze_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -832,12 +960,11 @@ static int pm_genpd_freeze_noirq(struct device *dev)
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
|
||||
ret = pm_generic_freeze_noirq(dev);
|
||||
ret = genpd_freeze_late(genpd, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (genpd->stop_device)
|
||||
genpd->stop_device(dev);
|
||||
genpd_stop_dev(genpd, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -864,10 +991,9 @@ static int pm_genpd_thaw_noirq(struct device *dev)
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
|
||||
if (genpd->start_device)
|
||||
genpd->start_device(dev);
|
||||
genpd_start_dev(genpd, dev);
|
||||
|
||||
return pm_generic_thaw_noirq(dev);
|
||||
return genpd_thaw_early(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -888,72 +1014,7 @@ static int pm_genpd_thaw(struct device *dev)
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : pm_generic_thaw(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_dev_poweroff - Power off a device belonging to an I/O PM domain.
|
||||
* @dev: Device to suspend.
|
||||
*
|
||||
* Power off a device under the assumption that its pm_domain field points to
|
||||
* the domain member of an object of type struct generic_pm_domain representing
|
||||
* a PM domain consisting of I/O devices.
|
||||
*/
|
||||
static int pm_genpd_dev_poweroff(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : pm_generic_poweroff(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_dev_poweroff_noirq - Late power off of a device from a PM domain.
|
||||
* @dev: Device to suspend.
|
||||
*
|
||||
* Carry out a late powering off of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a PM domain consisting of I/O devices.
|
||||
*/
|
||||
static int pm_genpd_dev_poweroff_noirq(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
|
||||
ret = pm_generic_poweroff_noirq(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dev->power.wakeup_path
|
||||
&& genpd->active_wakeup && genpd->active_wakeup(dev))
|
||||
return 0;
|
||||
|
||||
if (genpd->stop_device)
|
||||
genpd->stop_device(dev);
|
||||
|
||||
/*
|
||||
* Since all of the "noirq" callbacks are executed sequentially, it is
|
||||
* guaranteed that this function will never run twice in parallel for
|
||||
* the same PM domain, so it is not necessary to use locking here.
|
||||
*/
|
||||
genpd->suspended_count++;
|
||||
pm_genpd_sync_poweroff(genpd);
|
||||
|
||||
return 0;
|
||||
return genpd->suspend_power_off ? 0 : genpd_thaw_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -993,31 +1054,9 @@ static int pm_genpd_restore_noirq(struct device *dev)
|
||||
|
||||
pm_genpd_poweron(genpd);
|
||||
genpd->suspended_count--;
|
||||
if (genpd->start_device)
|
||||
genpd->start_device(dev);
|
||||
genpd_start_dev(genpd, dev);
|
||||
|
||||
return pm_generic_restore_noirq(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_restore - Restore a device belonging to an I/O power domain.
|
||||
* @dev: Device to resume.
|
||||
*
|
||||
* Restore a device under the assumption that its pm_domain field points to the
|
||||
* domain member of an object of type struct generic_pm_domain representing
|
||||
* a power domain consisting of I/O devices.
|
||||
*/
|
||||
static int pm_genpd_restore(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : pm_generic_restore(dev);
|
||||
return genpd_resume_early(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1067,20 +1106,19 @@ static void pm_genpd_complete(struct device *dev)
|
||||
#define pm_genpd_freeze_noirq NULL
|
||||
#define pm_genpd_thaw_noirq NULL
|
||||
#define pm_genpd_thaw NULL
|
||||
#define pm_genpd_dev_poweroff_noirq NULL
|
||||
#define pm_genpd_dev_poweroff NULL
|
||||
#define pm_genpd_restore_noirq NULL
|
||||
#define pm_genpd_restore NULL
|
||||
#define pm_genpd_complete NULL
|
||||
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
/**
|
||||
* pm_genpd_add_device - Add a device to an I/O PM domain.
|
||||
* __pm_genpd_add_device - Add a device to an I/O PM domain.
|
||||
* @genpd: PM domain to add the device to.
|
||||
* @dev: Device to be added.
|
||||
* @td: Set of PM QoS timing parameters to attach to the device.
|
||||
*/
|
||||
int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev)
|
||||
int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev,
|
||||
struct gpd_timing_data *td)
|
||||
{
|
||||
struct generic_pm_domain_data *gpd_data;
|
||||
struct pm_domain_data *pdd;
|
||||
@ -1123,6 +1161,8 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev)
|
||||
gpd_data->base.dev = dev;
|
||||
gpd_data->need_restore = false;
|
||||
list_add_tail(&gpd_data->base.list_node, &genpd->dev_list);
|
||||
if (td)
|
||||
gpd_data->td = *td;
|
||||
|
||||
out:
|
||||
genpd_release_lock(genpd);
|
||||
@ -1279,6 +1319,204 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_add_callbacks - Add PM domain callbacks to a given device.
|
||||
* @dev: Device to add the callbacks to.
|
||||
* @ops: Set of callbacks to add.
|
||||
* @td: Timing data to add to the device along with the callbacks (optional).
|
||||
*/
|
||||
int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops,
|
||||
struct gpd_timing_data *td)
|
||||
{
|
||||
struct pm_domain_data *pdd;
|
||||
int ret = 0;
|
||||
|
||||
if (!(dev && dev->power.subsys_data && ops))
|
||||
return -EINVAL;
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
device_pm_lock();
|
||||
|
||||
pdd = dev->power.subsys_data->domain_data;
|
||||
if (pdd) {
|
||||
struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
|
||||
|
||||
gpd_data->ops = *ops;
|
||||
if (td)
|
||||
gpd_data->td = *td;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
device_pm_unlock();
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_genpd_add_callbacks);
|
||||
|
||||
/**
|
||||
* __pm_genpd_remove_callbacks - Remove PM domain callbacks from a given device.
|
||||
* @dev: Device to remove the callbacks from.
|
||||
* @clear_td: If set, clear the device's timing data too.
|
||||
*/
|
||||
int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td)
|
||||
{
|
||||
struct pm_domain_data *pdd;
|
||||
int ret = 0;
|
||||
|
||||
if (!(dev && dev->power.subsys_data))
|
||||
return -EINVAL;
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
device_pm_lock();
|
||||
|
||||
pdd = dev->power.subsys_data->domain_data;
|
||||
if (pdd) {
|
||||
struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
|
||||
|
||||
gpd_data->ops = (struct gpd_dev_ops){ 0 };
|
||||
if (clear_td)
|
||||
gpd_data->td = (struct gpd_timing_data){ 0 };
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
device_pm_unlock();
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pm_genpd_remove_callbacks);
|
||||
|
||||
/* Default device callbacks for generic PM domains. */
|
||||
|
||||
/**
|
||||
* pm_genpd_default_save_state - Default "save device state" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_save_state(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev);
|
||||
struct device_driver *drv = dev->driver;
|
||||
|
||||
cb = dev_gpd_data(dev)->ops.save_state;
|
||||
if (cb)
|
||||
return cb(dev);
|
||||
|
||||
if (drv && drv->pm && drv->pm->runtime_suspend)
|
||||
return drv->pm->runtime_suspend(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_restore_state - Default PM domians "restore device state".
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_restore_state(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev);
|
||||
struct device_driver *drv = dev->driver;
|
||||
|
||||
cb = dev_gpd_data(dev)->ops.restore_state;
|
||||
if (cb)
|
||||
return cb(dev);
|
||||
|
||||
if (drv && drv->pm && drv->pm->runtime_resume)
|
||||
return drv->pm->runtime_resume(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_suspend - Default "device suspend" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_suspend(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.suspend;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_suspend(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_suspend_late - Default "late device suspend" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_suspend_late(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.suspend_late;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_suspend_noirq(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_resume_early - Default "early device resume" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_resume_early(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.resume_early;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_resume_noirq(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_resume - Default "device resume" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_resume(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.resume;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_resume(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_freeze - Default "device freeze" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_freeze(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_freeze(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_freeze_late - Default "late device freeze" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_freeze_late(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze_late;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_freeze_noirq(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_thaw_early - Default "early device thaw" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_thaw_early(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw_early;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_thaw_noirq(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_default_thaw - Default "device thaw" for PM domians.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
static int pm_genpd_default_thaw(struct device *dev)
|
||||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_thaw(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_init - Initialize a generic I/O PM domain object.
|
||||
* @genpd: PM domain object to initialize.
|
||||
@ -1305,6 +1543,7 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
|
||||
genpd->resume_count = 0;
|
||||
genpd->device_count = 0;
|
||||
genpd->suspended_count = 0;
|
||||
genpd->max_off_time_ns = -1;
|
||||
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
|
||||
genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume;
|
||||
genpd->domain.ops.runtime_idle = pm_generic_runtime_idle;
|
||||
@ -1317,11 +1556,21 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
|
||||
genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq;
|
||||
genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq;
|
||||
genpd->domain.ops.thaw = pm_genpd_thaw;
|
||||
genpd->domain.ops.poweroff = pm_genpd_dev_poweroff;
|
||||
genpd->domain.ops.poweroff_noirq = pm_genpd_dev_poweroff_noirq;
|
||||
genpd->domain.ops.poweroff = pm_genpd_suspend;
|
||||
genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
|
||||
genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq;
|
||||
genpd->domain.ops.restore = pm_genpd_restore;
|
||||
genpd->domain.ops.restore = pm_genpd_resume;
|
||||
genpd->domain.ops.complete = pm_genpd_complete;
|
||||
genpd->dev_ops.save_state = pm_genpd_default_save_state;
|
||||
genpd->dev_ops.restore_state = pm_genpd_default_restore_state;
|
||||
genpd->dev_ops.suspend = pm_genpd_default_suspend;
|
||||
genpd->dev_ops.suspend_late = pm_genpd_default_suspend_late;
|
||||
genpd->dev_ops.resume_early = pm_genpd_default_resume_early;
|
||||
genpd->dev_ops.resume = pm_genpd_default_resume;
|
||||
genpd->dev_ops.freeze = pm_genpd_default_freeze;
|
||||
genpd->dev_ops.freeze_late = pm_genpd_default_freeze_late;
|
||||
genpd->dev_ops.thaw_early = pm_genpd_default_thaw_early;
|
||||
genpd->dev_ops.thaw = pm_genpd_default_thaw;
|
||||
mutex_lock(&gpd_list_lock);
|
||||
list_add(&genpd->gpd_list_node, &gpd_list);
|
||||
mutex_unlock(&gpd_list_lock);
|
||||
|
156
drivers/base/power/domain_governor.c
Normal file
156
drivers/base/power/domain_governor.c
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* drivers/base/power/domain_governor.c - Governors for device PM domains.
|
||||
*
|
||||
* Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Renesas Electronics Corp.
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/hrtimer.h>
|
||||
|
||||
/**
|
||||
* default_stop_ok - Default PM domain governor routine for stopping devices.
|
||||
* @dev: Device to check.
|
||||
*/
|
||||
bool default_stop_ok(struct device *dev)
|
||||
{
|
||||
struct gpd_timing_data *td = &dev_gpd_data(dev)->td;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
if (dev->power.max_time_suspended_ns < 0 || td->break_even_ns == 0)
|
||||
return true;
|
||||
|
||||
return td->stop_latency_ns + td->start_latency_ns < td->break_even_ns
|
||||
&& td->break_even_ns < dev->power.max_time_suspended_ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* default_power_down_ok - Default generic PM domain power off governor routine.
|
||||
* @pd: PM domain to check.
|
||||
*
|
||||
* This routine must be executed under the PM domain's lock.
|
||||
*/
|
||||
static bool default_power_down_ok(struct dev_pm_domain *pd)
|
||||
{
|
||||
struct generic_pm_domain *genpd = pd_to_genpd(pd);
|
||||
struct gpd_link *link;
|
||||
struct pm_domain_data *pdd;
|
||||
s64 min_dev_off_time_ns;
|
||||
s64 off_on_time_ns;
|
||||
ktime_t time_now = ktime_get();
|
||||
|
||||
off_on_time_ns = genpd->power_off_latency_ns +
|
||||
genpd->power_on_latency_ns;
|
||||
/*
|
||||
* It doesn't make sense to remove power from the domain if saving
|
||||
* the state of all devices in it and the power off/power on operations
|
||||
* take too much time.
|
||||
*
|
||||
* All devices in this domain have been stopped already at this point.
|
||||
*/
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
|
||||
if (pdd->dev->driver)
|
||||
off_on_time_ns +=
|
||||
to_gpd_data(pdd)->td.save_state_latency_ns;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if subdomains can be off for enough time.
|
||||
*
|
||||
* All subdomains have been powered off already at this point.
|
||||
*/
|
||||
list_for_each_entry(link, &genpd->master_links, master_node) {
|
||||
struct generic_pm_domain *sd = link->slave;
|
||||
s64 sd_max_off_ns = sd->max_off_time_ns;
|
||||
|
||||
if (sd_max_off_ns < 0)
|
||||
continue;
|
||||
|
||||
sd_max_off_ns -= ktime_to_ns(ktime_sub(time_now,
|
||||
sd->power_off_time));
|
||||
/*
|
||||
* Check if the subdomain is allowed to be off long enough for
|
||||
* the current domain to turn off and on (that's how much time
|
||||
* it will have to wait worst case).
|
||||
*/
|
||||
if (sd_max_off_ns <= off_on_time_ns)
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the devices in the domain can be off enough time.
|
||||
*/
|
||||
min_dev_off_time_ns = -1;
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
|
||||
struct gpd_timing_data *td;
|
||||
struct device *dev = pdd->dev;
|
||||
s64 dev_off_time_ns;
|
||||
|
||||
if (!dev->driver || dev->power.max_time_suspended_ns < 0)
|
||||
continue;
|
||||
|
||||
td = &to_gpd_data(pdd)->td;
|
||||
dev_off_time_ns = dev->power.max_time_suspended_ns -
|
||||
(td->start_latency_ns + td->restore_state_latency_ns +
|
||||
ktime_to_ns(ktime_sub(time_now,
|
||||
dev->power.suspend_time)));
|
||||
if (dev_off_time_ns <= off_on_time_ns)
|
||||
return false;
|
||||
|
||||
if (min_dev_off_time_ns > dev_off_time_ns
|
||||
|| min_dev_off_time_ns < 0)
|
||||
min_dev_off_time_ns = dev_off_time_ns;
|
||||
}
|
||||
|
||||
if (min_dev_off_time_ns < 0) {
|
||||
/*
|
||||
* There are no latency constraints, so the domain can spend
|
||||
* arbitrary time in the "off" state.
|
||||
*/
|
||||
genpd->max_off_time_ns = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The difference between the computed minimum delta and the time needed
|
||||
* to turn the domain on is the maximum theoretical time this domain can
|
||||
* spend in the "off" state.
|
||||
*/
|
||||
min_dev_off_time_ns -= genpd->power_on_latency_ns;
|
||||
|
||||
/*
|
||||
* If the difference between the computed minimum delta and the time
|
||||
* needed to turn the domain off and back on on is smaller than the
|
||||
* domain's power break even time, removing power from the domain is not
|
||||
* worth it.
|
||||
*/
|
||||
if (genpd->break_even_ns >
|
||||
min_dev_off_time_ns - genpd->power_off_latency_ns)
|
||||
return false;
|
||||
|
||||
genpd->max_off_time_ns = min_dev_off_time_ns;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct dev_power_governor simple_qos_governor = {
|
||||
.stop_ok = default_stop_ok,
|
||||
.power_down_ok = default_power_down_ok,
|
||||
};
|
||||
|
||||
static bool always_on_power_down_ok(struct dev_pm_domain *domain)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_gov_always_on - A governor implementing an always-on policy
|
||||
*/
|
||||
struct dev_power_governor pm_domain_always_on_gov = {
|
||||
.power_down_ok = always_on_power_down_ok,
|
||||
.stop_ok = default_stop_ok,
|
||||
};
|
@ -420,3 +420,28 @@ int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier)
|
||||
return blocking_notifier_chain_unregister(&dev_pm_notifiers, notifier);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dev_pm_qos_remove_global_notifier);
|
||||
|
||||
/**
|
||||
* dev_pm_qos_add_ancestor_request - Add PM QoS request for device's ancestor.
|
||||
* @dev: Device whose ancestor to add the request for.
|
||||
* @req: Pointer to the preallocated handle.
|
||||
* @value: Constraint latency value.
|
||||
*/
|
||||
int dev_pm_qos_add_ancestor_request(struct device *dev,
|
||||
struct dev_pm_qos_request *req, s32 value)
|
||||
{
|
||||
struct device *ancestor = dev->parent;
|
||||
int error = -ENODEV;
|
||||
|
||||
while (ancestor && !ancestor->power.ignore_children)
|
||||
ancestor = ancestor->parent;
|
||||
|
||||
if (ancestor)
|
||||
error = dev_pm_qos_add_request(ancestor, req, value);
|
||||
|
||||
if (error)
|
||||
req->dev = NULL;
|
||||
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
@ -46,6 +47,7 @@ struct st1232_ts_data {
|
||||
struct i2c_client *client;
|
||||
struct input_dev *input_dev;
|
||||
struct st1232_ts_finger finger[MAX_FINGERS];
|
||||
struct dev_pm_qos_request low_latency_req;
|
||||
};
|
||||
|
||||
static int st1232_ts_read_data(struct st1232_ts_data *ts)
|
||||
@ -118,8 +120,17 @@ static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
|
||||
}
|
||||
|
||||
/* SYN_MT_REPORT only if no contact */
|
||||
if (!count)
|
||||
if (!count) {
|
||||
input_mt_sync(input_dev);
|
||||
if (ts->low_latency_req.dev) {
|
||||
dev_pm_qos_remove_request(&ts->low_latency_req);
|
||||
ts->low_latency_req.dev = NULL;
|
||||
}
|
||||
} else if (!ts->low_latency_req.dev) {
|
||||
/* First contact, request 100 us latency. */
|
||||
dev_pm_qos_add_ancestor_request(&ts->client->dev,
|
||||
&ts->low_latency_req, 100);
|
||||
}
|
||||
|
||||
/* SYN_REPORT */
|
||||
input_sync(input_dev);
|
||||
|
@ -354,6 +354,8 @@ int __init register_intc_controller(struct intc_desc *desc)
|
||||
if (desc->force_enable)
|
||||
intc_enable_disable_enum(desc, d, desc->force_enable, 1);
|
||||
|
||||
d->skip_suspend = desc->skip_syscore_suspend;
|
||||
|
||||
nr_intc_controllers++;
|
||||
|
||||
return 0;
|
||||
@ -386,6 +388,9 @@ static int intc_suspend(void)
|
||||
list_for_each_entry(d, &intc_list, list) {
|
||||
int irq;
|
||||
|
||||
if (d->skip_suspend)
|
||||
continue;
|
||||
|
||||
/* enable wakeup irqs belonging to this intc controller */
|
||||
for_each_active_irq(irq) {
|
||||
struct irq_data *data;
|
||||
@ -409,6 +414,9 @@ static void intc_resume(void)
|
||||
list_for_each_entry(d, &intc_list, list) {
|
||||
int irq;
|
||||
|
||||
if (d->skip_suspend)
|
||||
continue;
|
||||
|
||||
for_each_active_irq(irq) {
|
||||
struct irq_data *data;
|
||||
struct irq_chip *chip;
|
||||
|
@ -67,6 +67,7 @@ struct intc_desc_int {
|
||||
struct intc_window *window;
|
||||
unsigned int nr_windows;
|
||||
struct irq_chip chip;
|
||||
bool skip_suspend;
|
||||
};
|
||||
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define _LINUX_PM_DOMAIN_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
enum gpd_status {
|
||||
GPD_STATE_ACTIVE = 0, /* PM domain is active */
|
||||
@ -21,6 +22,23 @@ enum gpd_status {
|
||||
|
||||
struct dev_power_governor {
|
||||
bool (*power_down_ok)(struct dev_pm_domain *domain);
|
||||
bool (*stop_ok)(struct device *dev);
|
||||
};
|
||||
|
||||
struct gpd_dev_ops {
|
||||
int (*start)(struct device *dev);
|
||||
int (*stop)(struct device *dev);
|
||||
int (*save_state)(struct device *dev);
|
||||
int (*restore_state)(struct device *dev);
|
||||
int (*suspend)(struct device *dev);
|
||||
int (*suspend_late)(struct device *dev);
|
||||
int (*resume_early)(struct device *dev);
|
||||
int (*resume)(struct device *dev);
|
||||
int (*freeze)(struct device *dev);
|
||||
int (*freeze_late)(struct device *dev);
|
||||
int (*thaw_early)(struct device *dev);
|
||||
int (*thaw)(struct device *dev);
|
||||
bool (*active_wakeup)(struct device *dev);
|
||||
};
|
||||
|
||||
struct generic_pm_domain {
|
||||
@ -32,6 +50,7 @@ struct generic_pm_domain {
|
||||
struct mutex lock;
|
||||
struct dev_power_governor *gov;
|
||||
struct work_struct power_off_work;
|
||||
char *name;
|
||||
unsigned int in_progress; /* Number of devices being suspended now */
|
||||
atomic_t sd_count; /* Number of subdomains with power "on" */
|
||||
enum gpd_status status; /* Current state of the domain */
|
||||
@ -44,10 +63,13 @@ struct generic_pm_domain {
|
||||
bool suspend_power_off; /* Power status before system suspend */
|
||||
bool dev_irq_safe; /* Device callbacks are IRQ-safe */
|
||||
int (*power_off)(struct generic_pm_domain *domain);
|
||||
s64 power_off_latency_ns;
|
||||
int (*power_on)(struct generic_pm_domain *domain);
|
||||
int (*start_device)(struct device *dev);
|
||||
int (*stop_device)(struct device *dev);
|
||||
bool (*active_wakeup)(struct device *dev);
|
||||
s64 power_on_latency_ns;
|
||||
struct gpd_dev_ops dev_ops;
|
||||
s64 break_even_ns; /* Power break even for the entire domain. */
|
||||
s64 max_off_time_ns; /* Maximum allowed "suspended" time. */
|
||||
ktime_t power_off_time;
|
||||
};
|
||||
|
||||
static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd)
|
||||
@ -62,8 +84,18 @@ struct gpd_link {
|
||||
struct list_head slave_node;
|
||||
};
|
||||
|
||||
struct gpd_timing_data {
|
||||
s64 stop_latency_ns;
|
||||
s64 start_latency_ns;
|
||||
s64 save_state_latency_ns;
|
||||
s64 restore_state_latency_ns;
|
||||
s64 break_even_ns;
|
||||
};
|
||||
|
||||
struct generic_pm_domain_data {
|
||||
struct pm_domain_data base;
|
||||
struct gpd_dev_ops ops;
|
||||
struct gpd_timing_data td;
|
||||
bool need_restore;
|
||||
};
|
||||
|
||||
@ -73,18 +105,54 @@ static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data *
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_GENERIC_DOMAINS
|
||||
extern int pm_genpd_add_device(struct generic_pm_domain *genpd,
|
||||
struct device *dev);
|
||||
static inline struct generic_pm_domain_data *dev_gpd_data(struct device *dev)
|
||||
{
|
||||
return to_gpd_data(dev->power.subsys_data->domain_data);
|
||||
}
|
||||
|
||||
extern struct dev_power_governor simple_qos_governor;
|
||||
|
||||
extern struct generic_pm_domain *dev_to_genpd(struct device *dev);
|
||||
extern int __pm_genpd_add_device(struct generic_pm_domain *genpd,
|
||||
struct device *dev,
|
||||
struct gpd_timing_data *td);
|
||||
|
||||
static inline int pm_genpd_add_device(struct generic_pm_domain *genpd,
|
||||
struct device *dev)
|
||||
{
|
||||
return __pm_genpd_add_device(genpd, dev, NULL);
|
||||
}
|
||||
|
||||
extern int pm_genpd_remove_device(struct generic_pm_domain *genpd,
|
||||
struct device *dev);
|
||||
extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
|
||||
struct generic_pm_domain *new_subdomain);
|
||||
extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
|
||||
struct generic_pm_domain *target);
|
||||
extern int pm_genpd_add_callbacks(struct device *dev,
|
||||
struct gpd_dev_ops *ops,
|
||||
struct gpd_timing_data *td);
|
||||
extern int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td);
|
||||
extern void pm_genpd_init(struct generic_pm_domain *genpd,
|
||||
struct dev_power_governor *gov, bool is_off);
|
||||
|
||||
extern int pm_genpd_poweron(struct generic_pm_domain *genpd);
|
||||
|
||||
extern bool default_stop_ok(struct device *dev);
|
||||
|
||||
extern struct dev_power_governor pm_domain_always_on_gov;
|
||||
#else
|
||||
|
||||
static inline struct generic_pm_domain *dev_to_genpd(struct device *dev)
|
||||
{
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
static inline int __pm_genpd_add_device(struct generic_pm_domain *genpd,
|
||||
struct device *dev,
|
||||
struct gpd_timing_data *td)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
static inline int pm_genpd_add_device(struct generic_pm_domain *genpd,
|
||||
struct device *dev)
|
||||
{
|
||||
@ -105,14 +173,35 @@ static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
static inline void pm_genpd_init(struct generic_pm_domain *genpd,
|
||||
struct dev_power_governor *gov, bool is_off) {}
|
||||
static inline int pm_genpd_add_callbacks(struct device *dev,
|
||||
struct gpd_dev_ops *ops,
|
||||
struct gpd_timing_data *td)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
static inline int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
static inline void pm_genpd_init(struct generic_pm_domain *genpd, bool is_off)
|
||||
{
|
||||
}
|
||||
static inline int pm_genpd_poweron(struct generic_pm_domain *genpd)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
static inline bool default_stop_ok(struct device *dev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#define pm_domain_always_on_gov NULL
|
||||
#endif
|
||||
|
||||
static inline int pm_genpd_remove_callbacks(struct device *dev)
|
||||
{
|
||||
return __pm_genpd_remove_callbacks(dev, true);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_GENERIC_DOMAINS_RUNTIME
|
||||
extern void genpd_queue_power_off_work(struct generic_pm_domain *genpd);
|
||||
extern void pm_genpd_poweroff_unused(void);
|
||||
|
@ -92,6 +92,8 @@ int dev_pm_qos_add_global_notifier(struct notifier_block *notifier);
|
||||
int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier);
|
||||
void dev_pm_qos_constraints_init(struct device *dev);
|
||||
void dev_pm_qos_constraints_destroy(struct device *dev);
|
||||
int dev_pm_qos_add_ancestor_request(struct device *dev,
|
||||
struct dev_pm_qos_request *req, s32 value);
|
||||
#else
|
||||
static inline int pm_qos_update_target(struct pm_qos_constraints *c,
|
||||
struct plist_node *node,
|
||||
@ -153,6 +155,9 @@ static inline void dev_pm_qos_constraints_destroy(struct device *dev)
|
||||
{
|
||||
dev->power.power_state = PMSG_INVALID;
|
||||
}
|
||||
static inline int dev_pm_qos_add_ancestor_request(struct device *dev,
|
||||
struct dev_pm_qos_request *req, s32 value)
|
||||
{ return 0; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -95,6 +95,7 @@ struct intc_desc {
|
||||
unsigned int num_resources;
|
||||
intc_enum force_enable;
|
||||
intc_enum force_disable;
|
||||
bool skip_syscore_suspend;
|
||||
struct intc_hw_desc hw;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user