This patch adds system suspend and resume support for Tegra210
clocks.

Signed-off-by: Sowjanya Komatineni <skomatin...@nvidia.com>
---
 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

Reply via email to