// 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) #define TSENSOR_SLOWDOWN BIT(23) 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) { struct tegra_clk_super_mux *super = to_clk_super_mux(hw); u32 val = readl_relaxed(super->reg); unsigned int div2; /* check whether thermal throttling is active */ if (val & TSENSOR_SLOWDOWN) div2 = 1; else div2 = 0; if (cclk_super_get_parent(hw) == PLLX_INDEX) return parent_rate >> div2; return tegra_clk_super_ops.recalc_rate(hw, parent_rate) >> div2; } 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 { struct clk_rate_request parent = { .rate = req->rate, .best_parent_rate = pllp_rate, }; clk_hw_get_rate_range(hw, &parent.min_rate, &parent.max_rate); tegra_clk_super_ops.determine_rate(hw, &parent); pllp_rate = parent.best_parent_rate; rate = parent.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); }