diff options
Diffstat (limited to 'drivers/clk/tegra')
-rw-r--r-- | drivers/clk/tegra/Kconfig | 4 | ||||
-rw-r--r-- | drivers/clk/tegra/Makefile | 4 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-pll.c | 12 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-tegra-super-cclk.c | 212 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-tegra124-emc.c (renamed from drivers/clk/tegra/clk-emc.c) | 0 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-tegra20.c | 7 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-tegra210-emc.c | 369 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-tegra210.c | 94 | ||||
-rw-r--r-- | drivers/clk/tegra/clk-tegra30.c | 6 | ||||
-rw-r--r-- | drivers/clk/tegra/clk.h | 24 |
10 files changed, 700 insertions, 32 deletions
diff --git a/drivers/clk/tegra/Kconfig b/drivers/clk/tegra/Kconfig index 4d99a8770485..deaa4605824c 100644 --- a/drivers/clk/tegra/Kconfig +++ b/drivers/clk/tegra/Kconfig @@ -1,8 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -config TEGRA_CLK_EMC - def_bool y - depends on TEGRA124_EMC - config CLK_TEGRA_BPMP def_bool y depends on TEGRA_BPMP diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile index 1f7c30f87ece..eec2313fd37e 100644 --- a/drivers/clk/tegra/Makefile +++ b/drivers/clk/tegra/Makefile @@ -13,8 +13,8 @@ obj-y += clk-super.o obj-y += clk-tegra-audio.o obj-y += clk-tegra-periph.o obj-y += clk-tegra-fixed.o +obj-y += clk-tegra-super-cclk.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 @@ -22,8 +22,10 @@ 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 +obj-$(CONFIG_TEGRA124_EMC) += clk-tegra124-emc.o obj-$(CONFIG_ARCH_TEGRA_132_SOC) += clk-tegra124.o obj-y += cvb.o obj-$(CONFIG_ARCH_TEGRA_210_SOC) += clk-tegra210.o +obj-$(CONFIG_ARCH_TEGRA_210_SOC) += clk-tegra210-emc.o obj-$(CONFIG_CLK_TEGRA_BPMP) += clk-bpmp.o obj-y += clk-utils.o diff --git a/drivers/clk/tegra/clk-pll.c b/drivers/clk/tegra/clk-pll.c index 531c2b3d814e..0b212cf2e794 100644 --- a/drivers/clk/tegra/clk-pll.c +++ b/drivers/clk/tegra/clk-pll.c @@ -744,13 +744,19 @@ static int _program_pll(struct clk_hw *hw, struct tegra_clk_pll_freq_table *cfg, state = clk_pll_is_enabled(hw); + if (state && pll->params->pre_rate_change) { + ret = pll->params->pre_rate_change(); + if (WARN_ON(ret)) + return ret; + } + _get_pll_mnp(pll, &old_cfg); if (state && pll->params->defaults_set && pll->params->dyn_ramp && (cfg->m == old_cfg.m) && (cfg->p == old_cfg.p)) { ret = pll->params->dyn_ramp(pll, cfg); if (!ret) - return 0; + goto done; } if (state) { @@ -772,6 +778,10 @@ static int _program_pll(struct clk_hw *hw, struct tegra_clk_pll_freq_table *cfg, pll_clk_start_ss(pll); } +done: + if (state && pll->params->post_rate_change) + pll->params->post_rate_change(); + return ret; } diff --git a/drivers/clk/tegra/clk-tegra-super-cclk.c b/drivers/clk/tegra/clk-tegra-super-cclk.c new file mode 100644 index 000000000000..a03119c30456 --- /dev/null +++ b/drivers/clk/tegra/clk-tegra-super-cclk.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Based on clk-super.c + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * Based on older tegra20-cpufreq driver by Colin Cross <ccross@google.com> + * Copyright (C) 2010 Google, Inc. + * + * Author: Dmitry Osipenko <digetx@gmail.com> + * Copyright (C) 2019 GRATE-DRIVER project + */ + +#include <linux/bits.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include "clk.h" + +#define PLLP_INDEX 4 +#define PLLX_INDEX 8 + +#define SUPER_CDIV_ENB BIT(31) + +static struct tegra_clk_super_mux *cclk_super; +static bool cclk_on_pllx; + +static u8 cclk_super_get_parent(struct clk_hw *hw) +{ + return tegra_clk_super_ops.get_parent(hw); +} + +static int cclk_super_set_parent(struct clk_hw *hw, u8 index) +{ + return tegra_clk_super_ops.set_parent(hw, index); +} + +static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return tegra_clk_super_ops.set_rate(hw, rate, parent_rate); +} + +static unsigned long cclk_super_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + if (cclk_super_get_parent(hw) == PLLX_INDEX) + return parent_rate; + + return tegra_clk_super_ops.recalc_rate(hw, parent_rate); +} + +static int cclk_super_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *pllp_hw = clk_hw_get_parent_by_index(hw, PLLP_INDEX); + struct clk_hw *pllx_hw = clk_hw_get_parent_by_index(hw, PLLX_INDEX); + struct tegra_clk_super_mux *super = to_clk_super_mux(hw); + unsigned long pllp_rate; + long rate = req->rate; + + if (WARN_ON_ONCE(!pllp_hw || !pllx_hw)) + return -EINVAL; + + /* + * Switch parent to PLLP for all CCLK rates that are suitable for PLLP. + * PLLX will be disabled in this case, saving some power. + */ + pllp_rate = clk_hw_get_rate(pllp_hw); + + if (rate <= pllp_rate) { + if (super->flags & TEGRA20_SUPER_CLK) + rate = pllp_rate; + else + rate = tegra_clk_super_ops.round_rate(hw, rate, + &pllp_rate); + + req->best_parent_rate = pllp_rate; + req->best_parent_hw = pllp_hw; + req->rate = rate; + } else { + rate = clk_hw_round_rate(pllx_hw, rate); + req->best_parent_rate = rate; + req->best_parent_hw = pllx_hw; + req->rate = rate; + } + + if (WARN_ON_ONCE(rate <= 0)) + return -EINVAL; + + return 0; +} + +static const struct clk_ops tegra_cclk_super_ops = { + .get_parent = cclk_super_get_parent, + .set_parent = cclk_super_set_parent, + .set_rate = cclk_super_set_rate, + .recalc_rate = cclk_super_recalc_rate, + .determine_rate = cclk_super_determine_rate, +}; + +static const struct clk_ops tegra_cclk_super_mux_ops = { + .get_parent = cclk_super_get_parent, + .set_parent = cclk_super_set_parent, + .determine_rate = cclk_super_determine_rate, +}; + +struct clk *tegra_clk_register_super_cclk(const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, void __iomem *reg, u8 clk_super_flags, + spinlock_t *lock) +{ + struct tegra_clk_super_mux *super; + struct clk *clk; + struct clk_init_data init; + u32 val; + + if (WARN_ON(cclk_super)) + return ERR_PTR(-EBUSY); + + super = kzalloc(sizeof(*super), GFP_KERNEL); + if (!super) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.flags = flags; + init.parent_names = parent_names; + init.num_parents = num_parents; + + super->reg = reg; + super->lock = lock; + super->width = 4; + super->flags = clk_super_flags; + super->hw.init = &init; + + if (super->flags & TEGRA20_SUPER_CLK) { + init.ops = &tegra_cclk_super_mux_ops; + } else { + init.ops = &tegra_cclk_super_ops; + + super->frac_div.reg = reg + 4; + super->frac_div.shift = 16; + super->frac_div.width = 8; + super->frac_div.frac_width = 1; + super->frac_div.lock = lock; + super->div_ops = &tegra_clk_frac_div_ops; + } + + /* + * Tegra30+ has the following CPUG clock topology: + * + * +---+ +-------+ +-+ +-+ +-+ + * PLLP+->+ +->+DIVIDER+->+0| +-------->+0| ------------->+0| + * | | +-------+ | | | +---+ | | | | | + * PLLC+->+MUX| | +->+ | S | | +->+ | +->+CPU + * ... | | | | | | K | | | | +-------+ | | + * PLLX+->+-->+------------>+1| +->+ I +->+1| +->+ DIV2 +->+1| + * +---+ +++ | P | +++ |SKIPPER| +++ + * ^ | P | ^ +-------+ ^ + * | | E | | | + * PLLX_SEL+--+ | R | | OVERHEAT+--+ + * +---+ | + * | + * SUPER_CDIV_ENB+--+ + * + * Tegra20 is similar, but simpler. It doesn't have the divider and + * thermal DIV2 skipper. + * + * At least for now we're not going to use clock-skipper, hence let's + * ensure that it is disabled. + */ + val = readl_relaxed(reg + 4); + val &= ~SUPER_CDIV_ENB; + writel_relaxed(val, reg + 4); + + clk = clk_register(NULL, &super->hw); + if (IS_ERR(clk)) + kfree(super); + else + cclk_super = super; + + return clk; +} + +int tegra_cclk_pre_pllx_rate_change(void) +{ + if (IS_ERR_OR_NULL(cclk_super)) + return -EINVAL; + + if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX) + cclk_on_pllx = true; + else + cclk_on_pllx = false; + + /* + * CPU needs to be temporarily re-parented away from PLLX if PLLX + * changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs. + */ + if (cclk_on_pllx) + cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX); + + return 0; +} + +void tegra_cclk_post_pllx_rate_change(void) +{ + if (cclk_on_pllx) + cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX); +} diff --git a/drivers/clk/tegra/clk-emc.c b/drivers/clk/tegra/clk-tegra124-emc.c index 745f9faa98d8..745f9faa98d8 100644 --- a/drivers/clk/tegra/clk-emc.c +++ b/drivers/clk/tegra/clk-tegra124-emc.c diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c index 085feb04e913..3efc651b42e3 100644 --- a/drivers/clk/tegra/clk-tegra20.c +++ b/drivers/clk/tegra/clk-tegra20.c @@ -391,6 +391,8 @@ static struct tegra_clk_pll_params pll_x_params = { .lock_delay = 300, .freq_table = pll_x_freq_table, .flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_HAS_LOCK_ENABLE, + .pre_rate_change = tegra_cclk_pre_pllx_rate_change, + .post_rate_change = tegra_cclk_post_pllx_rate_change, }; static struct tegra_clk_pll_params pll_e_params = { @@ -702,9 +704,10 @@ static void tegra20_super_clk_init(void) struct clk *clk; /* CCLK */ - clk = tegra_clk_register_super_mux("cclk", cclk_parents, + clk = tegra_clk_register_super_cclk("cclk", cclk_parents, ARRAY_SIZE(cclk_parents), CLK_SET_RATE_PARENT, - clk_base + CCLK_BURST_POLICY, 0, 4, 0, 0, NULL); + clk_base + CCLK_BURST_POLICY, TEGRA20_SUPER_CLK, + NULL); clks[TEGRA20_CLK_CCLK] = clk; /* SCLK */ diff --git a/drivers/clk/tegra/clk-tegra210-emc.c b/drivers/clk/tegra/clk-tegra210-emc.c new file mode 100644 index 000000000000..352a2c3fc374 --- /dev/null +++ b/drivers/clk/tegra/clk-tegra210-emc.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/clk/tegra.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/slab.h> + +#define CLK_SOURCE_EMC 0x19c +#define CLK_SOURCE_EMC_2X_CLK_SRC GENMASK(31, 29) +#define CLK_SOURCE_EMC_MC_EMC_SAME_FREQ BIT(16) +#define CLK_SOURCE_EMC_2X_CLK_DIVISOR GENMASK(7, 0) + +#define CLK_SRC_PLLM 0 +#define CLK_SRC_PLLC 1 +#define CLK_SRC_PLLP 2 +#define CLK_SRC_CLK_M 3 +#define CLK_SRC_PLLM_UD 4 +#define CLK_SRC_PLLMB_UD 5 +#define CLK_SRC_PLLMB 6 +#define CLK_SRC_PLLP_UD 7 + +struct tegra210_clk_emc { + struct clk_hw hw; + void __iomem *regs; + + struct tegra210_clk_emc_provider *provider; + + struct clk *parents[8]; +}; + +static inline struct tegra210_clk_emc * +to_tegra210_clk_emc(struct clk_hw *hw) +{ + return container_of(hw, struct tegra210_clk_emc, hw); +} + +static const char *tegra210_clk_emc_parents[] = { + "pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb_ud", + "pll_mb", "pll_p_ud", +}; + +static u8 tegra210_clk_emc_get_parent(struct clk_hw *hw) +{ + struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw); + u32 value; + u8 src; + + value = readl_relaxed(emc->regs + CLK_SOURCE_EMC); + src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, value); + + return src; +} + +static unsigned long tegra210_clk_emc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw); + u32 value, div; + + /* + * CCF assumes that neither the parent nor its rate will change during + * ->set_rate(), so the parent rate passed in here was cached from the + * parent before the ->set_rate() call. + * + * This can lead to wrong results being reported for the EMC clock if + * the parent and/or parent rate have changed as part of the EMC rate + * change sequence. Fix this by overriding the parent clock with what + * we know to be the correct value after the rate change. + */ + parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw)); + + value = readl_relaxed(emc->regs + CLK_SOURCE_EMC); + + div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, value); + div += 2; + + return DIV_ROUND_UP(parent_rate * 2, div); +} + +static long tegra210_clk_emc_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw); + struct tegra210_clk_emc_provider *provider = emc->provider; + unsigned int i; + + if (!provider || !provider->configs || provider->num_configs == 0) + return clk_hw_get_rate(hw); + + for (i = 0; i < provider->num_configs; i++) { + if (provider->configs[i].rate >= rate) + return provider->configs[i].rate; + } + + return provider->configs[i - 1].rate; +} + +static struct clk *tegra210_clk_emc_find_parent(struct tegra210_clk_emc *emc, + u8 index) +{ + struct clk_hw *parent = clk_hw_get_parent_by_index(&emc->hw, index); + const char *name = clk_hw_get_name(parent); + + /* XXX implement cache? */ + + return __clk_lookup(name); +} + +static int tegra210_clk_emc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw); + struct tegra210_clk_emc_provider *provider = emc->provider; + struct tegra210_clk_emc_config *config; + struct device *dev = provider->dev; + struct clk_hw *old, *new, *parent; + u8 old_idx, new_idx, index; + struct clk *clk; + unsigned int i; + int err; + + if (!provider || !provider->configs || provider->num_configs == 0) + return -EINVAL; + + for (i = 0; i < provider->num_configs; i++) { + if (provider->configs[i].rate >= rate) { + config = &provider->configs[i]; + break; + } + } + + if (i == provider->num_configs) + config = &provider->configs[i - 1]; + + old_idx = tegra210_clk_emc_get_parent(hw); + new_idx = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value); + + old = clk_hw_get_parent_by_index(hw, old_idx); + new = clk_hw_get_parent_by_index(hw, new_idx); + + /* if the rate has changed... */ + if (config->parent_rate != clk_hw_get_rate(old)) { + /* ... but the clock source remains the same ... */ + if (new_idx == old_idx) { + /* ... switch to the alternative clock source. */ + switch (new_idx) { + case CLK_SRC_PLLM: + new_idx = CLK_SRC_PLLMB; + break; + + case CLK_SRC_PLLM_UD: + new_idx = CLK_SRC_PLLMB_UD; + break; + + case CLK_SRC_PLLMB_UD: + new_idx = CLK_SRC_PLLM_UD; + break; + + case CLK_SRC_PLLMB: + new_idx = CLK_SRC_PLLM; + break; + } + + /* + * This should never happen because we can't deal with + * it. + */ + if (WARN_ON(new_idx == old_idx)) + return -EINVAL; + + new = clk_hw_get_parent_by_index(hw, new_idx); + } + + index = new_idx; + parent = new; + } else { + index = old_idx; + parent = old; + } + + clk = tegra210_clk_emc_find_parent(emc, index); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + dev_err(dev, "failed to get parent clock for index %u: %d\n", + index, err); + return err; + } + + /* set the new parent clock to the required rate */ + if (clk_get_rate(clk) != config->parent_rate) { + err = clk_set_rate(clk, config->parent_rate); + if (err < 0) { + dev_err(dev, "failed to set rate %lu Hz for %pC: %d\n", + config->parent_rate, clk, err); + return err; + } + } + + /* enable the new parent clock */ + if (parent != old) { + err = clk_prepare_enable(clk); + if (err < 0) { + dev_err(dev, "failed to enable parent clock %pC: %d\n", + clk, err); + return err; + } + } + + /* update the EMC source configuration to reflect the new parent */ + config->value &= ~CLK_SOURCE_EMC_2X_CLK_SRC; + config->value |= FIELD_PREP(CLK_SOURCE_EMC_2X_CLK_SRC, index); + + /* + * Finally, switch the EMC programming with both old and new parent + * clocks enabled. + */ + err = provider->set_rate(dev, config); + if (err < 0) { + dev_err(dev, "failed to set EMC rate to %lu Hz: %d\n", rate, + err); + + /* + * If we're unable to switch to the new EMC frequency, we no + * longer need the new parent to be enabled. + */ + if (parent != old) + clk_disable_unprepare(clk); + + return err; + } + + /* reparent to new parent clock and disable the old parent clock */ + if (parent != old) { + clk = tegra210_clk_emc_find_parent(emc, old_idx); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + dev_err(dev, + "failed to get parent clock for index %u: %d\n", + old_idx, err); + return err; + } + + clk_hw_reparent(hw, parent); + clk_disable_unprepare(clk); + } + + return err; +} + +static const struct clk_ops tegra210_clk_emc_ops = { + .get_parent = tegra210_clk_emc_get_parent, + .recalc_rate = tegra210_clk_emc_recalc_rate, + .round_rate = tegra210_clk_emc_round_rate, + .set_rate = tegra210_clk_emc_set_rate, +}; + +struct clk *tegra210_clk_register_emc(struct device_node *np, + void __iomem *regs) +{ + struct tegra210_clk_emc *emc; + struct clk_init_data init; + struct clk *clk; + + emc = kzalloc(sizeof(*emc), GFP_KERNEL); + if (!emc) + return ERR_PTR(-ENOMEM); + + emc->regs = regs; + + init.name = "emc"; + init.ops = &tegra210_clk_emc_ops; + init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE; + init.parent_names = tegra210_clk_emc_parents; + init.num_parents = ARRAY_SIZE(tegra210_clk_emc_parents); + emc->hw.init = &init; + + clk = clk_register(NULL, &emc->hw); + if (IS_ERR(clk)) { + kfree(emc); + return clk; + } + + return clk; +} + +int tegra210_clk_emc_attach(struct clk *clk, + struct tegra210_clk_emc_provider *provider) +{ + struct clk_hw *hw = __clk_get_hw(clk); + struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw); + struct device *dev = provider->dev; + unsigned int i; + int err; + + if (!try_module_get(provider->owner)) + return -ENODEV; + + for (i = 0; i < provider->num_configs; i++) { + struct tegra210_clk_emc_config *config = &provider->configs[i]; + struct clk_hw *parent; + bool same_freq; + u8 div, src; + + div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, config->value); + src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value); + + /* do basic sanity checking on the EMC timings */ + if (div & 0x1) { + dev_err(dev, "invalid odd divider %u for rate %lu Hz\n", + div, config->rate); + err = -EINVAL; + goto put; + } + + same_freq = config->value & CLK_SOURCE_EMC_MC_EMC_SAME_FREQ; + + if (same_freq != config->same_freq) { + dev_err(dev, + "ambiguous EMC to MC ratio for rate %lu Hz\n", + config->rate); + err = -EINVAL; + goto put; + } + + parent = clk_hw_get_parent_by_index(hw, src); + config->parent = src; + + if (src == CLK_SRC_PLLM || src == CLK_SRC_PLLM_UD) { + config->parent_rate = config->rate * (1 + div / 2); + } else { + unsigned long rate = config->rate * (1 + div / 2); + + config->parent_rate = clk_hw_get_rate(parent); + + if (config->parent_rate != rate) { + dev_err(dev, + "rate %lu Hz does not match input\n", + config->rate); + err = -EINVAL; + goto put; + } + } + } + + emc->provider = provider; + + return 0; + +put: + module_put(provider->owner); + return err; +} +EXPORT_SYMBOL_GPL(tegra210_clk_emc_attach); + +void tegra210_clk_emc_detach(struct clk *clk) +{ + struct tegra210_clk_emc *emc = to_tegra210_clk_emc(__clk_get_hw(clk)); + + module_put(emc->provider->owner); + emc->provider = NULL; +} +EXPORT_SYMBOL_GPL(tegra210_clk_emc_detach); diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index defe3b7ebfa4..68cbb98af567 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -37,6 +37,7 @@ #define CLK_SOURCE_LA 0x1f8 #define CLK_SOURCE_SDMMC2 0x154 #define CLK_SOURCE_SDMMC4 0x164 +#define CLK_SOURCE_EMC_DLL 0x664 #define PLLC_BASE 0x80 #define PLLC_OUT 0x84 @@ -227,6 +228,10 @@ #define RST_DFLL_DVCO 0x2f4 #define DVFS_DFLL_RESET_SHIFT 0 +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET 0x284 +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR 0x288 +#define CLK_OUT_ENB_X_CLK_ENB_EMC_DLL BIT(14) + #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8 #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac #define CPU_SOFTRST_CTRL 0x380 @@ -314,12 +319,6 @@ static unsigned long tegra210_input_freq[] = { [8] = 12000000, }; -static const char *mux_pllmcp_clkm[] = { - "pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb", "pll_mb", - "pll_p", -}; -#define mux_pllmcp_clkm_idx NULL - #define PLL_ENABLE (1 << 30) #define PLLCX_MISC1_IDDQ (1 << 27) @@ -555,6 +554,27 @@ void tegra210_set_sata_pll_seq_sw(bool state) } EXPORT_SYMBOL_GPL(tegra210_set_sata_pll_seq_sw); +void tegra210_clk_emc_dll_enable(bool flag) +{ + u32 offset = flag ? CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET : + CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR; + + writel_relaxed(CLK_OUT_ENB_X_CLK_ENB_EMC_DLL, clk_base + offset); +} +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_enable); + +void tegra210_clk_emc_dll_update_setting(u32 emc_dll_src_value) +{ + writel_relaxed(emc_dll_src_value, clk_base + CLK_SOURCE_EMC_DLL); +} +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_update_setting); + +void tegra210_clk_emc_update_setting(u32 emc_src_value) +{ + writel_relaxed(emc_src_value, clk_base + CLK_SOURCE_EMC); +} +EXPORT_SYMBOL_GPL(tegra210_clk_emc_update_setting); + static void tegra210_generic_mbist_war(struct tegra210_domain_mbist_war *mbist) { u32 val; @@ -2310,7 +2330,6 @@ static struct tegra_clk tegra210_clks[tegra_clk_max] __initdata = { [tegra_clk_i2c2] = { .dt_id = TEGRA210_CLK_I2C2, .present = true }, [tegra_clk_uartc_8] = { .dt_id = TEGRA210_CLK_UARTC, .present = true }, [tegra_clk_mipi_cal] = { .dt_id = TEGRA210_CLK_MIPI_CAL, .present = true }, - [tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = true }, [tegra_clk_usb2] = { .dt_id = TEGRA210_CLK_USB2, .present = true }, [tegra_clk_bsev] = { .dt_id = TEGRA210_CLK_BSEV, .present = true }, [tegra_clk_uartd_8] = { .dt_id = TEGRA210_CLK_UARTD, .present = true }, @@ -2953,6 +2972,27 @@ static const char * const sor1_parents[] = { static u32 sor1_parents_idx[] = { 0, 2, 5, 6 }; +static const struct clk_div_table mc_div_table_tegra210[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 4 }, + { .val = 2, .div = 1 }, + { .val = 3, .div = 2 }, + { .val = 0, .div = 0 }, +}; + +static void tegra210_clk_register_mc(const char *name, + const char *parent_name) +{ + struct clk *clk; + + clk = clk_register_divider_table(NULL, name, parent_name, + CLK_IS_CRITICAL, + clk_base + CLK_SOURCE_EMC, + 15, 2, CLK_DIVIDER_READ_ONLY, + mc_div_table_tegra210, &emc_lock); + clks[TEGRA210_CLK_MC] = clk; +} + static const char * const sor1_out_parents[] = { /* * Bit 0 of the mux selects sor1_pad_clkout, irrespective of bit 1, so @@ -2995,7 +3035,8 @@ static const char * const la_parents[] = { static struct tegra_clk_periph tegra210_la = TEGRA_CLK_PERIPH(29, 7, 9, 0, 8, 1, TEGRA_DIVIDER_ROUND_UP, 76, 0, NULL, NULL); -static __init void tegra210_periph_clk_init(void __iomem *clk_base, +static __init void tegra210_periph_clk_init(struct device_node *np, + void __iomem *clk_base, void __iomem *pmc_base) { struct clk *clk; @@ -3035,22 +3076,19 @@ static __init void tegra210_periph_clk_init(void __iomem *clk_base, periph_clk_enb_refcnt); clks[TEGRA210_CLK_DSIB] = clk; + /* csi_tpg */ + clk = clk_register_gate(NULL, "csi_tpg", "pll_d", + CLK_SET_RATE_PARENT, clk_base + PLLD_BASE, + 23, 0, &pll_d_lock); + clk_register_clkdev(clk, "csi_tpg", NULL); + clks[TEGRA210_CLK_CSI_TPG] = clk; + /* la */ clk = tegra_clk_register_periph("la", la_parents, ARRAY_SIZE(la_parents), &tegra210_la, clk_base, CLK_SOURCE_LA, 0); clks[TEGRA210_CLK_LA] = clk; - /* emc mux */ - clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm, - ARRAY_SIZE(mux_pllmcp_clkm), 0, - clk_base + CLK_SOURCE_EMC, - 29, 3, 0, &emc_lock); - - clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC, - &emc_lock); - clks[TEGRA210_CLK_MC] = clk; - /* cml0 */ clk = clk_register_gate(NULL, "cml0", "pll_e", 0, clk_base + PLLE_AUX, 0, 0, &pll_e_lock); @@ -3093,6 +3131,13 @@ static __init void tegra210_periph_clk_init(void __iomem *clk_base, } tegra_periph_clk_init(clk_base, pmc_base, tegra210_clks, &pll_p_params); + + /* emc */ + clk = tegra210_clk_register_emc(np, clk_base); + clks[TEGRA210_CLK_EMC] = clk; + + /* mc */ + tegra210_clk_register_mc("mc", "emc"); } static void __init tegra210_pll_init(void __iomem *clk_base, @@ -3153,6 +3198,17 @@ static void __init tegra210_pll_init(void __iomem *clk_base, clk_register_clkdev(clk, "pll_m_ud", NULL); clks[TEGRA210_CLK_PLL_M_UD] = clk; + /* PLLMB_UD */ + clk = clk_register_fixed_factor(NULL, "pll_mb_ud", "pll_mb", + CLK_SET_RATE_PARENT, 1, 1); + clk_register_clkdev(clk, "pll_mb_ud", NULL); + clks[TEGRA210_CLK_PLL_MB_UD] = clk; + + /* PLLP_UD */ + clk = clk_register_fixed_factor(NULL, "pll_p_ud", "pll_p", + 0, 1, 1); + clks[TEGRA210_CLK_PLL_P_UD] = clk; + /* PLLU_VCO */ if (!tegra210_init_pllu()) { clk = clk_register_fixed_rate(NULL, "pll_u_vco", "pll_ref", 0, @@ -3680,7 +3736,7 @@ static void __init tegra210_clock_init(struct device_node *np) tegra_fixed_clk_init(tegra210_clks); tegra210_pll_init(clk_base, pmc_base); - tegra210_periph_clk_init(clk_base, pmc_base); + tegra210_periph_clk_init(np, clk_base, pmc_base); tegra_audio_clk_init(clk_base, pmc_base, tegra210_clks, tegra210_audio_plls, ARRAY_SIZE(tegra210_audio_plls), 24576000); diff --git a/drivers/clk/tegra/clk-tegra30.c b/drivers/clk/tegra/clk-tegra30.c index 3255f82e61b5..37244a7e68c2 100644 --- a/drivers/clk/tegra/clk-tegra30.c +++ b/drivers/clk/tegra/clk-tegra30.c @@ -499,6 +499,8 @@ static struct tegra_clk_pll_params pll_x_params __ro_after_init = { .freq_table = pll_x_freq_table, .flags = TEGRA_PLL_HAS_CPCON | TEGRA_PLL_SET_DCCON | TEGRA_PLL_USE_LOCK | TEGRA_PLL_HAS_LOCK_ENABLE, + .pre_rate_change = tegra_cclk_pre_pllx_rate_change, + .post_rate_change = tegra_cclk_post_pllx_rate_change, }; static struct tegra_clk_pll_params pll_e_params __ro_after_init = { @@ -926,11 +928,11 @@ static void __init tegra30_super_clk_init(void) clk_register_clkdev(clk, "pll_p_out4_cclkg", NULL); /* CCLKG */ - clk = tegra_clk_register_super_mux("cclk_g", cclk_g_parents, + clk = tegra_clk_register_super_cclk("cclk_g", cclk_g_parents, ARRAY_SIZE(cclk_g_parents), CLK_SET_RATE_PARENT, clk_base + CCLKG_BURST_POLICY, - 0, 4, 0, 0, NULL); + 0, NULL); clks[TEGRA30_CLK_CCLK_G] = clk; /* diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h index 2c9a68302e02..6b565f6b5f66 100644 --- a/drivers/clk/tegra/clk.h +++ b/drivers/clk/tegra/clk.h @@ -266,6 +266,10 @@ struct tegra_clk_pll; * disabled. * @dyn_ramp: Callback which can be used to define a custom * dynamic ramp function for a given PLL. + * @pre_rate_change: Callback which is invoked just before changing + * PLL's rate. + * @post_rate_change: Callback which is invoked right after changing + * PLL's rate. * * Flags: * TEGRA_PLL_USE_LOCK - This flag indicated to use lock bits for @@ -342,6 +346,8 @@ struct tegra_clk_pll_params { void (*set_defaults)(struct tegra_clk_pll *pll); int (*dyn_ramp)(struct tegra_clk_pll *pll, struct tegra_clk_pll_freq_table *cfg); + int (*pre_rate_change)(void); + void (*post_rate_change)(void); }; #define TEGRA_PLL_USE_LOCK BIT(0) @@ -729,8 +735,10 @@ struct clk *tegra_clk_register_periph_data(void __iomem *clk_base, * TEGRA_DIVIDER_2 - LP cluster has additional divider. This flag indicates * that this is LP cluster clock. * TEGRA210_CPU_CLK - This flag is used to identify CPU cluster for gen5 - * super mux parent using PLLP branches. To use PLLP branches to CPU, need - * to configure additional bit PLLP_OUT_CPU in the clock registers. + * super mux parent using PLLP branches. To use PLLP branches to CPU, need + * to configure additional bit PLLP_OUT_CPU in the clock registers. + * TEGRA20_SUPER_CLK - Tegra20 doesn't have a dedicated divider for Super + * clocks, it only has a clock-skipper. */ struct tegra_clk_super_mux { struct clk_hw hw; @@ -748,6 +756,7 @@ struct tegra_clk_super_mux { #define TEGRA_DIVIDER_2 BIT(0) #define TEGRA210_CPU_CLK BIT(1) +#define TEGRA20_SUPER_CLK BIT(2) extern const struct clk_ops tegra_clk_super_ops; struct clk *tegra_clk_register_super_mux(const char *name, @@ -758,6 +767,12 @@ struct clk *tegra_clk_register_super_clk(const char *name, const char * const *parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 clk_super_flags, spinlock_t *lock); +struct clk *tegra_clk_register_super_cclk(const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, void __iomem *reg, u8 clk_super_flags, + spinlock_t *lock); +int tegra_cclk_pre_pllx_rate_change(void); +void tegra_cclk_post_pllx_rate_change(void); /** * struct tegra_sdmmc_mux - switch divider with Low Jitter inputs for SDMMC @@ -866,7 +881,7 @@ void tegra_super_clk_gen5_init(void __iomem *clk_base, void __iomem *pmc_base, struct tegra_clk *tegra_clks, struct tegra_clk_pll_params *pll_params); -#ifdef CONFIG_TEGRA_CLK_EMC +#ifdef CONFIG_TEGRA124_EMC struct clk *tegra_clk_register_emc(void __iomem *base, struct device_node *np, spinlock_t *lock); #else @@ -907,4 +922,7 @@ void tegra_clk_periph_resume(void); bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw); struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter); +struct clk *tegra210_clk_register_emc(struct device_node *np, + void __iomem *regs); + #endif /* TEGRA_CLK_H */ |