clk: tegra20/30: Add custom EMC clock implementation
Subsystems (3)
PATCH 1/1 clk: tegra20/30: Add custom EMC clock implementation
A proper External Memory Controller clock rounding and parent selection functionality is required by the EMC drivers. It is not available using the generic clock implementation, hence add a custom one. The clock rate rounding shall be done by the EMC drivers because they have information about available memory timings, so the drivers will have to register a callback that will round the requested rate. EMC clock users won't be able to request EMC clock by getting -EPROBE_DEFER until EMC driver is probed and the callback is set up. The functionality is somewhat similar to the clk-emc.c which serves Tegra124+ SoC's, the later HW generations support more parent clock sources and the HW configuration and integration with the EMC drivers differs a tad from the older gens, hence it's not really worth to try to squash everything into a single source file.
--- drivers/clk/tegra/Makefile | 2 + drivers/clk/tegra/clk-tegra20-emc.c | 274 ++++++++++++++++++++++++++++ drivers/clk/tegra/clk-tegra20.c | 51 ++---- drivers/clk/tegra/clk-tegra30.c | 35 ++-- drivers/clk/tegra/clk.h | 6 + include/linux/clk/tegra.h | 3 + 6 files changed, 321 insertions(+), 50 deletions(-) create mode 100644 drivers/clk/tegra/clk-tegra20-emc.c
drivers/clk/tegra/Makefile +2 -0
@@ -17,7 +17,9 @@ obj-y += clk-tegra-fixed.o obj-y += clk-tegra-super-gen4.o obj-$(CONFIG_TEGRA_CLK_EMC) += clk-emc.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20-emc.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra30.o +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra20-emc.o obj-$(CONFIG_ARCH_TEGRA_114_SOC) += clk-tegra114.o obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124.o obj-$(CONFIG_TEGRA_CLK_DFLL) += clk-tegra124-dfll-fcpu.o
drivers/clk/tegra/clk-tegra20-emc.c new file +274 -0
274 lines not on this page · raw message
drivers/clk/tegra/clk-tegra20.c +12 -39
81 lines not on this page · raw message
drivers/clk/tegra/clk-tegra30.c +24 -11
@@ -162,7 +162,6 @@ static unsigned long input_freq;static DEFINE_SPINLOCK (cml_lock);static DEFINE_SPINLOCK (pll_d_lock); -static DEFINE_SPINLOCK (emc_lock);#define TEGRA_INIT_DATA_MUX (_name, _parents, _offset, \ _clk_num, _gate_flags, _clk_id) \
@@ -819,7 +818,7 @@ static struct tegra_clk tegra30_clks[tegra_clk_max] __initdata = { [tegra_clk_pll_a] = { .dt_id = TEGRA30_CLK_PLL_A, .present = true }, [tegra_clk_pll_a_out0] = { .dt_id = TEGRA30_CLK_PLL_A_OUT0, .present = true }, [tegra_clk_cec] = { .dt_id = TEGRA30_CLK_CEC, .present = true }, - [tegra_clk_emc] = { .dt_id = TEGRA30_CLK_EMC, .present = true }, + [tegra_clk_emc] = { .dt_id = TEGRA30_CLK_EMC, .present = false }, };static const char *pll_e_parents[] = {"pll_ref" ,"pll_p" };
@@ -1006,7 +1005,6 @@ static void __init tegra30_super_clk_init(void)static const char *mux_pllacp_clkm[] = {"pll_a_out0" ,"unused" ,"pll_p" ,"clk_m" };static const char *mux_pllpcm_clkm[] = {"pll_p" ,"pll_c" ,"pll_m" ,"clk_m" }; -static const char *mux_pllmcp_clkm[] = {"pll_m" ,"pll_c" ,"pll_p" ,"clk_m" };static const char *spdif_out_parents[] = {"pll_a_out0" ,"spdif_2x" ,"pll_p" ,"clk_m" };static const char *mux_pllmcpa[] = {"pll_m" ,"pll_c" ,"pll_p" ,"pll_a_out0" };
@@ -1055,14 +1053,12 @@ static void __init tegra30_periph_clk_init(void) clks[TEGRA30_CLK_AFI ] = clk;/* emc */ - clk =clk_register_mux (NULL ,"emc_mux" , mux_pllmcp_clkm, -ARRAY_SIZE (mux_pllmcp_clkm), -CLK_SET_RATE_NO_REPARENT , - clk_base +CLK_SOURCE_EMC , -30 ,2 ,0 , &emc_lock); + clk =tegra30_clk_register_emc (clk_base +CLK_SOURCE_EMC ); + + clks[TEGRA30_CLK_EMC ] = clk; - clk =tegra_clk_register_mc ("mc" ,"emc_mux" , clk_base +CLK_SOURCE_EMC , - &emc_lock); + clk =tegra_clk_register_mc ("mc" ,"emc" , clk_base +CLK_SOURCE_EMC , +NULL ); clks[TEGRA30_CLK_MC ] = clk;/* cml0 */
@@ -1313,6 +1309,23 @@ static struct tegra_audio_clk_info tegra30_audio_plls[] = { { "pll_a", &pll_a_params, tegra_clk_pll_a, "pll_p_out1" }, }; +static struct clk *tegra30_clk_src_onecell_get (struct of_phandle_args *clkspec, +void *data) +{ +struct clk *clk; + + clk =of_clk_src_onecell_get (clkspec, data); +if (IS_ERR (clk)) +return clk; + +if (clkspec->args[0 ] ==TEGRA30_CLK_EMC ) { +if (!tegra30_clk_emc_scaling_available ()) +return ERR_PTR (-EPROBE_DEFER ); + } + +return clk; +} +static void __init tegra30_clock_init (struct device_node *np) {struct device_node *node;
@@ -1356,7 +1369,7 @@ static void __init tegra30_clock_init(struct device_node *np)tegra_init_dup_clks (tegra_clk_duplicates, clks,TEGRA30_CLK_CLK_MAX ); -tegra_add_of_provider (np, of_clk_src_onecell_get); +tegra_add_of_provider (np, tegra30_clk_src_onecell_get);tegra_register_devclks (devclks,ARRAY_SIZE (devclks)); tegra_clk_apply_init_table = tegra30_clock_apply_init_table;
drivers/clk/tegra/clk.h +6 -0
@@ -849,4 +849,10 @@ int div_frac_get(unsigned long rate, unsigned parent_rate, u8 width,udelay (delay); \ } while (0) +bool tegra20_clk_emc_scaling_available(void); +struct clk *tegra20_clk_register_emc (void __iomem *ioaddr); + +bool tegra30_clk_emc_scaling_available (void ); +struct clk *tegra30_clk_register_emc (void __iomem *ioaddr); +#endif /* TEGRA_CLK_H */
include/linux/clk/tegra.h +3 -0
@@ -130,4 +130,7 @@ extern void tegra210_put_utmipll_in_iddq(void);extern void tegra210_put_utmipll_out_iddq (void );extern int tegra210_clk_handle_mbist_war (unsigned int id); +void tegra20_clk_set_emc_round_callback (void *round_cb,void *arg_cb); +void tegra30_clk_set_emc_round_callback (void *round_cb,void *arg_cb); +#endif /* __LINUX_CLK_TEGRA_H_ */
Signature · 1 line
2.21.0
Quoted 13 lines · Dmitry Osipenko
A proper External Memory Controller clock rounding and parent selection functionality is required by the EMC drivers. It is not available using the generic clock implementation, hence add a custom one. The clock rate rounding shall be done by the EMC drivers because they have information about available memory timings, so the drivers will have to register a callback that will round the requested rate. EMC clock users won't be able to request EMC clock by getting -EPROBE_DEFER until EMC driver is probed and the callback is set up. The functionality is somewhat similar to the
clk-emc.cwhich serves Tegra124+ SoC's, the later HW generations support more parent clock sources and the HW configuration and integration with the EMC drivers differs a tad from the older gens, hence it's not really worth to try to squash everything into a single source file.
..
drivers/clk/tegra/clk-tegra20-emc.c line 100
Quoted 11 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +11
-0+ val =readl_relaxed (emc->ioaddr); + val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ; + val |= div; + + parent = val >>CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ; + +if (parent ==EMC_SRC_PLL_M && div ==0 && emc->want_low_jitter) + val |=USE_PLLM_UD ; +else + val &= ~USE_PLLM_UD ; +
Note that low jitter means the divider is bypassed, so you can only use this when div == 1.
Quoted 18 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +18
-0+writel_relaxed (val, emc->ioaddr); + +return 0 ; +} + +static int emc_set_rate_and_parent (struct clk_hw *hw, +unsigned long rate, +unsigned long parent_rate, +u8 index) +{ +struct tegra_clk_emc *emc =to_tegra_clk_emc (hw); +u32 val, div; + + div =div_frac_get (rate, parent_rate,8 ,1 ,0 ); + + val =readl_relaxed (emc->ioaddr); + + val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK ;
drivers/clk/tegra/clk-tegra20-emc.c line 128
Quoted 10 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +10
-0+ val |= index <<CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ; + + val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ; + val |= div; + +if (index ==EMC_SRC_PLL_M && div ==0 && emc->want_low_jitter) + val |=USE_PLLM_UD ; +else + val &= ~USE_PLLM_UD ; +
Same here ofcourse.
Peter.
12.04.2019 10:46, Peter De Schrijver пишет:
Quoted 53 lines · Dmitry Osipenko
Quoted 13 lines
A proper External Memory Controller clock rounding and parent selection functionality is required by the EMC drivers. It is not available using the generic clock implementation, hence add a custom one. The clock rate rounding shall be done by the EMC drivers because they have information about available memory timings, so the drivers will have to register a callback that will round the requested rate. EMC clock users won't be able to request EMC clock by getting -EPROBE_DEFER until EMC driver is probed and the callback is set up. The functionality is somewhat similar to the
clk-emc.cwhich serves Tegra124+ SoC's, the later HW generations support more parent clock sources and the HW configuration and integration with the EMC drivers differs a tad from the older gens, hence it's not really worth to try to squash everything into a single source file...
drivers/clk/tegra/clk-tegra20-emc.c line 100
Quoted 11 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +11
-0+ val =readl_relaxed (emc->ioaddr); + val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ; + val |= div; + + parent = val >>CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ; + +if (parent ==EMC_SRC_PLL_M && div ==0 && emc->want_low_jitter) + val |=USE_PLLM_UD ; +else + val &= ~USE_PLLM_UD ; +Note that low jitter means the divider is bypassed, so you can only use this when div == 1.
Quoted 22 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +22
-0+writel_relaxed (val, emc->ioaddr); + +return 0 ; +} + +static int emc_set_rate_and_parent (struct clk_hw *hw, +unsigned long rate, +unsigned long parent_rate, +u8 index) +{ +struct tegra_clk_emc *emc =to_tegra_clk_emc (hw); +u32 val, div; + + div =div_frac_get (rate, parent_rate,8 ,1 ,0 ); + + val =readl_relaxed (emc->ioaddr); + + val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK ; + val |= index <<CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ; + + val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ; + val |= div;
Quoted 8 lines
drivers/clk/tegra/clk-tegra20-emc.c line 75
Quoted 6 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +6
-0+ +if (index ==EMC_SRC_PLL_M && div ==0 && emc->want_low_jitter) + val |=USE_PLLM_UD ; +else + val &= ~USE_PLLM_UD ; +Same here ofcourse.
Please note that hw_div = div + 1.0, hence writing div=0 means that HW divider is disabled and thus can be bypassed to get a low jitter clock. Also please note that div_frac_get() returns the "hw_div" value. Tegra30 doesn't work well if low jitter isn't enabled for the high clock rate (hangs after couple seconds) and Tegra20 is the opposite. Everything should be fine and it is working well, I don't see any problem.
12.04.2019 13:09, Dmitry Osipenko пишет:
Quoted 63 lines
12.04.2019 10:46, Peter De Schrijver пишет:
Quoted 53 lines · Dmitry Osipenko
Quoted 13 lines
A proper External Memory Controller clock rounding and parent selection functionality is required by the EMC drivers. It is not available using the generic clock implementation, hence add a custom one. The clock rate rounding shall be done by the EMC drivers because they have information about available memory timings, so the drivers will have to register a callback that will round the requested rate. EMC clock users won't be able to request EMC clock by getting -EPROBE_DEFER until EMC driver is probed and the callback is set up. The functionality is somewhat similar to the
clk-emc.cwhich serves Tegra124+ SoC's, the later HW generations support more parent clock sources and the HW configuration and integration with the EMC drivers differs a tad from the older gens, hence it's not really worth to try to squash everything into a single source file...
drivers/clk/tegra/clk-tegra20-emc.c line 100
Quoted 11 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +11
-0+ val =readl_relaxed (emc->ioaddr); + val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ; + val |= div; + + parent = val >>CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ; + +if (parent ==EMC_SRC_PLL_M && div ==0 && emc->want_low_jitter) + val |=USE_PLLM_UD ; +else + val &= ~USE_PLLM_UD ; +Note that low jitter means the divider is bypassed, so you can only use this when div == 1.
Quoted 22 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +22
-0+writel_relaxed (val, emc->ioaddr); + +return 0 ; +} + +static int emc_set_rate_and_parent (struct clk_hw *hw, +unsigned long rate, +unsigned long parent_rate, +u8 index) +{ +struct tegra_clk_emc *emc =to_tegra_clk_emc (hw); +u32 val, div; + + div =div_frac_get (rate, parent_rate,8 ,1 ,0 ); + + val =readl_relaxed (emc->ioaddr); + + val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK ; + val |= index <<CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT ; + + val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK ; + val |= div;Quoted 8 lines
drivers/clk/tegra/clk-tegra20-emc.c line 75
Quoted 6 lines
drivers/clk/tegra/clk-tegra20-emc.c new file +6
-0+ +if (index ==EMC_SRC_PLL_M && div ==0 && emc->want_low_jitter) + val |=USE_PLLM_UD ; +else + val &= ~USE_PLLM_UD ; +Same here ofcourse.
Quoted 2 lines
Please note that
hw_div= div + 1.0, hence writing div=0 means that HW divider is disabled and thus can be bypassed to get a low jitter clock. Also please note thatdiv_frac_get()returns the "hw_div" value. Tegra30 doesn't work well if low jitter isn't enabled for the high clock rate (hangs after couple seconds) and Tegra20 is the opposite. Everything should be fine and it is working well, I don't see any problem.
Oh, div_frac_get() returns the "div" value of course.