diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c index dee14d25f75d40..0085179f8d7a75 100644 --- a/drivers/clk/spacemit/ccu-k1.c +++ b/drivers/clk/spacemit/ccu-k1.c @@ -41,17 +41,47 @@ static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { CCU_PLL_RATE(3200000000UL, 0x0050dd67, 0x43eaaaab), }; -CCU_PLL_DEFINE(pll1, pll1_rate_tbl, APBS_PLL1_SWCR1, APBS_PLL1_SWCR3, MPMU_POSR, POSR_PLL1_LOCK, - CLK_SET_RATE_GATE); -CCU_PLL_DEFINE(pll2, pll2_rate_tbl, APBS_PLL2_SWCR1, APBS_PLL2_SWCR3, MPMU_POSR, POSR_PLL2_LOCK, - CLK_SET_RATE_GATE); -CCU_PLL_DEFINE(pll3, pll3_rate_tbl, APBS_PLL3_SWCR1, APBS_PLL3_SWCR3, MPMU_POSR, POSR_PLL3_LOCK, - CLK_SET_RATE_GATE); - -CCU_FACTOR_GATE_DEFINE(pll1_d2, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(1), 2, 1); +/* PLL1 supplies the fallback and must retain its firmware configuration. */ +CCU_PLL_SYNC_DEFINE(pll1, pll1_rate_tbl, APBS_PLL1_SWCR1, APBS_PLL1_SWCR2, + APBS_PLL1_SWCR3, MPMU_POSR, POSR_PLL1_LOCK, + CLK_SET_RATE_GATE, NULL); + +static const struct ccu_pll_sync pll2_sync = { + .safe_pll = &pll1, +}; + +static const struct ccu_pll_cpu_mux pll3_cpu_muxes[] = { + { APMU_CPU_C1_CLK_CTRL, GENMASK(2, 0), 5 }, + { APMU_CPU_C1_CLK_CTRL, GENMASK(2, 0), 7 }, + { APMU_CPU_C0_CLK_CTRL, GENMASK(2, 0), 5 }, + { APMU_CPU_C0_CLK_CTRL, GENMASK(2, 0), 7 }, +}; + +static const struct ccu_pll_sync pll3_sync = { + .safe_pll = &pll1, + .apmu_compatible = "spacemit,k1-syscon-apmu", + .muxes = pll3_cpu_muxes, + .num_muxes = ARRAY_SIZE(pll3_cpu_muxes), + .cpu_outputs = GENMASK(2, 0), + .safe_sel = 4, + .slow_sel = 3, + .reg_safe_gate = MPMU_ACGR, +}; + +CCU_PLL_SYNC_DEFINE(pll2, pll2_rate_tbl, APBS_PLL2_SWCR1, APBS_PLL2_SWCR2, + APBS_PLL2_SWCR3, MPMU_POSR, POSR_PLL2_LOCK, + CLK_SET_RATE_GATE, &pll2_sync); +CCU_PLL_SYNC_DEFINE(pll3, pll3_rate_tbl, APBS_PLL3_SWCR1, APBS_PLL3_SWCR2, + APBS_PLL3_SWCR3, MPMU_POSR, POSR_PLL3_LOCK, + CLK_SET_RATE_GATE, &pll3_sync); + +/* A failed CPU FC handshake must not lose either fallback clock. */ +CCU_FACTOR_GATE_FLAGS_DEFINE(pll1_d2, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(1), 2, 1, + CLK_IS_CRITICAL); CCU_FACTOR_GATE_DEFINE(pll1_d3, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(2), 3, 1); CCU_FACTOR_GATE_DEFINE(pll1_d4, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(3), 4, 1); -CCU_FACTOR_GATE_DEFINE(pll1_d5, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(4), 5, 1); +CCU_FACTOR_GATE_FLAGS_DEFINE(pll1_d5, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(4), 5, 1, + CLK_IS_CRITICAL); CCU_FACTOR_GATE_DEFINE(pll1_d6, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(5), 6, 1); CCU_FACTOR_GATE_DEFINE(pll1_d7, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(6), 7, 1); CCU_FACTOR_GATE_FLAGS_DEFINE(pll1_d8, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(7), 8, 1, @@ -111,7 +141,7 @@ CCU_FACTOR_DEFINE(pll1_d3072_0p8, CCU_PARENT_HW(pll1_d384_6p4), 8, 1); CCU_GATE_DEFINE(pll1_d6_409p6, CCU_PARENT_HW(pll1_d6), MPMU_ACGR, BIT(0), 0); CCU_FACTOR_GATE_DEFINE(pll1_d12_204p8, CCU_PARENT_HW(pll1_d6), MPMU_ACGR, BIT(5), 2, 1); -CCU_GATE_DEFINE(pll1_d5_491p52, CCU_PARENT_HW(pll1_d5), MPMU_ACGR, BIT(21), 0); +CCU_GATE_DEFINE(pll1_d5_491p52, CCU_PARENT_HW(pll1_d5), MPMU_ACGR, BIT(21), CLK_IS_CRITICAL); CCU_FACTOR_GATE_DEFINE(pll1_d10_245p76, CCU_PARENT_HW(pll1_d5), MPMU_ACGR, BIT(18), 2, 1); CCU_GATE_DEFINE(pll1_d4_614p4, CCU_PARENT_HW(pll1_d4), MPMU_ACGR, BIT(15), 0); @@ -120,7 +150,7 @@ CCU_FACTOR_GATE_DEFINE(pll1_d78_31p5, CCU_PARENT_HW(pll1_d4), MPMU_ACGR, BIT(6), CCU_GATE_DEFINE(pll1_d3_819p2, CCU_PARENT_HW(pll1_d3), MPMU_ACGR, BIT(14), 0); -CCU_GATE_DEFINE(pll1_d2_1228p8, CCU_PARENT_HW(pll1_d2), MPMU_ACGR, BIT(16), 0); +CCU_GATE_DEFINE(pll1_d2_1228p8, CCU_PARENT_HW(pll1_d2), MPMU_ACGR, BIT(16), CLK_IS_CRITICAL); CCU_GATE_DEFINE(slow_uart, CCU_PARENT_NAME(osc), MPMU_ACGR, BIT(1), CLK_IGNORE_UNUSED); CCU_DDN_DEFINE(slow_uart1_14p74, pll1_d16_153p6, MPMU_SUCCR, 16, 13, 0, 13, 2, 0); @@ -391,8 +421,8 @@ static const struct clk_parent_data cpu_c0_clk_parents[] = { CCU_PARENT_HW(pll2_d3), CCU_PARENT_HW(cpu_c0_hi_clk), }; -CCU_MUX_FC_DEFINE(cpu_c0_core_clk, cpu_c0_clk_parents, APMU_CPU_C0_CLK_CTRL, BIT(12), 0, 3, - CLK_IS_CRITICAL); +CCU_MUX_DIV_FC_DEFINE(cpu_c0_core_clk, cpu_c0_clk_parents, APMU_CPU_C0_CLK_CTRL, + 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL); CCU_DIV_DEFINE(cpu_c0_ace_clk, CCU_PARENT_HW(cpu_c0_core_clk), APMU_CPU_C0_CLK_CTRL, 6, 3, CLK_IS_CRITICAL); CCU_DIV_DEFINE(cpu_c0_tcm_clk, CCU_PARENT_HW(cpu_c0_core_clk), APMU_CPU_C0_CLK_CTRL, 9, 3, @@ -413,8 +443,8 @@ static const struct clk_parent_data cpu_c1_clk_parents[] = { CCU_PARENT_HW(pll2_d3), CCU_PARENT_HW(cpu_c1_hi_clk), }; -CCU_MUX_FC_DEFINE(cpu_c1_core_clk, cpu_c1_clk_parents, APMU_CPU_C1_CLK_CTRL, BIT(12), 0, 3, - CLK_IS_CRITICAL); +CCU_MUX_DIV_FC_DEFINE(cpu_c1_core_clk, cpu_c1_clk_parents, APMU_CPU_C1_CLK_CTRL, + 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL); CCU_DIV_DEFINE(cpu_c1_ace_clk, CCU_PARENT_HW(cpu_c1_core_clk), APMU_CPU_C1_CLK_CTRL, 6, 3, CLK_IS_CRITICAL); diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index 92b930d5ff3057..b01f2ce5d394fc 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -55,25 +55,77 @@ static const struct ccu_pll_rate_tbl pll8_rate_tbl[] = { CCU_PLLA_DEFINE(pll1, pll1_rate_tbl, APBS_PLL1_SWCR1, APBS_PLL1_SWCR2, APBS_PLL1_SWCR3, MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll2, pll2_rate_tbl, APBS_PLL2_SWCR1, APBS_PLL2_SWCR2, APBS_PLL2_SWCR3, - MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll3, pll3_rate_tbl, APBS_PLL3_SWCR1, APBS_PLL3_SWCR2, APBS_PLL3_SWCR3, - MPMU_POSR, POSR_PLL3_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll4, pll4_rate_tbl, APBS_PLL4_SWCR1, APBS_PLL4_SWCR2, APBS_PLL4_SWCR3, - MPMU_POSR, POSR_PLL4_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll5, pll5_rate_tbl, APBS_PLL5_SWCR1, APBS_PLL5_SWCR2, APBS_PLL5_SWCR3, - MPMU_POSR, POSR_PLL5_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll6, pll6_rate_tbl, APBS_PLL6_SWCR1, APBS_PLL6_SWCR2, APBS_PLL6_SWCR3, - MPMU_POSR, POSR_PLL6_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll7, pll7_rate_tbl, APBS_PLL7_SWCR1, APBS_PLL7_SWCR2, APBS_PLL7_SWCR3, - MPMU_POSR, POSR_PLL7_LOCK, CLK_SET_RATE_GATE); -CCU_PLLA_DEFINE(pll8, pll8_rate_tbl, APBS_PLL8_SWCR1, APBS_PLL8_SWCR2, APBS_PLL8_SWCR3, - MPMU_POSR, POSR_PLL8_LOCK, CLK_SET_RATE_GATE); - -CCU_FACTOR_GATE_DEFINE(pll1_d2, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(1), 2, 1); + +/* PLLs without a managed CPU path require all output gates to be off. */ +static const struct ccu_pll_sync idle_pll_sync = { + .safe_pll = &pll1, +}; + +/* Park the secondary cluster first when it shares the primary cluster PLL. */ +static const struct ccu_pll_cpu_mux pll3_cpu_muxes[] = { + { APMU_CPU_C1_CLK_CTRL, BIT(13) | GENMASK(2, 0), BIT(13) | 7 }, + { APMU_CPU_C0_CLK_CTRL, GENMASK(2, 0), 7 }, +}; + +static const struct ccu_pll_cpu_mux pll4_cpu_muxes[] = { + { APMU_CPU_C1_CLK_CTRL, BIT(13) | GENMASK(2, 0), 7 }, +}; + +static const struct ccu_pll_cpu_mux pll5_cpu_muxes[] = { + { APMU_CPU_C3_CLK_CTRL, BIT(13) | GENMASK(2, 0), BIT(13) | 7 }, + { APMU_CPU_C2_CLK_CTRL, GENMASK(2, 0), 7 }, +}; + +static const struct ccu_pll_cpu_mux pll8_cpu_muxes[] = { + { APMU_CPU_C3_CLK_CTRL, BIT(13) | GENMASK(2, 0), 7 }, +}; + +#define K3_CPU_PLL_SYNC(_pll) \ +static const struct ccu_pll_sync _pll##_sync = { \ + .safe_pll = &pll1, \ + .apmu_compatible = "spacemit,k3-syscon-apmu", \ + .muxes = _pll##_cpu_muxes, \ + .num_muxes = ARRAY_SIZE(_pll##_cpu_muxes), \ + .cpu_outputs = BIT(0), \ + .safe_sel = 5, \ + .slow_sel = 1, \ + .reg_safe_gate = MPMU_ACGR, \ +} + +K3_CPU_PLL_SYNC(pll3); +K3_CPU_PLL_SYNC(pll4); +K3_CPU_PLL_SYNC(pll5); +K3_CPU_PLL_SYNC(pll8); + +CCU_PLLA_SYNC_DEFINE(pll2, pll2_rate_tbl, APBS_PLL2_SWCR1, APBS_PLL2_SWCR2, + APBS_PLL2_SWCR3, MPMU_POSR, POSR_PLL2_LOCK, + CLK_SET_RATE_GATE, &idle_pll_sync); +CCU_PLLA_SYNC_DEFINE(pll3, pll3_rate_tbl, APBS_PLL3_SWCR1, APBS_PLL3_SWCR2, + APBS_PLL3_SWCR3, MPMU_POSR, POSR_PLL3_LOCK, + CLK_SET_RATE_GATE, &pll3_sync); +CCU_PLLA_SYNC_DEFINE(pll4, pll4_rate_tbl, APBS_PLL4_SWCR1, APBS_PLL4_SWCR2, + APBS_PLL4_SWCR3, MPMU_POSR, POSR_PLL4_LOCK, + CLK_SET_RATE_GATE, &pll4_sync); +CCU_PLLA_SYNC_DEFINE(pll5, pll5_rate_tbl, APBS_PLL5_SWCR1, APBS_PLL5_SWCR2, + APBS_PLL5_SWCR3, MPMU_POSR, POSR_PLL5_LOCK, + CLK_SET_RATE_GATE, &pll5_sync); +CCU_PLLA_SYNC_DEFINE(pll6, pll6_rate_tbl, APBS_PLL6_SWCR1, APBS_PLL6_SWCR2, + APBS_PLL6_SWCR3, MPMU_POSR, POSR_PLL6_LOCK, + CLK_SET_RATE_GATE, &idle_pll_sync); +CCU_PLLA_SYNC_DEFINE(pll7, pll7_rate_tbl, APBS_PLL7_SWCR1, APBS_PLL7_SWCR2, + APBS_PLL7_SWCR3, MPMU_POSR, POSR_PLL7_LOCK, + CLK_SET_RATE_GATE, &idle_pll_sync); +CCU_PLLA_SYNC_DEFINE(pll8, pll8_rate_tbl, APBS_PLL8_SWCR1, APBS_PLL8_SWCR2, + APBS_PLL8_SWCR3, MPMU_POSR, POSR_PLL8_LOCK, + CLK_SET_RATE_GATE, &pll8_sync); + +/* A failed CPU FC handshake must not lose either fallback clock. */ +CCU_FACTOR_GATE_FLAGS_DEFINE(pll1_d2, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(1), 2, 1, + CLK_IS_CRITICAL); CCU_FACTOR_GATE_DEFINE(pll1_d3, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(2), 3, 1); CCU_FACTOR_GATE_DEFINE(pll1_d4, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(3), 4, 1); -CCU_FACTOR_GATE_DEFINE(pll1_d5, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(4), 5, 1); +CCU_FACTOR_GATE_FLAGS_DEFINE(pll1_d5, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(4), 5, 1, + CLK_IS_CRITICAL); CCU_FACTOR_GATE_DEFINE(pll1_d6, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(5), 6, 1); CCU_FACTOR_GATE_DEFINE(pll1_d7, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(6), 7, 1); CCU_FACTOR_GATE_FLAGS_DEFINE(pll1_d8, CCU_PARENT_HW(pll1), APBS_PLL1_SWCR2, BIT(7), 8, 1, @@ -178,7 +230,7 @@ CCU_FACTOR_DEFINE(pll1_d3072_0p8, CCU_PARENT_HW(pll1_d384_6p4), 8, 1); CCU_GATE_DEFINE(pll1_d6_409p6, CCU_PARENT_HW(pll1_d6), MPMU_ACGR, BIT(0), 0); CCU_FACTOR_GATE_DEFINE(pll1_d12_204p8, CCU_PARENT_HW(pll1_d6), MPMU_ACGR, BIT(5), 2, 1); -CCU_GATE_DEFINE(pll1_d5_491p52, CCU_PARENT_HW(pll1_d5), MPMU_ACGR, BIT(21), 0); +CCU_GATE_DEFINE(pll1_d5_491p52, CCU_PARENT_HW(pll1_d5), MPMU_ACGR, BIT(21), CLK_IS_CRITICAL); CCU_FACTOR_GATE_DEFINE(pll1_d10_245p76, CCU_PARENT_HW(pll1_d5), MPMU_ACGR, BIT(18), 2, 1); CCU_GATE_DEFINE(pll1_d4_614p4, CCU_PARENT_HW(pll1_d4), MPMU_ACGR, BIT(15), 0); @@ -187,7 +239,7 @@ CCU_FACTOR_GATE_DEFINE(pll1_d78_31p5, CCU_PARENT_HW(pll1_d4), MPMU_ACGR, BIT(6), CCU_GATE_DEFINE(pll1_d3_819p2, CCU_PARENT_HW(pll1_d3), MPMU_ACGR, BIT(14), 0); -CCU_GATE_DEFINE(pll1_d2_1228p8, CCU_PARENT_HW(pll1_d2), MPMU_ACGR, BIT(16), 0); +CCU_GATE_DEFINE(pll1_d2_1228p8, CCU_PARENT_HW(pll1_d2), MPMU_ACGR, BIT(16), CLK_IS_CRITICAL); static const struct clk_parent_data apb_parents[] = { CCU_PARENT_HW(pll1_d96_25p6), @@ -587,52 +639,64 @@ static const struct clk_parent_data cpu_c0_clk_parents[] = { CCU_PARENT_HW(pll1_d5_491p52), CCU_PARENT_HW(pll1_d4_614p4), CCU_PARENT_HW(pll2_d3), - CCU_PARENT_HW(pll3_d2), + { .index = -1 }, /* Leave selector 4 unmodeled, as in the BSP. */ CCU_PARENT_HW(pll1_d2_1228p8), CCU_PARENT_HW(pll2_d2), CCU_PARENT_HW(pll3_d1), }; -CCU_MUX_DIV_FC_DEFINE(cpu_c0_core_clk, cpu_c0_clk_parents, APMU_CPU_C0_CLK_CTRL, - 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL); +CCU_MUX_DIV_BYPASS_FC_DEFINE(cpu_c0_core_clk, cpu_c0_clk_parents, APMU_CPU_C0_CLK_CTRL, + 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL, GENMASK(7, 4)); + +static const struct clk_parent_data cpu_c1_pll_src_parents[] = { + CCU_PARENT_HW(pll4_d1), + CCU_PARENT_HW(pll3_d1), +}; +CCU_MUX_DEFINE(cpu_c1_pll_src, cpu_c1_pll_src_parents, APMU_CPU_C1_CLK_CTRL, 13, 1, 0); static const struct clk_parent_data cpu_c1_clk_parents[] = { CCU_PARENT_HW(pll1_d3_819p2), CCU_PARENT_HW(pll1_d5_491p52), CCU_PARENT_HW(pll1_d4_614p4), CCU_PARENT_HW(pll2_d3), - CCU_PARENT_HW(pll4_d2), + { .index = -1 }, /* Leave selector 4 unmodeled, as in the BSP. */ CCU_PARENT_HW(pll1_d2_1228p8), CCU_PARENT_HW(pll2_d2), - CCU_PARENT_HW(pll4_d1), + CCU_PARENT_HW(cpu_c1_pll_src), }; -CCU_MUX_DIV_FC_DEFINE(cpu_c1_core_clk, cpu_c1_clk_parents, APMU_CPU_C1_CLK_CTRL, - 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL); +CCU_MUX_DIV_BYPASS_FC_DEFINE(cpu_c1_core_clk, cpu_c1_clk_parents, APMU_CPU_C1_CLK_CTRL, + 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL, GENMASK(7, 4)); static const struct clk_parent_data cpu_c2_clk_parents[] = { CCU_PARENT_HW(pll1_d3_819p2), CCU_PARENT_HW(pll1_d5_491p52), CCU_PARENT_HW(pll1_d4_614p4), CCU_PARENT_HW(pll2_d3), - CCU_PARENT_HW(pll5_d2), + { .index = -1 }, /* Leave selector 4 unmodeled, as in the BSP. */ CCU_PARENT_HW(pll1_d2_1228p8), CCU_PARENT_HW(pll2_d2), CCU_PARENT_HW(pll5_d1), }; -CCU_MUX_DIV_FC_DEFINE(cpu_c2_core_clk, cpu_c2_clk_parents, APMU_CPU_C2_CLK_CTRL, - 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL); +CCU_MUX_DIV_BYPASS_FC_DEFINE(cpu_c2_core_clk, cpu_c2_clk_parents, APMU_CPU_C2_CLK_CTRL, + 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL, GENMASK(7, 4)); + +static const struct clk_parent_data cpu_c3_pll_src_parents[] = { + CCU_PARENT_HW(pll8_d1), + CCU_PARENT_HW(pll5_d1), +}; +CCU_MUX_DEFINE(cpu_c3_pll_src, cpu_c3_pll_src_parents, APMU_CPU_C3_CLK_CTRL, 13, 1, 0); static const struct clk_parent_data cpu_c3_clk_parents[] = { CCU_PARENT_HW(pll1_d3_819p2), CCU_PARENT_HW(pll1_d5_491p52), CCU_PARENT_HW(pll1_d4_614p4), CCU_PARENT_HW(pll2_d3), - CCU_PARENT_HW(pll8_d2), + { .index = -1 }, /* Leave selector 4 unmodeled, as in the BSP. */ CCU_PARENT_HW(pll1_d2_1228p8), CCU_PARENT_HW(pll2_d2), - CCU_PARENT_HW(pll8_d1), + CCU_PARENT_HW(cpu_c3_pll_src), }; -CCU_MUX_DIV_FC_DEFINE(cpu_c3_core_clk, cpu_c3_clk_parents, APMU_CPU_C3_CLK_CTRL, - 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL); +CCU_MUX_DIV_BYPASS_FC_DEFINE(cpu_c3_core_clk, cpu_c3_clk_parents, APMU_CPU_C3_CLK_CTRL, + 3, 3, BIT(12), 0, 3, CLK_IS_CRITICAL, GENMASK(7, 4)); static const struct clk_parent_data ccic2phy_parents[] = { CCU_PARENT_HW(pll1_d24_102p4), @@ -1447,10 +1511,17 @@ static struct clk_hw *k3_ccu_apmu_hws[] = { [CLK_APMU_ISIM_VCLK3] = &isim_vclk_out3.common.hw, }; +static struct clk_hw *k3_ccu_apmu_internal_hws[] = { + &cpu_c1_pll_src.common.hw, + &cpu_c3_pll_src.common.hw, +}; + static const struct spacemit_ccu_data k3_ccu_apmu_data = { .reset_name = "k3-apmu-reset", .hws = k3_ccu_apmu_hws, .num = ARRAY_SIZE(k3_ccu_apmu_hws), + .internal_hws = k3_ccu_apmu_internal_hws, + .num_internal = ARRAY_SIZE(k3_ccu_apmu_internal_hws), }; static struct clk_hw *k3_ccu_dciu_hws[] = { diff --git a/drivers/clk/spacemit/ccu_common.c b/drivers/clk/spacemit/ccu_common.c index 8696bb9cba2d8f..759da6988ca78c 100644 --- a/drivers/clk/spacemit/ccu_common.c +++ b/drivers/clk/spacemit/ccu_common.c @@ -30,6 +30,18 @@ static int spacemit_ccu_register(struct device *dev, clk_data->num = data->num; + /* Internal muxes have no binding IDs but must precede their children. */ + for (i = 0; i < data->num_internal; i++) { + struct clk_hw *hw = data->internal_hws[i]; + struct ccu_common *common = hw_to_ccu_common(hw); + + common->regmap = regmap; + common->lock_regmap = lock_regmap; + ret = devm_clk_hw_register(dev, hw); + if (ret) + return ret; + } + for (i = 0; i < data->num; i++) { struct clk_hw *hw = data->hws[i]; struct ccu_common *common; diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h index 8691698e007d97..659dc68ab902a4 100644 --- a/drivers/clk/spacemit/ccu_common.h +++ b/drivers/clk/spacemit/ccu_common.h @@ -43,6 +43,8 @@ struct spacemit_ccu_data { const char *reset_name; struct clk_hw **hws; size_t num; + struct clk_hw **internal_hws; + size_t num_internal; }; #define ccu_read(c, reg) \ diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c index a8b407049bf4da..68596b9fa5b8f1 100644 --- a/drivers/clk/spacemit/ccu_mix.c +++ b/drivers/clk/spacemit/ccu_mix.c @@ -13,6 +13,8 @@ #define MIX_FC_TIMEOUT_US 10000 #define MIX_FC_DELAY_US 5 +static u8 ccu_mux_get_parent(struct clk_hw *hw); + static void ccu_gate_disable(struct clk_hw *hw) { struct ccu_mix *mix = hw_to_ccu_mix(hw); @@ -57,6 +59,9 @@ static unsigned long ccu_div_recalc_rate(struct clk_hw *hw, struct ccu_div_config *div = &mix->div; unsigned long val; + if (div->bypass & BIT(ccu_mux_get_parent(hw))) + return parent_rate; + val = ccu_read(&mix->common, ctrl) >> div->shift; val &= (1 << div->width) - 1; @@ -107,22 +112,27 @@ ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate, struct ccu_mix *mix = hw_to_ccu_mix(hw); unsigned int parent_num = clk_hw_get_num_parents(hw); struct ccu_div_config *div = &mix->div; - u32 div_max = 1 << div->width; unsigned long best_rate = 0; + unsigned long best_delta = ULONG_MAX; for (int i = 0; i < parent_num; i++) { struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); unsigned long parent_rate; + u32 div_max = div->bypass & BIT(i) ? 1 : 1 << div->width; if (!parent) continue; parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + continue; for (int j = 1; j <= div_max; j++) { - unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j); + unsigned long tmp = DIV_ROUND_UP_ULL(parent_rate, j); + unsigned long delta = abs_diff(tmp, rate); - if (abs(tmp - rate) < abs(best_rate - rate)) { + if (delta < best_delta) { + best_delta = delta; best_rate = tmp; if (div_val) @@ -146,7 +156,7 @@ static int ccu_mix_determine_rate(struct clk_hw *hw, &req->best_parent_hw, &req->best_parent_rate, NULL); - return 0; + return req->rate ? 0 : -EINVAL; } static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate, @@ -155,9 +165,22 @@ static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate, struct ccu_mix *mix = hw_to_ccu_mix(hw); struct ccu_common *common = &mix->common; struct ccu_div_config *div = &mix->div; - u32 current_div, target_div, mask; + u32 current_div, target_div = 0, mask; + unsigned long best_delta = ULONG_MAX; - ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div); + if (div->bypass & BIT(ccu_mux_get_parent(hw))) + return rate == parent_rate ? 0 : -EINVAL; + + /* set_rate must use the parent selected by CCF, not search other parents. */ + for (u32 i = 1; i <= BIT(div->width); i++) { + unsigned long divided = DIV_ROUND_UP_ULL(parent_rate, i); + unsigned long delta = abs_diff(divided, rate); + + if (delta < best_delta) { + best_delta = delta; + target_div = i - 1; + } + } current_div = ccu_read(common, ctrl) >> div->shift; current_div &= (1 << div->width) - 1; diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h index dbba9bf49b3bd0..ac88c282d660a9 100644 --- a/drivers/clk/spacemit/ccu_mix.h +++ b/drivers/clk/spacemit/ccu_mix.h @@ -36,6 +36,8 @@ struct ccu_mux_config { struct ccu_div_config { u8 shift; u8 width; + /* Parent indices which bypass the divider; zero means no bypass. */ + u32 bypass; }; struct ccu_mix { @@ -203,6 +205,22 @@ static struct ccu_mix _name = { \ }, \ } +#define CCU_MUX_DIV_BYPASS_FC_DEFINE(_name, _parents, _reg_ctrl, \ + _mshift, _mwidth, _mask_fc, \ + _muxshift, _muxwidth, _flags, \ + _bypass) \ +static struct ccu_mix _name = { \ + .div = { .shift = _mshift, .width = _mwidth, .bypass = _bypass }, \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .reg_fc = _reg_ctrl, \ + .mask_fc = _mask_fc, \ + CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_mux_div_ops, _flags), \ + }, \ +} + #define CCU_MUX_FC_DEFINE(_name, _parents, _reg_ctrl, _mask_fc, _muxshift, \ _muxwidth, _flags) \ static struct ccu_mix _name = { \ diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c index d4066a0ed45263..3d8624c4ce5d11 100644 --- a/drivers/clk/spacemit/ccu_pll.c +++ b/drivers/clk/spacemit/ccu_pll.c @@ -4,8 +4,12 @@ * Copyright (c) 2024-2025 Haylen Chu */ +#include #include #include +#include +#include +#include #include #include "ccu_common.h" @@ -14,19 +18,40 @@ #define PLL_TIMEOUT_US 3000 #define PLL_DELAY_US 5 +#define PLL_SWCR1_PREDIV GENMASK(13, 12) +#define PLL_SWCR1_INTERNAL BIT(29) +#define PLL_SWCR3_INT GENMASK(30, 24) +#define PLL_SWCR3_FRAC GENMASK(23, 0) + #define PLL_SWCR3_EN ((u32)BIT(31)) #define PLL_SWCR3_MASK GENMASK(30, 0) #define PLLA_SWCR2_EN ((u32)BIT(16)) #define PLLA_SWCR2_MASK GENMASK(15, 8) +#define PLLA_SWCR1_USER_MODE BIT(25) +#define PLLA_SWCR1_INT GENMASK(22, 16) +#define PLLA_SWCR1_REFSEL GENMASK(15, 14) +#define PLLA_SWCR1_FRAC GENMASK(13, 0) +#define PLLA_SWCR3_PREDIV GENMASK(21, 20) +#define PLL_FRAC_BITS 22 + +#define PLL_OUTPUT_GATES GENMASK(7, 0) +#define PLL_POWERDOWN_BYPASS BIT(23) +#define PLL_SAFE_OUTPUT_GATE (BIT(1) | BIT(4)) +#define PLL_SAFE_MPMU_GATE (BIT(16) | BIT(21)) +#define PLL_CPU_SEL GENMASK(2, 0) +#define PLL_CPU_FC BIT(12) +#define PLL_CPU_TIMEOUT_US 10000 +#define PLL_MAX_CPU_MUXES 4 + static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll, unsigned long rate) { struct ccu_pll_config *config = &pll->config; - const struct ccu_pll_rate_tbl *best_entry; + const struct ccu_pll_rate_tbl *best_entry = NULL; unsigned long best_delta = ULONG_MAX; - int i; + u32 i; for (i = 0; i < config->tbl_num; i++) { const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i]; @@ -41,39 +66,25 @@ static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *p return best_entry; } -static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll) -{ - struct ccu_pll_config *config = &pll->config; - u32 swcr1, swcr3; - int i; - - swcr1 = ccu_read(&pll->common, swcr1); - swcr3 = ccu_read(&pll->common, swcr3); - swcr3 &= PLL_SWCR3_MASK; - - for (i = 0; i < config->tbl_num; i++) { - const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i]; - - if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3) - return entry; - } - - return NULL; -} - -static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry) +static int ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry) { struct ccu_common *common = &pll->common; + int ret; - regmap_write(common->regmap, common->reg_swcr1, entry->swcr1); - ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3); + ret = regmap_write(common->regmap, common->reg_swcr1, entry->swcr1); + if (ret) + return ret; + return ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3); } static int ccu_pll_is_enabled(struct clk_hw *hw) { struct ccu_common *common = hw_to_ccu_common(hw); + u32 val; + int ret; - return ccu_read(common, swcr3) & PLL_SWCR3_EN; + ret = regmap_read(common->regmap, common->reg_swcr3, &val); + return ret ? ret : !!(val & PLL_SWCR3_EN); } static int ccu_pll_enable(struct clk_hw *hw) @@ -81,8 +92,11 @@ static int ccu_pll_enable(struct clk_hw *hw) struct ccu_pll *pll = hw_to_ccu_pll(hw); struct ccu_common *common = &pll->common; unsigned int tmp; + int ret; - ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN); + ret = ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN); + if (ret) + return ret; /* check lock status */ return regmap_read_poll_timeout_atomic(common->lock_regmap, @@ -99,6 +113,17 @@ static void ccu_pll_disable(struct clk_hw *hw) ccu_update(common, swcr3, PLL_SWCR3_EN, 0); } +static int ccu_pll_check_stopped(struct ccu_pll *pll) +{ + u32 val; + int ret; + + ret = regmap_read(pll->common.lock_regmap, pll->config.reg_lock, &val); + if (ret) + return ret; + return val & pll->config.mask_lock ? -EBUSY : 0; +} + /* * PLLs must be gated before changing rate, which is ensured by * flag CLK_SET_RATE_GATE. @@ -108,86 +133,81 @@ static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate, { struct ccu_pll *pll = hw_to_ccu_pll(hw); const struct ccu_pll_rate_tbl *entry; + int ret; - entry = ccu_pll_lookup_best_rate(pll, rate); - ccu_pll_update_param(pll, entry); + /* CLK_SET_RATE_GATE does not account for firmware-only users. */ + ret = ccu_pll_is_enabled(hw); + if (ret) + return ret < 0 ? ret : -EBUSY; + ret = ccu_pll_check_stopped(pll); + if (ret) + return ret; - return 0; + entry = ccu_pll_lookup_best_rate(pll, rate); + if (!entry) + return -EINVAL; + return ccu_pll_update_param(pll, entry); } -static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) +static unsigned long ccu_pll_calc_rate(const struct ccu_pll_rate_tbl *params, + unsigned long parent_rate) { - struct ccu_pll *pll = hw_to_ccu_pll(hw); - const struct ccu_pll_rate_tbl *entry; + u32 swcr1 = params->swcr1, swcr3 = params->swcr3, prediv; + s64 divider; + u64 rate; - entry = ccu_pll_lookup_matched_entry(pll); + /* The programmed divider is not used in internal configuration mode. */ + if (swcr1 & PLL_SWCR1_INTERNAL) + return 0; - WARN_ON_ONCE(!entry); + prediv = FIELD_GET(PLL_SWCR1_PREDIV, swcr1) + 1; + divider = (s64)FIELD_GET(PLL_SWCR3_INT, swcr3) << PLL_FRAC_BITS; + /* The 24-bit fractional code is signed, with an LSB of 2^-22. */ + divider += sign_extend32(FIELD_GET(PLL_SWCR3_FRAC, swcr3), 23); + if (divider <= 0) + return 0; - return entry ? entry->rate : 0; + /* Fvco = Fref * Npre * (Nint + Nfrac). */ + rate = (u64)parent_rate * prediv * divider; + return DIV_ROUND_CLOSEST_ULL(rate, BIT_ULL(PLL_FRAC_BITS)); } static int ccu_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct ccu_pll *pll = hw_to_ccu_pll(hw); - - req->rate = ccu_pll_lookup_best_rate(pll, req->rate)->rate; - - return 0; -} - -static int ccu_pll_init(struct clk_hw *hw) -{ - struct ccu_pll *pll = hw_to_ccu_pll(hw); - - if (ccu_pll_lookup_matched_entry(pll)) - return 0; - - ccu_pll_disable(hw); - ccu_pll_update_param(pll, &pll->config.rate_tbl[0]); - - return 0; -} - -static const struct ccu_pll_rate_tbl *ccu_plla_lookup_matched_entry(struct ccu_pll *pll) -{ - struct ccu_pll_config *config = &pll->config; const struct ccu_pll_rate_tbl *entry; - u32 i, swcr1, swcr2, swcr3; - - swcr1 = ccu_read(&pll->common, swcr1); - swcr2 = ccu_read(&pll->common, swcr2); - swcr2 &= PLLA_SWCR2_MASK; - swcr3 = ccu_read(&pll->common, swcr3); - for (i = 0; i < config->tbl_num; i++) { - entry = &config->rate_tbl[i]; - - if (swcr1 == entry->swcr1 && - swcr2 == entry->swcr2 && - swcr3 == entry->swcr3) - return entry; - } + entry = ccu_pll_lookup_best_rate(pll, req->rate); + if (!entry) + return -EINVAL; + req->rate = entry->rate; - return NULL; + return 0; } -static void ccu_plla_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry) +static int ccu_plla_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry) { struct ccu_common *common = &pll->common; - - regmap_write(common->regmap, common->reg_swcr1, entry->swcr1); - regmap_write(common->regmap, common->reg_swcr3, entry->swcr3); - ccu_update(common, swcr2, PLLA_SWCR2_MASK, entry->swcr2); + int ret; + + ret = regmap_write(common->regmap, common->reg_swcr1, entry->swcr1); + if (ret) + return ret; + ret = regmap_write(common->regmap, common->reg_swcr3, entry->swcr3); + if (ret) + return ret; + return ccu_update(common, swcr2, PLLA_SWCR2_MASK, entry->swcr2); } static int ccu_plla_is_enabled(struct clk_hw *hw) { struct ccu_common *common = hw_to_ccu_common(hw); + u32 val; + int ret; - return ccu_read(common, swcr2) & PLLA_SWCR2_EN; + ret = regmap_read(common->regmap, common->reg_swcr2, &val); + return ret ? ret : !!(val & PLLA_SWCR2_EN); } static int ccu_plla_enable(struct clk_hw *hw) @@ -195,8 +215,11 @@ static int ccu_plla_enable(struct clk_hw *hw) struct ccu_pll *pll = hw_to_ccu_pll(hw); struct ccu_common *common = &pll->common; unsigned int tmp; + int ret; - ccu_update(common, swcr2, PLLA_SWCR2_EN, PLLA_SWCR2_EN); + ret = ccu_update(common, swcr2, PLLA_SWCR2_EN, PLLA_SWCR2_EN); + if (ret) + return ret; /* check lock status */ return regmap_read_poll_timeout_atomic(common->lock_regmap, @@ -222,39 +245,365 @@ static int ccu_plla_set_rate(struct clk_hw *hw, unsigned long rate, { struct ccu_pll *pll = hw_to_ccu_pll(hw); const struct ccu_pll_rate_tbl *entry; + int ret; + + ret = ccu_plla_is_enabled(hw); + if (ret) + return ret < 0 ? ret : -EBUSY; + ret = ccu_pll_check_stopped(pll); + if (ret) + return ret; entry = ccu_pll_lookup_best_rate(pll, rate); - ccu_plla_update_param(pll, entry); + if (!entry) + return -EINVAL; + return ccu_plla_update_param(pll, entry); +} - return 0; +static unsigned long ccu_plla_calc_rate(const struct ccu_pll_rate_tbl *params, + unsigned long parent_rate) +{ + u32 swcr1 = params->swcr1, swcr2 = params->swcr2; + u32 swcr3 = params->swcr3, prediv, frac; + u64 divider, rate; + + /* Decode the software-controlled mode described by the PLL calculator. */ + if (!(swcr1 & PLLA_SWCR1_USER_MODE) || + (swcr1 & PLLA_SWCR1_REFSEL)) + return 0; + + prediv = FIELD_GET(PLLA_SWCR3_PREDIV, swcr3) + 1; + frac = FIELD_GET(PLLA_SWCR1_FRAC, swcr1) << 8; + frac |= FIELD_GET(PLLA_SWCR2_MASK, swcr2); + divider = (u64)FIELD_GET(PLLA_SWCR1_INT, swcr1) << PLL_FRAC_BITS; + divider += frac; + + /* Fvco = Fref * Npre * (Nint + Nfrac), with an unsigned fraction. */ + rate = (u64)parent_rate * prediv * divider; + return DIV_ROUND_CLOSEST_ULL(rate, BIT_ULL(PLL_FRAC_BITS)); +} + +static int ccu_pll_get_params(struct ccu_pll *pll, + struct ccu_pll_rate_tbl *params, bool plla) +{ + struct ccu_common *common = &pll->common; + int ret; + + ret = regmap_read(common->regmap, common->reg_swcr1, ¶ms->swcr1); + if (ret) + return ret; + params->swcr2 = 0; + if (plla) { + ret = regmap_read(common->regmap, common->reg_swcr2, ¶ms->swcr2); + if (ret) + return ret; + } + return regmap_read(common->regmap, common->reg_swcr3, ¶ms->swcr3); +} + +static bool ccu_pll_params_equal(const struct ccu_pll_rate_tbl *a, + const struct ccu_pll_rate_tbl *b, bool plla) +{ + if (a->swcr1 != b->swcr1) + return false; + if (plla) + return a->swcr3 == b->swcr3 && + !((a->swcr2 ^ b->swcr2) & PLLA_SWCR2_MASK); + return !((a->swcr3 ^ b->swcr3) & PLL_SWCR3_MASK); +} + +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_pll_rate_tbl params; + + if (ccu_pll_get_params(hw_to_ccu_pll(hw), ¶ms, false)) + return 0; + return ccu_pll_calc_rate(¶ms, parent_rate); } static unsigned long ccu_plla_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { - struct ccu_pll *pll = hw_to_ccu_pll(hw); - const struct ccu_pll_rate_tbl *entry; + struct ccu_pll_rate_tbl params; - entry = ccu_plla_lookup_matched_entry(pll); + if (ccu_pll_get_params(hw_to_ccu_pll(hw), ¶ms, true)) + return 0; + return ccu_plla_calc_rate(¶ms, parent_rate); +} - WARN_ON_ONCE(!entry); +struct ccu_pll_park { + struct regmap *apmu; + u32 saved[PLL_MAX_CPU_MUXES]; + u32 selected[PLL_MAX_CPU_MUXES]; + unsigned long parked; +}; - return entry ? entry->rate : 0; +static int ccu_pll_select_cpu(struct regmap *regmap, u32 reg, u32 sel) +{ + u32 val; + int ret; + + ret = regmap_update_bits(regmap, reg, PLL_CPU_SEL, sel); + if (ret) + return ret; + ret = regmap_update_bits(regmap, reg, PLL_CPU_FC, PLL_CPU_FC); + if (ret) + return ret; + ret = regmap_read_poll_timeout_atomic(regmap, reg, val, + !(val & PLL_CPU_FC), PLL_DELAY_US, + PLL_CPU_TIMEOUT_US); + if (ret) + return ret; + return (val & PLL_CPU_SEL) == sel ? 0 : -EIO; } -static int ccu_plla_init(struct clk_hw *hw) +static void ccu_pll_unpark(const struct ccu_pll_sync *sync, + struct ccu_pll_park *park) { - struct ccu_pll *pll = hw_to_ccu_pll(hw); + int i; + + /* Restore sharing-capable secondary clusters last. */ + for (i = sync->num_muxes - 1; i >= 0; i--) { + if (!(park->parked & BIT(i))) + continue; + if (!ccu_pll_select_cpu(park->apmu, sync->muxes[i].reg, + park->saved[i] & PLL_CPU_SEL)) + park->parked &= ~BIT(i); + } + + /* + * The fallback gates remain critical: after an FC timeout the selector + * register alone cannot prove that the CPU has left the temporary path. + */ +} + +static int ccu_pll_park_cpus(const struct ccu_pll_sync *sync, + struct ccu_pll_park *park, bool plla, + unsigned long rate, unsigned long parent_rate) +{ + struct ccu_pll *pll1 = sync->safe_pll; + struct ccu_common *safe = &pll1->common; + struct ccu_pll_rate_tbl params; + struct device_node *np; + unsigned long safe_rate; + u32 val; + u32 i; + int ret; + bool needed = false; + + if (!sync->num_muxes) + return 0; + if (sync->num_muxes > PLL_MAX_CPU_MUXES) + return -EINVAL; + /* Like CCU probe, create the regmap before its clocks are registered. */ + np = of_find_compatible_node(NULL, NULL, sync->apmu_compatible); + if (!np) + return -ENODEV; + park->apmu = device_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(park->apmu)) + return PTR_ERR(park->apmu); + + for (i = 0; i < sync->num_muxes; i++) { + const struct ccu_pll_cpu_mux *mux = &sync->muxes[i]; + + ret = regmap_read(park->apmu, mux->reg, &park->saved[i]); + if (ret) + return ret; + if (park->saved[i] & PLL_CPU_FC) + return -EBUSY; + /* K3 selector 4 is unmodeled; do not infer a live parent. */ + if (plla && (park->saved[i] & PLL_CPU_SEL) == 4) + return -EINVAL; + needed |= (park->saved[i] & mux->mask) == mux->value; + } + if (!needed) + return 0; + + /* PLL1 is never repaired here, nor used as a fallback if unrecognized. */ + if (!safe->regmap || !safe->lock_regmap) + return -ENODEV; + if (!pll1->config.tbl_num) + return -EINVAL; + ret = ccu_pll_get_params(pll1, ¶ms, plla); + if (ret) + return ret; + if (!ccu_pll_params_equal(¶ms, &pll1->config.rate_tbl[0], plla)) + return -EINVAL; + ret = regmap_read(safe->lock_regmap, pll1->config.reg_lock, &val); + if (ret) + return ret; + if (!(val & pll1->config.mask_lock)) + return -EBUSY; + + safe_rate = plla ? ccu_plla_calc_rate(¶ms, parent_rate) : + ccu_pll_calc_rate(¶ms, parent_rate); + for (i = 0; i < sync->num_muxes; i++) { + u32 old = park->saved[i], sel = old & PLL_CPU_SEL; + u32 div = ((old >> 3) & 7) + 1; + unsigned long cpu_rate, fast_rate, slow_rate; + + if ((old & sync->muxes[i].mask) != sync->muxes[i].value) + continue; + if (plla) { + cpu_rate = rate; + fast_rate = safe_rate / 2; + } else { + cpu_rate = rate / (sel == 5 ? 3 : (old & BIT(13) ? 1 : 2)); + cpu_rate /= div; + fast_rate = safe_rate / 2 / div; + } + slow_rate = safe_rate / 5 / div; + /* Never increase a CPU's rate without a corresponding voltage vote. */ + if (fast_rate <= cpu_rate) + park->selected[i] = sync->safe_sel; + else if (slow_rate <= cpu_rate) + park->selected[i] = sync->slow_sel; + else + return -ERANGE; + } + /* Both candidate parents are derived from the always-on PLL1. */ + ret = ccu_update(safe, swcr2, PLL_SAFE_OUTPUT_GATE, PLL_SAFE_OUTPUT_GATE); + if (ret) + return ret; + ret = regmap_update_bits(safe->lock_regmap, sync->reg_safe_gate, + PLL_SAFE_MPMU_GATE, PLL_SAFE_MPMU_GATE); + if (ret) + return ret; + + for (i = 0; i < sync->num_muxes; i++) { + const struct ccu_pll_cpu_mux *mux = &sync->muxes[i]; + + if ((park->saved[i] & mux->mask) != mux->value) + continue; + park->parked |= BIT(i); + ret = ccu_pll_select_cpu(park->apmu, mux->reg, park->selected[i]); + if (ret) + return ret; + } + return 0; +} - if (ccu_plla_lookup_matched_entry(pll)) +static int ccu_pll_stop(struct ccu_pll *pll, bool plla) +{ + struct ccu_common *common = &pll->common; + u32 val; + int ret; + + ret = plla ? ccu_update(common, swcr2, PLLA_SWCR2_EN, 0) : + ccu_update(common, swcr3, PLL_SWCR3_EN, 0); + if (ret) + return ret; + /* Hardware or firmware may override the software enable bit. */ + return regmap_read_poll_timeout_atomic(common->lock_regmap, + pll->config.reg_lock, val, + !(val & pll->config.mask_lock), + PLL_DELAY_US, PLL_TIMEOUT_US); +} + +static int ccu_pll_sync_init(struct clk_hw *hw, bool plla) +{ + struct ccu_pll *pll = hw_to_ccu_pll(hw); + const struct ccu_pll_sync *sync = pll->config.sync; + const struct ccu_pll_rate_tbl *entry = NULL; + unsigned long (*calc)(const struct ccu_pll_rate_tbl *params, + unsigned long parent_rate); + int (*update)(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *params); + int (*enable)(struct clk_hw *hw); + struct ccu_pll_rate_tbl old; + struct ccu_pll_park park = {}; + struct clk_hw *parent; + unsigned long parent_rate, rate; + u32 outputs, lock, i; + bool enabled; + int ret; + + /* Synchronization is opt-in; in particular PLL1 has no sync descriptor. */ + if (!sync) + return 0; + parent = clk_hw_get_parent_by_index(hw, 0); + if (!parent) + return 0; + parent_rate = clk_hw_get_rate(parent); + calc = plla ? ccu_plla_calc_rate : ccu_pll_calc_rate; + update = plla ? ccu_plla_update_param : ccu_pll_update_param; + enable = plla ? ccu_plla_enable : ccu_pll_enable; + ret = ccu_pll_get_params(pll, &old, plla); + if (ret) + goto warn; + rate = calc(&old, parent_rate); + if (!rate) return 0; - ccu_plla_disable(hw); - ccu_plla_update_param(pll, &pll->config.rate_tbl[0]); + /* Compare encoded rates, including fractional-divider quantization. */ + for (i = 0; i < pll->config.tbl_num; i++) { + if (rate == calc(&pll->config.rate_tbl[i], parent_rate)) { + entry = &pll->config.rate_tbl[i]; + break; + } + } + if (!entry || ccu_pll_params_equal(&old, entry, plla)) + return 0; + if ((plla ? old.swcr3 : old.swcr1) & PLL_POWERDOWN_BYPASS) + return 0; + ret = regmap_read(pll->common.regmap, pll->common.reg_swcr2, &outputs); + if (ret) + goto warn; + /* Do not interrupt peripheral users, including unregistered consumers. */ + if (outputs & PLL_OUTPUT_GATES & ~sync->cpu_outputs) + return 0; + ret = regmap_read(pll->common.lock_regmap, pll->config.reg_lock, &lock); + if (ret) + goto warn; + enabled = plla ? old.swcr2 & PLLA_SWCR2_EN : old.swcr3 & PLL_SWCR3_EN; + if (enabled != !!(lock & pll->config.mask_lock)) + return 0; + /* + * .init runs under the CCF prepare lock, before this PLL is linked to + * its children. Restore the hardware muxes before CCF adopts them. + */ + ret = ccu_pll_park_cpus(sync, &park, plla, rate, parent_rate); + if (ret) + goto unpark; + ret = ccu_pll_stop(pll, plla); + if (ret) + goto restart; + ret = update(pll, entry); + if (!ret && enabled) + ret = enable(hw); + if (!ret) + goto unpark; + + /* Restore the old parameters before considering the original parents. */ + if (ccu_pll_stop(pll, plla) || update(pll, &old)) + goto warn; +restart: + if (enabled && enable(hw)) + goto warn; +unpark: + ccu_pll_unpark(sync, &park); + if (park.parked) + ret = -ETIMEDOUT; +warn: + if (ret) + pr_warn("%s: PLL synchronization failed: %d; retaining safe clocks\n", + clk_hw_get_name(hw), ret); + /* Failed synchronization must not unwind clocks needed to keep booting. */ return 0; } +static int ccu_pll_init(struct clk_hw *hw) +{ + return ccu_pll_sync_init(hw, false); +} + +static int ccu_plla_init(struct clk_hw *hw) +{ + return ccu_pll_sync_init(hw, true); +} + const struct clk_ops spacemit_ccu_pll_ops = { .init = ccu_pll_init, .enable = ccu_pll_enable, diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h index e41db5c97c1a81..f77afafddba55c 100644 --- a/drivers/clk/spacemit/ccu_pll.h +++ b/drivers/clk/spacemit/ccu_pll.h @@ -46,11 +46,31 @@ struct ccu_pll_rate_tbl { struct ccu_pll_config { const struct ccu_pll_rate_tbl *rate_tbl; + const struct ccu_pll_sync *sync; u32 tbl_num; u32 reg_lock; u32 mask_lock; }; +/* A CPU mux selection which consumes this PLL. */ +struct ccu_pll_cpu_mux { + u32 reg; + u32 mask; + u32 value; +}; + +/* Only CPU-exclusive outputs may remain gated on during synchronization. */ +struct ccu_pll_sync { + struct ccu_pll *safe_pll; + const char *apmu_compatible; + const struct ccu_pll_cpu_mux *muxes; + u32 num_muxes; + u32 cpu_outputs; + u32 safe_sel; + u32 slow_sel; + u32 reg_safe_gate; +}; + #define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \ { \ .rate = _rate, \ @@ -71,12 +91,13 @@ struct ccu_pll { struct ccu_pll_config config; }; -#define CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock) \ +#define CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock, _sync) \ { \ .rate_tbl = _table, \ .tbl_num = ARRAY_SIZE(_table), \ .reg_lock = (_reg_lock), \ .mask_lock = (_mask_lock), \ + .sync = _sync, \ } #define CCU_PLL_COMMON_HWINIT(_name, _ops, _flags) \ @@ -89,9 +110,9 @@ struct ccu_pll { }) #define CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \ - _reg_lock, _mask_lock, _ops, _flags) \ + _reg_lock, _mask_lock, _ops, _flags, _sync) \ static struct ccu_pll _name = { \ - .config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock), \ + .config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock, _sync), \ .common = { \ .reg_swcr1 = _reg_swcr1, \ .reg_swcr2 = _reg_swcr2, \ @@ -103,12 +124,22 @@ static struct ccu_pll _name = { \ #define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3, _reg_lock, \ _mask_lock, _flags) \ CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, 0, _reg_swcr3, \ - _reg_lock, _mask_lock, &spacemit_ccu_pll_ops, _flags) + _reg_lock, _mask_lock, &spacemit_ccu_pll_ops, _flags, NULL) + +#define CCU_PLL_SYNC_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, \ + _reg_swcr3, _reg_lock, _mask_lock, _flags, _sync) \ + CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \ + _reg_lock, _mask_lock, &spacemit_ccu_pll_ops, _flags, _sync) #define CCU_PLLA_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \ - _reg_lock, _mask_lock, _flags) \ + _reg_lock, _mask_lock, _flags) \ + CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \ + _reg_lock, _mask_lock, &spacemit_ccu_plla_ops, _flags, NULL) + +#define CCU_PLLA_SYNC_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, \ + _reg_swcr3, _reg_lock, _mask_lock, _flags, _sync) \ CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \ - _reg_lock, _mask_lock, &spacemit_ccu_plla_ops, _flags) + _reg_lock, _mask_lock, &spacemit_ccu_plla_ops, _flags, _sync) static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw) {