tegrakernel/kernel/kernel-4.9/drivers/platform/tegra/powergate/powergate-t21x.c

1512 lines
36 KiB
C

/*
* Copyright (c) 2014-2018, 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 <dt-bindings/clock/tegra210-car.h>
#include <dt-bindings/reset/tegra210-car.h>
#include <linux/err.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/regulator/consumer.h>
#include <soc/tegra/tegra_powergate.h>
#include <soc/tegra/tegra-powergate-driver.h>
#include <soc/tegra/chip-id.h>
#include <linux/tegra_soctherm.h>
#include <soc/tegra/tegra-dvfs.h>
#include <trace/events/power.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra.h>
#include <soc/tegra/reset.h>
#include <soc/tegra/common.h>
#include <soc/tegra/fuse.h>
#include <linux/platform/tegra/mc.h>
#include <soc/tegra/pmc.h>
#define MAX_CLK_EN_NUM 15
#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;
struct clk *clk_ptr;
};
#define LVL2_CLK_GATE_OVRA 0xf8
#define LVL2_CLK_GATE_OVRC 0x3a0
#define LVL2_CLK_GATE_OVRD 0x3a4
#define LVL2_CLK_GATE_OVRE 0x554
/* sentinel for lvl2_ovr_info array */
#define LVL2_END 0x1000
/* I2S registers to handle during APE power ungating */
#define TEGRA210_I2S_BASE 0x11000
#define TEGRA210_I2S_SIZE 0x100
#define TEGRA210_I2S_CTRLS 5
#define TEGRA210_I2S_CG 0x88
#define TEGRA210_I2S_CTRL 0xa0
/* registers to control DISPA SLCG during power ungating */
#define DC_CMD_DISPLAY_COMMAND 0xc8
#define DC_COM_DSC_TOP_CTL 0xcf8
/* register to control VIC SLCG during power ungating */
#define NV_PVIC_THI_SLCG_OVERRIDE_LOW 0x8c
struct lvl2_ovr_info {
u32 offset;
u32 mask;
};
struct powergate_partition_info {
const char *name;
struct partition_clk_info clk_info[MAX_CLK_EN_NUM];
struct partition_clk_info slcg_info[MAX_CLK_EN_NUM];
struct lvl2_ovr_info lvl2_ovr_info[MAX_CLK_EN_NUM];
void (*handle_lvl2_ovr)(void);
unsigned long reset_id[MAX_CLK_EN_NUM];
int reset_id_num;
int refcount;
bool disable_after_boot;
struct mutex pg_mutex;
bool skip_reset;
};
#define EMULATION_MC_FLUSH_TIMEOUT 100
#define TEGRA210_POWER_DOMAIN_NVENC TEGRA210_POWER_DOMAIN_MPE
enum mc_client_type {
MC_CLIENT_AFI = 0,
MC_CLIENT_AVPC = 1,
MC_CLIENT_DC = 2,
MC_CLIENT_DCB = 3,
MC_CLIENT_HC = 6,
MC_CLIENT_HDA = 7,
MC_CLIENT_ISP2 = 8,
MC_CLIENT_NVENC = 11,
MC_CLIENT_SATA = 15,
MC_CLIENT_VI = 17,
MC_CLIENT_VIC = 18,
MC_CLIENT_XUSB_HOST = 19,
MC_CLIENT_XUSB_DEV = 20,
MC_CLIENT_BPMP = 21,
MC_CLIENT_TSEC = 22,
MC_CLIENT_SDMMC1 = 29,
MC_CLIENT_SDMMC2 = 30,
MC_CLIENT_SDMMC3 = 31,
MC_CLIENT_SDMMC4 = 32,
MC_CLIENT_ISP2B = 33,
MC_CLIENT_NVDEC = 37,
MC_CLIENT_APE = 38,
MC_CLIENT_SE = 39,
MC_CLIENT_NVJPG = 40,
MC_CLIENT_TSECB = 45,
MC_CLIENT_LAST = -1,
};
struct tegra210_mc_client_info {
enum mc_client_type hot_reset_clients[MAX_HOTRESET_CLIENT_NUM];
};
static struct tegra210_mc_client_info tegra210_pg_mc_info[] = {
[TEGRA210_POWER_DOMAIN_CRAIL] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_VENC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_ISP2,
[1] = MC_CLIENT_VI,
[2] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_PCIE] = {
.hot_reset_clients = {
[0] = MC_CLIENT_AFI,
[1] = MC_CLIENT_LAST,
},
},
#ifdef CONFIG_ARCH_TEGRA_HAS_SATA
[TEGRA210_POWER_DOMAIN_SATA] = {
.hot_reset_clients = {
[0] = MC_CLIENT_SATA,
[1] = MC_CLIENT_LAST,
},
},
#endif
[TEGRA210_POWER_DOMAIN_NVENC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_NVENC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_SOR] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_DISA] = {
.hot_reset_clients = {
[0] = MC_CLIENT_DC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_DISB] = {
.hot_reset_clients = {
[0] = MC_CLIENT_DCB,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_XUSBA] = {
.hot_reset_clients = {
[0] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_XUSBB] = {
.hot_reset_clients = {
[0] = MC_CLIENT_XUSB_DEV,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_XUSBC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_XUSB_HOST,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_VIC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_VIC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_NVDEC] = {
.hot_reset_clients = {
[0] = MC_CLIENT_NVDEC,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_NVJPG] = {
.hot_reset_clients = {
[0] = MC_CLIENT_NVJPG,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_APE] = {
.hot_reset_clients = {
[0] = MC_CLIENT_APE,
[1] = MC_CLIENT_LAST,
},
},
[TEGRA210_POWER_DOMAIN_VE2] = {
.hot_reset_clients = {
[0] = MC_CLIENT_ISP2B,
[1] = MC_CLIENT_LAST,
},
},
};
static void handle_lvl2_ovr_ape(void);
static void handle_lvl2_ovr_disp(void);
static void handle_lvl2_ovr_vic(void);
static struct powergate_partition_info tegra210_pg_partition_info[] = {
[TEGRA210_POWER_DOMAIN_VENC] = {
.name = "ve",
.clk_info = {
{ .clk_name = "ispa", .clk_type = CLK_ONLY },
{ .clk_name = "vi", .clk_type = CLK_ONLY },
{ .clk_name = "csi", .clk_type = CLK_ONLY },
{ .clk_name = "vii2c", .clk_type = CLK_ONLY },
{ .clk_name = "cilab", .clk_type = CLK_ONLY },
{ .clk_name = "cilcd", .clk_type = CLK_ONLY },
{ .clk_name = "cile", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "host1x" },
},
/* all lv2lovr functionality for this domain is handled by
* tegra210_venc_mbist_war
*/
.handle_lvl2_ovr = tegra210_venc_mbist_war,
.reset_id = { TEGRA210_CLK_ISPA, TEGRA210_RST_VI,
TEGRA210_CLK_CSI, TEGRA210_CLK_VI_I2C },
.reset_id_num = 4,
},
[TEGRA210_POWER_DOMAIN_PCIE] = {
.name = "pcie",
.skip_reset = true,
},
#ifdef CONFIG_ARCH_TEGRA_HAS_SATA
[TEGRA210_POWER_DOMAIN_SATA] = {
.name = "sata",
.disable_after_boot = false,
.clk_info = {
{ .clk_name = "sata_oob", .clk_type = CLK_ONLY },
{ .clk_name = "cml1", .clk_type = CLK_ONLY },
{ .clk_name = "sata_aux", .clk_type = CLK_ONLY },
{ .clk_name = "sata", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRC,
.mask = BIT(0) | BIT(17) | BIT(19) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_SATA_OOB, TEGRA210_CLK_SATA_COLD,
TEGRA210_CLK_SATA },
.reset_id_num = 3,
},
#endif
[TEGRA210_POWER_DOMAIN_NVENC] = {
.name = "nvenc",
.clk_info = {
{ .clk_name = "nvenc", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRE, .mask = BIT(29) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_NVENC },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_SOR] = {
.name = "sor",
.clk_info = {
{ .clk_name = "sor0", .clk_type = CLK_ONLY },
{ .clk_name = "dsia", .clk_type = CLK_ONLY },
{ .clk_name = "dsib", .clk_type = CLK_ONLY },
{ .clk_name = "sor1", .clk_type = CLK_ONLY },
{ .clk_name = "mipi-cal", .clk_type = CLK_ONLY },
{ .clk_name = "dpaux", .clk_type = CLK_ONLY },
{ .clk_name = "dpaux1", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "hda2hdmi" },
{ .clk_name = "hda2codec_2x" },
{ .clk_name = "disp1" },
{ .clk_name = "disp2" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRA,
.mask = BIT(1) | BIT(2) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_SOR0, TEGRA210_CLK_DSIA,
TEGRA210_CLK_DSIB, TEGRA210_CLK_SOR1,
TEGRA210_CLK_MIPI_CAL, TEGRA210_CLK_DPAUX,
TEGRA210_CLK_DPAUX1 },
.reset_id_num = 7,
},
[TEGRA210_POWER_DOMAIN_DISA] = {
.name = "disa",
.clk_info = {
{ .clk_name = "disp1", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "la" },
{ .clk_name = "host1x" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRA, .mask = BIT(1) },
{ .offset = LVL2_END },
},
.handle_lvl2_ovr = handle_lvl2_ovr_disp,
.reset_id = { TEGRA210_CLK_DISP1 },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_DISB] = {
.name = "disb",
.disable_after_boot = true,
.clk_info = {
{ .clk_name = "disp2", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "la" },
{ .clk_name = "host1x" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRA, .mask = BIT(2) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_DISP2 },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_XUSBA] = {
.name = "xusba",
.clk_info = {
{ .clk_name = "xusb_ss", .clk_type = CLK_ONLY },
{ .clk_name = "xusb_ssp_src", .clk_type = CLK_ONLY },
{ .clk_name = "xusb_hs_src", .clk_type = CLK_ONLY },
{ .clk_name = "xusb_fs_src", .clk_type = CLK_ONLY },
{ .clk_name = "xusb_dev_src", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "xusb_host" },
{ .clk_name = "xusb_dev" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRC,
.mask = BIT(30) | BIT(31) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_XUSB_SS },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_XUSBB] = {
.name = "xusbb",
.clk_info = {
{ .clk_name = "xusb_dev", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "xusb_ss" },
{ .clk_name = "xusb_host" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRC,
.mask = BIT(30) | BIT(31) },
{ .offset = LVL2_END },
},
.reset_id = { 95 },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_XUSBC] = {
.name = "xusbc",
.clk_info = {
{ .clk_name = "xusb_host", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "xusb_ss" },
{ .clk_name = "xusb_dev" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRC,
.mask = BIT(30) | BIT(31) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_XUSB_HOST },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_VIC] = {
.name = "vic",
.clk_info = {
{ .clk_name = "vic03", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "host1x" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRE, .mask = BIT(5) },
{ .offset = LVL2_END },
},
.handle_lvl2_ovr = handle_lvl2_ovr_vic,
.reset_id = { TEGRA210_CLK_VIC03 },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_NVDEC] = {
.name = "nvdec",
.clk_info = {
{ .clk_name = "nvdec", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "nvjpg" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRE,
.mask = BIT(9) | BIT(31) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_NVDEC },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_NVJPG] = {
.name = "nvjpg",
.clk_info = {
{ .clk_name = "nvjpg", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "nvdec" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRE,
.mask = BIT(9) | BIT(31) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_NVJPG },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_APE] = {
.name = "ape",
.clk_info = {
{ .clk_name = "ape", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
{ .clk_name = "aclk" },
{ .clk_name = "i2s0" },
{ .clk_name = "i2s1" },
{ .clk_name = "i2s2" },
{ .clk_name = "i2s3" },
{ .clk_name = "i2s4" },
{ .clk_name = "spdif_out" },
{ .clk_name = "d_audio" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRC, .mask = BIT(1) },
{ .offset = LVL2_CLK_GATE_OVRE,
.mask = BIT(10) | BIT(11) },
{ .offset = LVL2_END },
},
.handle_lvl2_ovr = handle_lvl2_ovr_ape,
.reset_id = { TEGRA210_CLK_APE },
.reset_id_num = 1,
},
[TEGRA210_POWER_DOMAIN_VE2] = {
.name = "ve2",
.clk_info = {
{ .clk_name = "ispb", .clk_type = CLK_ONLY },
},
.slcg_info = {
{ .clk_name = "mc_capa" },
{ .clk_name = "mc_cbpa" },
{ .clk_name = "mc_ccpa" },
{ .clk_name = "mc_cdpa" },
},
.lvl2_ovr_info = {
{ .offset = LVL2_CLK_GATE_OVRD, .mask = BIT(22) },
{ .offset = LVL2_END },
},
.reset_id = { TEGRA210_CLK_ISPB },
.reset_id_num = 1,
},
};
struct mc_client_hotreset_reg {
u32 control_reg;
u32 status_reg;
};
struct tegra210_powergate_info {
bool valid;
unsigned int mask;
int part_id;
struct tegra210_mc_client_info *mc_info;
struct powergate_partition_info *part_info;
};
#define T210_POWERGATE_INFO(_pg_id, _pg_bit, _part_id, _mc_id) \
[TEGRA210_POWER_DOMAIN_##_pg_id] = { \
.valid = true, \
.mask = BIT(_pg_bit), \
.part_id = TEGRA210_POWER_DOMAIN_##_part_id, \
.mc_info = &tegra210_pg_mc_info[TEGRA210_POWER_DOMAIN_##_mc_id], \
.part_info = &tegra210_pg_partition_info[TEGRA210_POWER_DOMAIN_##_part_id], \
}
static struct tegra210_powergate_info t210_pg_info[TEGRA210_POWER_DOMAIN_MAX] = {
T210_POWERGATE_INFO(CRAIL, 0, CRAIL, CRAIL),
[1] = { .valid = false },
T210_POWERGATE_INFO(VENC, 2, VENC, VENC),
T210_POWERGATE_INFO(PCIE, 3, PCIE, PCIE),
T210_POWERGATE_INFO(VDEC, 4, VDEC, VDEC),
T210_POWERGATE_INFO(L2, 5, L2, L2),
T210_POWERGATE_INFO(MPE, 6, MPE, MPE),
T210_POWERGATE_INFO(HEG, 7, HEG, HEG),
T210_POWERGATE_INFO(SATA, 8, SATA, SATA),
T210_POWERGATE_INFO(CPU1, 9, CPU1, CPU1),
T210_POWERGATE_INFO(CPU2, 10, CPU2, CPU2),
T210_POWERGATE_INFO(CPU3, 11, CPU3, CPU3),
T210_POWERGATE_INFO(CELP, 12, CELP, CELP),
T210_POWERGATE_INFO(3D1, 13, 3D1, 3D1),
T210_POWERGATE_INFO(CPU0, 14, CPU0, CPU0),
T210_POWERGATE_INFO(C0NC, 15, C0NC, C0NC),
T210_POWERGATE_INFO(C1NC, 16, C1NC, C1NC),
T210_POWERGATE_INFO(SOR, 17, SOR, SOR),
T210_POWERGATE_INFO(DISA, 18, DISA, DISA),
T210_POWERGATE_INFO(DISB, 19, DISB, DISB),
T210_POWERGATE_INFO(XUSBA, 20, XUSBA, XUSBA),
T210_POWERGATE_INFO(XUSBB, 21, XUSBB, XUSBB),
T210_POWERGATE_INFO(XUSBC, 22, XUSBC, XUSBC),
T210_POWERGATE_INFO(VIC, 23, VIC, VIC),
T210_POWERGATE_INFO(NVDEC, 25, NVDEC, NVDEC),
T210_POWERGATE_INFO(NVJPG, 26, NVJPG, NVJPG),
T210_POWERGATE_INFO(APE, 27, APE, APE),
T210_POWERGATE_INFO(VE2, 29, VE2, VE2),
};
#define PMC_GPU_RG_CONTROL 0x2d4
#define PWRGATE_CLAMP_STATUS 0x2c
#define PWRGATE_TOGGLE 0x30
#define PWRGATE_TOGGLE_START (1 << 8)
#define REMOVE_CLAMPING 0x34
#define PWRGATE_STATUS 0x38
static DEFINE_SPINLOCK(tegra210_pg_lock);
static void __iomem *tegra_mc;
static void __iomem *tegra_car;
static void __iomem *tegra_ape;
static void __iomem *tegra_dispa;
static void __iomem *tegra_vic;
static int tegra210_pg_powergate_partition(int id);
static int tegra210_pg_unpowergate_partition(int id);
static int tegra210_pg_mc_flush(int id);
static int tegra210_pg_mc_flush_done(int id);
static bool tegra210b01;
static u32 __maybe_unused mc_read(unsigned long reg)
{
return readl(tegra_mc + reg);
}
#define HOTRESET_READ_COUNTS 5
static const char *tegra210_pg_get_name(int id)
{
return t210_pg_info[id].part_info->name;
}
static spinlock_t *tegra210_pg_get_lock(void)
{
return &tegra210_pg_lock;
}
static bool tegra210_pg_skip(int id)
{
switch (t210_pg_info[id].part_id) {
case TEGRA210_POWER_DOMAIN_VENC:
case TEGRA210_POWER_DOMAIN_VE2:
/* T210b01 has SE2 in place of ISP2 and powergate
* is not supported for SE2.
*/
if (tegra210b01)
return true;
default:
return false;
}
}
static bool tegra210_pg_is_powered(int id)
{
u32 status = 0;
status = tegra_pmc_readl(PWRGATE_STATUS) & t210_pg_info[id].mask;
return !!status;
}
static int tegra_powergate_set(int id, bool new_state)
{
bool status;
unsigned long flags;
spinlock_t *lock;
u32 reg;
/* 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;
if (tegra_cpu_is_asim())
return 0;
lock = tegra210_pg_get_lock();
spin_lock_irqsave(lock, flags);
status = !!(tegra_pmc_readl(PWRGATE_STATUS) & t210_pg_info[id].mask);
if (status == new_state) {
spin_unlock_irqrestore(lock, flags);
return 0;
}
switch (id) {
case TEGRA210_POWER_DOMAIN_CPU0:
case TEGRA210_POWER_DOMAIN_CPU1:
case TEGRA210_POWER_DOMAIN_CPU2:
case TEGRA210_POWER_DOMAIN_CPU3:
/* CPU ungated in s/w only during boot/resume with outer
waiting loop and no contention from other CPUs */
tegra_pmc_writel_relaxed(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
tegra_pmc_readl(PWRGATE_TOGGLE);
spin_unlock_irqrestore(lock, flags);
return 0;
default:
break;
}
/* Wait if PMC is already processing some other power gating request */
do {
udelay(1);
reg = tegra_pmc_readl(PWRGATE_TOGGLE);
contention_timeout--;
} while ((contention_timeout > 0) && (reg & PWRGATE_TOGGLE_START));
if (contention_timeout <= 0)
pr_err(" Timed out waiting for PMC to submit \
new power gate request \n");
contention_timeout = 100;
/* Submit power gate request */
tegra_pmc_writel_relaxed(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE);
/* Wait while PMC accepts the request */
do {
udelay(1);
reg = tegra_pmc_readl(PWRGATE_TOGGLE);
contention_timeout--;
} while ((contention_timeout > 0) && (reg & PWRGATE_TOGGLE_START));
if (contention_timeout <= 0)
pr_err(" Timed out waiting for PMC to accept \
new power gate request \n");
contention_timeout = 100;
/* Check power gate status */
do {
do {
udelay(1);
status = !!(tegra_pmc_readl(PWRGATE_STATUS) &
t210_pg_info[id].mask);
toggle_timeout--;
} while ((status != new_state) && (toggle_timeout > 0));
toggle_timeout = 10;
contention_timeout--;
} while ((status != new_state) && (contention_timeout > 0));
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(tegra210_pg_get_name(id), new_state,
raw_smp_processor_id());
return 0;
}
static const char *clk_get_name(struct clk *clk)
{
return __clk_get_name(clk);
}
static int powergate_module(int id)
{
tegra210_pg_mc_flush(id);
return tegra_powergate_set(id, false);
}
static int partition_clk_enable(struct powergate_partition_info *pg_info)
{
int ret;
u32 idx;
struct clk *clk;
struct partition_clk_info *clk_info;
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
clk_info = &pg_info->clk_info[idx];
clk = clk_info->clk_ptr;
if (IS_ERR(clk) || !clk)
break;
if (clk_info->clk_type != RST_ONLY) {
ret = clk_prepare_enable(clk);
if (ret)
goto err_clk_en;
}
}
return 0;
err_clk_en:
WARN(1, "Could not enable clk %s, error %d", clk_get_name(clk), ret);
while (idx--) {
clk_info = &pg_info->clk_info[idx];
if (clk_info->clk_type != RST_ONLY)
clk_disable_unprepare(clk_info->clk_ptr);
}
return ret;
}
static void partition_clk_disable(struct powergate_partition_info *pg_info)
{
u32 idx;
struct clk *clk;
struct partition_clk_info *clk_info;
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
clk_info = &pg_info->clk_info[idx];
clk = clk_info->clk_ptr;
if (IS_ERR(clk) || !clk)
break;
if (clk_info->clk_type != RST_ONLY)
clk_disable_unprepare(clk);
}
}
static void get_clk_info(struct powergate_partition_info *pg_info)
{
int idx;
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
if (!pg_info->clk_info[idx].clk_name)
break;
pg_info->clk_info[idx].clk_ptr =
clk_get_sys(NULL, pg_info->clk_info[idx].clk_name);
if (IS_ERR_OR_NULL(pg_info->clk_info[idx].clk_ptr))
WARN(1, "Could not find clock %s for %s partition\n",
pg_info->clk_info[idx].clk_name,
pg_info->name);
}
}
static void powergate_partition_assert_reset(struct powergate_partition_info *pg_info)
{
tegra_rst_assertv(&pg_info->reset_id[0], pg_info->reset_id_num);
}
static void powergate_partition_deassert_reset(struct powergate_partition_info *pg_info)
{
tegra_rst_deassertv(&pg_info->reset_id[0], pg_info->reset_id_num);
}
static int slcg_clk_enable(struct powergate_partition_info *pg_info)
{
int ret;
u32 idx;
struct clk *clk;
struct partition_clk_info *slcg_info;
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
slcg_info = &pg_info->slcg_info[idx];
clk = slcg_info->clk_ptr;
if (IS_ERR(clk) || !clk)
break;
ret = clk_prepare_enable(clk);
if (ret)
goto err_clk_en;
}
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
u32 val;
struct lvl2_ovr_info *lvl2;
lvl2 = &pg_info->lvl2_ovr_info[idx];
if (lvl2->offset == LVL2_END)
break;
val = readl_relaxed(tegra_car + lvl2->offset);
val |= lvl2->mask;
writel(val, tegra_car + lvl2->offset);
}
return 0;
err_clk_en:
WARN(1, "Could not enable clk %s, error %d", __clk_get_name(clk), ret);
while (idx--) {
slcg_info = &pg_info->slcg_info[idx];
clk_disable_unprepare(slcg_info->clk_ptr);
}
return ret;
}
static void slcg_clk_disable(struct powergate_partition_info *pg_info)
{
u32 idx;
struct clk *clk;
struct partition_clk_info *slcg_info;
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
slcg_info = &pg_info->slcg_info[idx];
clk = slcg_info->clk_ptr;
if (IS_ERR(clk) || !clk)
break;
clk_disable_unprepare(clk);
}
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
u32 val;
struct lvl2_ovr_info *lvl2;
lvl2 = &pg_info->lvl2_ovr_info[idx];
lvl2 = &pg_info->lvl2_ovr_info[idx];
if (lvl2->offset == LVL2_END)
break;
val = readl_relaxed(tegra_car + lvl2->offset);
val &= ~lvl2->mask;
writel(val, tegra_car + lvl2->offset);
}
}
static void get_slcg_info(struct powergate_partition_info *pg_info)
{
int idx;
for (idx = 0; idx < MAX_CLK_EN_NUM; idx++) {
if (!pg_info->slcg_info[idx].clk_name)
break;
pg_info->slcg_info[idx].clk_ptr =
clk_get_sys(NULL, pg_info->slcg_info[idx].clk_name);
if (IS_ERR_OR_NULL(pg_info->slcg_info[idx].clk_ptr))
pr_err("### Could not find clock %s for %s partition\n",
pg_info->slcg_info[idx].clk_name,
pg_info->name);
}
}
static void handle_lvl2_ovr_ape(void)
{
unsigned long flags;
u32 i2s_ctrl;
spinlock_t *lock;
int i;
void __iomem *i2s_base;
lock = tegra210_pg_get_lock();
spin_lock_irqsave(lock, flags);
for (i = 0; i < TEGRA210_I2S_CTRLS; i++) {
i2s_base = tegra_ape + TEGRA210_I2S_BASE
+ i * TEGRA210_I2S_SIZE;
i2s_ctrl = readl_relaxed(i2s_base + TEGRA210_I2S_CTRL);
writel(i2s_ctrl | BIT(10), i2s_base + TEGRA210_I2S_CTRL);
writel(0, i2s_base + TEGRA210_I2S_CG);
readl_relaxed(i2s_base + TEGRA210_I2S_CG);
udelay(1);
writel(1, i2s_base + TEGRA210_I2S_CG);
writel(i2s_ctrl, i2s_base + TEGRA210_I2S_CTRL);
}
spin_unlock_irqrestore(lock, flags);
}
static void handle_lvl2_ovr_disp(void)
{
unsigned long flags;
spinlock_t *lock;
u32 val;
lock = tegra210_pg_get_lock();
spin_lock_irqsave(lock, flags);
val = readl_relaxed(tegra_dispa + DC_COM_DSC_TOP_CTL);
writel(val | BIT(2), tegra_dispa + DC_COM_DSC_TOP_CTL);
readl_relaxed(tegra_dispa + DC_CMD_DISPLAY_COMMAND);
writel(val, tegra_dispa + DC_COM_DSC_TOP_CTL);
readl_relaxed(tegra_dispa + DC_CMD_DISPLAY_COMMAND);
spin_unlock_irqrestore(lock, flags);
}
static void handle_lvl2_ovr_vic(void)
{
unsigned long flags;
spinlock_t *lock;
u32 val;
lock = tegra210_pg_get_lock();
spin_lock_irqsave(lock, flags);
val = readl_relaxed(tegra_vic + NV_PVIC_THI_SLCG_OVERRIDE_LOW);
writel(val | BIT(0) | GENMASK(7,2) | BIT(24),
tegra_vic + NV_PVIC_THI_SLCG_OVERRIDE_LOW);
readl_relaxed(tegra_vic + NV_PVIC_THI_SLCG_OVERRIDE_LOW);
udelay(1);
writel(val, tegra_vic + NV_PVIC_THI_SLCG_OVERRIDE_LOW);
spin_unlock_irqrestore(lock, flags);
}
static int tegra210_powergate_remove_clamping(int id)
{
u32 mask;
int contention_timeout = 100;
/*
* PCIE and VDE clamping masks are swapped with respect to their
* partition ids
*/
if (id == TEGRA210_POWER_DOMAIN_VDEC)
mask = t210_pg_info[TEGRA210_POWER_DOMAIN_PCIE].mask;
else if (id == TEGRA210_POWER_DOMAIN_PCIE)
mask = t210_pg_info[TEGRA210_POWER_DOMAIN_VDEC].mask;
else
mask = t210_pg_info[id].mask;
tegra_pmc_writel_relaxed(mask, REMOVE_CLAMPING);
/* Wait until clamp is removed */
do {
udelay(1);
contention_timeout--;
} while ((contention_timeout > 0)
&& (tegra_pmc_readl(PWRGATE_CLAMP_STATUS) & BIT(id)));
WARN(contention_timeout <= 0, "Couldn't remove clamping");
return 0;
}
static 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");
return ret;
}
udelay(10);
tegra210_pg_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;
}
static int tegra1xx_powergate(int id, struct powergate_partition_info *pg_info)
{
return __tegra1xx_powergate(id, pg_info);
}
static 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 (!pg_info->slcg_info[0].clk_ptr)
get_slcg_info(pg_info);
if (tegra210_pg_is_powered(id)) {
return 0;
}
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;
powergate_partition_assert_reset(pg_info);
udelay(10);
ret = tegra210_powergate_remove_clamping(id);
if (ret)
goto err_clamp;
udelay(10);
if (!pg_info->skip_reset) {
powergate_partition_deassert_reset(pg_info);
udelay(10);
}
tegra210_pg_mc_flush_done(id);
udelay(10);
if (!tegra210b01) {
slcg_clk_enable(pg_info);
if (pg_info->handle_lvl2_ovr)
(pg_info->handle_lvl2_ovr)();
slcg_clk_disable(pg_info);
}
/* 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;
}
static int tegra1xx_unpowergate(int id,
struct powergate_partition_info *pg_info)
{
return __tegra1xx_unpowergate(id, pg_info);
}
static int tegra210_pg_mc_flush(int id)
{
u32 idx;
enum mc_client_type mc_client_bit;
int ret = EINVAL;
for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) {
mc_client_bit =
t210_pg_info[id].mc_info->hot_reset_clients[idx];
if (mc_client_bit == MC_CLIENT_LAST)
break;
ret = tegra_mc_flush(mc_client_bit);
if (ret)
break;
}
return ret;
}
static int tegra210_pg_mc_flush_done(int id)
{
u32 idx;
enum mc_client_type mc_client_bit;
int ret = -EINVAL;
for (idx = 0; idx < MAX_HOTRESET_CLIENT_NUM; idx++) {
mc_client_bit =
t210_pg_info[id].mc_info->hot_reset_clients[idx];
if (mc_client_bit == MC_CLIENT_LAST)
break;
ret = tegra_mc_flush_done(mc_client_bit);
if (ret)
break;
}
wmb();
return ret;
}
static int tegra210_pg_powergate(int id)
{
struct powergate_partition_info *partition = t210_pg_info[id].part_info;
int ret = 0;
trace_powergate(__func__, tegra210_pg_get_name(id), id, 1, 0);
mutex_lock(&partition->pg_mutex);
if (--partition->refcount > 0)
goto exit_unlock;
if ((partition->refcount < 0) || !tegra210_pg_is_powered(id)) {
WARN(1, "Partition %s already powergated, refcount and status mismatch\n",
partition->name);
goto exit_unlock;
}
ret = tegra1xx_powergate(id, partition);
exit_unlock:
mutex_unlock(&partition->pg_mutex);
trace_powergate(__func__, tegra210_pg_get_name(id), id, 0, ret);
return ret;
}
static int tegra210_pg_unpowergate(int id)
{
struct powergate_partition_info *partition = t210_pg_info[id].part_info;
int ret = 0;
trace_powergate(__func__, tegra210_pg_get_name(id), id, 1, 0);
mutex_lock(&partition->pg_mutex);
if (partition->refcount++ > 0)
goto exit_unlock;
if (tegra210_pg_is_powered(id)) {
WARN(1, "Partition %s is already unpowergated, refcount and status mismatch\n",
partition->name);
goto exit_unlock;
}
ret = tegra1xx_unpowergate(id, partition);
exit_unlock:
mutex_unlock(&partition->pg_mutex);
trace_powergate(__func__, tegra210_pg_get_name(id), id, 0, ret);
return ret;
}
static int tegra210_pg_powergate_sor(int id)
{
int ret;
ret = tegra210_pg_powergate(id);
if (ret)
return ret;
ret = tegra210_pg_powergate_partition(TEGRA210_POWER_DOMAIN_SOR);
if (ret)
return ret;
return 0;
}
static int tegra210_pg_unpowergate_sor(int id)
{
int ret;
ret = tegra210_pg_unpowergate_partition(TEGRA210_POWER_DOMAIN_SOR);
if (ret)
return ret;
ret = tegra210_pg_unpowergate(id);
if (ret) {
tegra210_pg_powergate_partition(TEGRA210_POWER_DOMAIN_SOR);
return ret;
}
return 0;
}
static int tegra210_pg_nvdec_powergate(int id)
{
tegra210_pg_powergate(TEGRA210_POWER_DOMAIN_NVDEC);
tegra210_pg_powergate_partition(TEGRA210_POWER_DOMAIN_NVJPG);
return 0;
}
static int tegra210_pg_nvdec_unpowergate(int id)
{
tegra210_pg_unpowergate_partition(TEGRA210_POWER_DOMAIN_NVJPG);
tegra210_pg_unpowergate(TEGRA210_POWER_DOMAIN_NVDEC);
return 0;
}
static int tegra210_pg_sata_powergate(int id)
{
tegra210_set_sata_pll_seq_sw(true);
tegra210_pg_powergate(TEGRA210_POWER_DOMAIN_SATA);
return 0;
}
static int tegra210_pg_sata_unpowergate(int id)
{
tegra210_pg_unpowergate(TEGRA210_POWER_DOMAIN_SATA);
tegra210_set_sata_pll_seq_sw(false);
return 0;
}
static int tegra210_pg_powergate_partition(int id)
{
int ret;
switch (t210_pg_info[id].part_id) {
case TEGRA210_POWER_DOMAIN_DISA:
case TEGRA210_POWER_DOMAIN_DISB:
case TEGRA210_POWER_DOMAIN_VENC:
ret = tegra210_pg_powergate_sor(id);
break;
case TEGRA210_POWER_DOMAIN_NVDEC:
ret = tegra210_pg_nvdec_powergate(id);
break;
case TEGRA210_POWER_DOMAIN_SATA:
ret = tegra210_pg_sata_powergate(id);
break;
default:
ret = tegra210_pg_powergate(id);
}
return ret;
}
static int tegra210_pg_unpowergate_partition(int id)
{
int ret;
switch (t210_pg_info[id].part_id) {
case TEGRA210_POWER_DOMAIN_DISA:
case TEGRA210_POWER_DOMAIN_DISB:
case TEGRA210_POWER_DOMAIN_VENC:
ret = tegra210_pg_unpowergate_sor(id);
break;
case TEGRA210_POWER_DOMAIN_NVDEC:
ret = tegra210_pg_nvdec_unpowergate(id);
break;
case TEGRA210_POWER_DOMAIN_SATA:
ret = tegra210_pg_sata_unpowergate(id);
break;
default:
ret = tegra210_pg_unpowergate(id);
}
return ret;
}
static int tegra210_pg_init_refcount(void)
{
int i;
for (i = 0; i < TEGRA210_POWER_DOMAIN_MAX; i++) {
if (!t210_pg_info[i].valid)
continue;
/* Consider main partion ID only */
if (i != t210_pg_info[i].part_id)
continue;
if (tegra210_pg_is_powered(i))
t210_pg_info[i].part_info->refcount = 1;
else
t210_pg_info[i].part_info->disable_after_boot = 0;
mutex_init(&t210_pg_info[i].part_info->pg_mutex);
}
/* SOR refcount depends on other units */
t210_pg_info[TEGRA210_POWER_DOMAIN_SOR].part_info->refcount =
(tegra210_pg_is_powered(TEGRA210_POWER_DOMAIN_DISA) ? 1 : 0) +
(tegra210_pg_is_powered(TEGRA210_POWER_DOMAIN_DISB) ? 1 : 0) +
(tegra210_pg_is_powered(TEGRA210_POWER_DOMAIN_VENC) ? 1 : 0);
/* NVJPG refcount needs to plus 1 if NVDEC is enabled */
t210_pg_info[TEGRA210_POWER_DOMAIN_NVJPG].part_info->refcount +=
(tegra210_pg_is_powered(TEGRA210_POWER_DOMAIN_NVDEC) ? 1 : 0);
tegra210_pg_powergate_partition(TEGRA210_POWER_DOMAIN_XUSBA);
tegra210_pg_powergate_partition(TEGRA210_POWER_DOMAIN_XUSBB);
tegra210_pg_powergate_partition(TEGRA210_POWER_DOMAIN_XUSBC);
return 0;
}
static bool tegra210_powergate_id_is_valid(int id)
{
if ((id < 0) || (id >= TEGRA210_POWER_DOMAIN_MAX))
return false;
return t210_pg_info[id].valid;
}
static int tegra210_powergate_cpuid_to_powergate_id(int cpu)
{
switch (cpu) {
case 0:
return TEGRA210_POWER_DOMAIN_CPU0;
case 1:
return TEGRA210_POWER_DOMAIN_CPU1;
case 2:
return TEGRA210_POWER_DOMAIN_CPU2;
case 3:
return TEGRA210_POWER_DOMAIN_CPU3;
default:
return -1;
}
return -1;
}
static struct tegra_powergate_driver_ops tegra210_pg_ops = {
.soc_name = "tegra210",
.num_powerdomains = TEGRA210_POWER_DOMAIN_MAX,
.powergate_id_is_soc_valid = tegra210_powergate_id_is_valid,
.powergate_cpuid_to_powergate_id =
tegra210_powergate_cpuid_to_powergate_id,
.get_powergate_lock = tegra210_pg_get_lock,
.get_powergate_domain_name = tegra210_pg_get_name,
.powergate_partition = tegra210_pg_powergate_partition,
.unpowergate_partition = tegra210_pg_unpowergate_partition,
.powergate_mc_flush = tegra210_pg_mc_flush,
.powergate_mc_flush_done = tegra210_pg_mc_flush_done,
.powergate_skip = tegra210_pg_skip,
.powergate_is_powered = tegra210_pg_is_powered,
.powergate_init_refcount = tegra210_pg_init_refcount,
.powergate_remove_clamping = tegra210_powergate_remove_clamping,
};
#define TEGRA_MC_BASE 0x70019000
#define TEGRA_CAR_BASE 0x60006000
#define TEGRA_APE_BASE 0x702c0000
#define TEGRA_DISPA_BASE 0x54200000
#define TEGRA_VIC_BASE 0x54340000
#define TEGRA_VI_BASE 0x54080000
struct tegra_powergate_driver_ops *tegra210_powergate_init_chip_support(void)
{
u32 hid, chipid, major;
tegra_mc = ioremap(TEGRA_MC_BASE, 4096);
if (!tegra_mc)
return NULL;
tegra_car = ioremap(TEGRA_CAR_BASE, 4096);
if (!tegra_car)
return NULL;
tegra_ape = ioremap(TEGRA_APE_BASE, 256*1024);
if (!tegra_ape)
return NULL;
tegra_dispa = ioremap(TEGRA_DISPA_BASE, 256*1024);
if (!tegra_dispa)
return NULL;
tegra_vic = ioremap(TEGRA_VIC_BASE, 256*1024);
if (!tegra_vic)
return NULL;
hid = tegra_read_chipid();
chipid = tegra_hidrev_get_chipid(hid);
major = tegra_hidrev_get_majorrev(hid);
tegra210b01 = chipid == TEGRA210B01 && major >= 2;
return &tegra210_pg_ops;
}
static int __init tegra210_disable_boot_partitions(void)
{
int i;
if (!soc_is_tegra210_n_before())
return 0;
pr_info("Disable partitions left on by BL\n");
for (i = 0; i < TEGRA210_POWER_DOMAIN_MAX; i++) {
if (!t210_pg_info[i].valid)
continue;
/* consider main partion ID only */
if (i != t210_pg_info[i].part_id)
continue;
if (t210_pg_info[i].part_info->disable_after_boot) {
pr_info(" %s\n", t210_pg_info[i].part_info->name);
tegra210_pg_powergate_partition(i);
}
}
return 0;
}
late_initcall(tegra210_disable_boot_partitions);