diff options
Diffstat (limited to 'drivers/clk/tegra/clk-tegra210.c')
-rw-r--r-- | drivers/clk/tegra/clk-tegra210.c | 181 |
1 files changed, 155 insertions, 26 deletions
diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index df172d5772d7..762cd186f714 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -9,13 +9,13 @@ #include <linux/clkdev.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/syscore_ops.h> #include <linux/delay.h> #include <linux/export.h> #include <linux/mutex.h> #include <linux/clk/tegra.h> #include <dt-bindings/clock/tegra210-car.h> #include <dt-bindings/reset/tegra210-car.h> -#include <linux/iopoll.h> #include <linux/sizes.h> #include <soc/tegra/pmc.h> @@ -33,6 +33,7 @@ #define CLK_SOURCE_CSITE 0x1d4 #define CLK_SOURCE_EMC 0x19c #define CLK_SOURCE_SOR1 0x410 +#define CLK_SOURCE_SOR0 0x414 #define CLK_SOURCE_LA 0x1f8 #define CLK_SOURCE_SDMMC2 0x154 #define CLK_SOURCE_SDMMC4 0x164 @@ -220,11 +221,15 @@ #define CLK_M_DIVISOR_SHIFT 2 #define CLK_M_DIVISOR_MASK 0x3 +#define CLK_MASK_ARM 0x44 +#define MISC_CLK_ENB 0x48 + #define RST_DFLL_DVCO 0x2f4 #define DVFS_DFLL_RESET_SHIFT 0 #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8 #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac +#define CPU_SOFTRST_CTRL 0x380 #define LVL2_CLK_GATE_OVRA 0xf8 #define LVL2_CLK_GATE_OVRC 0x3a0 @@ -298,6 +303,7 @@ static DEFINE_SPINLOCK(pll_d_lock); static DEFINE_SPINLOCK(pll_e_lock); static DEFINE_SPINLOCK(pll_re_lock); static DEFINE_SPINLOCK(pll_u_lock); +static DEFINE_SPINLOCK(sor0_lock); static DEFINE_SPINLOCK(sor1_lock); static DEFINE_SPINLOCK(emc_lock); static DEFINE_MUTEX(lvl2_ovr_lock); @@ -2351,9 +2357,9 @@ static struct tegra_clk tegra210_clks[tegra_clk_max] __initdata = { [tegra_clk_dpaux] = { .dt_id = TEGRA210_CLK_DPAUX, .present = true }, [tegra_clk_dpaux1] = { .dt_id = TEGRA210_CLK_DPAUX1, .present = true }, [tegra_clk_sor0] = { .dt_id = TEGRA210_CLK_SOR0, .present = true }, - [tegra_clk_sor0_lvds] = { .dt_id = TEGRA210_CLK_SOR0_LVDS, .present = true }, + [tegra_clk_sor0_out] = { .dt_id = TEGRA210_CLK_SOR0_OUT, .present = true }, [tegra_clk_sor1] = { .dt_id = TEGRA210_CLK_SOR1, .present = true }, - [tegra_clk_sor1_src] = { .dt_id = TEGRA210_CLK_SOR1_SRC, .present = true }, + [tegra_clk_sor1_out] = { .dt_id = TEGRA210_CLK_SOR1_OUT, .present = true }, [tegra_clk_gpu] = { .dt_id = TEGRA210_CLK_GPU, .present = true }, [tegra_clk_pll_g_ref] = { .dt_id = TEGRA210_CLK_PLL_G_REF, .present = true, }, [tegra_clk_uartb_8] = { .dt_id = TEGRA210_CLK_UARTB, .present = true }, @@ -2551,7 +2557,6 @@ static struct tegra_devclk devclks[] __initdata = { { .con_id = "pll_c4_out2", .dt_id = TEGRA210_CLK_PLL_C4_OUT2 }, { .con_id = "pll_c4_out3", .dt_id = TEGRA210_CLK_PLL_C4_OUT3 }, { .con_id = "dpaux", .dt_id = TEGRA210_CLK_DPAUX }, - { .con_id = "sor0", .dt_id = TEGRA210_CLK_SOR0 }, }; static struct tegra_audio_clk_info tegra210_audio_plls[] = { @@ -2825,6 +2830,7 @@ static int tegra210_enable_pllu(void) struct tegra_clk_pll_freq_table *fentry; struct tegra_clk_pll pllu; u32 reg; + int ret; for (fentry = pll_u_freq_table; fentry->input_rate; fentry++) { if (fentry->input_rate == pll_ref_freq) @@ -2841,7 +2847,7 @@ static int tegra210_enable_pllu(void) reg = readl_relaxed(clk_base + pllu.params->ext_misc_reg[0]); reg &= ~BIT(pllu.params->iddq_bit_idx); writel_relaxed(reg, clk_base + pllu.params->ext_misc_reg[0]); - udelay(5); + fence_udelay(5, clk_base); reg = readl_relaxed(clk_base + PLLU_BASE); reg &= ~GENMASK(20, 0); @@ -2849,13 +2855,18 @@ static int tegra210_enable_pllu(void) reg |= fentry->n << 8; reg |= fentry->p << 16; writel(reg, clk_base + PLLU_BASE); - udelay(1); + fence_udelay(1, clk_base); reg |= PLL_ENABLE; writel(reg, clk_base + PLLU_BASE); - readl_relaxed_poll_timeout_atomic(clk_base + PLLU_BASE, reg, - reg & PLL_BASE_LOCK, 2, 1000); - if (!(reg & PLL_BASE_LOCK)) { + /* + * During clocks resume, same PLLU init and enable sequence get + * executed. So, readx_poll_timeout_atomic can't be used here as it + * uses ktime_get() and timekeeping resume doesn't happen by that + * time. So, using tegra210_wait_for_mask for PLL LOCK. + */ + ret = tegra210_wait_for_mask(&pllu, PLLU_BASE, PLL_BASE_LOCK); + if (ret) { pr_err("Timed out waiting for PLL_U to lock\n"); return -ETIMEDOUT; } @@ -2895,12 +2906,12 @@ static int tegra210_init_pllu(void) reg = readl_relaxed(clk_base + XUSB_PLL_CFG0); reg &= ~XUSB_PLL_CFG0_PLLU_LOCK_DLY_MASK; writel_relaxed(reg, clk_base + XUSB_PLL_CFG0); - udelay(1); + fence_udelay(1, clk_base); reg = readl_relaxed(clk_base + PLLU_HW_PWRDN_CFG0); reg |= PLLU_HW_PWRDN_CFG0_SEQ_ENABLE; writel_relaxed(reg, clk_base + PLLU_HW_PWRDN_CFG0); - udelay(1); + fence_udelay(1, clk_base); reg = readl_relaxed(clk_base + PLLU_BASE); reg &= ~PLLU_BASE_CLKENABLE_USB; @@ -2915,6 +2926,39 @@ static int tegra210_init_pllu(void) return 0; } +/* + * The SOR hardware blocks are driven by two clocks: a module clock that is + * used to access registers and a pixel clock that is sourced from the same + * pixel clock that also drives the head attached to the SOR. The module + * clock is typically called sorX (with X being the SOR instance) and the + * pixel clock is called sorX_out. The source for the SOR pixel clock is + * referred to as the "parent" clock. + * + * On Tegra186 and newer, clocks are provided by the BPMP. Unfortunately the + * BPMP implementation for the SOR clocks doesn't exactly match the above in + * some aspects. For example, the SOR module is really clocked by the pad or + * sor_safe clocks, but BPMP models the sorX clock as being sourced by the + * pixel clocks. Conversely the sorX_out clock is sourced by the sor_safe or + * pad clocks on BPMP. + * + * In order to allow the display driver to deal with all SoC generations in + * a unified way, implement the BPMP semantics in this driver. + */ + +static const char * const sor0_parents[] = { + "pll_d_out0", +}; + +static const char * const sor0_out_parents[] = { + "sor_safe", "sor0_pad_clkout", +}; + +static const char * const sor1_parents[] = { + "pll_p", "pll_d_out0", "pll_d2_out0", "clk_m", +}; + +static u32 sor1_parents_idx[] = { 0, 2, 5, 6 }; + static const char * const sor1_out_parents[] = { /* * Bit 0 of the mux selects sor1_pad_clkout, irrespective of bit 1, so @@ -2923,20 +2967,31 @@ static const char * const sor1_out_parents[] = { * these bits to 0b11. While not an invalid setting, code should * always set the bits to 0b01 to select sor1_pad_clkout. */ - "sor_safe", "sor1_pad_clkout", "sor1", "sor1_pad_clkout", + "sor_safe", "sor1_pad_clkout", "sor1_out", "sor1_pad_clkout", }; -static const char * const sor1_parents[] = { - "pll_p", "pll_d_out0", "pll_d2_out0", "clk_m", -}; - -static u32 sor1_parents_idx[] = { 0, 2, 5, 6 }; - static struct tegra_periph_init_data tegra210_periph[] = { + /* + * On Tegra210, the sor0 clock doesn't have a mux it bitfield 31:29, + * but it is hardwired to the pll_d_out0 clock. + */ + TEGRA_INIT_DATA_TABLE("sor0", NULL, NULL, sor0_parents, + CLK_SOURCE_SOR0, 29, 0x0, 0, 0, 0, 0, + 0, 182, 0, tegra_clk_sor0, NULL, 0, + &sor0_lock), + TEGRA_INIT_DATA_TABLE("sor0_out", NULL, NULL, sor0_out_parents, + CLK_SOURCE_SOR0, 14, 0x1, 0, 0, 0, 0, + 0, 0, TEGRA_PERIPH_NO_GATE, tegra_clk_sor0_out, + NULL, 0, &sor0_lock), TEGRA_INIT_DATA_TABLE("sor1", NULL, NULL, sor1_parents, CLK_SOURCE_SOR1, 29, 0x7, 0, 0, 8, 1, - TEGRA_DIVIDER_ROUND_UP, 183, 0, tegra_clk_sor1, - sor1_parents_idx, 0, &sor1_lock), + TEGRA_DIVIDER_ROUND_UP, 183, 0, + tegra_clk_sor1, sor1_parents_idx, 0, + &sor1_lock), + TEGRA_INIT_DATA_TABLE("sor1_out", NULL, NULL, sor1_out_parents, + CLK_SOURCE_SOR1, 14, 0x3, 0, 0, 0, 0, + 0, 0, TEGRA_PERIPH_NO_GATE, + tegra_clk_sor1_out, NULL, 0, &sor1_lock), }; static const char * const la_parents[] = { @@ -2969,12 +3024,6 @@ static __init void tegra210_periph_clk_init(void __iomem *clk_base, 1, 17, 207); clks[TEGRA210_CLK_DPAUX1] = clk; - clk = clk_register_mux_table(NULL, "sor1_out", sor1_out_parents, - ARRAY_SIZE(sor1_out_parents), 0, - clk_base + CLK_SOURCE_SOR1, 14, 0x3, - 0, NULL, &sor1_lock); - clks[TEGRA210_CLK_SOR1_OUT] = clk; - /* pll_d_dsi_out */ clk = clk_register_gate(NULL, "pll_d_dsi_out", "pll_d_out0", 0, clk_base + PLLD_MISC0, 21, 0, &pll_d_lock); @@ -3287,6 +3336,77 @@ static void tegra210_disable_cpu_clock(u32 cpu) } #ifdef CONFIG_PM_SLEEP +#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 spare_reg_ctx, misc_clk_enb_ctx, clk_msk_arm_ctx; +static u32 cpu_softrst_ctx[3]; + +static int tegra210_clk_suspend(void) +{ + unsigned int i; + + clk_save_context(); + + /* + * Save the bootloader configured clock registers SPARE_REG0, + * MISC_CLK_ENB, CLK_MASK_ARM, CPU_SOFTRST_CTRL. + */ + spare_reg_ctx = readl_relaxed(clk_base + SPARE_REG0); + misc_clk_enb_ctx = readl_relaxed(clk_base + MISC_CLK_ENB); + clk_msk_arm_ctx = readl_relaxed(clk_base + CLK_MASK_ARM); + + for (i = 0; i < ARRAY_SIZE(cpu_softrst_ctx); i++) + cpu_softrst_ctx[i] = car_readl(CPU_SOFTRST_CTRL, i); + + tegra_clk_periph_suspend(); + return 0; +} + +static void tegra210_clk_resume(void) +{ + unsigned int i; + + tegra_clk_osc_resume(clk_base); + + /* + * Restore the bootloader configured clock registers SPARE_REG0, + * MISC_CLK_ENB, CLK_MASK_ARM, CPU_SOFTRST_CTRL from saved context. + */ + writel_relaxed(spare_reg_ctx, clk_base + SPARE_REG0); + writel_relaxed(misc_clk_enb_ctx, clk_base + MISC_CLK_ENB); + writel_relaxed(clk_msk_arm_ctx, clk_base + CLK_MASK_ARM); + + for (i = 0; i < ARRAY_SIZE(cpu_softrst_ctx); i++) + car_writel(cpu_softrst_ctx[i], CPU_SOFTRST_CTRL, i); + + /* + * Tegra clock programming sequence recommends peripheral clock to + * be enabled prior to changing its clock source and divider to + * prevent glitchless frequency switch. + * So, enable all peripheral clocks before restoring their source + * and dividers. + */ + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_L, clk_base + CLK_OUT_ENB_L); + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_H, clk_base + CLK_OUT_ENB_H); + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_U, clk_base + CLK_OUT_ENB_U); + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_V, clk_base + CLK_OUT_ENB_V); + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_W, clk_base + CLK_OUT_ENB_W); + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_X, clk_base + CLK_OUT_ENB_X); + writel_relaxed(TEGRA210_CLK_ENB_VLD_MSK_Y, clk_base + CLK_OUT_ENB_Y); + + /* wait for all writes to happen to have all the clocks enabled */ + fence_udelay(2, clk_base); + + /* restore PLLs and all peripheral clock rates */ + tegra210_init_pllu(); + clk_restore_context(); + + /* restore saved context of peripheral clocks and reset state */ + tegra_clk_periph_resume(); +} + static void tegra210_cpu_clock_suspend(void) { /* switch coresite to clk_m, save off original source */ @@ -3302,6 +3422,13 @@ static void tegra210_cpu_clock_resume(void) } #endif +static struct syscore_ops tegra_clk_syscore_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = tegra210_clk_suspend, + .resume = tegra210_clk_resume, +#endif +}; + static struct tegra_cpu_car_ops tegra210_cpu_car_ops = { .wait_for_reset = tegra210_wait_cpu_in_reset, .disable_clock = tegra210_disable_cpu_clock, @@ -3586,5 +3713,7 @@ static void __init tegra210_clock_init(struct device_node *np) tegra210_mbist_clk_init(); tegra_cpu_car_ops = &tegra210_cpu_car_ops; + + register_syscore_ops(&tegra_clk_syscore_ops); } CLK_OF_DECLARE(tegra210, "nvidia,tegra210-car", tegra210_clock_init); |