This patch adds system suspend and resume support for Tegra210 clocks. Signed-off-by: Sowjanya Komatineni <skomatineni@xxxxxxxxxx> --- drivers/clk/tegra/clk-tegra210.c | 382 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index ed3c7df75d1e..d012b53ca132 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -20,10 +20,12 @@ #include <linux/clkdev.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_platform.h> #include <linux/delay.h> #include <linux/export.h> #include <linux/mutex.h> #include <linux/clk/tegra.h> +#include <linux/syscore_ops.h> #include <dt-bindings/clock/tegra210-car.h> #include <dt-bindings/reset/tegra210-car.h> #include <linux/iopoll.h> @@ -31,6 +33,7 @@ #include <soc/tegra/pmc.h> #include "clk.h" +#include "clk-dfll.h" #include "clk-id.h" /* @@ -48,6 +51,9 @@ #define CLK_SOURCE_SDMMC2 0x154 #define CLK_SOURCE_SDMMC4 0x164 +#define CLK_OUT_ENB_Y 0x298 +#define CLK_ENB_PLLP_OUT_CPU BIT(31) + #define PLLC_BASE 0x80 #define PLLC_OUT 0x84 #define PLLC_MISC0 0x88 @@ -71,6 +77,8 @@ #define PLLM_MISC1 0x98 #define PLLM_MISC2 0x9c #define PLLP_BASE 0xa0 +#define PLLP_OUTA 0xa4 +#define PLLP_OUTB 0xa8 #define PLLP_MISC0 0xac #define PLLP_MISC1 0x680 #define PLLA_BASE 0xb0 @@ -227,9 +235,18 @@ #define XUSB_PLL_CFG0_UTMIPLL_LOCK_DLY 0x3ff #define XUSB_PLL_CFG0_PLLU_LOCK_DLY_MASK (0x3ff << 14) +#define SCLK_BURST_POLICY 0x28 +#define SYSTEM_CLK_RATE 0x30 +#define CLK_MASK_ARM 0x44 +#define MISC_CLK_ENB 0x48 +#define CCLKG_BURST_POLICY 0x368 +#define CCLKLP_BURST_POLICY 0x370 +#define CPU_SOFTRST_CTRL 0x380 +#define SYS_CLK_DIV 0x400 #define SPARE_REG0 0x55c #define CLK_M_DIVISOR_SHIFT 2 #define CLK_M_DIVISOR_MASK 0x3 +#define BURST_POLICY_REG_SIZE 2 #define RST_DFLL_DVCO 0x2f4 #define DVFS_DFLL_RESET_SHIFT 0 @@ -3381,6 +3398,367 @@ static struct tegra_clk_init_table init_table[] __initdata = { { TEGRA210_CLK_CLK_MAX, TEGRA210_CLK_CLK_MAX, 0, 0 }, }; +#ifdef CONFIG_PM_SLEEP +static unsigned long pll_c_rate, pll_c2_rate, pll_c3_rate, pll_x_rate; +static unsigned long pll_c4_rate, pll_d2_rate, pll_dp_rate; +static unsigned long pll_re_vco_rate, pll_d_rate, pll_a_rate, pll_a1_rate; +static unsigned long pll_c_out1_rate; +static unsigned long pll_a_out0_rate, pll_c4_out3_rate; +static unsigned long pll_p_out_rate[5]; +static unsigned long pll_u_out1_rate, pll_u_out2_rate; +static unsigned long pll_mb_rate; +static u32 pll_m_v; +static u32 pll_p_outa, pll_p_outb; +static u32 pll_re_out_div, pll_re_out_1; +static u32 cpu_softrst_ctx[3]; +static u32 cclkg_burst_policy_ctx[2]; +static u32 cclklp_burst_policy_ctx[2]; +static u32 sclk_burst_policy_ctx[3]; +static u32 sclk_ctx, spare_ctx, misc_clk_enb_ctx, clk_arm_ctx; + +static struct platform_device *dfll_pdev; +#define car_readl(_base, _off) \ + readl_relaxed(clk_base + (_base) + ((_off) * 4)) +#define car_writel(_val, _base, _off) \ + writel_relaxed(_val, clk_base + (_base) + ((_off) * 4)) + +static u32 *periph_clk_src_ctx; +struct periph_source_bank { + u32 start; + u32 end; +}; + +static struct periph_source_bank periph_srcs[] = { + [0] = { + .start = 0x100, + .end = 0x198, + }, + [1] = { + .start = 0x1a0, + .end = 0x1f8, + }, + [2] = { + .start = 0x3b4, + .end = 0x42c, + }, + [3] = { + .start = 0x49c, + .end = 0x4b4, + }, + [4] = { + .start = 0x560, + .end = 0x564, + }, + [5] = { + .start = 0x600, + .end = 0x678, + }, + [6] = { + .start = 0x694, + .end = 0x6a0, + }, + [7] = { + .start = 0x6b8, + .end = 0x718, + }, +}; + +/* This array lists the valid clocks for each periph clk bank */ +static u32 periph_clks_on[] = { + 0xdcd7dff9, + 0x87d1f3e7, + 0xf3fed3fa, + 0xffc18cfb, + 0x793fb7ff, + 0x3fe66fff, + 0xfc1fc7ff, +}; + +static inline unsigned long clk_get_rate_nolock(struct clk *clk) +{ + if (IS_ERR_OR_NULL(clk)) { + WARN_ON(1); + return 0; + } + + return clk_hw_get_rate(__clk_get_hw(clk)); +} + +static inline struct clk *pll_p_clk(unsigned int x) +{ + if (x < 4) { + return clks[TEGRA210_CLK_PLL_P_OUT1 + x]; + } else if (x != 4) { + WARN_ON(1); + return NULL; + } else { + return clks[TEGRA210_CLK_PLL_P_OUT5]; + } +} + +static u32 * __init tegra210_init_suspend_ctx(void) +{ + int i, size = 0; + + for (i = 0; i < ARRAY_SIZE(periph_srcs); i++) + size += periph_srcs[i].end - periph_srcs[i].start + 4; + + periph_clk_src_ctx = kmalloc(size, GFP_KERNEL); + + return periph_clk_src_ctx; +} + +static int tegra210_clk_suspend(void) +{ + int i; + unsigned long off; + struct device_node *node; + u32 *clk_rst_ctx = periph_clk_src_ctx; + u32 val; + + pll_a_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_A]); + pll_a_out0_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_A_OUT0]); + pll_a1_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_A1]); + pll_c2_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_C2]); + pll_c3_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_C3]); + pll_c_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_C]); + pll_c_out1_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_C_OUT1]); + pll_x_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_X]); + pll_c4_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_C4]); + pll_c4_out3_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_C4_OUT3]); + pll_dp_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_DP]); + pll_d_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_D]); + pll_d2_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_D2]); + pll_re_vco_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_RE_VCO]); + pll_re_out_div = car_readl(PLLRE_BASE, 0) & (0xf << 16); + pll_re_out_1 = car_readl(PLLRE_OUT1, 0); + pll_u_out1_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_U_OUT1]); + pll_u_out2_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_U_OUT2]); + pll_mb_rate = clk_get_rate_nolock(clks[TEGRA210_CLK_PLL_MB]); + pll_m_v = car_readl(PLLM_BASE, 0); + pll_p_outa = car_readl(PLLP_OUTA, 0); + pll_p_outb = car_readl(PLLP_OUTB, 0); + + for (i = 0; i < ARRAY_SIZE(cpu_softrst_ctx); i++) + cpu_softrst_ctx[i] = car_readl(CPU_SOFTRST_CTRL, i); + + for (i = 0; i < ARRAY_SIZE(pll_p_out_rate); i++) + pll_p_out_rate[i] = clk_get_rate_nolock(pll_p_clk(i)); + + for (i = 0; i < BURST_POLICY_REG_SIZE; i++) { + cclkg_burst_policy_ctx[i] = car_readl(CCLKG_BURST_POLICY, i); + cclklp_burst_policy_ctx[i] = car_readl(CCLKLP_BURST_POLICY, i); + sclk_burst_policy_ctx[i] = car_readl(SCLK_BURST_POLICY, i); + } + sclk_burst_policy_ctx[i] = car_readl(SYS_CLK_DIV, 0); + + sclk_ctx = car_readl(SYSTEM_CLK_RATE, 0); + spare_ctx = car_readl(SPARE_REG0, 0); + misc_clk_enb_ctx = car_readl(MISC_CLK_ENB, 0); + clk_arm_ctx = car_readl(CLK_MASK_ARM, 0); + + for (i = 0; i < ARRAY_SIZE(periph_srcs); i++) + for (off = periph_srcs[i].start; off <= periph_srcs[i].end; + off += 4) + *clk_rst_ctx++ = car_readl(off, 0); + + if (!dfll_pdev) { + node = of_find_compatible_node(NULL, NULL, + "nvidia,tegra210-dfll"); + if (node) + dfll_pdev = of_find_device_by_node(node); + of_node_put(node); + if (!dfll_pdev) + pr_err("dfll node not found. no suspend for dfll\n"); + } + + if (dfll_pdev) + tegra_dfll_suspend(dfll_pdev); + + /* Enable PLLP_OUT_CPU after dfll suspend */ + val = car_readl(CLK_OUT_ENB_Y, 0); + val |= CLK_ENB_PLLP_OUT_CPU; + car_writel(val, CLK_OUT_ENB_Y, 0); + + tegra_clk_periph_suspend(clk_base); + return 0; +} + +static void tegra210_clk_resume(void) +{ + int i; + unsigned long off; + u32 val; + u32 *clk_rst_ctx = periph_clk_src_ctx; + struct clk_hw *parent; + + tegra_clk_osc_resume(clk_base); + + for (i = 0; i < ARRAY_SIZE(cpu_softrst_ctx); i++) + car_writel(cpu_softrst_ctx[i], CPU_SOFTRST_CTRL, i); + + /* + * Since we are going to reset devices and switch clock sources in this + * function, plls and secondary dividers is required to be enabled. The + * actual value will be restored back later. + */ + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_P_OUT1], + pll_p_out_rate[0]); + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_P_OUT3], + pll_p_out_rate[2]); + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_P_OUT4], + pll_p_out_rate[3]); + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_P_OUT5], + pll_p_out_rate[4]); + + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_A1], pll_a1_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_C2], pll_c2_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_C3], pll_c3_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_C], pll_c_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_X], pll_x_rate); + + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_A], pll_a_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_D], pll_d_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_D2], pll_d2_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_DP], pll_dp_rate); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_C4], pll_c4_rate); + + /* enable the PLLD */ + val = car_readl(PLLD_MISC0, 0); + val |= PLLD_MISC0_DSI_CLKENABLE; + car_writel(val, PLLD_MISC0, 0); + + /* reprogram PLLRE post divider, VCO, 2ndary divider (in this order) */ + if (!__clk_is_enabled(clks[TEGRA210_CLK_PLL_RE_VCO])) { + val = car_readl(PLLRE_BASE, 0); + val &= ~(0xf << 16); + car_writel(val | pll_re_out_div, PLLRE_BASE, 0); + } + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_RE_VCO], pll_re_vco_rate); + + car_writel(pll_re_out_1, PLLRE_OUT1, 0); + + /* resume PLLU */ + tegra210_init_pllu(); + + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_U_OUT1], + pll_u_out1_rate); + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_U_OUT2], + pll_u_out2_rate); + + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_C_OUT1], + pll_c_out1_rate); + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_A_OUT0], + pll_a_out0_rate); + tegra_clk_pll_out_resume(clks[TEGRA210_CLK_PLL_C4_OUT3], + pll_c4_out3_rate); + /* + * resume SCLK and CPULP clocks + * for SCLk 1st set safe dividers values, then restore source, + * then restore dividers + */ + car_writel(0x1, SYSTEM_CLK_RATE, 0); + val = car_readl(SYS_CLK_DIV, 0); + i = BURST_POLICY_REG_SIZE; + if (val < sclk_burst_policy_ctx[i]) + car_writel(sclk_burst_policy_ctx[i], SYS_CLK_DIV, 0); + fence_udelay(2, clk_base); + for (i = 0; i < BURST_POLICY_REG_SIZE; i++) { + car_writel(cclklp_burst_policy_ctx[i], CCLKLP_BURST_POLICY, i); + car_writel(sclk_burst_policy_ctx[i], SCLK_BURST_POLICY, i); + } + car_writel(sclk_burst_policy_ctx[i], SYS_CLK_DIV, 0); + + car_writel(sclk_ctx, SYSTEM_CLK_RATE, 0); + car_writel(spare_ctx, SPARE_REG0, 0); + car_writel(misc_clk_enb_ctx, MISC_CLK_ENB, 0); + car_writel(clk_arm_ctx, CLK_MASK_ARM, 0); + + /* enable all clocks before configuring clock sources */ + tegra_clk_periph_force_on(periph_clks_on, ARRAY_SIZE(periph_clks_on), + clk_base); + + wmb(); + fence_udelay(2, clk_base); + + for (i = 0; i < ARRAY_SIZE(periph_srcs); i++) + for (off = periph_srcs[i].start; off <= periph_srcs[i].end; + off += 4) + car_writel(*clk_rst_ctx++, off, 0); + + /* propagate and restore resets, restore clock state */ + fence_udelay(5, clk_base); + tegra_clk_periph_resume(clk_base); + + /* restore (sync) the actual PLL and secondary divider values */ + car_writel(pll_p_outa, PLLP_OUTA, 0); + car_writel(pll_p_outb, PLLP_OUTB, 0); + + tegra_clk_sync_state_pll_out(clks[TEGRA210_CLK_PLL_U_OUT1]); + tegra_clk_sync_state_pll_out(clks[TEGRA210_CLK_PLL_U_OUT2]); + + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_A1]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_C2]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_C3]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_C]); + + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_RE_VCO]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_C4]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_D2]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_DP]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_A]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_D]); + + tegra_clk_sync_state_pll_out(clks[TEGRA210_CLK_PLL_C_OUT1]); + tegra_clk_sync_state_pll_out(clks[TEGRA210_CLK_PLL_A_OUT0]); + + /* + * restore CPUG clocks: + * - enable DFLL in open loop mode + * - switch CPUG to DFLL clock source + * - close DFLL loop + * - sync PLLX state + */ + if (dfll_pdev) + tegra_dfll_resume(dfll_pdev, false); + + for (i = 0; i < BURST_POLICY_REG_SIZE; i++) + car_writel(cclkg_burst_policy_ctx[i], CCLKG_BURST_POLICY, i); + fence_udelay(2, clk_base); + + if (dfll_pdev) + tegra_dfll_resume(dfll_pdev, true); + + parent = clk_hw_get_parent(__clk_get_hw(clks[TEGRA210_CLK_CCLK_G])); + if (parent != __clk_get_hw(clks[TEGRA210_CLK_PLL_X])) + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_X]); + + /* Disable PLL_OUT_CPU after DFLL resume */ + val = car_readl(CLK_OUT_ENB_Y, 0); + val &= ~CLK_ENB_PLLP_OUT_CPU; + car_writel(val, CLK_OUT_ENB_Y, 0); + + car_writel(pll_m_v, PLLM_BASE, 0); + tegra_clk_pll_resume(clks[TEGRA210_CLK_PLL_MB], pll_mb_rate); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_M]); + tegra_clk_sync_state_pll(clks[TEGRA210_CLK_PLL_MB]); + + tegra_clk_plle_tegra210_resume(clks[TEGRA210_CLK_PLL_E]); +} +#else +#define tegra210_clk_suspend NULL +#define tegra210_clk_resume NULL +static inline u32 *tegra210_init_suspend_ctx(void) +{ + return NULL; +} +#endif + +static struct syscore_ops tegra_clk_syscore_ops = { + .suspend = tegra210_clk_suspend, + .resume = tegra210_clk_resume, +}; + /** * tegra210_clock_apply_init_table - initialize clocks on Tegra210 SoCs * @@ -3591,5 +3969,9 @@ static void __init tegra210_clock_init(struct device_node *np) tegra210_mbist_clk_init(); tegra_cpu_car_ops = &tegra210_cpu_car_ops; + + if (tegra210_init_suspend_ctx()) + register_syscore_ops(&tegra_clk_syscore_ops); + } CLK_OF_DECLARE(tegra210, "nvidia,tegra210-car", tegra210_clock_init); -- 2.7.4