/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);