diff options
author | Mayuresh Kulkarni <mkulkarni@nvidia.com> | 2012-11-16 19:28:17 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2013-09-14 12:49:30 -0700 |
commit | f3d71c369c0c86088abf3d89bc588ed4d196a859 (patch) | |
tree | a5b6faf512d81939bbfe147e1368b3d9b111ae61 /arch | |
parent | 40e6d10a2e1a50f706404775811b3d884fe1ca36 (diff) |
arm: tegra: refactor powergate code per-SoC
- split the current power-gate code into common and
SoC specific code
- every SoC now exports a set of ops to the common APIs
- following is the new file structure:
powergate.c - common powergate APIs
powergate-ops-txx.c - t20/t30 specific common implementation
powergate-ops-t1xx.c - t114 specific common implementation.
this will also take care of t148 and t124 (in future)
powergate-t20 - t20 specific structures and ids
powergate-t30 - t30 specific strcutures and ids
powergate-t11x - t11x specific structures and ids
powergate-t14x - t14x specific structures and ids (stub in
this commit. it will be populated in a separate commit
as a part of bug 1190194)
- powergate.c also provides a low level common APIs
for all SoCs
- each SoC now has 2 arrays: 1 for power partition info
and other for mc client list info. amongst these 2 arrays,
mc client list array is static
Bug 1180197
Change-Id: I62b2d894c683fe9a18b82c7f9d87c08ce5b3864f
Signed-off-by: Mayuresh Kulkarni <mkulkarni@nvidia.com>
Reviewed-on: http://git-master/r/145591
Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com>
GVS: Gerrit_Virtual_Submit
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 8 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-ops-t1xx.c | 109 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-ops-t1xx.h | 22 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-ops-txx.c | 149 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-ops-txx.h | 31 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-priv.h | 154 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-t11x.c | 666 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-t14x.c | 35 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-t20.c | 348 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate-t30.c | 358 | ||||
-rw-r--r-- | arch/arm/mach-tegra/powergate.c | 1661 |
11 files changed, 2171 insertions, 1370 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 7dcd49a3cc31..e2ed380b0ac0 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -59,7 +59,15 @@ AFLAGS_sleep.o :=-Wa,-march=armv7-a$(plus_sec) obj-y += tegra_fuse.o obj-y += kfuse.o obj-y += csi.o + obj-y += powergate.o +obj-y += powergate-ops-txx.o +obj-y += powergate-ops-t1xx.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += powergate-t20.o +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += powergate-t30.o +obj-$(CONFIG_ARCH_TEGRA_11x_SOC) += powergate-t11x.o +obj-$(CONFIG_ARCH_TEGRA_14x_SOC) += powergate-t14x.o + obj-y += apbio.o obj-y += mc.o ifeq ($(CONFIG_USB_SUPPORT),y) diff --git a/arch/arm/mach-tegra/powergate-ops-t1xx.c b/arch/arm/mach-tegra/powergate-ops-t1xx.c new file mode 100644 index 000000000000..dd2d0aba9630 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-ops-t1xx.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/delay.h> + +#include <mach/powergate.h> + +#include "powergate-priv.h" + +int tegra1xx_powergate(int id, struct powergate_partition_info *pg_info) +{ + int ret; + + /* If first clk_ptr is null, fill clk info for the partition */ + if (!pg_info->clk_info[0].clk_ptr) + get_clk_info(pg_info); + + ret = partition_clk_enable(pg_info); + if (ret) + WARN(1, "Couldn't enable clock"); + + udelay(10); + + tegra_powergate_mc_flush(id); + + udelay(10); + + powergate_partition_assert_reset(pg_info); + + udelay(10); + + /* Powergating is done only if refcnt of all clks is 0 */ + partition_clk_disable(pg_info); + + udelay(10); + + ret = tegra_powergate_set(id, false); + if (ret) + goto err_power_off; + + return 0; + +err_power_off: + WARN(1, "Could not Powergate Partition %d", id); + return ret; +} + +int tegra1xx_unpowergate(int id, struct powergate_partition_info *pg_info) +{ + int ret; + + /* If first clk_ptr is null, fill clk info for the partition */ + if (!pg_info->clk_info[0].clk_ptr) + get_clk_info(pg_info); + + if (tegra_powergate_is_powered(id)) + return tegra_powergate_reset_module(pg_info); + + ret = tegra_powergate_set(id, true); + if (ret) + goto err_power; + + udelay(10); + + /* Un-Powergating fails if all clks are not enabled */ + ret = partition_clk_enable(pg_info); + if (ret) + goto err_clk_on; + + udelay(10); + + ret = tegra_powergate_remove_clamping(id); + if (ret) + goto err_clamp; + + udelay(10); + + powergate_partition_deassert_reset(pg_info); + + udelay(10); + + tegra_powergate_mc_flush_done(id); + + udelay(10); + + /* Disable all clks enabled earlier. Drivers should enable clks */ + partition_clk_disable(pg_info); + + return 0; + +err_clamp: + partition_clk_disable(pg_info); +err_clk_on: + powergate_module(id); +err_power: + WARN(1, "Could not Un-Powergate %d", id); + return ret; +} diff --git a/arch/arm/mach-tegra/powergate-ops-t1xx.h b/arch/arm/mach-tegra/powergate-ops-t1xx.h new file mode 100644 index 000000000000..4b9528e7374a --- /dev/null +++ b/arch/arm/mach-tegra/powergate-ops-t1xx.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __POWERGATE_OPS_T1XX_H__ +#define __POWERGATE_OPS_T1XX_H__ + +/* Common APIs for tegra1xx SoCs */ +int tegra1xx_powergate(int id, struct powergate_partition_info *pg_info); +int tegra1xx_unpowergate(int id, struct powergate_partition_info *pg_info); + +#endif /* __POWERGATE_OPS_T1XX_H__ */ diff --git a/arch/arm/mach-tegra/powergate-ops-txx.c b/arch/arm/mach-tegra/powergate-ops-txx.c new file mode 100644 index 000000000000..75246c610577 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-ops-txx.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/delay.h> + +#include <mach/powergate.h> + +#include "powergate-priv.h" + +int tegraxx_powergate_partition(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + /* If first clk_ptr is null, fill clk info for the partition */ + if (pg_info->clk_info[0].clk_ptr) + get_clk_info(pg_info); + + powergate_partition_assert_reset(pg_info); + + /* Powergating is done only if refcnt of all clks is 0 */ + ret = is_partition_clk_disabled(pg_info); + if (ret) + goto err_clk_off; + + ret = powergate_module(id); + if (ret) + goto err_power_off; + + return 0; + +err_power_off: + WARN(1, "Could not Powergate Partition %d", id); +err_clk_off: + WARN(1, "Could not Powergate Partition %d, all clks not disabled", id); + return ret; +} + +int tegraxx_unpowergate_partition(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + /* If first clk_ptr is null, fill clk info for the partition */ + if (!pg_info->clk_info[0].clk_ptr) + get_clk_info(pg_info); + + if (tegra_powergate_is_powered(id)) + return tegra_powergate_reset_module(pg_info); + + ret = unpowergate_module(id); + if (ret) + goto err_power; + + powergate_partition_assert_reset(pg_info); + + /* Un-Powergating fails if all clks are not enabled */ + ret = partition_clk_enable(pg_info); + if (ret) + goto err_clk_on; + + udelay(10); + + ret = tegra_powergate_remove_clamping(id); + if (ret) + goto err_clamp; + + udelay(10); + + powergate_partition_deassert_reset(pg_info); + + tegra_powergate_mc_flush_done(id); + + /* Disable all clks enabled earlier. Drivers should enable clks */ + partition_clk_disable(pg_info); + + return 0; + +err_clamp: + partition_clk_disable(pg_info); +err_clk_on: + powergate_module(id); +err_power: + WARN(1, "Could not Un-Powergate %d", id); + + return ret; +} + +int tegraxx_powergate_partition_with_clk_off(int id, + struct powergate_partition_info *pg_info) +{ + int ret = 0; + + /* Disable clks for the partition */ + partition_clk_disable(pg_info); + + ret = is_partition_clk_disabled(pg_info); + if (ret) + goto err_powergate_clk; + + ret = tegra_powergate_partition(id); + if (ret) + goto err_powergating; + + return ret; + +err_powergate_clk: + WARN(1, "Could not Powergate Partition %d, all clks not disabled", id); +err_powergating: + partition_clk_enable(pg_info); + WARN(1, "Could not Powergate Partition %d", id); + return ret; +} + +int tegraxx_unpowergate_partition_with_clk_on(int id, + struct powergate_partition_info *pg_info) +{ + int ret = 0; + + ret = tegra_unpowergate_partition(id); + if (ret) + goto err_unpowergating; + + /* Enable clks for the partition */ + ret = partition_clk_enable(pg_info); + if (ret) + goto err_unpowergate_clk; + + return ret; + +err_unpowergate_clk: + tegra_powergate_partition(id); + WARN(1, "Could not Un-Powergate %d, err in enabling clk", id); +err_unpowergating: + WARN(1, "Could not Un-Powergate %d", id); + + return ret; +} diff --git a/arch/arm/mach-tegra/powergate-ops-txx.h b/arch/arm/mach-tegra/powergate-ops-txx.h new file mode 100644 index 000000000000..7acbd06efed4 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-ops-txx.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __POWERGATE_OPS_TXX_H__ +#define __POWERGATE_OPS_TXX_H__ + +/* Common APIs for tegra2 and tegra3 SoCs */ +int tegraxx_powergate_partition(int id, + struct powergate_partition_info *pg_info); + +int tegraxx_unpowergate_partition(int id, + struct powergate_partition_info *pg_info); + +int tegraxx_powergate_partition_with_clk_off(int id, + struct powergate_partition_info *pg_info); + +int tegraxx_unpowergate_partition_with_clk_on(int id, + struct powergate_partition_info *pg_info); + +#endif /* __POWERGATE_OPS_TXX_H__ */ diff --git a/arch/arm/mach-tegra/powergate-priv.h b/arch/arm/mach-tegra/powergate-priv.h new file mode 100644 index 000000000000..a4f7acb518a9 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-priv.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __POWERGATE_PRIV_H__ +#define __POWERGATE_PRIV_H__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/io.h> + +#include <mach/clk.h> +#include <mach/iomap.h> +#include <mach/powergate.h> + +#include "clock.h" +#include "fuse.h" + +#define MAX_CLK_EN_NUM 9 +#define MAX_HOTRESET_CLIENT_NUM 4 + +#define PWRGATE_TOGGLE 0x30 +#define PWRGATE_TOGGLE_START (1 << 8) +#define REMOVE_CLAMPING 0x34 +#define PWRGATE_STATUS 0x38 + +/* MC register read/write */ +static void __iomem *mc = IO_ADDRESS(TEGRA_MC_BASE); +static inline u32 mc_read(unsigned long reg) +{ + return readl(mc + reg); +} + +static inline void mc_write(u32 val, unsigned long reg) +{ + writel_relaxed(val, mc + reg); +} + +/* PMC register read/write */ +static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); +static inline u32 pmc_read(unsigned long reg) +{ + return readl(pmc + reg); +} + +static inline void pmc_write(u32 val, unsigned long reg) +{ + writel_relaxed(val, pmc + reg); +} + +enum clk_type { + CLK_AND_RST, + RST_ONLY, + CLK_ONLY, +}; + +struct partition_clk_info { + const char *clk_name; + enum clk_type clk_type; + struct clk *clk_ptr; +}; + +struct powergate_partition_info { + const char *name; + struct partition_clk_info clk_info[MAX_CLK_EN_NUM]; +}; + +struct powergate_ops { + const char *soc_name; + + int num_powerdomains; + int num_cpu_domains; + u8 *cpu_domains; + + spinlock_t *(*get_powergate_lock)(void); + + const char *(*get_powergate_domain_name)(int id); + + int (*powergate_partition)(int); + int (*unpowergate_partition)(int id); + + int (*powergate_partition_with_clk_off)(int); + int (*unpowergate_partition_with_clk_on)(int); + + int (*powergate_mc_enable)(int id); + int (*powergate_mc_disable)(int id); + + int (*powergate_mc_flush)(int id); + int (*powergate_mc_flush_done)(int id); +}; + +void get_clk_info(struct powergate_partition_info *pg_info); +int tegra_powergate_remove_clamping(int id); +int partition_clk_enable(struct powergate_partition_info *pg_info); +void partition_clk_disable(struct powergate_partition_info *pg_info); +int is_partition_clk_disabled(struct powergate_partition_info *pg_info); +void powergate_partition_deassert_reset(struct powergate_partition_info *pg_info); +void powergate_partition_assert_reset(struct powergate_partition_info *pg_info); +int tegra_powergate_reset_module(struct powergate_partition_info *pg_info); +int powergate_module(int id); +int unpowergate_module(int id); +int tegra_powergate_set(int id, bool new_state); + +/* INIT APIs: New SoC needs to add its support here */ +#if defined(CONFIG_ARCH_TEGRA_2x_SOC) +struct powergate_ops *tegra2_powergate_init_chip_support(void); +#else +static inline struct powergate_ops *tegra2_powergate_init_chip_support(void) +{ + return NULL; +} +#endif + +#if defined(CONFIG_ARCH_TEGRA_3x_SOC) +struct powergate_ops *tegra3_powergate_init_chip_support(void); +#else +static inline struct powergate_ops *tegra3_powergate_init_chip_support(void) +{ + return NULL; +} +#endif + +#if defined(CONFIG_ARCH_TEGRA_11x_SOC) +struct powergate_ops *tegra11x_powergate_init_chip_support(void); +#else +static inline struct powergate_ops *tegra11x_powergate_init_chip_support(void) +{ + return NULL; +} +#endif + +#if defined(CONFIG_ARCH_TEGRA_14x_SOC) +struct powergate_ops *tegra14x_powergate_init_chip_support(void); +#else +static inline struct powergate_ops *tegra14x_powergate_init_chip_support(void) +{ + return NULL; +} +#endif + +#endif /* __POWERGATE_PRIV_H__ */ diff --git a/arch/arm/mach-tegra/powergate-t11x.c b/arch/arm/mach-tegra/powergate-t11x.c new file mode 100644 index 000000000000..1fba772c0e66 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-t11x.c @@ -0,0 +1,666 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/spinlock.h> +#include <linux/delay.h> + +#include <asm/atomic.h> + +#include <mach/powergate.h> + +#include "powergate-priv.h" +#include "powergate-ops-txx.h" +#include "powergate-ops-t1xx.h" + +enum mc_client { + MC_CLIENT_AVPC = 1, + MC_CLIENT_DC = 2, + MC_CLIENT_DCB = 3, + MC_CLIENT_EPP = 4, + MC_CLIENT_G2 = 5, + MC_CLIENT_HC = 6, + MC_CLIENT_HDA = 7, + MC_CLIENT_ISP = 8, + MC_CLIENT_MPCORE = 9, + MC_CLIENT_MPCORELP = 10, + MC_CLIENT_MSENC = 11, + MC_CLIENT_NV = 12, + MC_CLIENT_PPCS = 14, + MC_CLIENT_VDE = 16, + MC_CLIENT_VI = 17, + MC_CLIENT_XUSB_HOST = 19, + MC_CLIENT_XUSB_DEV = 20, + MC_CLIENT_EMUCIF = 21, + MC_CLIENT_TSEC = 22, + MC_CLIENT_LAST = -1, + MC_CLIENT_AFI = MC_CLIENT_LAST, + MC_CLIENT_MPE = MC_CLIENT_LAST, + MC_CLIENT_NV2 = MC_CLIENT_LAST, + MC_CLIENT_SATA = MC_CLIENT_LAST, +}; + +struct tegra11x_powergate_mc_client_info { + enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM]; +}; + +static struct tegra11x_powergate_mc_client_info tegra11x_pg_mc_info[] = { + [TEGRA_POWERGATE_3D] = { + .hot_reset_clients = { + [0] = MC_CLIENT_NV, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_VDEC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_VDE, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_MPE] = { + .hot_reset_clients = { + [0] = MC_CLIENT_MSENC, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_VENC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_ISP, + [1] = MC_CLIENT_VI, + [2] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_HEG] = { + .hot_reset_clients = { + [0] = MC_CLIENT_G2, + [1] = MC_CLIENT_EPP, + [2] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_DISA] = { + .hot_reset_clients = { + [0] = MC_CLIENT_DC, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_DISB] = { + .hot_reset_clients = { + [0] = MC_CLIENT_DCB, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_XUSBA] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_XUSBB] = { + .hot_reset_clients = { + [0] = MC_CLIENT_XUSB_DEV, + [1] = MC_CLIENT_LAST + }, + }, + [TEGRA_POWERGATE_XUSBC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_XUSB_HOST, + [1] = MC_CLIENT_LAST, + }, + }, +}; + +static struct powergate_partition_info tegra11x_powergate_partition_info[] = { + [TEGRA_POWERGATE_3D] = { + .name = "3d", + .clk_info = { + [0] = { .clk_name = "3d", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_VDEC] = { + .name = "vde", + .clk_info = { + [0] = { .clk_name = "vde", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_MPE] = { + .name = "mpe", + .clk_info = { + [0] = { .clk_name = "msenc.cbus", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_VENC] = { + .name = "ve", + .clk_info = { + [0] = { .clk_name = "isp", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "vi", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "csi", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_HEG] = { + .name = "heg", + .clk_info = { + [0] = { .clk_name = "2d.cbus", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "epp.cbus", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_DISA] = { + .name = "disa", + .clk_info = { + [0] = { .clk_name = "disp1", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "dsia", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "dsib", .clk_type = CLK_AND_RST }, + [3] = { .clk_name = "csi", .clk_type = CLK_AND_RST }, + [4] = { .clk_name = "mipi-cal", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_DISB] = { + .name = "disb", + .clk_info = { + [0] = { .clk_name = "disp2", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "hdmi", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_XUSBA] = { + .name = "xusba", + .clk_info = { + [0] = { .clk_name = "xusb_ss", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_XUSBB] = { + .name = "xusbb", + .clk_info = { + [0] = { .clk_name = "xusb_dev", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_XUSBC] = { + .name = "xusbc", + .clk_info = { + [0] = { .clk_name = "xusb_host", .clk_type = CLK_AND_RST }, + }, + }, +}; + +static atomic_t ref_count_a = ATOMIC_INIT(1); /* for TEGRA_POWERGATE_DISA */ +static atomic_t ref_count_b = ATOMIC_INIT(1); /* for TEGRA_POWERGATE_DISB */ + +static void __iomem *mipi_cal = IO_ADDRESS(TEGRA_MIPI_CAL_BASE); +static u32 mipi_cal_read(unsigned long reg) +{ + return readl(mipi_cal + reg); +} + +static void mipi_cal_write(u32 val, unsigned long reg) +{ + writel_relaxed(val, mipi_cal + reg); +} + +#define MC_CLIENT_HOTRESET_CTRL 0x200 +#define MC_CLIENT_HOTRESET_STAT 0x204 + +static DEFINE_SPINLOCK(tegra11x_powergate_lock); + +/* Forward Declarations */ +int tegra11x_powergate_mc_flush(int id); +int tegra11x_powergate_mc_flush_done(int id); +int tegra11x_unpowergate_partition_with_clk_on(int id); +int tegra11x_powergate_partition_with_clk_off(int id); + +static bool skip_pg_check(int id, bool is_unpowergate) +{ + /* + * FIXME: need to stress test partition power gating before + * enabling power gating for T11x + * List of T11x partition id which skip power gating + */ + static int skip_pg_t11x_list[] = { + /* + * CPU and 3D partitions enable/disable + * is managed by respective modules + */ + TEGRA_POWERGATE_DISA, + TEGRA_POWERGATE_DISB + }; + int i; + + /* + * skip unnecessary multiple calls e.g. powergate call when + * partition is already powered-off or vice-versa + */ + if ((tegra_powergate_is_powered(id) && is_unpowergate) || + (!(tegra_powergate_is_powered(id)) && (!is_unpowergate))) { + + pr_err("Partition %s already powered-%s and %spowergate skipped\n", + tegra_powergate_get_name(id), + (tegra_powergate_is_powered(id)) ? + "on" : "off", + (is_unpowergate) ? "un" : ""); + + return true; + } + + /* unpowergate is allowed for all partitions */ + if (!tegra_powergate_is_powered(id) && is_unpowergate) + return false; + + for (i = 0; i < ARRAY_SIZE(skip_pg_t11x_list); i++) { + if (id == skip_pg_t11x_list[i]) { + pr_err("Partition %s %spowergate skipped\n", + tegra_powergate_get_name(id), + (is_unpowergate) ? "un" : ""); + return true; + } + } + + return false; +} + +#define HOTRESET_READ_COUNT 5 +static bool tegra11x_stable_hotreset_check(u32 *stat) +{ + int i; + u32 cur_stat; + u32 prv_stat; + unsigned long flags; + + spin_lock_irqsave(&tegra11x_powergate_lock, flags); + prv_stat = mc_read(MC_CLIENT_HOTRESET_STAT); + for (i = 0; i < HOTRESET_READ_COUNT; i++) { + cur_stat = mc_read(MC_CLIENT_HOTRESET_STAT); + if (cur_stat != prv_stat) { + spin_unlock_irqrestore(&tegra11x_powergate_lock, flags); + return false; + } + } + *stat = cur_stat; + spin_unlock_irqrestore(&tegra11x_powergate_lock, flags); + return true; +} + +/* + * FIXME: sw war for mipi-cal calibration when unpowergating DISA partition + */ +static void tegra11x_mipical_calibrate(int id) +{ + struct reg_offset_val { + u32 offset; + u32 por_value; + }; + u32 status; + unsigned long flags; + +#define MIPI_CAL_MIPI_CAL_CTRL_0 0x0 +#define MIPI_CAL_CIL_MIPI_CAL_STATUS_0 0x8 +#define MIPI_CAL_CILA_MIPI_CAL_CONFIG_0 0x14 +#define MIPI_CAL_CILB_MIPI_CAL_CONFIG_0 0x18 +#define MIPI_CAL_CILC_MIPI_CAL_CONFIG_0 0x1c +#define MIPI_CAL_CILD_MIPI_CAL_CONFIG_0 0x20 +#define MIPI_CAL_CILE_MIPI_CAL_CONFIG_0 0x24 +#define MIPI_CAL_DSIA_MIPI_CAL_CONFIG_0 0x38 +#define MIPI_CAL_DSIB_MIPI_CAL_CONFIG_0 0x3c +#define MIPI_CAL_DSIC_MIPI_CAL_CONFIG_0 0x40 +#define MIPI_CAL_DSID_MIPI_CAL_CONFIG_0 0x44 + + static struct reg_offset_val mipi_cal_por_values[] = { + { MIPI_CAL_MIPI_CAL_CTRL_0, 0x2a000000 }, + { MIPI_CAL_CILA_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_CILB_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_CILC_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_CILD_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_CILE_MIPI_CAL_CONFIG_0, 0x00000000 }, + { MIPI_CAL_DSIA_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_DSIB_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_DSIC_MIPI_CAL_CONFIG_0, 0x00200000 }, + { MIPI_CAL_DSID_MIPI_CAL_CONFIG_0, 0x00200000 }, + }; + int i; + + if (id != TEGRA_POWERGATE_DISA) + return; + + spin_lock_irqsave(&tegra11x_powergate_lock, flags); + + /* mipi cal por restore */ + for (i = 0; i < ARRAY_SIZE(mipi_cal_por_values); i++) { + mipi_cal_write(mipi_cal_por_values[i].por_value, + mipi_cal_por_values[i].offset); + } + + /* mipi cal status clear */ + status = mipi_cal_read(MIPI_CAL_CIL_MIPI_CAL_STATUS_0); + mipi_cal_write(status, MIPI_CAL_CIL_MIPI_CAL_STATUS_0); + + /* mipi cal status read - to flush writes */ + status = mipi_cal_read(MIPI_CAL_CIL_MIPI_CAL_STATUS_0); + + spin_unlock_irqrestore(&tegra11x_powergate_lock, flags); +} + +static int tegra11x_powergate_partition_internal(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + if (tegra_powergate_is_powered(id)) { + ret = is_partition_clk_disabled(pg_info); + if (ret < 0) { + /* clock enabled */ + ret = tegra11x_powergate_partition_with_clk_off(id); + if (ret < 0) + return ret; + } else { + ret = tegra_powergate_partition(id); + if (ret < 0) + return ret; + } + } + return 0; +} + +static int tegra11x_unpowergate_partition_internal(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + if (!tegra_powergate_is_powered(id)) { + ret = is_partition_clk_disabled(pg_info); + if (ret) { + /* clock disabled */ + ret = tegra11x_unpowergate_partition_with_clk_on(id); + if (ret < 0) + return ret; + } else { + ret = tegra_unpowergate_partition(id); + if (ret < 0) + return ret; + } + } + return 0; +} + +/* + * Tegra11x has powergate dependencies between partitions. + * This function captures the dependencies. + */ +static int tegra11x_check_partition_pg_seq(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + if (id == TEGRA_POWERGATE_DISA) { + ret = tegra11x_powergate_partition_internal(TEGRA_POWERGATE_VENC, + pg_info); + if (ret < 0) + return ret; + + ret = tegra11x_powergate_partition_internal(TEGRA_POWERGATE_DISB, + pg_info); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * This function captures power-ungate dependencies between tegra11x partitions + */ +static int tegra11x_check_partition_pug_seq(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + switch (id) { + case TEGRA_POWERGATE_DISB: + case TEGRA_POWERGATE_VENC: + ret = tegra11x_unpowergate_partition_internal(TEGRA_POWERGATE_DISA, + pg_info); + if (ret < 0) + return ret; + + break; + } + return 0; +} + +int tegra11x_powergate_mc_enable(int id) +{ + return 0; +} + +int tegra11x_powergate_mc_disable(int id) +{ + return 0; +} + +int tegra11x_powergate_mc_flush(int id) +{ + u32 idx, rst_ctrl, rst_stat; + enum mc_client mcClientBit; + unsigned long flags; + bool ret; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra11x_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra11x_powergate_lock, flags); + rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL); + rst_ctrl |= (1 << mcClientBit); + mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL); + + spin_unlock_irqrestore(&tegra11x_powergate_lock, flags); + + do { + udelay(10); + rst_stat = 0; + ret = tegra11x_stable_hotreset_check(&rst_stat); + if (!ret) + continue; + } while (!(rst_stat & (1 << mcClientBit))); + } + + return 0; +} + +int tegra11x_powergate_mc_flush_done(int id) +{ + u32 idx, rst_ctrl; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra11x_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra11x_powergate_lock, flags); + + rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL); + rst_ctrl &= ~(1 << mcClientBit); + mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL); + + spin_unlock_irqrestore(&tegra11x_powergate_lock, flags); + } + + wmb(); + + return 0; +} + +static int tegra11x_unpowergate(int id, + struct powergate_partition_info *pg_info) +{ + int ret; + + /* If first clk_ptr is null, fill clk info for the partition */ + if (!pg_info->clk_info[0].clk_ptr) + get_clk_info(pg_info); + + if (tegra_powergate_is_powered(id)) + return tegra_powergate_reset_module(pg_info); + + ret = tegra_powergate_set(id, true); + if (ret) + goto err_power; + + udelay(10); + + /* Un-Powergating fails if all clks are not enabled */ + ret = partition_clk_enable(pg_info); + if (ret) + goto err_clk_on; + + udelay(10); + + ret = tegra_powergate_remove_clamping(id); + if (ret) + goto err_clamp; + + udelay(10); + + tegra11x_mipical_calibrate(id); + + powergate_partition_deassert_reset(pg_info); + + udelay(10); + + tegra_powergate_mc_flush_done(id); + + udelay(10); + + /* Disable all clks enabled earlier. Drivers should enable clks */ + partition_clk_disable(pg_info); + + return 0; + +err_clamp: + partition_clk_disable(pg_info); +err_clk_on: + powergate_module(id); +err_power: + WARN(1, "Could not Un-Powergate %d", id); + return ret; +} + +int tegra11x_powergate_partition(int id) +{ + int ret; + + WARN_ONCE(atomic_read(&ref_count_a) < 0, "ref count A underflow"); + WARN_ONCE(atomic_read(&ref_count_b) < 0, "ref count B underflow"); + + if (id == TEGRA_POWERGATE_DISA && atomic_dec_return(&ref_count_a) != 0) + return 0; + else if (id == TEGRA_POWERGATE_DISB && + atomic_dec_return(&ref_count_b) != 0) + return 0; + + if (skip_pg_check(id, false)) + return 0; + + ret = tegra11x_check_partition_pg_seq(id, + &tegra11x_powergate_partition_info[id]); + if (ret) + return ret; + + /* call common power-gate API for t1xx */ + ret = tegra1xx_powergate(id, + &tegra11x_powergate_partition_info[id]); + + return ret; +} + +int tegra11x_unpowergate_partition(int id) +{ + int ret; + + WARN_ONCE(atomic_read(&ref_count_a) < 0, "ref count A underflow"); + WARN_ONCE(atomic_read(&ref_count_b) < 0, "ref count B underflow"); + + if (id == TEGRA_POWERGATE_DISA && atomic_inc_return(&ref_count_a) != 1) + return 0; + else if (id == TEGRA_POWERGATE_DISB && + atomic_inc_return(&ref_count_b) != 1) + return 0; + + if (skip_pg_check(id, true)) + return 0; + + ret = tegra11x_check_partition_pug_seq(id, + &tegra11x_powergate_partition_info[id]); + if (ret) + return ret; + + /* t11x needs to calibrate mipi in un-power-gate sequence + * hence it cannot use common un-power-gate api tegra1xx_unpowergate */ + ret = tegra11x_unpowergate(id, + &tegra11x_powergate_partition_info[id]); + + return ret; +} + +int tegra11x_powergate_partition_with_clk_off(int id) +{ + if (skip_pg_check(id, false)) + return 0; + + return tegraxx_powergate_partition_with_clk_off(id, + &tegra11x_powergate_partition_info[id]); +} + +int tegra11x_unpowergate_partition_with_clk_on(int id) +{ + if (skip_pg_check(id, true)) + return 0; + + return tegraxx_unpowergate_partition_with_clk_on(id, + &tegra11x_powergate_partition_info[id]); +} + +const char *tegra11x_get_powergate_domain_name(int id) +{ + return tegra11x_powergate_partition_info[id].name; +} + +spinlock_t *tegra11x_get_powergate_lock(void) +{ + return &tegra11x_powergate_lock; +} + +static struct powergate_ops tegra11x_powergate_ops = { + .soc_name = "tegra11x", + + .num_powerdomains = TEGRA_NUM_POWERGATE, + + .get_powergate_lock = tegra11x_get_powergate_lock, + .get_powergate_domain_name = tegra11x_get_powergate_domain_name, + + .powergate_partition = tegra11x_powergate_partition, + .unpowergate_partition = tegra11x_unpowergate_partition, + + .powergate_partition_with_clk_off = tegra11x_powergate_partition_with_clk_off, + .unpowergate_partition_with_clk_on = tegra11x_unpowergate_partition_with_clk_on, + + .powergate_mc_enable = tegra11x_powergate_mc_enable, + .powergate_mc_disable = tegra11x_powergate_mc_disable, + + .powergate_mc_flush = tegra11x_powergate_mc_flush, + .powergate_mc_flush_done = tegra11x_powergate_mc_flush_done, +}; + +struct powergate_ops *tegra11x_powergate_init_chip_support(void) +{ + return &tegra11x_powergate_ops; +} diff --git a/arch/arm/mach-tegra/powergate-t14x.c b/arch/arm/mach-tegra/powergate-t14x.c new file mode 100644 index 000000000000..a6a46dff41f7 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-t14x.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/spinlock.h> +#include <linux/delay.h> + +#include <asm/atomic.h> + +#include <mach/powergate.h> + +#include "powergate-priv.h" +#include "powergate-ops-txx.h" +#include "powergate-ops-t1xx.h" + +static struct powergate_ops tegra14x_powergate_ops = { + .soc_name = "tegra14x", + + .num_powerdomains = TEGRA_NUM_POWERGATE, +}; + +struct powergate_ops *tegra11x_powergate_init_chip_support(void) +{ + return NULL; +} diff --git a/arch/arm/mach-tegra/powergate-t20.c b/arch/arm/mach-tegra/powergate-t20.c new file mode 100644 index 000000000000..32a48e1a9084 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-t20.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/spinlock.h> +#include <linux/delay.h> + +#include <mach/powergate.h> + +#include "powergate-priv.h" +#include "powergate-ops-txx.h" + +enum mc_client { + MC_CLIENT_AVPC = 0, + MC_CLIENT_DC = 1, + MC_CLIENT_DCB = 2, + MC_CLIENT_EPP = 3, + MC_CLIENT_G2 = 4, + MC_CLIENT_HC = 5, + MC_CLIENT_ISP = 6, + MC_CLIENT_MPCORE = 7, + MC_CLIENT_MPEA = 8, + MC_CLIENT_MPEB = 9, + MC_CLIENT_MPEC = 10, + MC_CLIENT_NV = 11, + MC_CLIENT_PPCS = 12, + MC_CLIENT_VDE = 13, + MC_CLIENT_VI = 14, + MC_CLIENT_LAST = -1, + MC_CLIENT_AFI = MC_CLIENT_LAST, +}; + +struct tegra2_powergate_mc_client_info { + enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM]; +}; + +static struct tegra2_powergate_mc_client_info tegra2_pg_mc_info[] = { + [TEGRA_POWERGATE_CPU] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_L2] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_3D] = { + .hot_reset_clients = { + [0] = MC_CLIENT_NV, + [1] = MC_CLIENT_LAST, + }, + }, +#ifdef CONFIG_ARCH_TEGRA_HAS_PCIE + [TEGRA_POWERGATE_PCIE] = { + .hot_reset_clients = { + [0] = MC_CLIENT_AFI, + [1] = MC_CLIENT_LAST, + }, + }, +#endif + [TEGRA_POWERGATE_VDEC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_VDE, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_MPE] = { + .hot_reset_clients = { + [0] = MC_CLIENT_MPEA, + [1] = MC_CLIENT_MPEB, + [2] = MC_CLIENT_MPEC, + [3] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_VENC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_ISP, + [1] = MC_CLIENT_VI, + [2] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_HEG] = { + .hot_reset_clients = { + [0] = MC_CLIENT_G2, + [1] = MC_CLIENT_EPP, + [2] = MC_CLIENT_HC, + [3] = MC_CLIENT_LAST, + }, + }, +}; + +static struct powergate_partition_info tegra2_powergate_partition_info[] = { + [TEGRA_POWERGATE_CPU] = { .name = "cpu0" }, + [TEGRA_POWERGATE_L2] = { .name = "l2" }, + [TEGRA_POWERGATE_3D] = { + .name = "3d0", + .clk_info = { + [0] = { .clk_name = "3d", .clk_type = CLK_AND_RST }, + }, + }, +#ifdef CONFIG_ARCH_TEGRA_HAS_PCIE + [TEGRA_POWERGATE_PCIE] = { + .name = "pcie", + .clk_info = { + [0] = { .clk_name = "afi", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "pcie", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "pciex", .clk_type = RST_ONLY }, + }, + }, +#endif + [TEGRA_POWERGATE_VDEC] = { + .name = "vde", + .clk_info = { + [0] = { .clk_name = "vde", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_MPE] = { + .name = "mpe", + .clk_info = { + [0] = { .clk_name = "mpe", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_VENC] = { + .name = "ve", + .clk_info = { + [0] = { .clk_name = "isp", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "vi", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "csi", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_HEG] = { + .name = "heg", + .clk_info = { + [0] = { .clk_name = "2d", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "epp", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "host1x", .clk_type = CLK_AND_RST }, + }, + }, +}; + +#define MC_CLIENT_CTRL 0x100 +#define MC_CLIENT_HOTRESETN 0x104 +#define MC_CLIENT_ORRC_BASE 0x140 + +static DEFINE_SPINLOCK(tegra2_powergate_lock); + +int tegra2_powergate_partition(int id) +{ + return tegraxx_powergate_partition(id, + &tegra2_powergate_partition_info[id]); +} + +int tegra2_unpowergate_partition(int id) +{ + return tegraxx_unpowergate_partition(id, + &tegra2_powergate_partition_info[id]); +} + +int tegra2_powergate_partition_with_clk_off(int id) +{ + /* Restrict functions use to selected partitions */ + if (id != TEGRA_POWERGATE_PCIE) { + WARN_ON(1); + return -EINVAL; + } + + return tegraxx_powergate_partition_with_clk_off(id, + &tegra2_powergate_partition_info[id]); +} + +int tegra2_unpowergate_partition_with_clk_on(int id) +{ + /* Restrict this functions use to few partitions */ + if (id != TEGRA_POWERGATE_PCIE) { + WARN_ON(1); + return -EINVAL; + } + + return tegraxx_unpowergate_partition_with_clk_on(id, + &tegra2_powergate_partition_info[id]); +} + +int tegra2_powergate_mc_enable(int id) +{ + u32 idx, clt_ctrl; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra2_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra2_powergate_lock, flags); + + /* enable client */ + clt_ctrl = mc_read(MC_CLIENT_CTRL); + clt_ctrl |= (1 << mcClientBit); + mc_write(clt_ctrl, MC_CLIENT_CTRL); + + /* read back to flush write */ + clt_ctrl = mc_read(MC_CLIENT_CTRL); + + spin_unlock_irqrestore(&tegra2_powergate_lock, flags); + } + + return 0; +} + +int tegra2_powergate_mc_disable(int id) +{ + u32 idx, clt_ctrl, orrc_reg; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra2_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra2_powergate_lock, flags); + + /* clear client enable bit */ + clt_ctrl = mc_read(MC_CLIENT_CTRL); + clt_ctrl &= ~(1 << mcClientBit); + mc_write(clt_ctrl, MC_CLIENT_CTRL); + + /* read back to flush write */ + clt_ctrl = mc_read(MC_CLIENT_CTRL); + + spin_unlock_irqrestore(&tegra2_powergate_lock, flags); + + /* wait for outstanding requests to reach 0 */ + orrc_reg = MC_CLIENT_ORRC_BASE + (mcClientBit * 4); + while (mc_read(orrc_reg) != 0) + udelay(10); + } + + return 0; +} + +int tegra2_powergate_mc_flush(int id) +{ + u32 idx, hot_rstn; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra2_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra2_powergate_lock, flags); + + /* assert hotreset (client module is currently in reset) */ + hot_rstn = mc_read(MC_CLIENT_HOTRESETN); + hot_rstn &= ~(1 << mcClientBit); + mc_write(hot_rstn, MC_CLIENT_HOTRESETN); + + /* read back to flush write */ + hot_rstn = mc_read(MC_CLIENT_HOTRESETN); + + spin_unlock_irqrestore(&tegra2_powergate_lock, flags); + } + + return 0; +} + +int tegra2_powergate_mc_flush_done(int id) +{ + u32 idx, hot_rstn; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra2_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra2_powergate_lock, flags); + + /* deassert hotreset */ + hot_rstn = mc_read(MC_CLIENT_HOTRESETN); + hot_rstn |= (1 << mcClientBit); + mc_write(hot_rstn, MC_CLIENT_HOTRESETN); + + /* read back to flush write */ + hot_rstn = mc_read(MC_CLIENT_HOTRESETN); + + spin_unlock_irqrestore(&tegra2_powergate_lock, flags); + } + + return 0; +} + +const char *tegra2_get_powergate_domain_name(int id) +{ + return tegra2_powergate_partition_info[id].name; +} + +spinlock_t *tegra2_get_powergate_lock(void) +{ + return &tegra2_powergate_lock; +} + +static struct powergate_ops tegra2_powergate_ops = { + .soc_name = "tegra2", + + .num_powerdomains = TEGRA_NUM_POWERGATE, + .num_cpu_domains = 0, + .cpu_domains = NULL, + + .get_powergate_lock = tegra2_get_powergate_lock, + + .get_powergate_domain_name = tegra2_get_powergate_domain_name, + + .powergate_partition = tegra2_powergate_partition, + .unpowergate_partition = tegra2_unpowergate_partition, + + .powergate_partition_with_clk_off = tegra2_powergate_partition_with_clk_off, + .unpowergate_partition_with_clk_on = tegra2_unpowergate_partition_with_clk_on, + + .powergate_mc_enable = tegra2_powergate_mc_enable, + .powergate_mc_disable = tegra2_powergate_mc_disable, + + .powergate_mc_flush = tegra2_powergate_mc_flush, + .powergate_mc_flush_done = tegra2_powergate_mc_flush_done, +}; + +struct powergate_ops *tegra2_powergate_init_chip_support(void) +{ + return &tegra2_powergate_ops; +} diff --git a/arch/arm/mach-tegra/powergate-t30.c b/arch/arm/mach-tegra/powergate-t30.c new file mode 100644 index 000000000000..a645f364ab69 --- /dev/null +++ b/arch/arm/mach-tegra/powergate-t30.c @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/spinlock.h> +#include <linux/delay.h> + +#include <mach/powergate.h> + +#include "powergate-priv.h" +#include "powergate-ops-txx.h" + +enum mc_client { + MC_CLIENT_AFI = 0, + MC_CLIENT_AVPC = 1, + MC_CLIENT_DC = 2, + MC_CLIENT_DCB = 3, + MC_CLIENT_EPP = 4, + MC_CLIENT_G2 = 5, + MC_CLIENT_HC = 6, + MC_CLIENT_HDA = 7, + MC_CLIENT_ISP = 8, + MC_CLIENT_MPCORE = 9, + MC_CLIENT_MPCORELP = 10, + MC_CLIENT_MPE = 11, + MC_CLIENT_NV = 12, + MC_CLIENT_NV2 = 13, + MC_CLIENT_PPCS = 14, + MC_CLIENT_SATA = 15, + MC_CLIENT_VDE = 16, + MC_CLIENT_VI = 17, + MC_CLIENT_LAST = -1, +}; + +struct tegra3_powergate_mc_client_info { + enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM]; +}; + +static struct tegra3_powergate_mc_client_info tegra3_pg_mc_info[] = { + [TEGRA_POWERGATE_CPU] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_L2] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_3D] = { + .hot_reset_clients = { + [0] = MC_CLIENT_NV, + [1] = MC_CLIENT_LAST, + }, + }, +#ifdef CONFIG_ARCH_TEGRA_HAS_PCIE + [TEGRA_POWERGATE_PCIE] = { + .hot_reset_clients ={ + [0] = MC_CLIENT_AFI, + [1] = MC_CLIENT_LAST, + }, + }, +#endif + [TEGRA_POWERGATE_VDEC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_VDE, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_MPE] = { + .hot_reset_clients = { + [0] = MC_CLIENT_MPE, + [1] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_VENC] = { + .hot_reset_clients = { + [0] = MC_CLIENT_ISP, + [1] = MC_CLIENT_VI, + [2] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_CPU1] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_CPU2] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_CPU3] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, + [TEGRA_POWERGATE_CELP] = { + .hot_reset_clients = { + [0] = MC_CLIENT_LAST, + }, + }, +#ifdef CONFIG_ARCH_TEGRA_HAS_SATA + [TEGRA_POWERGATE_SATA] = { + .hot_reset_clients ={ + [0] = MC_CLIENT_SATA, + [1] = MC_CLIENT_LAST + }, + }, +#endif +#ifdef CONFIG_ARCH_TEGRA_HAS_DUAL_3D + [TEGRA_POWERGATE_3D1] = { + .hot_reset_clients = { + [0] = MC_CLIENT_NV2, + [1] = MC_CLIENT_LAST + }, + }, +#endif + [TEGRA_POWERGATE_HEG] = { + .hot_reset_clients = { + [0] = MC_CLIENT_G2, + [1] = MC_CLIENT_EPP, + [2] = MC_CLIENT_HC, + [3] = MC_CLIENT_LAST + }, + }, +}; + +static struct powergate_partition_info tegra3_powergate_partition_info[] = { + [TEGRA_POWERGATE_CPU] = { .name = "cpu0" }, + [TEGRA_POWERGATE_L2] = { .name = "l2" }, + [TEGRA_POWERGATE_3D] = { + .name = "3d0", + .clk_info = { + [0] = { .clk_name = "3d", .clk_type = CLK_AND_RST }, + }, + }, +#ifdef CONFIG_ARCH_TEGRA_HAS_PCIE + [TEGRA_POWERGATE_PCIE] = { + .name = "pcie", + .clk_info = { + [0] = { .clk_name = "afi", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "pcie", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "cml0", .clk_type = CLK_ONLY }, + [3] = { .clk_name = "pciex", .clk_type = RST_ONLY }, + }, + }, +#endif + [TEGRA_POWERGATE_VDEC] = { + .name = "vde", + .clk_info = { + [0] = { .clk_name = "vde", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_MPE] = { + .name = "mpe", + .clk_info = { + [0] = { .clk_name = "mpe.cbus", CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_VENC] = { + .name = "ve", + .clk_info = { + [0] = { .clk_name = "isp", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "vi", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "csi", .clk_type = CLK_AND_RST }, + }, + }, + [TEGRA_POWERGATE_CPU1] = { .name = "cpu1" }, + [TEGRA_POWERGATE_CPU2] = { .name = "cpu2" }, + [TEGRA_POWERGATE_CPU3] = { .name = "cpu3" }, + [TEGRA_POWERGATE_CELP] = { .name = "celp" }, +#ifdef CONFIG_ARCH_TEGRA_HAS_SATA + [TEGRA_POWERGATE_SATA] = { + .name = "sata", + .clk_info = { + [0] = { .clk_name = "sata", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "sata_oob", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "cml1", .clk_type = CLK_ONLY }, + [3] = { .clk_name = "sata_cold", .clk_type = RST_ONLY }, + }, + }, +#endif +#ifdef CONFIG_ARCH_TEGRA_HAS_DUAL_3D + [TEGRA_POWERGATE_3D1] = { + .name = "3d1", + .clk_info = { + [0] = { .clk_name = "3d2", .clk_type = CLK_AND_RST }, + }, + }, +#endif + [TEGRA_POWERGATE_HEG] = { + .name = "heg", + .clk_info = { + [0] = { .clk_name = "2d.cbus", .clk_type = CLK_AND_RST }, + [1] = { .clk_name = "epp.cbus", .clk_type = CLK_AND_RST }, + [2] = { .clk_name = "host1x.cbus", .clk_type = CLK_AND_RST }, + }, + }, +}; + +static u8 tegra3_quad_cpu_domains[] = { + TEGRA_POWERGATE_CPU0, + TEGRA_POWERGATE_CPU1, + TEGRA_POWERGATE_CPU2, + TEGRA_POWERGATE_CPU3, +}; + +#define MC_CLIENT_HOTRESET_CTRL 0x200 +#define MC_CLIENT_HOTRESET_STAT 0x204 + +static DEFINE_SPINLOCK(tegra3_powergate_lock); + +int tegra3_powergate_partition(int id) +{ + return tegraxx_powergate_partition(id, + &tegra3_powergate_partition_info[id]); +} + +int tegra3_unpowergate_partition(int id) +{ + return tegraxx_unpowergate_partition(id, + &tegra3_powergate_partition_info[id]); +} + +int tegra3_powergate_partition_with_clk_off(int id) +{ + if (id != TEGRA_POWERGATE_PCIE && id != TEGRA_POWERGATE_SATA) { + WARN_ON(1); + return -EINVAL; + } + + return tegraxx_powergate_partition_with_clk_off(id, + &tegra3_powergate_partition_info[id]); +} + +int tegra3_unpowergate_partition_with_clk_on(int id) +{ + if (id != TEGRA_POWERGATE_SATA && id != TEGRA_POWERGATE_PCIE) { + WARN_ON(1); + return -EINVAL; + } + + return tegraxx_unpowergate_partition_with_clk_on(id, + &tegra3_powergate_partition_info[id]); +} + +int tegra3_powergate_mc_enable(int id) +{ + return 0; +} + +int tegra3_powergate_mc_disable(int id) +{ + return 0; +} + +int tegra3_powergate_mc_flush(int id) +{ + u32 idx, rst_ctrl, rst_stat; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra3_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra3_powergate_lock, flags); + rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL); + rst_ctrl |= (1 << mcClientBit); + mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL); + spin_unlock_irqrestore(&tegra3_powergate_lock, flags); + + do { + udelay(10); + rst_stat = mc_read(MC_CLIENT_HOTRESET_STAT); + } while (!(rst_stat & (1 << mcClientBit))); + } + + return 0; +} + +int tegra3_powergate_mc_flush_done(int id) +{ + u32 idx, rst_ctrl; + enum mc_client mcClientBit; + unsigned long flags; + + for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { + mcClientBit = + tegra3_pg_mc_info[id].hot_reset_clients[idx]; + if (mcClientBit == MC_CLIENT_LAST) + break; + + spin_lock_irqsave(&tegra3_powergate_lock, flags); + + rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL); + rst_ctrl &= ~(1 << mcClientBit); + mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL); + + spin_unlock_irqrestore(&tegra3_powergate_lock, flags); + } + + wmb(); + + return 0; +} + +const char *tegra3_get_powergate_domain_name(int id) +{ + return tegra3_powergate_partition_info[id].name; +} + +spinlock_t *tegra3_get_powergate_lock(void) +{ + return &tegra3_powergate_lock; +} + +static struct powergate_ops tegra3_powergate_ops = { + .soc_name = "tegra3", + + .num_powerdomains = TEGRA_NUM_POWERGATE, + .num_cpu_domains = 4, + .cpu_domains = tegra3_quad_cpu_domains, + + .get_powergate_lock = tegra3_get_powergate_lock, + + .get_powergate_domain_name = tegra3_get_powergate_domain_name, + + .powergate_partition = tegra3_powergate_partition, + .unpowergate_partition = tegra3_unpowergate_partition, + + .powergate_partition_with_clk_off = tegra3_powergate_partition_with_clk_off, + .unpowergate_partition_with_clk_on = tegra3_unpowergate_partition_with_clk_on, + + .powergate_mc_enable = tegra3_powergate_mc_enable, + .powergate_mc_disable = tegra3_powergate_mc_disable, + + .powergate_mc_flush = tegra3_powergate_mc_flush, + .powergate_mc_flush_done = tegra3_powergate_mc_flush_done, +}; + +struct powergate_ops *tegra3_powergate_init_chip_support(void) +{ + return &tegra3_powergate_ops; +} diff --git a/arch/arm/mach-tegra/powergate.c b/arch/arm/mach-tegra/powergate.c index 21d9d4bf2c79..de8af12df3a3 100644 --- a/arch/arm/mach-tegra/powergate.c +++ b/arch/arm/mach-tegra/powergate.c @@ -1,8 +1,8 @@ /* - * drivers/powergate/tegra-powergate.c + * arch/arm/mach-tegra/powergate.c * * Copyright (c) 2010 Google, Inc - * Copyright (C) 2011-2012 NVIDIA Corporation. + * Copyright (c) 2011 - 2012, NVIDIA CORPORATION. All rights reserved. * * Author: * Colin Cross <ccross@google.com> @@ -38,620 +38,40 @@ #include "clock.h" #include "fuse.h" #include "iomap.h" +#include "powergate-priv.h" -#if defined(DEBUG_T11x_POWERGATE) -static void test_powergate_parts(void); -#endif -#if defined(DEBUG_T11x_POWERUNGATE) -static void test_powerungate_parts(void); -#endif -#if defined(DEBUG_T11x_POWERGATE_CLK_OFF) -static void test_powergate_clk_off_parts(void); -#endif -#if defined(DEBUG_T11x_POWERUNGATE_CLK_OFF) -static void test_unpowergate_clk_on_parts(void); -#endif - -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static int tegra11x_check_partition_pug_seq(int id); -static int tegra11x_unpowergate(int id); -#endif - -#define PWRGATE_TOGGLE 0x30 -#define PWRGATE_TOGGLE_START (1 << 8) - -#define REMOVE_CLAMPING 0x34 - -#define PWRGATE_STATUS 0x38 - -#if defined(CONFIG_ARCH_TEGRA_3x_SOC) -enum mc_client { - MC_CLIENT_AFI = 0, - MC_CLIENT_AVPC = 1, - MC_CLIENT_DC = 2, - MC_CLIENT_DCB = 3, - MC_CLIENT_EPP = 4, - MC_CLIENT_G2 = 5, - MC_CLIENT_HC = 6, - MC_CLIENT_HDA = 7, - MC_CLIENT_ISP = 8, - MC_CLIENT_MPCORE = 9, - MC_CLIENT_MPCORELP = 10, - MC_CLIENT_MPE = 11, - MC_CLIENT_NV = 12, - MC_CLIENT_NV2 = 13, - MC_CLIENT_PPCS = 14, - MC_CLIENT_SATA = 15, - MC_CLIENT_VDE = 16, - MC_CLIENT_VI = 17, - MC_CLIENT_LAST = -1, -}; -#elif defined(CONFIG_ARCH_TEGRA_2x_SOC) -enum mc_client { - MC_CLIENT_AVPC = 0, - MC_CLIENT_DC = 1, - MC_CLIENT_DCB = 2, - MC_CLIENT_EPP = 3, - MC_CLIENT_G2 = 4, - MC_CLIENT_HC = 5, - MC_CLIENT_ISP = 6, - MC_CLIENT_MPCORE = 7, - MC_CLIENT_MPEA = 8, - MC_CLIENT_MPEB = 9, - MC_CLIENT_MPEC = 10, - MC_CLIENT_NV = 11, - MC_CLIENT_PPCS = 12, - MC_CLIENT_VDE = 13, - MC_CLIENT_VI = 14, - MC_CLIENT_LAST = -1, - MC_CLIENT_AFI = MC_CLIENT_LAST, -}; -#else -/* bit positions are specific to chip */ -enum mc_client { - MC_CLIENT_AVPC = 1, - MC_CLIENT_DC = 2, - MC_CLIENT_DCB = 3, - MC_CLIENT_EPP = 4, - MC_CLIENT_G2 = 5, - MC_CLIENT_HC = 6, - MC_CLIENT_HDA = 7, - MC_CLIENT_ISP = 8, - MC_CLIENT_MPCORE = 9, - MC_CLIENT_MPCORELP = 10, - MC_CLIENT_MSENC = 11, - MC_CLIENT_NV = 12, - MC_CLIENT_PPCS = 14, - MC_CLIENT_VDE = 16, - MC_CLIENT_VI = 17, - MC_CLIENT_XUSB_HOST = 19, - MC_CLIENT_XUSB_DEV = 20, - MC_CLIENT_EMUCIF = 21, - MC_CLIENT_TSEC = 22, - MC_CLIENT_LAST = -1, - MC_CLIENT_AFI = MC_CLIENT_LAST, - MC_CLIENT_MPE = MC_CLIENT_LAST, - MC_CLIENT_NV2 = MC_CLIENT_LAST, - MC_CLIENT_SATA = MC_CLIENT_LAST, -}; -#endif - -#define MAX_CLK_EN_NUM 9 - -static int tegra_num_powerdomains; -static int tegra_num_cpu_domains; -static u8 *tegra_cpu_domains; -static u8 tegra_quad_cpu_domains[] = { - TEGRA_POWERGATE_CPU0, - TEGRA_POWERGATE_CPU1, - TEGRA_POWERGATE_CPU2, - TEGRA_POWERGATE_CPU3, -}; - -static DEFINE_SPINLOCK(tegra_powergate_lock); - -#define MAX_HOTRESET_CLIENT_NUM 4 - -enum clk_type { - CLK_AND_RST, - RST_ONLY, - CLK_ONLY, -}; - -struct partition_clk_info { - const char *clk_name; - enum clk_type clk_type; - /* true if clk is only used in assert/deassert reset and not while enable-den*/ - struct clk *clk_ptr; -}; - -struct powergate_partition { - const char *name; - enum mc_client hot_reset_clients[MAX_HOTRESET_CLIENT_NUM]; - struct partition_clk_info clk_info[MAX_CLK_EN_NUM]; -}; - -static struct powergate_partition powergate_partition_info[TEGRA_NUM_POWERGATE] = { - [TEGRA_POWERGATE_CPU] = { "cpu0", {MC_CLIENT_LAST}, }, -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) - [TEGRA_POWERGATE_L2] = { "l2", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_3D] = { "3d0", -#else - [TEGRA_POWERGATE_3D] = { "3d", -#endif - {MC_CLIENT_NV, MC_CLIENT_LAST}, - {{"3d", CLK_AND_RST} }, }, -/* T11x does not have pcie */ -#if !defined(CONFIG_ARCH_TEGRA_11x_SOC) -#ifdef CONFIG_ARCH_TEGRA_HAS_PCIE - [TEGRA_POWERGATE_PCIE] = { "pcie", - {MC_CLIENT_AFI, MC_CLIENT_LAST}, - {{"afi", CLK_AND_RST}, - {"pcie", CLK_AND_RST}, -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - {"cml0", CLK_ONLY}, -#endif - {"pciex", RST_ONLY} }, }, -#endif -#endif - [TEGRA_POWERGATE_VDEC] = { "vde", - {MC_CLIENT_VDE, MC_CLIENT_LAST}, - {{"vde", CLK_AND_RST} }, }, - [TEGRA_POWERGATE_MPE] = { "mpe", -#ifdef CONFIG_ARCH_TEGRA_3x_SOC - {MC_CLIENT_MPE, MC_CLIENT_LAST}, - {{"mpe.cbus", CLK_AND_RST}, }, -#elif defined(CONFIG_ARCH_TEGRA_2x_SOC) - {MC_CLIENT_MPEA, MC_CLIENT_MPEB, - MC_CLIENT_MPEC, MC_CLIENT_LAST}, - {{"mpe", CLK_AND_RST}, }, -#else - {MC_CLIENT_MSENC, - MC_CLIENT_LAST}, - {{"msenc.cbus", CLK_AND_RST}, }, -#endif - }, - [TEGRA_POWERGATE_VENC] = { "ve", - { - MC_CLIENT_ISP, - MC_CLIENT_VI, - MC_CLIENT_LAST - }, - { - {"isp", CLK_AND_RST}, - {"vi", CLK_AND_RST}, - {"csi", CLK_AND_RST} - }, - }, -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) - [TEGRA_POWERGATE_CPU1] = { "cpu1", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_CPU2] = { "cpu2", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_CPU3] = { "cpu3", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_CELP] = { "celp", {MC_CLIENT_LAST}, }, -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) -#ifdef CONFIG_ARCH_TEGRA_HAS_SATA - [TEGRA_POWERGATE_SATA] = { "sata", {MC_CLIENT_SATA, MC_CLIENT_LAST}, - {{"sata", CLK_AND_RST}, - {"sata_oob", CLK_AND_RST}, - {"cml1", CLK_ONLY}, - {"sata_cold", RST_ONLY} }, }, -#endif -#ifdef CONFIG_ARCH_TEGRA_HAS_DUAL_3D - [TEGRA_POWERGATE_3D1] = { "3d1", - {MC_CLIENT_NV2, MC_CLIENT_LAST}, - {{"3d2", CLK_AND_RST} }, }, -#endif -#endif - [TEGRA_POWERGATE_HEG] = { "heg", - { - MC_CLIENT_G2, - MC_CLIENT_EPP, -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) - MC_CLIENT_HC, -#endif - MC_CLIENT_LAST - }, - { -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) - {"2d", CLK_AND_RST}, - {"epp", CLK_AND_RST}, - {"host1x", CLK_AND_RST}, -#elif defined(CONFIG_ARCH_TEGRA_3x_SOC) - {"2d.cbus", CLK_AND_RST}, - {"epp.cbus", CLK_AND_RST}, - {"host1x.cbus", CLK_AND_RST}, -#else - {"2d.cbus", CLK_AND_RST}, - {"epp.cbus", CLK_AND_RST}, -#endif - }, - }, -#endif -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - [TEGRA_POWERGATE_CRAIL] = { "crail", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_C0NC] = { "c0nc", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_C1NC] = { "c1nc", {MC_CLIENT_LAST}, }, - [TEGRA_POWERGATE_DISA] = { "disa", - { - MC_CLIENT_DC, - MC_CLIENT_LAST - }, - { - {"disp1", CLK_AND_RST}, - {"dsia", CLK_AND_RST}, - {"dsib", CLK_AND_RST}, - {"csi", CLK_AND_RST}, - {"mipi-cal", CLK_AND_RST} - }, - }, - [TEGRA_POWERGATE_DISB] = { "disb", - { - MC_CLIENT_DCB, - MC_CLIENT_LAST - }, - { - {"disp2", CLK_AND_RST}, - {"hdmi", CLK_AND_RST} - }, - }, - [TEGRA_POWERGATE_XUSBA] = { "xusba", - { MC_CLIENT_LAST }, - { - {"xusb_ss", CLK_AND_RST} - }, - }, - [TEGRA_POWERGATE_XUSBB] = { "xusbb", - { - MC_CLIENT_XUSB_DEV, - MC_CLIENT_LAST - }, - { - {"xusb_dev", CLK_AND_RST}, - }, - }, - [TEGRA_POWERGATE_XUSBC] = { "xusbc", - { - MC_CLIENT_XUSB_HOST, - MC_CLIENT_LAST - }, - { - {"xusb_host", CLK_AND_RST}, - }, - }, -#endif - -}; - -static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); - -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static void __iomem *mipi_cal = IO_ADDRESS(TEGRA_MIPI_CAL_BASE); - -static u32 mipi_cal_read(unsigned long reg) -{ - return readl(mipi_cal + reg); -} - -static void mipi_cal_write(u32 val, unsigned long reg) -{ - writel_relaxed(val, mipi_cal + reg); -} - -static void __iomem *clk_rst = IO_ADDRESS(TEGRA_CLK_RESET_BASE); - -static u32 clk_rst_read(unsigned long reg) -{ - return readl(clk_rst + reg); -} -#endif - -static u32 pmc_read(unsigned long reg) -{ - return readl(pmc + reg); -} - -static void pmc_write(u32 val, unsigned long reg) -{ - writel_relaxed(val, pmc + reg); -} - -static void __iomem *mc = IO_ADDRESS(TEGRA_MC_BASE); - -static u32 mc_read(unsigned long reg) -{ - return readl(mc + reg); -} - -static void mc_write(u32 val, unsigned long reg) -{ - writel_relaxed(val, mc + reg); -} - -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && \ - !defined(CONFIG_TEGRA_SIMULATION_PLATFORM) - -#define MC_CLIENT_HOTRESET_CTRL 0x200 -#define MC_CLIENT_HOTRESET_STAT 0x204 - -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && \ - !defined(CONFIG_ARCH_TEGRA_3x_SOC) -/* FIXME: this is sw workaround for unstable hotreset status - * for T11x. - */ -#define HOTRESET_READ_COUNT 5 -static bool tegra11x_stable_hotreset_check(u32 *stat) -{ - int i; - u32 cur_stat; - u32 prv_stat; - unsigned long flags; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - prv_stat = mc_read(MC_CLIENT_HOTRESET_STAT); - for (i = 0; i < HOTRESET_READ_COUNT; i++) { - cur_stat = mc_read(MC_CLIENT_HOTRESET_STAT); - if (cur_stat != prv_stat) { - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - return false; - } - } - *stat = cur_stat; - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - return true; -} -#endif - -static void mc_flush(int id) -{ - u32 idx, rst_ctrl, rst_stat; - enum mc_client mcClientBit; - unsigned long flags; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && \ - !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool ret; -#endif - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); - - for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { - mcClientBit = - powergate_partition_info[id].hot_reset_clients[idx]; - if (mcClientBit == MC_CLIENT_LAST) - break; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL); - rst_ctrl |= (1 << mcClientBit); - mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL); - - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - - do { - udelay(10); -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) - rst_stat = mc_read(MC_CLIENT_HOTRESET_STAT); -#else - rst_stat = 0; - ret = tegra11x_stable_hotreset_check(&rst_stat); - if (!ret) - continue; -#endif - } while (!(rst_stat & (1 << mcClientBit))); - } -} - -static void mc_flush_done(int id) -{ - u32 idx, rst_ctrl; - enum mc_client mcClientBit; - unsigned long flags; - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); - - for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { - mcClientBit = - powergate_partition_info[id].hot_reset_clients[idx]; - if (mcClientBit == MC_CLIENT_LAST) - break; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - - rst_ctrl = mc_read(MC_CLIENT_HOTRESET_CTRL); - rst_ctrl &= ~(1 << mcClientBit); - mc_write(rst_ctrl, MC_CLIENT_HOTRESET_CTRL); - - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - } - - wmb(); -} - -int tegra_powergate_mc_flush(int id) -{ - if (id < 0 || id >= TEGRA_NUM_POWERGATE) - return -EINVAL; - mc_flush(id); - return 0; -} - -int tegra_powergate_mc_flush_done(int id) -{ - if (id < 0 || id >= TEGRA_NUM_POWERGATE) - return -EINVAL; - mc_flush_done(id); - return 0; -} - -int tegra_powergate_mc_disable(int id) -{ - return 0; -} - -int tegra_powergate_mc_enable(int id) -{ - return 0; -} - -#else - -#define MC_CLIENT_CTRL 0x100 -#define MC_CLIENT_HOTRESETN 0x104 -#define MC_CLIENT_ORRC_BASE 0x140 - -int tegra_powergate_mc_disable(int id) -{ - u32 idx, clt_ctrl, orrc_reg; - enum mc_client mcClientBit; - unsigned long flags; - - if (id < 0 || id >= TEGRA_NUM_POWERGATE) { - WARN_ON(1); - return -EINVAL; - } - - for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { - mcClientBit = - powergate_partition_info[id].hot_reset_clients[idx]; - if (mcClientBit == MC_CLIENT_LAST) - break; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - - /* clear client enable bit */ - clt_ctrl = mc_read(MC_CLIENT_CTRL); - clt_ctrl &= ~(1 << mcClientBit); - mc_write(clt_ctrl, MC_CLIENT_CTRL); - - /* read back to flush write */ - clt_ctrl = mc_read(MC_CLIENT_CTRL); - - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - - /* wait for outstanding requests to reach 0 */ - orrc_reg = MC_CLIENT_ORRC_BASE + (mcClientBit * 4); - while (mc_read(orrc_reg) != 0) - udelay(10); - } - return 0; -} - -int tegra_powergate_mc_flush(int id) -{ - u32 idx, hot_rstn; - enum mc_client mcClientBit; - unsigned long flags; - - if (id < 0 || id >= TEGRA_NUM_POWERGATE) { - WARN_ON(1); - return -EINVAL; - } - - for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { - mcClientBit = - powergate_partition_info[id].hot_reset_clients[idx]; - if (mcClientBit == MC_CLIENT_LAST) - break; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - - /* assert hotreset (client module is currently in reset) */ - hot_rstn = mc_read(MC_CLIENT_HOTRESETN); - hot_rstn &= ~(1 << mcClientBit); - mc_write(hot_rstn, MC_CLIENT_HOTRESETN); - - /* read back to flush write */ - hot_rstn = mc_read(MC_CLIENT_HOTRESETN); - - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - } - return 0; -} - -int tegra_powergate_mc_flush_done(int id) -{ - u32 idx, hot_rstn; - enum mc_client mcClientBit; - unsigned long flags; - - if (id < 0 || id >= TEGRA_NUM_POWERGATE) { - WARN_ON(1); - return -EINVAL; - } - - for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { - mcClientBit = - powergate_partition_info[id].hot_reset_clients[idx]; - if (mcClientBit == MC_CLIENT_LAST) - break; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - - /* deassert hotreset */ - hot_rstn = mc_read(MC_CLIENT_HOTRESETN); - hot_rstn |= (1 << mcClientBit); - mc_write(hot_rstn, MC_CLIENT_HOTRESETN); - - /* read back to flush write */ - hot_rstn = mc_read(MC_CLIENT_HOTRESETN); +static struct powergate_ops *pg_ops; - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - } - return 0; -} - -int tegra_powergate_mc_enable(int id) +static spinlock_t *tegra_get_powergate_lock(void) { - u32 idx, clt_ctrl; - enum mc_client mcClientBit; - unsigned long flags; - - if (id < 0 || id >= TEGRA_NUM_POWERGATE) { - WARN_ON(1); - return -EINVAL; - } - - for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) { - mcClientBit = - powergate_partition_info[id].hot_reset_clients[idx]; - if (mcClientBit == MC_CLIENT_LAST) - break; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - - /* enable client */ - clt_ctrl = mc_read(MC_CLIENT_CTRL); - clt_ctrl |= (1 << mcClientBit); - mc_write(clt_ctrl, MC_CLIENT_CTRL); - - /* read back to flush write */ - clt_ctrl = mc_read(MC_CLIENT_CTRL); + if (pg_ops && pg_ops->get_powergate_lock) + return pg_ops->get_powergate_lock(); + else + WARN_ON_ONCE("This SOC does not export powergate lock"); - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - } - return 0; + return NULL; } -static void mc_flush(int id) {} -static void mc_flush_done(int id) {} -#endif - -static int tegra_powergate_set(int id, bool new_state) +int tegra_powergate_set(int id, bool new_state) { #ifndef CONFIG_TEGRA_SIMULATION_PLATFORM bool status; unsigned long flags; + spinlock_t *lock = tegra_get_powergate_lock(); + /* 10us timeout for toggle operation if it takes affect*/ int toggle_timeout = 10; + /* 100 * 10 = 1000us timeout for toggle command to take affect in case of contention with h/w initiated CPU power gating */ int contention_timeout = 100; - spin_lock_irqsave(&tegra_powergate_lock, flags); + spin_lock_irqsave(lock, flags); status = !!(pmc_read(PWRGATE_STATUS) & (1 << id)); if (status == new_state) { - spin_unlock_irqrestore(&tegra_powergate_lock, flags); + spin_unlock_irqrestore(lock, flags); return 0; } @@ -659,12 +79,12 @@ static int tegra_powergate_set(int id, bool new_state) /* CPU ungated in s/w only during boot/resume with outer waiting loop and no contention from other CPUs */ pmc_write(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); - spin_unlock_irqrestore(&tegra_powergate_lock, flags); + spin_unlock_irqrestore(lock, flags); return 0; } + pmc_write(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); do { - pmc_write(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); do { udelay(1); status = !!(pmc_read(PWRGATE_STATUS) & (1 << id)); @@ -675,202 +95,82 @@ static int tegra_powergate_set(int id, bool new_state) contention_timeout--; } while ((status != new_state) && (contention_timeout > 0)); - spin_unlock_irqrestore(&tegra_powergate_lock, flags); + spin_unlock_irqrestore(lock, flags); if (status != new_state) { WARN(1, "Could not set powergate %d to %d", id, new_state); return -EBUSY; } - trace_power_domain_target(powergate_partition_info[id].name, new_state, + trace_power_domain_target(tegra_powergate_get_name(id), new_state, smp_processor_id()); #endif return 0; } -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static bool tegra11x_check_plld_plld2_disable(void) -{ - /* FIXME: - * add check for plld and plld2 disable - */ -#define CLK_RST_CONTROLLER_PLLD_BASE_0 0xd0 -#define CLK_RST_CONTROLLER_PLLD_BASE_0_PLLD_ENABLE_LSB 30 -#define CLK_RST_CONTROLLER_PLLD2_BASE_0 0x4b8 -#define CLK_RST_CONTROLLER_PLLD2_BASE_0_PLLD2_ENABLE_LSB 30 - u32 status; - status = clk_rst_read(CLK_RST_CONTROLLER_PLLD_BASE_0); - if (status & (1 << CLK_RST_CONTROLLER_PLLD_BASE_0_PLLD_ENABLE_LSB)) - return false; - status = clk_rst_read(CLK_RST_CONTROLLER_PLLD2_BASE_0); - if (status & (1 << CLK_RST_CONTROLLER_PLLD2_BASE_0_PLLD2_ENABLE_LSB)) - return false; - return true; -} - -static bool tegra11x_pg_sw_war_missing(int id) +int is_partition_clk_disabled(struct powergate_partition_info *pg_info) { - bool ret; - - switch (id) { - case TEGRA_POWERGATE_DISA: - /* FIXME: - * [SW WAR bug 954988]: - * Disable PLLD and PLLD2 by clearing bits: -a. CLK_RST_CONTROLLER_PLLD_BASE_0_PLLD_ENABLE -b. CLK_RST_CONTROLLER_PLLD2_BASE_0_PLLD2_ENABLE - * We should not need to disable PLLD and PLLD2 - * for linux/android implementation - * adding check in case PLLD or PLLD2 is/are ON - */ - ret = tegra11x_check_plld_plld2_disable(); - if (!ret) - return true; - - break; - } - return false; -} -#endif + u32 idx; + struct clk *clk; + struct partition_clk_info *clk_info; + int ret = 0; -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) -static int unpowergate_module(int id) -{ - if (id < 0 || id >= tegra_num_powerdomains) - return -EINVAL; - return tegra_powergate_set(id, true); -} -#endif + for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { + clk_info = &pg_info->clk_info[idx]; + clk = clk_info->clk_ptr; -static int powergate_module(int id) -{ -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool need_sw_war; -#endif - if (id < 0 || id >= tegra_num_powerdomains) - return -EINVAL; + if (!clk) + break; - mc_flush(id); -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - need_sw_war = tegra11x_pg_sw_war_missing(id); - if (need_sw_war) { - pr_err("Error: missing powergate sw war in file: %s, func: %s, line=%d\n", - __FILE__, __func__, __LINE__); - return -1; + if (clk_info->clk_type != RST_ONLY) { + if (tegra_is_clk_enabled(clk)) { + ret = -1; + break; + } + } } -#endif - return tegra_powergate_set(id, false); -} - -bool tegra_powergate_is_powered(int id) -{ - u32 status; - if (id < 0 || id >= tegra_num_powerdomains) - return -EINVAL; - - status = pmc_read(PWRGATE_STATUS) & (1 << id); - return !!status; + return ret; } -EXPORT_SYMBOL(tegra_powergate_is_powered); -int tegra_powergate_remove_clamping(int id) +int powergate_module(int id) { - u32 mask; - int contention_timeout = 100; - - if (id < 0 || id >= tegra_num_powerdomains) + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); return -EINVAL; + } - /* - * PCIE and VDE clamping masks are swapped with respect to their - * partition ids - */ - if (id == TEGRA_POWERGATE_VDEC) - mask = (1 << TEGRA_POWERGATE_PCIE); - else if (id == TEGRA_POWERGATE_PCIE) - mask = (1 << TEGRA_POWERGATE_VDEC); - else - mask = (1 << id); - - pmc_write(mask, REMOVE_CLAMPING); - /* Wait until clamp is removed */ - do { - udelay(1); - contention_timeout--; - } while ((contention_timeout > 0) - && (pmc_read(REMOVE_CLAMPING) & mask)); + if (id < 0 || id >= pg_ops->num_powerdomains) + return -EINVAL; - WARN(contention_timeout <= 0, "Couldn't remove clamping"); + tegra_powergate_mc_flush(id); - return 0; + return tegra_powergate_set(id, false); } -static void get_clk_info(int id) +int unpowergate_module(int id) { - int idx; - - for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { - if (!powergate_partition_info[id].clk_info[idx].clk_name) - break; - powergate_partition_info[id]. - clk_info[idx].clk_ptr = - tegra_get_clock_by_name( - powergate_partition_info[id].clk_info[idx].clk_name); + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; } -} -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static bool tegra11x_pug_clk_n_rst_skip(int id, u32 idx) -{ - switch (id) { - case TEGRA_POWERGATE_VENC: - if ((powergate_partition_info[id].clk_info[idx].clk_name) && - (!(strncmp("csi", - powergate_partition_info[id].clk_info[idx].clk_name, - 3)))) { - /* DIS powered ON then do clk enable CSI */ - if (!tegra_powergate_is_powered( - TEGRA_POWERGATE_DISA)) - return true; - } - break; - case TEGRA_POWERGATE_DISA: - if ((powergate_partition_info[id].clk_info[idx].clk_name) && - (!(strncmp("csi", - powergate_partition_info[id].clk_info[idx].clk_name, - 3)))) { - /* DIS powered ON then do clk enable CSI */ - if (!tegra_powergate_is_powered( - TEGRA_POWERGATE_VENC)) - return true; - } - break; - } - return false; + if (id < 0 || id >= pg_ops->num_powerdomains) + return -EINVAL; + + return tegra_powergate_set(id, true); } -#endif -static int partition_clk_enable(int id) +int partition_clk_enable(struct powergate_partition_info *pg_info) { int ret; u32 idx; struct clk *clk; struct partition_clk_info *clk_info; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool skip_enable; -#endif - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - skip_enable = tegra11x_pug_clk_n_rst_skip(id, idx); - if (skip_enable) - continue; -#endif - clk_info = &powergate_partition_info[id].clk_info[idx]; + clk_info = &pg_info->clk_info[idx]; clk = clk_info->clk_ptr; if (!clk) break; @@ -887,7 +187,7 @@ static int partition_clk_enable(int id) err_clk_en: WARN(1, "Could not enable clk %s, error %d", clk->name, ret); while (idx--) { - clk_info = &powergate_partition_info[id].clk_info[idx]; + clk_info = &pg_info->clk_info[idx]; if (clk_info->clk_type != RST_ONLY) tegra_clk_disable_unprepare(clk_info->clk_ptr); } @@ -895,773 +195,389 @@ err_clk_en: return ret; } -static int is_partition_clk_disabled(int id) +void partition_clk_disable(struct powergate_partition_info *pg_info) { u32 idx; struct clk *clk; struct partition_clk_info *clk_info; - int ret = 0; - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { - clk_info = &powergate_partition_info[id].clk_info[idx]; + clk_info = &pg_info->clk_info[idx]; clk = clk_info->clk_ptr; + if (!clk) break; - if (clk_info->clk_type != RST_ONLY) { - if (tegra_is_clk_enabled(clk)) { - ret = -1; - break; - } - } + if (clk_info->clk_type != RST_ONLY) + tegra_clk_disable_unprepare(clk); } - - return ret; } -static void partition_clk_disable(int id) +void get_clk_info(struct powergate_partition_info *pg_info) { - u32 idx; - struct clk *clk; - struct partition_clk_info *clk_info; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool skip_disable; -#endif - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); + int idx; for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - if (id == TEGRA_POWERGATE_DISA) { - skip_disable = tegra11x_pug_clk_n_rst_skip(id, idx); - if (skip_disable) - continue; - } -#endif - clk_info = &powergate_partition_info[id].clk_info[idx]; - clk = clk_info->clk_ptr; - if (!clk) + if (!pg_info->clk_info[idx].clk_name) break; - if (clk_info->clk_type != RST_ONLY) - tegra_clk_disable_unprepare(clk); + pg_info->clk_info[idx].clk_ptr = tegra_get_clock_by_name( + pg_info->clk_info[idx].clk_name); } } -static void powergate_partition_assert_reset(int id) +void powergate_partition_assert_reset(struct powergate_partition_info *pg_info) { u32 idx; struct clk *clk_ptr; struct partition_clk_info *clk_info; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool skip_reset; -#endif - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - if (id == TEGRA_POWERGATE_DISA) { - skip_reset = tegra11x_pug_clk_n_rst_skip(id, idx); - if (skip_reset) - continue; - } -#endif - clk_info = &powergate_partition_info[id].clk_info[idx]; + clk_info = &pg_info->clk_info[idx]; clk_ptr = clk_info->clk_ptr; + if (!clk_ptr) break; + if (clk_info->clk_type != CLK_ONLY) tegra_periph_reset_assert(clk_ptr); } } -static void powergate_partition_deassert_reset(int id) +void powergate_partition_deassert_reset(struct powergate_partition_info *pg_info) { u32 idx; struct clk *clk_ptr; struct partition_clk_info *clk_info; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool skip_reset; -#endif - - BUG_ON(id < 0 || id >= TEGRA_NUM_POWERGATE); for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) { -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - skip_reset = tegra11x_pug_clk_n_rst_skip(id, idx); - if (skip_reset) - continue; -#endif - clk_info = &powergate_partition_info[id].clk_info[idx]; + clk_info = &pg_info->clk_info[idx]; clk_ptr = clk_info->clk_ptr; + if (!clk_ptr) break; + if (clk_info->clk_type != CLK_ONLY) tegra_periph_reset_deassert(clk_ptr); } } -/* Must be called with clk disabled, and returns with clk disabled */ -static int tegra_powergate_reset_module(int id) +int tegra_powergate_reset_module(struct powergate_partition_info *pg_info) { int ret; - powergate_partition_assert_reset(id); + powergate_partition_assert_reset(pg_info); udelay(10); - ret = partition_clk_enable(id); + ret = partition_clk_enable(pg_info); if (ret) return ret; udelay(10); - powergate_partition_deassert_reset(id); + powergate_partition_deassert_reset(pg_info); - partition_clk_disable(id); + partition_clk_disable(pg_info); return 0; } -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -/* - * FIXME: sw war for mipi-cal calibration when unpowergating DISA partition - */ -static void tegra11x_mipical_calibrate(int id) +int tegra_powergate_remove_clamping(int id) { - struct reg_offset_val { - u32 offset; - u32 por_value; - }; - u32 status; - unsigned long flags; -#define MIPI_CAL_MIPI_CAL_CTRL_0 0x0 -#define MIPI_CAL_CIL_MIPI_CAL_STATUS_0 0x8 -#define MIPI_CAL_CILA_MIPI_CAL_CONFIG_0 0x14 -#define MIPI_CAL_CILB_MIPI_CAL_CONFIG_0 0x18 -#define MIPI_CAL_CILC_MIPI_CAL_CONFIG_0 0x1c -#define MIPI_CAL_CILD_MIPI_CAL_CONFIG_0 0x20 -#define MIPI_CAL_CILE_MIPI_CAL_CONFIG_0 0x24 -#define MIPI_CAL_DSIA_MIPI_CAL_CONFIG_0 0x38 -#define MIPI_CAL_DSIB_MIPI_CAL_CONFIG_0 0x3c -#define MIPI_CAL_DSIC_MIPI_CAL_CONFIG_0 0x40 -#define MIPI_CAL_DSID_MIPI_CAL_CONFIG_0 0x44 - static struct reg_offset_val mipi_cal_por_values[] = { - { MIPI_CAL_MIPI_CAL_CTRL_0, 0x2a000000 }, - { MIPI_CAL_CILA_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_CILB_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_CILC_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_CILD_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_CILE_MIPI_CAL_CONFIG_0, 0x00000000 }, - { MIPI_CAL_DSIA_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_DSIB_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_DSIC_MIPI_CAL_CONFIG_0, 0x00200000 }, - { MIPI_CAL_DSID_MIPI_CAL_CONFIG_0, 0x00200000 }, - }; - int i; + u32 mask; + int contention_timeout = 100; - if (id != TEGRA_POWERGATE_DISA) - return; - spin_lock_irqsave(&tegra_powergate_lock, flags); - /* mipi cal por restore */ - for (i = 0; i < ARRAY_SIZE(mipi_cal_por_values); i++) { - mipi_cal_write(mipi_cal_por_values[i].por_value, - mipi_cal_por_values[i].offset); + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; } - /* mipi cal status clear */ - status = mipi_cal_read(MIPI_CAL_CIL_MIPI_CAL_STATUS_0); - mipi_cal_write(status, MIPI_CAL_CIL_MIPI_CAL_STATUS_0); - /* mipi cal status read - to flush writes */ - status = mipi_cal_read(MIPI_CAL_CIL_MIPI_CAL_STATUS_0); - spin_unlock_irqrestore(&tegra_powergate_lock, flags); -} -#endif -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static bool skip_pg_check(int id, bool is_unpowergate) -{ - /* - * FIXME: need to stress test partition power gating before - * enabling power gating for T11x - * List of T11x partition id which skip power gating - */ - static int skip_pg_t11x_list[] = { - /* - * CPU and 3D partitions enable/disable - * is managed by respective modules - */ - }; - int i; + if (id < 0 || id >= pg_ops->num_powerdomains) + return -EINVAL; /* - * skip unnecessary multiple calls e.g. powergate call when - * partition is already powered-off or vice-versa + * PCIE and VDE clamping masks are swapped with respect to their + * partition ids */ - if ((tegra_powergate_is_powered(id) && - is_unpowergate) || - (!(tegra_powergate_is_powered(id)) && - (!is_unpowergate))) { - pr_err("Partition %s already powered-%s and %spowergate skipped\n", - tegra_powergate_get_name(id), - (tegra_powergate_is_powered(id)) ? - "on" : "off", - (is_unpowergate) ? "un" : ""); - return true; - } - /* unpowergate is allowed for all partitions */ - if (!tegra_powergate_is_powered(id) && - is_unpowergate) - return false; - for (i = 0; i < ARRAY_SIZE(skip_pg_t11x_list); i++) { - if (id == skip_pg_t11x_list[i]) { - pr_err("Partition %s %spowergate skipped\n", - tegra_powergate_get_name(id), - (is_unpowergate) ? "un" : ""); - return true; - } - } - - return false; -} -#endif - -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static atomic_t ref_count_a = ATOMIC_INIT(1); /* for TEGRA_POWERGATE_DISA */ -static atomic_t ref_count_b = ATOMIC_INIT(1); /* for TEGRA_POWERGATE_DISB */ -#endif - -/* - * Must be called with clk disabled, and returns with clk disabled - * Drivers should enable clks for partition. Unpowergates only the - * partition. - */ -int tegra_unpowergate_partition(int id) -{ - int ret; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool is_pg_skip; - - WARN_ONCE(atomic_read(&ref_count_a) < 0, "ref count A underflow"); - WARN_ONCE(atomic_read(&ref_count_b) < 0, "ref count B underflow"); - if (id == TEGRA_POWERGATE_DISA && atomic_inc_return(&ref_count_a) != 1) - return 0; - else if (id == TEGRA_POWERGATE_DISB && - atomic_inc_return(&ref_count_b) != 1) - return 0; - - is_pg_skip = skip_pg_check(id, true); - if (is_pg_skip) - return 0; - ret = tegra11x_check_partition_pug_seq(id); - if (ret) - return ret; - - ret = tegra11x_unpowergate(id); - return ret; -#else - /* FIXME: not changing previous chip's power-ungate implementation */ - - /* If first clk_ptr is null, fill clk info for the partition */ - if (!powergate_partition_info[id].clk_info[0].clk_ptr) - get_clk_info(id); - - if (tegra_powergate_is_powered(id)) - return tegra_powergate_reset_module(id); - - ret = unpowergate_module(id); - if (ret) - goto err_power; - - powergate_partition_assert_reset(id); + if (id == TEGRA_POWERGATE_VDEC) + mask = (1 << TEGRA_POWERGATE_PCIE); + else if (id == TEGRA_POWERGATE_PCIE) + mask = (1 << TEGRA_POWERGATE_VDEC); + else + mask = (1 << id); - /* Un-Powergating fails if all clks are not enabled */ - ret = partition_clk_enable(id); - if (ret) - goto err_clk_on; + pmc_write(mask, REMOVE_CLAMPING); + /* Wait until clamp is removed */ + do { + udelay(1); + contention_timeout--; + } while ((contention_timeout > 0) + && (pmc_read(REMOVE_CLAMPING) & mask)); - udelay(10); + WARN(contention_timeout <= 0, "Couldn't remove clamping"); - ret = tegra_powergate_remove_clamping(id); - if (ret) - goto err_clamp; + return 0; +} - udelay(10); +/* EXTERNALY VISIBLE APIS */ - powergate_partition_deassert_reset(id); +bool tegra_powergate_is_powered(int id) +{ + u32 status; - mc_flush_done(id); + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - /* Disable all clks enabled earlier. Drivers should enable clks */ - partition_clk_disable(id); + if (id < 0 || id >= pg_ops->num_powerdomains) + return -EINVAL; - return 0; + status = pmc_read(PWRGATE_STATUS) & (1 << id); -err_clamp: - partition_clk_disable(id); -err_clk_on: - powergate_module(id); -err_power: - WARN(1, "Could not Un-Powergate %d", id); - return ret; -#endif + return !!status; } -EXPORT_SYMBOL(tegra_unpowergate_partition); +EXPORT_SYMBOL(tegra_powergate_is_powered); int tegra_cpu_powergate_id(int cpuid) { - if (cpuid > 0 && cpuid < tegra_num_cpu_domains) - return tegra_cpu_domains[cpuid]; - - return -EINVAL; -} + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } -int __init tegra_powergate_init(void) -{ - switch (tegra_chip_id) { - case TEGRA20: - tegra_num_powerdomains = 7; - break; - case TEGRA30: - tegra_num_powerdomains = 14; - tegra_num_cpu_domains = 4; - tegra_cpu_domains = tegra_quad_cpu_domains; - break; - case TEGRA11X: - tegra_num_powerdomains = 23; - tegra_num_cpu_domains = 4; - tegra_cpu_domains = tegra_quad_cpu_domains; - break; - case TEGRA14X: - tegra_num_powerdomains = 25; - tegra_num_cpu_domains = 4; - tegra_cpu_domains = tegra_quad_cpu_domains; - break; - default: - /* Unknown Tegra variant. Disable powergating */ - tegra_num_powerdomains = 0; - break; + if (cpuid < 0 || cpuid >= pg_ops->num_cpu_domains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; } -#if defined(DEBUG_T11x_POWERGATE) - test_powergate_parts(); -#endif -#if defined(DEBUG_T11x_POWERUNGATE) - test_unpowergate_parts(); -#endif -#if defined(DEBUG_T11x_POWERGATE_CLK_OFF) - test_powergate_clk_off_parts(); -#endif -#if defined(DEBUG_T11x_POWERUNGATE_CLK_ON) - test_unpowergate_clk_on_parts(); -#endif - return 0; + if (pg_ops->cpu_domains) + return pg_ops->cpu_domains[cpuid]; + else + WARN_ON_ONCE("This SOC does not support CPU powergate\n"); + + return -EINVAL; } +EXPORT_SYMBOL(tegra_cpu_powergate_id); -/* - * Must be called with clk disabled, and returns with clk enabled - * Unpowergates the partition and enables all required clks. - */ -int tegra_unpowergate_partition_with_clk_on(int id) +int tegra_powergate_partition(int id) { - int ret = 0; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool is_pg_skip; - - is_pg_skip = skip_pg_check(id, true); - if (is_pg_skip) - return 0; -#endif -#if defined(CONFIG_ARCH_TEGRA_3x_SOC) - /* Restrict this functions use to few partitions */ - BUG_ON(id != TEGRA_POWERGATE_SATA && id != TEGRA_POWERGATE_PCIE); -#elif defined(CONFIG_ARCH_TEGRA_2x_SOC) - /* Restrict this functions use to few partitions */ - BUG_ON(id != TEGRA_POWERGATE_PCIE); -#endif - - ret = tegra_unpowergate_partition(id); - if (ret) - goto err_unpowergating; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - /* Enable clks for the partition */ - ret = partition_clk_enable(id); - if (ret) - goto err_unpowergate_clk; + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; + } - return ret; + if (pg_ops->powergate_partition) + return pg_ops->powergate_partition(id); + else + WARN_ON_ONCE("This SOC doesn't support powergating"); -err_unpowergate_clk: - tegra_powergate_partition(id); - WARN(1, "Could not Un-Powergate %d, err in enabling clk", id); -err_unpowergating: - WARN(1, "Could not Un-Powergate %d", id); - return ret; + return -EINVAL; } +EXPORT_SYMBOL(tegra_powergate_partition); -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) -static int tegra11x_powergate_set(int id, bool new_state) +int tegra_unpowergate_partition(int id) { -#ifndef CONFIG_TEGRA_SIMULATION_PLATFORM - bool status; - unsigned long flags; - /* 10us timeout for toggle operation if it takes affect*/ - int toggle_timeout = 10; - /* 100 * 10 = 1000us timeout for toggle command to take affect in case - of contention with h/w initiated CPU power gating */ - int contention_timeout = 100; - - spin_lock_irqsave(&tegra_powergate_lock, flags); - - status = !!(pmc_read(PWRGATE_STATUS) & (1 << id)); - - if (status == new_state) { - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - return 0; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; } - pmc_write(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); - do { - do { - udelay(1); - status = !!(pmc_read(PWRGATE_STATUS) & (1 << id)); - - toggle_timeout--; - } while ((status != new_state) && (toggle_timeout > 0)); - - contention_timeout--; - } while ((status != new_state) && (contention_timeout > 0)); - - spin_unlock_irqrestore(&tegra_powergate_lock, flags); - - if (status != new_state) { - WARN(1, "Could not set powergate %d to %d", id, new_state); - return -EBUSY; + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; } - trace_power_domain_target(powergate_partition_info[id].name, new_state, - smp_processor_id()); -#endif + if (pg_ops->unpowergate_partition) + return pg_ops->unpowergate_partition(id); + else + WARN_ON_ONCE("This SOC doesn't support un-powergating"); - return 0; + return -EINVAL; } +EXPORT_SYMBOL(tegra_unpowergate_partition); -static int tegra11x_powergate(int id) +int tegra_powergate_partition_with_clk_off(int id) { - int ret; - - /* If first clk_ptr is null, fill clk info for the partition */ - if (powergate_partition_info[id].clk_info[0].clk_ptr) - get_clk_info(id); - - ret = partition_clk_enable(id); - if (ret) - WARN(1, "Couldn't enable clock"); - - udelay(10); - - mc_flush(id); - - udelay(10); - - powergate_partition_assert_reset(id); - - udelay(10); - - /* Powergating is done only if refcnt of all clks is 0 */ - partition_clk_disable(id); - - udelay(10); + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - ret = tegra11x_powergate_set(id, false); - if (ret) - goto err_power_off; + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; + } - return 0; + if (pg_ops->powergate_partition_with_clk_off) + return pg_ops->powergate_partition_with_clk_off(id); + else + WARN_ON_ONCE("This SOC doesn't support powergating with clk off"); -err_power_off: - WARN(1, "Could not Powergate Partition %d", id); - return ret; + return -EINVAL; } +EXPORT_SYMBOL(tegra_powergate_partition_with_clk_off); -static int tegra11x_unpowergate(int id) +int tegra_unpowergate_partition_with_clk_on(int id) { - int ret; - /* If first clk_ptr is null, fill clk info for the partition */ - if (!powergate_partition_info[id].clk_info[0].clk_ptr) - get_clk_info(id); - - if (tegra_powergate_is_powered(id)) - return tegra_powergate_reset_module(id); - - ret = tegra11x_powergate_set(id, true); - if (ret) - goto err_power; - - udelay(10); - - /* Un-Powergating fails if all clks are not enabled */ - ret = partition_clk_enable(id); - if (ret) - goto err_clk_on; - - udelay(10); - - ret = tegra_powergate_remove_clamping(id); - if (ret) - goto err_clamp; - - udelay(10); - -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - tegra11x_mipical_calibrate(id); -#endif - powergate_partition_deassert_reset(id); - - udelay(10); - - mc_flush_done(id); - - udelay(10); + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - /* Disable all clks enabled earlier. Drivers should enable clks */ - partition_clk_disable(id); + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; + } - return 0; + if (pg_ops->unpowergate_partition_with_clk_on) + return pg_ops->unpowergate_partition_with_clk_on(id); + else + WARN_ON_ONCE("This SOC doesn't support power un-gating with clk on"); -err_clamp: - partition_clk_disable(id); -err_clk_on: - powergate_module(id); -err_power: - WARN(1, "Could not Un-Powergate %d", id); - return ret; + return -EINVAL; } +EXPORT_SYMBOL(tegra_unpowergate_partition_with_clk_on); -static int tegra11x_powergate_partition(int id) +int tegra_powergate_mc_enable(int id) { - int ret; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - if (tegra_powergate_is_powered(id)) { - ret = is_partition_clk_disabled(id); - if (ret < 0) { - /* clock enabled */ - ret = tegra_powergate_partition_with_clk_off(id); - if (ret < 0) - return ret; - } else { - ret = tegra_powergate_partition(id); - if (ret < 0) - return ret; - } + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; } - return 0; -} -static int tegra11x_unpowergate_partition(int id) -{ - int ret; + if (pg_ops->powergate_mc_enable) + return pg_ops->powergate_mc_enable(id); + else + WARN_ON_ONCE("This SOC does not support powergate mc enable"); - if (!tegra_powergate_is_powered(id)) { - ret = is_partition_clk_disabled(id); - if (ret) { - /* clock disabled */ - ret = tegra_unpowergate_partition_with_clk_on(id); - if (ret < 0) - return ret; - } else { - ret = tegra_unpowergate_partition(id); - if (ret < 0) - return ret; - } - } - return 0; + return -EINVAL; } +EXPORT_SYMBOL(tegra_powergate_mc_enable); -/* - * Tegra11x has powergate dependencies between partitions. - * This function captures the dependencies. - */ -static int tegra11x_check_partition_pg_seq(int id) +int tegra_powergate_mc_disable(int id) { - int ret; - - switch (id) { - case TEGRA_POWERGATE_DISA: - ret = tegra11x_powergate_partition(TEGRA_POWERGATE_VENC); - if (ret < 0) - return ret; - ret = tegra11x_powergate_partition(TEGRA_POWERGATE_DISB); - if (ret < 0) - return ret; - break; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; } - return 0; -} -/* - * This function captures power-ungate dependencies between tegra11x partitions - */ -static int tegra11x_check_partition_pug_seq(int id) -{ - int ret; + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; + } - switch (id) { - case TEGRA_POWERGATE_DISB: - case TEGRA_POWERGATE_VENC: - ret = tegra11x_unpowergate_partition(TEGRA_POWERGATE_DISA); - if (ret < 0) - return ret; + if (pg_ops->powergate_mc_disable) + return pg_ops->powergate_mc_disable(id); + else + WARN_ON_ONCE("This SOC does not support powergate mc disable"); - break; - } - return 0; + return -EINVAL; } -#endif +EXPORT_SYMBOL(tegra_powergate_mc_disable); -/* - * Must be called with clk disabled. Powergates the partition only - */ -int tegra_powergate_partition(int id) +int tegra_powergate_mc_flush(int id) { - int ret; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool is_pg_skip; - - WARN_ONCE(atomic_read(&ref_count_a) < 0, "ref count A underflow"); - WARN_ONCE(atomic_read(&ref_count_b) < 0, "ref count B underflow"); - if (id == TEGRA_POWERGATE_DISA && atomic_dec_return(&ref_count_a) != 0) - return 0; - else if (id == TEGRA_POWERGATE_DISB && - atomic_dec_return(&ref_count_b) != 0) - return 0; - - is_pg_skip = skip_pg_check(id, false); - if (is_pg_skip) - return 0; - - ret = tegra11x_check_partition_pg_seq(id); - if (ret) - return ret; - - /* All Tegra11x partition powergate */ - ret = tegra11x_powergate(id); - return ret; -#else - /* FIXME: not changing previous chip's powergate implementation */ - - /* If first clk_ptr is null, fill clk info for the partition */ - if (powergate_partition_info[id].clk_info[0].clk_ptr) - get_clk_info(id); - - powergate_partition_assert_reset(id); - - /* Powergating is done only if refcnt of all clks is 0 */ - ret = is_partition_clk_disabled(id); - if (ret) - goto err_clk_off; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - ret = powergate_module(id); - if (ret) - goto err_power_off; + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; + } - return 0; + if (pg_ops->powergate_mc_flush) + return pg_ops->powergate_mc_flush(id); + else + WARN_ON_ONCE("This SOC does not support powergate mc flush"); -err_power_off: - WARN(1, "Could not Powergate Partition %d", id); -err_clk_off: - WARN(1, "Could not Powergate Partition %d, all clks not disabled", id); - return ret; -#endif + return -EINVAL; } -EXPORT_SYMBOL(tegra_powergate_partition); +EXPORT_SYMBOL(tegra_powergate_mc_flush); -int tegra_powergate_partition_with_clk_off(int id) +int tegra_powergate_mc_flush_done(int id) { - int ret = 0; -#if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) - bool is_pg_skip; - - is_pg_skip = skip_pg_check(id, false); - if (is_pg_skip) - return 0; -#endif -#if defined(CONFIG_ARCH_TEGRA_3x_SOC) - /* Restrict functions use to selected partitions */ - BUG_ON(id != TEGRA_POWERGATE_PCIE && id != TEGRA_POWERGATE_SATA); -#elif defined(CONFIG_ARCH_TEGRA_2x_SOC) - /* Restrict functions use to selected partitions */ - BUG_ON(id != TEGRA_POWERGATE_PCIE); -#endif - /* Disable clks for the partition */ - partition_clk_disable(id); - - ret = is_partition_clk_disabled(id); - if (ret) - goto err_powergate_clk; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return -EINVAL; + } - ret = tegra_powergate_partition(id); - if (ret) - goto err_powergating; + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("%s: invalid powergate id\n", __func__); + return -EINVAL; + } - return ret; + if (pg_ops->powergate_mc_flush_done) + return pg_ops->powergate_mc_flush_done(id); + else + WARN_ON_ONCE("This SOC does not support powergate mc flush done"); -err_powergate_clk: - WARN(1, "Could not Powergate Partition %d, all clks not disabled", id); -err_powergating: - partition_clk_enable(id); - WARN(1, "Could not Powergate Partition %d", id); - return ret; + return -EINVAL; } +EXPORT_SYMBOL(tegra_powergate_mc_flush_done); -#if defined(DEBUG_T11x_POWERGATE) -static void test_powergate_parts(void) +const char *tegra_powergate_get_name(int id) { - int i; + if (!pg_ops) { + pr_info("This SOC doesn't support powergating\n"); + return NULL; + } - for (i = 0; i < TEGRA_NUM_POWERGATE; i++) - tegra_powergate_partition(i); -} -#endif + if (id < 0 || id >= pg_ops->num_powerdomains) { + pr_info("invalid powergate id\n"); + return "invalid"; + } -#if defined(DEBUG_T11x_POWERUNGATE) -static void test_powerungate_parts(void) -{ - int i; + if (pg_ops->get_powergate_domain_name) + return pg_ops->get_powergate_domain_name(id); + else + WARN_ON_ONCE("This SOC does not support CPU powergate"); - for (i = 0; i < TEGRA_NUM_POWERGATE; i++) - tegra_unpowergate_partition(i); + return "invalid"; } -#endif +EXPORT_SYMBOL(tegra_powergate_get_name); -#if defined(DEBUG_T11x_POWERGATE_CLK_OFF) -static void test_powergate_clk_off_parts(void) +int __init tegra_powergate_init(void) { - int i; + switch (tegra_chip_id) { + case TEGRA20: + pg_ops = tegra2_powergate_init_chip_support(); + break; - for (i = 0; i < TEGRA_NUM_POWERGATE; i++) - tegra_powergate_partition_with_clk_off(i); -} -#endif + case TEGRA30: + pg_ops = tegra3_powergate_init_chip_support(); + break; -#if defined(DEBUG_T11x_POWERUNGATE_CLK_OFF) -static void test_unpowergate_clk_on_parts(void) -{ - int i; + case TEGRA11X: + pg_ops = tegra11x_powergate_init_chip_support(); + break; - for (i = 0; i < TEGRA_NUM_POWERGATE; i++) - tegra_unpowergate_partition_with_clk_on(i); -} -#endif + case TEGRA14X: + pg_ops = tegra14x_powergate_init_chip_support(); + break; -const char *tegra_powergate_get_name(int id) -{ - if (id < 0 || id >= TEGRA_NUM_POWERGATE) - return "invalid"; + default: + pg_ops = NULL; + pr_info("%s: Unknown Tegra variant. Disabling powergate\n", __func__); + break; + } + + pr_info("%s: DONE\n", __func__); - return powergate_partition_info[id].name; + return (pg_ops ? 0 : -EINVAL); } #ifdef CONFIG_DEBUG_FS @@ -1671,10 +587,15 @@ static int powergate_show(struct seq_file *s, void *data) int i; const char *name; + if (!pg_ops) { + seq_printf(s, "This SOC doesn't support powergating\n"); + return -EINVAL; + } + seq_printf(s, " powergate powered\n"); seq_printf(s, "------------------\n"); - for (i = 0; i < tegra_num_powerdomains; i++) { + for (i = 0; i < pg_ops->num_powerdomains; i++) { name = tegra_powergate_get_name(i); if (name) seq_printf(s, " %9s %7s\n", name, |