From fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 Mon Sep 17 00:00:00 2001 From: Jonathan Herman Date: Tue, 22 Jan 2013 10:38:37 -0500 Subject: Added missing tegra files. --- arch/sh/kernel/cpu/hwblk.c | 159 +++++++++++++++ arch/sh/kernel/cpu/sh4a/hwblk-sh7722.c | 106 ++++++++++ arch/sh/kernel/cpu/sh4a/hwblk-sh7723.c | 117 ++++++++++++ arch/sh/kernel/cpu/sh4a/hwblk-sh7724.c | 121 ++++++++++++ arch/sh/kernel/cpu/shmobile/pm_runtime.c | 319 +++++++++++++++++++++++++++++++ 5 files changed, 822 insertions(+) create mode 100644 arch/sh/kernel/cpu/hwblk.c create mode 100644 arch/sh/kernel/cpu/sh4a/hwblk-sh7722.c create mode 100644 arch/sh/kernel/cpu/sh4a/hwblk-sh7723.c create mode 100644 arch/sh/kernel/cpu/sh4a/hwblk-sh7724.c create mode 100644 arch/sh/kernel/cpu/shmobile/pm_runtime.c (limited to 'arch/sh/kernel/cpu') diff --git a/arch/sh/kernel/cpu/hwblk.c b/arch/sh/kernel/cpu/hwblk.c new file mode 100644 index 00000000000..3e985aae5d9 --- /dev/null +++ b/arch/sh/kernel/cpu/hwblk.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(hwblk_lock); + +static void hwblk_area_mod_cnt(struct hwblk_info *info, + int area, int counter, int value, int goal) +{ + struct hwblk_area *hap = info->areas + area; + + hap->cnt[counter] += value; + + if (hap->cnt[counter] != goal) + return; + + if (hap->flags & HWBLK_AREA_FLAG_PARENT) + hwblk_area_mod_cnt(info, hap->parent, counter, value, goal); +} + + +static int __hwblk_mod_cnt(struct hwblk_info *info, int hwblk, + int counter, int value, int goal) +{ + struct hwblk *hp = info->hwblks + hwblk; + + hp->cnt[counter] += value; + if (hp->cnt[counter] == goal) + hwblk_area_mod_cnt(info, hp->area, counter, value, goal); + + return hp->cnt[counter]; +} + +static void hwblk_mod_cnt(struct hwblk_info *info, int hwblk, + int counter, int value, int goal) +{ + unsigned long flags; + + spin_lock_irqsave(&hwblk_lock, flags); + __hwblk_mod_cnt(info, hwblk, counter, value, goal); + spin_unlock_irqrestore(&hwblk_lock, flags); +} + +void hwblk_cnt_inc(struct hwblk_info *info, int hwblk, int counter) +{ + hwblk_mod_cnt(info, hwblk, counter, 1, 1); +} + +void hwblk_cnt_dec(struct hwblk_info *info, int hwblk, int counter) +{ + hwblk_mod_cnt(info, hwblk, counter, -1, 0); +} + +void hwblk_enable(struct hwblk_info *info, int hwblk) +{ + struct hwblk *hp = info->hwblks + hwblk; + unsigned long tmp; + unsigned long flags; + int ret; + + spin_lock_irqsave(&hwblk_lock, flags); + + ret = __hwblk_mod_cnt(info, hwblk, HWBLK_CNT_USAGE, 1, 1); + if (ret == 1) { + tmp = __raw_readl(hp->mstp); + tmp &= ~(1 << hp->bit); + __raw_writel(tmp, hp->mstp); + } + + spin_unlock_irqrestore(&hwblk_lock, flags); +} + +void hwblk_disable(struct hwblk_info *info, int hwblk) +{ + struct hwblk *hp = info->hwblks + hwblk; + unsigned long tmp; + unsigned long flags; + int ret; + + spin_lock_irqsave(&hwblk_lock, flags); + + ret = __hwblk_mod_cnt(info, hwblk, HWBLK_CNT_USAGE, -1, 0); + if (ret == 0) { + tmp = __raw_readl(hp->mstp); + tmp |= 1 << hp->bit; + __raw_writel(tmp, hp->mstp); + } + + spin_unlock_irqrestore(&hwblk_lock, flags); +} + +struct hwblk_info *hwblk_info; + +int __init hwblk_register(struct hwblk_info *info) +{ + hwblk_info = info; + return 0; +} + +int __init __weak arch_hwblk_init(void) +{ + return 0; +} + +int __weak arch_hwblk_sleep_mode(void) +{ + return SUSP_SH_SLEEP; +} + +int __init hwblk_init(void) +{ + return arch_hwblk_init(); +} + +/* allow clocks to enable and disable hardware blocks */ +static int sh_hwblk_clk_enable(struct clk *clk) +{ + if (!hwblk_info) + return -ENOENT; + + hwblk_enable(hwblk_info, clk->arch_flags); + return 0; +} + +static void sh_hwblk_clk_disable(struct clk *clk) +{ + if (hwblk_info) + hwblk_disable(hwblk_info, clk->arch_flags); +} + +static struct clk_ops sh_hwblk_clk_ops = { + .enable = sh_hwblk_clk_enable, + .disable = sh_hwblk_clk_disable, + .recalc = followparent_recalc, +}; + +int __init sh_hwblk_clk_register(struct clk *clks, int nr) +{ + struct clk *clkp; + int ret = 0; + int k; + + for (k = 0; !ret && (k < nr); k++) { + clkp = clks + k; + + /* skip over clocks using hwblk 0 (HWBLK_UNKNOWN) */ + if (!clkp->arch_flags) + continue; + + clkp->ops = &sh_hwblk_clk_ops; + ret |= clk_register(clkp); + } + + return ret; +} diff --git a/arch/sh/kernel/cpu/sh4a/hwblk-sh7722.c b/arch/sh/kernel/cpu/sh4a/hwblk-sh7722.c new file mode 100644 index 00000000000..a288b5d9234 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4a/hwblk-sh7722.c @@ -0,0 +1,106 @@ +/* + * arch/sh/kernel/cpu/sh4a/hwblk-sh7722.c + * + * SH7722 hardware block support + * + * Copyright (C) 2009 Magnus Damm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +/* SH7722 registers */ +#define MSTPCR0 0xa4150030 +#define MSTPCR1 0xa4150034 +#define MSTPCR2 0xa4150038 + +/* SH7722 Power Domains */ +enum { CORE_AREA, SUB_AREA, CORE_AREA_BM }; +static struct hwblk_area sh7722_hwblk_area[] = { + [CORE_AREA] = HWBLK_AREA(0, 0), + [CORE_AREA_BM] = HWBLK_AREA(HWBLK_AREA_FLAG_PARENT, CORE_AREA), + [SUB_AREA] = HWBLK_AREA(0, 0), +}; + +/* Table mapping HWBLK to Module Stop Bit and Power Domain */ +static struct hwblk sh7722_hwblk[HWBLK_NR] = { + [HWBLK_TLB] = HWBLK(MSTPCR0, 31, CORE_AREA), + [HWBLK_IC] = HWBLK(MSTPCR0, 30, CORE_AREA), + [HWBLK_OC] = HWBLK(MSTPCR0, 29, CORE_AREA), + [HWBLK_URAM] = HWBLK(MSTPCR0, 28, CORE_AREA), + [HWBLK_XYMEM] = HWBLK(MSTPCR0, 26, CORE_AREA), + [HWBLK_INTC] = HWBLK(MSTPCR0, 22, CORE_AREA), + [HWBLK_DMAC] = HWBLK(MSTPCR0, 21, CORE_AREA_BM), + [HWBLK_SHYWAY] = HWBLK(MSTPCR0, 20, CORE_AREA), + [HWBLK_HUDI] = HWBLK(MSTPCR0, 19, CORE_AREA), + [HWBLK_UBC] = HWBLK(MSTPCR0, 17, CORE_AREA), + [HWBLK_TMU] = HWBLK(MSTPCR0, 15, CORE_AREA), + [HWBLK_CMT] = HWBLK(MSTPCR0, 14, SUB_AREA), + [HWBLK_RWDT] = HWBLK(MSTPCR0, 13, SUB_AREA), + [HWBLK_FLCTL] = HWBLK(MSTPCR0, 10, CORE_AREA), + [HWBLK_SCIF0] = HWBLK(MSTPCR0, 7, CORE_AREA), + [HWBLK_SCIF1] = HWBLK(MSTPCR0, 6, CORE_AREA), + [HWBLK_SCIF2] = HWBLK(MSTPCR0, 5, CORE_AREA), + [HWBLK_SIO] = HWBLK(MSTPCR0, 3, CORE_AREA), + [HWBLK_SIOF0] = HWBLK(MSTPCR0, 2, CORE_AREA), + [HWBLK_SIOF1] = HWBLK(MSTPCR0, 1, CORE_AREA), + + [HWBLK_IIC] = HWBLK(MSTPCR1, 9, CORE_AREA), + [HWBLK_RTC] = HWBLK(MSTPCR1, 8, SUB_AREA), + + [HWBLK_TPU] = HWBLK(MSTPCR2, 25, CORE_AREA), + [HWBLK_IRDA] = HWBLK(MSTPCR2, 24, CORE_AREA), + [HWBLK_SDHI] = HWBLK(MSTPCR2, 18, CORE_AREA), + [HWBLK_SIM] = HWBLK(MSTPCR2, 16, CORE_AREA), + [HWBLK_KEYSC] = HWBLK(MSTPCR2, 14, SUB_AREA), + [HWBLK_TSIF] = HWBLK(MSTPCR2, 13, SUB_AREA), + [HWBLK_USBF] = HWBLK(MSTPCR2, 11, CORE_AREA), + [HWBLK_2DG] = HWBLK(MSTPCR2, 9, CORE_AREA_BM), + [HWBLK_SIU] = HWBLK(MSTPCR2, 8, CORE_AREA), + [HWBLK_JPU] = HWBLK(MSTPCR2, 6, CORE_AREA_BM), + [HWBLK_VOU] = HWBLK(MSTPCR2, 5, CORE_AREA_BM), + [HWBLK_BEU] = HWBLK(MSTPCR2, 4, CORE_AREA_BM), + [HWBLK_CEU] = HWBLK(MSTPCR2, 3, CORE_AREA_BM), + [HWBLK_VEU] = HWBLK(MSTPCR2, 2, CORE_AREA_BM), + [HWBLK_VPU] = HWBLK(MSTPCR2, 1, CORE_AREA_BM), + [HWBLK_LCDC] = HWBLK(MSTPCR2, 0, CORE_AREA_BM), +}; + +static struct hwblk_info sh7722_hwblk_info = { + .areas = sh7722_hwblk_area, + .nr_areas = ARRAY_SIZE(sh7722_hwblk_area), + .hwblks = sh7722_hwblk, + .nr_hwblks = ARRAY_SIZE(sh7722_hwblk), +}; + +int arch_hwblk_sleep_mode(void) +{ + if (!sh7722_hwblk_area[CORE_AREA].cnt[HWBLK_CNT_USAGE]) + return SUSP_SH_STANDBY | SUSP_SH_SF; + + if (!sh7722_hwblk_area[CORE_AREA_BM].cnt[HWBLK_CNT_USAGE]) + return SUSP_SH_SLEEP | SUSP_SH_SF; + + return SUSP_SH_SLEEP; +} + +int __init arch_hwblk_init(void) +{ + return hwblk_register(&sh7722_hwblk_info); +} diff --git a/arch/sh/kernel/cpu/sh4a/hwblk-sh7723.c b/arch/sh/kernel/cpu/sh4a/hwblk-sh7723.c new file mode 100644 index 00000000000..a7f4684d203 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4a/hwblk-sh7723.c @@ -0,0 +1,117 @@ +/* + * arch/sh/kernel/cpu/sh4a/hwblk-sh7723.c + * + * SH7723 hardware block support + * + * Copyright (C) 2009 Magnus Damm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +/* SH7723 registers */ +#define MSTPCR0 0xa4150030 +#define MSTPCR1 0xa4150034 +#define MSTPCR2 0xa4150038 + +/* SH7723 Power Domains */ +enum { CORE_AREA, SUB_AREA, CORE_AREA_BM }; +static struct hwblk_area sh7723_hwblk_area[] = { + [CORE_AREA] = HWBLK_AREA(0, 0), + [CORE_AREA_BM] = HWBLK_AREA(HWBLK_AREA_FLAG_PARENT, CORE_AREA), + [SUB_AREA] = HWBLK_AREA(0, 0), +}; + +/* Table mapping HWBLK to Module Stop Bit and Power Domain */ +static struct hwblk sh7723_hwblk[HWBLK_NR] = { + [HWBLK_TLB] = HWBLK(MSTPCR0, 31, CORE_AREA), + [HWBLK_IC] = HWBLK(MSTPCR0, 30, CORE_AREA), + [HWBLK_OC] = HWBLK(MSTPCR0, 29, CORE_AREA), + [HWBLK_L2C] = HWBLK(MSTPCR0, 28, CORE_AREA), + [HWBLK_ILMEM] = HWBLK(MSTPCR0, 27, CORE_AREA), + [HWBLK_FPU] = HWBLK(MSTPCR0, 24, CORE_AREA), + [HWBLK_INTC] = HWBLK(MSTPCR0, 22, CORE_AREA), + [HWBLK_DMAC0] = HWBLK(MSTPCR0, 21, CORE_AREA_BM), + [HWBLK_SHYWAY] = HWBLK(MSTPCR0, 20, CORE_AREA), + [HWBLK_HUDI] = HWBLK(MSTPCR0, 19, CORE_AREA), + [HWBLK_DBG] = HWBLK(MSTPCR0, 18, CORE_AREA), + [HWBLK_UBC] = HWBLK(MSTPCR0, 17, CORE_AREA), + [HWBLK_SUBC] = HWBLK(MSTPCR0, 16, CORE_AREA), + [HWBLK_TMU0] = HWBLK(MSTPCR0, 15, CORE_AREA), + [HWBLK_CMT] = HWBLK(MSTPCR0, 14, SUB_AREA), + [HWBLK_RWDT] = HWBLK(MSTPCR0, 13, SUB_AREA), + [HWBLK_DMAC1] = HWBLK(MSTPCR0, 12, CORE_AREA_BM), + [HWBLK_TMU1] = HWBLK(MSTPCR0, 11, CORE_AREA), + [HWBLK_FLCTL] = HWBLK(MSTPCR0, 10, CORE_AREA), + [HWBLK_SCIF0] = HWBLK(MSTPCR0, 9, CORE_AREA), + [HWBLK_SCIF1] = HWBLK(MSTPCR0, 8, CORE_AREA), + [HWBLK_SCIF2] = HWBLK(MSTPCR0, 7, CORE_AREA), + [HWBLK_SCIF3] = HWBLK(MSTPCR0, 6, CORE_AREA), + [HWBLK_SCIF4] = HWBLK(MSTPCR0, 5, CORE_AREA), + [HWBLK_SCIF5] = HWBLK(MSTPCR0, 4, CORE_AREA), + [HWBLK_MSIOF0] = HWBLK(MSTPCR0, 2, CORE_AREA), + [HWBLK_MSIOF1] = HWBLK(MSTPCR0, 1, CORE_AREA), + [HWBLK_MERAM] = HWBLK(MSTPCR0, 0, CORE_AREA), + + [HWBLK_IIC] = HWBLK(MSTPCR1, 9, CORE_AREA), + [HWBLK_RTC] = HWBLK(MSTPCR1, 8, SUB_AREA), + + [HWBLK_ATAPI] = HWBLK(MSTPCR2, 28, CORE_AREA_BM), + [HWBLK_ADC] = HWBLK(MSTPCR2, 27, CORE_AREA), + [HWBLK_TPU] = HWBLK(MSTPCR2, 25, CORE_AREA), + [HWBLK_IRDA] = HWBLK(MSTPCR2, 24, CORE_AREA), + [HWBLK_TSIF] = HWBLK(MSTPCR2, 22, CORE_AREA), + [HWBLK_ICB] = HWBLK(MSTPCR2, 21, CORE_AREA_BM), + [HWBLK_SDHI0] = HWBLK(MSTPCR2, 18, CORE_AREA), + [HWBLK_SDHI1] = HWBLK(MSTPCR2, 17, CORE_AREA), + [HWBLK_KEYSC] = HWBLK(MSTPCR2, 14, SUB_AREA), + [HWBLK_USB] = HWBLK(MSTPCR2, 11, CORE_AREA), + [HWBLK_2DG] = HWBLK(MSTPCR2, 10, CORE_AREA_BM), + [HWBLK_SIU] = HWBLK(MSTPCR2, 8, CORE_AREA), + [HWBLK_VEU2H1] = HWBLK(MSTPCR2, 6, CORE_AREA_BM), + [HWBLK_VOU] = HWBLK(MSTPCR2, 5, CORE_AREA_BM), + [HWBLK_BEU] = HWBLK(MSTPCR2, 4, CORE_AREA_BM), + [HWBLK_CEU] = HWBLK(MSTPCR2, 3, CORE_AREA_BM), + [HWBLK_VEU2H0] = HWBLK(MSTPCR2, 2, CORE_AREA_BM), + [HWBLK_VPU] = HWBLK(MSTPCR2, 1, CORE_AREA_BM), + [HWBLK_LCDC] = HWBLK(MSTPCR2, 0, CORE_AREA_BM), +}; + +static struct hwblk_info sh7723_hwblk_info = { + .areas = sh7723_hwblk_area, + .nr_areas = ARRAY_SIZE(sh7723_hwblk_area), + .hwblks = sh7723_hwblk, + .nr_hwblks = ARRAY_SIZE(sh7723_hwblk), +}; + +int arch_hwblk_sleep_mode(void) +{ + if (!sh7723_hwblk_area[CORE_AREA].cnt[HWBLK_CNT_USAGE]) + return SUSP_SH_STANDBY | SUSP_SH_SF; + + if (!sh7723_hwblk_area[CORE_AREA_BM].cnt[HWBLK_CNT_USAGE]) + return SUSP_SH_SLEEP | SUSP_SH_SF; + + return SUSP_SH_SLEEP; +} + +int __init arch_hwblk_init(void) +{ + return hwblk_register(&sh7723_hwblk_info); +} diff --git a/arch/sh/kernel/cpu/sh4a/hwblk-sh7724.c b/arch/sh/kernel/cpu/sh4a/hwblk-sh7724.c new file mode 100644 index 00000000000..1613ad6013c --- /dev/null +++ b/arch/sh/kernel/cpu/sh4a/hwblk-sh7724.c @@ -0,0 +1,121 @@ +/* + * arch/sh/kernel/cpu/sh4a/hwblk-sh7724.c + * + * SH7724 hardware block support + * + * Copyright (C) 2009 Magnus Damm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +/* SH7724 registers */ +#define MSTPCR0 0xa4150030 +#define MSTPCR1 0xa4150034 +#define MSTPCR2 0xa4150038 + +/* SH7724 Power Domains */ +enum { CORE_AREA, SUB_AREA, CORE_AREA_BM }; +static struct hwblk_area sh7724_hwblk_area[] = { + [CORE_AREA] = HWBLK_AREA(0, 0), + [CORE_AREA_BM] = HWBLK_AREA(HWBLK_AREA_FLAG_PARENT, CORE_AREA), + [SUB_AREA] = HWBLK_AREA(0, 0), +}; + +/* Table mapping HWBLK to Module Stop Bit and Power Domain */ +static struct hwblk sh7724_hwblk[HWBLK_NR] = { + [HWBLK_TLB] = HWBLK(MSTPCR0, 31, CORE_AREA), + [HWBLK_IC] = HWBLK(MSTPCR0, 30, CORE_AREA), + [HWBLK_OC] = HWBLK(MSTPCR0, 29, CORE_AREA), + [HWBLK_RSMEM] = HWBLK(MSTPCR0, 28, CORE_AREA), + [HWBLK_ILMEM] = HWBLK(MSTPCR0, 27, CORE_AREA), + [HWBLK_L2C] = HWBLK(MSTPCR0, 26, CORE_AREA), + [HWBLK_FPU] = HWBLK(MSTPCR0, 24, CORE_AREA), + [HWBLK_INTC] = HWBLK(MSTPCR0, 22, CORE_AREA), + [HWBLK_DMAC0] = HWBLK(MSTPCR0, 21, CORE_AREA_BM), + [HWBLK_SHYWAY] = HWBLK(MSTPCR0, 20, CORE_AREA), + [HWBLK_HUDI] = HWBLK(MSTPCR0, 19, CORE_AREA), + [HWBLK_DBG] = HWBLK(MSTPCR0, 18, CORE_AREA), + [HWBLK_UBC] = HWBLK(MSTPCR0, 17, CORE_AREA), + [HWBLK_TMU0] = HWBLK(MSTPCR0, 15, CORE_AREA), + [HWBLK_CMT] = HWBLK(MSTPCR0, 14, SUB_AREA), + [HWBLK_RWDT] = HWBLK(MSTPCR0, 13, SUB_AREA), + [HWBLK_DMAC1] = HWBLK(MSTPCR0, 12, CORE_AREA_BM), + [HWBLK_TMU1] = HWBLK(MSTPCR0, 10, CORE_AREA), + [HWBLK_SCIF0] = HWBLK(MSTPCR0, 9, CORE_AREA), + [HWBLK_SCIF1] = HWBLK(MSTPCR0, 8, CORE_AREA), + [HWBLK_SCIF2] = HWBLK(MSTPCR0, 7, CORE_AREA), + [HWBLK_SCIF3] = HWBLK(MSTPCR0, 6, CORE_AREA), + [HWBLK_SCIF4] = HWBLK(MSTPCR0, 5, CORE_AREA), + [HWBLK_SCIF5] = HWBLK(MSTPCR0, 4, CORE_AREA), + [HWBLK_MSIOF0] = HWBLK(MSTPCR0, 2, CORE_AREA), + [HWBLK_MSIOF1] = HWBLK(MSTPCR0, 1, CORE_AREA), + + [HWBLK_KEYSC] = HWBLK(MSTPCR1, 12, SUB_AREA), + [HWBLK_RTC] = HWBLK(MSTPCR1, 11, SUB_AREA), + [HWBLK_IIC0] = HWBLK(MSTPCR1, 9, CORE_AREA), + [HWBLK_IIC1] = HWBLK(MSTPCR1, 8, CORE_AREA), + + [HWBLK_MMC] = HWBLK(MSTPCR2, 29, CORE_AREA), + [HWBLK_ETHER] = HWBLK(MSTPCR2, 28, CORE_AREA_BM), + [HWBLK_ATAPI] = HWBLK(MSTPCR2, 26, CORE_AREA_BM), + [HWBLK_TPU] = HWBLK(MSTPCR2, 25, CORE_AREA), + [HWBLK_IRDA] = HWBLK(MSTPCR2, 24, CORE_AREA), + [HWBLK_TSIF] = HWBLK(MSTPCR2, 22, CORE_AREA), + [HWBLK_USB1] = HWBLK(MSTPCR2, 21, CORE_AREA), + [HWBLK_USB0] = HWBLK(MSTPCR2, 20, CORE_AREA), + [HWBLK_2DG] = HWBLK(MSTPCR2, 19, CORE_AREA_BM), + [HWBLK_SDHI0] = HWBLK(MSTPCR2, 18, CORE_AREA), + [HWBLK_SDHI1] = HWBLK(MSTPCR2, 17, CORE_AREA), + [HWBLK_VEU1] = HWBLK(MSTPCR2, 15, CORE_AREA_BM), + [HWBLK_CEU1] = HWBLK(MSTPCR2, 13, CORE_AREA_BM), + [HWBLK_BEU1] = HWBLK(MSTPCR2, 12, CORE_AREA_BM), + [HWBLK_2DDMAC] = HWBLK(MSTPCR2, 10, CORE_AREA_BM), + [HWBLK_SPU] = HWBLK(MSTPCR2, 9, CORE_AREA_BM), + [HWBLK_JPU] = HWBLK(MSTPCR2, 6, CORE_AREA_BM), + [HWBLK_VOU] = HWBLK(MSTPCR2, 5, CORE_AREA_BM), + [HWBLK_BEU0] = HWBLK(MSTPCR2, 4, CORE_AREA_BM), + [HWBLK_CEU0] = HWBLK(MSTPCR2, 3, CORE_AREA_BM), + [HWBLK_VEU0] = HWBLK(MSTPCR2, 2, CORE_AREA_BM), + [HWBLK_VPU] = HWBLK(MSTPCR2, 1, CORE_AREA_BM), + [HWBLK_LCDC] = HWBLK(MSTPCR2, 0, CORE_AREA_BM), +}; + +static struct hwblk_info sh7724_hwblk_info = { + .areas = sh7724_hwblk_area, + .nr_areas = ARRAY_SIZE(sh7724_hwblk_area), + .hwblks = sh7724_hwblk, + .nr_hwblks = ARRAY_SIZE(sh7724_hwblk), +}; + +int arch_hwblk_sleep_mode(void) +{ + if (!sh7724_hwblk_area[CORE_AREA].cnt[HWBLK_CNT_USAGE]) + return SUSP_SH_STANDBY | SUSP_SH_SF; + + if (!sh7724_hwblk_area[CORE_AREA_BM].cnt[HWBLK_CNT_USAGE]) + return SUSP_SH_SLEEP | SUSP_SH_SF; + + return SUSP_SH_SLEEP; +} + +int __init arch_hwblk_init(void) +{ + return hwblk_register(&sh7724_hwblk_info); +} diff --git a/arch/sh/kernel/cpu/shmobile/pm_runtime.c b/arch/sh/kernel/cpu/shmobile/pm_runtime.c new file mode 100644 index 00000000000..bf280c812d2 --- /dev/null +++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c @@ -0,0 +1,319 @@ +/* + * arch/sh/kernel/cpu/shmobile/pm_runtime.c + * + * Runtime PM support code for SuperH Mobile + * + * Copyright (C) 2009 Magnus Damm + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(hwblk_lock); +static LIST_HEAD(hwblk_idle_list); +static struct work_struct hwblk_work; + +extern struct hwblk_info *hwblk_info; + +static void platform_pm_runtime_not_idle(struct platform_device *pdev) +{ + unsigned long flags; + + /* remove device from idle list */ + spin_lock_irqsave(&hwblk_lock, flags); + if (test_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags)) { + list_del(&pdev->archdata.entry); + __clear_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags); + } + spin_unlock_irqrestore(&hwblk_lock, flags); +} + +static int __platform_pm_runtime_resume(struct platform_device *pdev) +{ + struct device *d = &pdev->dev; + struct pdev_archdata *ad = &pdev->archdata; + int hwblk = ad->hwblk_id; + int ret = -ENOSYS; + + dev_dbg(d, "__platform_pm_runtime_resume() [%d]\n", hwblk); + + if (d->driver) { + hwblk_enable(hwblk_info, hwblk); + ret = 0; + + if (test_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags)) { + if (d->driver->pm && d->driver->pm->runtime_resume) + ret = d->driver->pm->runtime_resume(d); + + if (!ret) + clear_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags); + else + hwblk_disable(hwblk_info, hwblk); + } + } + + dev_dbg(d, "__platform_pm_runtime_resume() [%d] - returns %d\n", + hwblk, ret); + + return ret; +} + +static int __platform_pm_runtime_suspend(struct platform_device *pdev) +{ + struct device *d = &pdev->dev; + struct pdev_archdata *ad = &pdev->archdata; + int hwblk = ad->hwblk_id; + int ret = -ENOSYS; + + dev_dbg(d, "__platform_pm_runtime_suspend() [%d]\n", hwblk); + + if (d->driver) { + BUG_ON(!test_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags)); + ret = 0; + + if (d->driver->pm && d->driver->pm->runtime_suspend) { + hwblk_enable(hwblk_info, hwblk); + ret = d->driver->pm->runtime_suspend(d); + hwblk_disable(hwblk_info, hwblk); + } + + if (!ret) { + set_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags); + platform_pm_runtime_not_idle(pdev); + hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE); + } + } + + dev_dbg(d, "__platform_pm_runtime_suspend() [%d] - returns %d\n", + hwblk, ret); + + return ret; +} + +static void platform_pm_runtime_work(struct work_struct *work) +{ + struct platform_device *pdev; + unsigned long flags; + int ret; + + /* go through the idle list and suspend one device at a time */ + do { + spin_lock_irqsave(&hwblk_lock, flags); + if (list_empty(&hwblk_idle_list)) + pdev = NULL; + else + pdev = list_first_entry(&hwblk_idle_list, + struct platform_device, + archdata.entry); + spin_unlock_irqrestore(&hwblk_lock, flags); + + if (pdev) { + mutex_lock(&pdev->archdata.mutex); + ret = __platform_pm_runtime_suspend(pdev); + + /* at this point the platform device may be: + * suspended: ret = 0, FLAG_SUSP set, clock stopped + * failed: ret < 0, FLAG_IDLE set, clock stopped + */ + mutex_unlock(&pdev->archdata.mutex); + } else { + ret = -ENODEV; + } + } while (!ret); +} + +/* this function gets called from cpuidle context when all devices in the + * main power domain are unused but some are counted as idle, ie the hwblk + * counter values are (HWBLK_CNT_USAGE == 0) && (HWBLK_CNT_IDLE != 0) + */ +void platform_pm_runtime_suspend_idle(void) +{ + queue_work(pm_wq, &hwblk_work); +} + +static int default_platform_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pdev_archdata *ad = &pdev->archdata; + unsigned long flags; + int hwblk = ad->hwblk_id; + int ret = 0; + + dev_dbg(dev, "%s() [%d]\n", __func__, hwblk); + + /* ignore off-chip platform devices */ + if (!hwblk) + goto out; + + /* interrupt context not allowed */ + might_sleep(); + + /* catch misconfigured drivers not starting with resume */ + if (test_bit(PDEV_ARCHDATA_FLAG_INIT, &ad->flags)) { + ret = -EINVAL; + goto out; + } + + /* serialize */ + mutex_lock(&ad->mutex); + + /* disable clock */ + hwblk_disable(hwblk_info, hwblk); + + /* put device on idle list */ + spin_lock_irqsave(&hwblk_lock, flags); + list_add_tail(&ad->entry, &hwblk_idle_list); + __set_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags); + spin_unlock_irqrestore(&hwblk_lock, flags); + + /* increase idle count */ + hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_IDLE); + + /* at this point the platform device is: + * idle: ret = 0, FLAG_IDLE set, clock stopped + */ + mutex_unlock(&ad->mutex); + +out: + dev_dbg(dev, "%s() [%d] returns %d\n", + __func__, hwblk, ret); + + return ret; +} + +static int default_platform_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pdev_archdata *ad = &pdev->archdata; + int hwblk = ad->hwblk_id; + int ret = 0; + + dev_dbg(dev, "%s() [%d]\n", __func__, hwblk); + + /* ignore off-chip platform devices */ + if (!hwblk) + goto out; + + /* interrupt context not allowed */ + might_sleep(); + + /* serialize */ + mutex_lock(&ad->mutex); + + /* make sure device is removed from idle list */ + platform_pm_runtime_not_idle(pdev); + + /* decrease idle count */ + if (!test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags) && + !test_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags)) + hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE); + + /* resume the device if needed */ + ret = __platform_pm_runtime_resume(pdev); + + /* the driver has been initialized now, so clear the init flag */ + clear_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); + + /* at this point the platform device may be: + * resumed: ret = 0, flags = 0, clock started + * failed: ret < 0, FLAG_SUSP set, clock stopped + */ + mutex_unlock(&ad->mutex); +out: + dev_dbg(dev, "%s() [%d] returns %d\n", + __func__, hwblk, ret); + + return ret; +} + +static int default_platform_runtime_idle(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + int hwblk = pdev->archdata.hwblk_id; + int ret = 0; + + dev_dbg(dev, "%s() [%d]\n", __func__, hwblk); + + /* ignore off-chip platform devices */ + if (!hwblk) + goto out; + + /* interrupt context not allowed, use pm_runtime_put()! */ + might_sleep(); + + /* suspend synchronously to disable clocks immediately */ + ret = pm_runtime_suspend(dev); +out: + dev_dbg(dev, "%s() [%d] done!\n", __func__, hwblk); + return ret; +} + +static struct dev_pm_domain default_pm_domain = { + .ops = { + .runtime_suspend = default_platform_runtime_suspend, + .runtime_resume = default_platform_runtime_resume, + .runtime_idle = default_platform_runtime_idle, + USE_PLATFORM_PM_SLEEP_OPS + }, +}; + +static int platform_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + struct platform_device *pdev = to_platform_device(dev); + int hwblk = pdev->archdata.hwblk_id; + + /* ignore off-chip platform devices */ + if (!hwblk) + return 0; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + INIT_LIST_HEAD(&pdev->archdata.entry); + mutex_init(&pdev->archdata.mutex); + /* platform devices without drivers should be disabled */ + hwblk_enable(hwblk_info, hwblk); + hwblk_disable(hwblk_info, hwblk); + /* make sure driver re-inits itself once */ + __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); + dev->pm_domain = &default_pm_domain; + break; + /* TODO: add BUS_NOTIFY_BIND_DRIVER and increase idle count */ + case BUS_NOTIFY_BOUND_DRIVER: + /* keep track of number of devices in use per hwblk */ + hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_DEVICES); + break; + case BUS_NOTIFY_UNBOUND_DRIVER: + /* keep track of number of devices in use per hwblk */ + hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_DEVICES); + /* make sure driver re-inits itself once */ + __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); + break; + case BUS_NOTIFY_DEL_DEVICE: + dev->pm_domain = NULL; + break; + } + return 0; +} + +static struct notifier_block platform_bus_notifier = { + .notifier_call = platform_bus_notify +}; + +static int __init sh_pm_runtime_init(void) +{ + INIT_WORK(&hwblk_work, platform_pm_runtime_work); + + bus_register_notifier(&platform_bus_type, &platform_bus_notifier); + return 0; +} +core_initcall(sh_pm_runtime_init); -- cgit v1.2.2