|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * Based on drivers/clk/tegra/clk-emc.c | 
|  | * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved. | 
|  | * | 
|  | * Author: Dmitry Osipenko <digetx@gmail.com> | 
|  | * Copyright (C) 2019 GRATE-DRIVER project | 
|  | */ | 
|  |  | 
|  | #define pr_fmt(fmt)	"tegra-emc-clk: " fmt | 
|  |  | 
|  | #include <linux/bits.h> | 
|  | #include <linux/clk-provider.h> | 
|  | #include <linux/clk/tegra.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/export.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/slab.h> | 
|  |  | 
|  | #include "clk.h" | 
|  |  | 
|  | #define CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK	GENMASK(7, 0) | 
|  | #define CLK_SOURCE_EMC_2X_CLK_SRC_MASK		GENMASK(31, 30) | 
|  | #define CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT		30 | 
|  |  | 
|  | #define MC_EMC_SAME_FREQ	BIT(16) | 
|  | #define USE_PLLM_UD		BIT(29) | 
|  |  | 
|  | #define EMC_SRC_PLL_M		0 | 
|  | #define EMC_SRC_PLL_C		1 | 
|  | #define EMC_SRC_PLL_P		2 | 
|  | #define EMC_SRC_CLK_M		3 | 
|  |  | 
|  | static const char * const emc_parent_clk_names[] = { | 
|  | "pll_m", "pll_c", "pll_p", "clk_m", | 
|  | }; | 
|  |  | 
|  | struct tegra_clk_emc { | 
|  | struct clk_hw hw; | 
|  | void __iomem *reg; | 
|  | bool mc_same_freq; | 
|  | bool want_low_jitter; | 
|  |  | 
|  | tegra20_clk_emc_round_cb *round_cb; | 
|  | void *cb_arg; | 
|  | }; | 
|  |  | 
|  | static inline struct tegra_clk_emc *to_tegra_clk_emc(struct clk_hw *hw) | 
|  | { | 
|  | return container_of(hw, struct tegra_clk_emc, hw); | 
|  | } | 
|  |  | 
|  | static unsigned long emc_recalc_rate(struct clk_hw *hw, | 
|  | unsigned long parent_rate) | 
|  | { | 
|  | struct tegra_clk_emc *emc = to_tegra_clk_emc(hw); | 
|  | u32 val, div; | 
|  |  | 
|  | val = readl_relaxed(emc->reg); | 
|  | div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK; | 
|  |  | 
|  | return DIV_ROUND_UP(parent_rate * 2, div + 2); | 
|  | } | 
|  |  | 
|  | static u8 emc_get_parent(struct clk_hw *hw) | 
|  | { | 
|  | struct tegra_clk_emc *emc = to_tegra_clk_emc(hw); | 
|  |  | 
|  | return readl_relaxed(emc->reg) >> CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT; | 
|  | } | 
|  |  | 
|  | static int emc_set_parent(struct clk_hw *hw, u8 index) | 
|  | { | 
|  | struct tegra_clk_emc *emc = to_tegra_clk_emc(hw); | 
|  | u32 val, div; | 
|  |  | 
|  | val = readl_relaxed(emc->reg); | 
|  | val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK; | 
|  | val |= index << CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT; | 
|  |  | 
|  | div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK; | 
|  |  | 
|  | if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter) | 
|  | val |= USE_PLLM_UD; | 
|  | else | 
|  | val &= ~USE_PLLM_UD; | 
|  |  | 
|  | if (emc->mc_same_freq) | 
|  | val |= MC_EMC_SAME_FREQ; | 
|  | else | 
|  | val &= ~MC_EMC_SAME_FREQ; | 
|  |  | 
|  | writel_relaxed(val, emc->reg); | 
|  |  | 
|  | fence_udelay(1, emc->reg); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int emc_set_rate(struct clk_hw *hw, unsigned long rate, | 
|  | unsigned long parent_rate) | 
|  | { | 
|  | struct tegra_clk_emc *emc = to_tegra_clk_emc(hw); | 
|  | unsigned int index; | 
|  | u32 val, div; | 
|  |  | 
|  | div = div_frac_get(rate, parent_rate, 8, 1, 0); | 
|  |  | 
|  | val = readl_relaxed(emc->reg); | 
|  | val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK; | 
|  | val |= div; | 
|  |  | 
|  | index = val >> CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT; | 
|  |  | 
|  | if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter) | 
|  | val |= USE_PLLM_UD; | 
|  | else | 
|  | val &= ~USE_PLLM_UD; | 
|  |  | 
|  | if (emc->mc_same_freq) | 
|  | val |= MC_EMC_SAME_FREQ; | 
|  | else | 
|  | val &= ~MC_EMC_SAME_FREQ; | 
|  |  | 
|  | writel_relaxed(val, emc->reg); | 
|  |  | 
|  | fence_udelay(1, emc->reg); | 
|  |  | 
|  | 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->reg); | 
|  |  | 
|  | 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; | 
|  |  | 
|  | if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter) | 
|  | val |= USE_PLLM_UD; | 
|  | else | 
|  | val &= ~USE_PLLM_UD; | 
|  |  | 
|  | if (emc->mc_same_freq) | 
|  | val |= MC_EMC_SAME_FREQ; | 
|  | else | 
|  | val &= ~MC_EMC_SAME_FREQ; | 
|  |  | 
|  | writel_relaxed(val, emc->reg); | 
|  |  | 
|  | fence_udelay(1, emc->reg); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int emc_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) | 
|  | { | 
|  | struct tegra_clk_emc *emc = to_tegra_clk_emc(hw); | 
|  | struct clk_hw *parent_hw; | 
|  | unsigned long divided_rate; | 
|  | unsigned long parent_rate; | 
|  | unsigned int i; | 
|  | long emc_rate; | 
|  | int div; | 
|  |  | 
|  | emc_rate = emc->round_cb(req->rate, req->min_rate, req->max_rate, | 
|  | emc->cb_arg); | 
|  | if (emc_rate < 0) | 
|  | return emc_rate; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(emc_parent_clk_names); i++) { | 
|  | parent_hw = clk_hw_get_parent_by_index(hw, i); | 
|  |  | 
|  | if (req->best_parent_hw == parent_hw) | 
|  | parent_rate = req->best_parent_rate; | 
|  | else | 
|  | parent_rate = clk_hw_get_rate(parent_hw); | 
|  |  | 
|  | if (emc_rate > parent_rate) | 
|  | continue; | 
|  |  | 
|  | div = div_frac_get(emc_rate, parent_rate, 8, 1, 0); | 
|  | divided_rate = DIV_ROUND_UP(parent_rate * 2, div + 2); | 
|  |  | 
|  | if (divided_rate != emc_rate) | 
|  | continue; | 
|  |  | 
|  | req->best_parent_rate = parent_rate; | 
|  | req->best_parent_hw = parent_hw; | 
|  | req->rate = emc_rate; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (i == ARRAY_SIZE(emc_parent_clk_names)) { | 
|  | pr_err_once("can't find parent for rate %lu emc_rate %lu\n", | 
|  | req->rate, emc_rate); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct clk_ops tegra_clk_emc_ops = { | 
|  | .recalc_rate = emc_recalc_rate, | 
|  | .get_parent = emc_get_parent, | 
|  | .set_parent = emc_set_parent, | 
|  | .set_rate = emc_set_rate, | 
|  | .set_rate_and_parent = emc_set_rate_and_parent, | 
|  | .determine_rate = emc_determine_rate, | 
|  | }; | 
|  |  | 
|  | void tegra20_clk_set_emc_round_callback(tegra20_clk_emc_round_cb *round_cb, | 
|  | void *cb_arg) | 
|  | { | 
|  | struct clk *clk = __clk_lookup("emc"); | 
|  | struct tegra_clk_emc *emc; | 
|  | struct clk_hw *hw; | 
|  |  | 
|  | if (clk) { | 
|  | hw = __clk_get_hw(clk); | 
|  | emc = to_tegra_clk_emc(hw); | 
|  |  | 
|  | emc->round_cb = round_cb; | 
|  | emc->cb_arg = cb_arg; | 
|  | } | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(tegra20_clk_set_emc_round_callback); | 
|  |  | 
|  | bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw) | 
|  | { | 
|  | return to_tegra_clk_emc(emc_hw)->round_cb != NULL; | 
|  | } | 
|  |  | 
|  | struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter) | 
|  | { | 
|  | struct tegra_clk_emc *emc; | 
|  | struct clk_init_data init; | 
|  | struct clk *clk; | 
|  |  | 
|  | emc = kzalloc(sizeof(*emc), GFP_KERNEL); | 
|  | if (!emc) | 
|  | return NULL; | 
|  |  | 
|  | /* | 
|  | * EMC stands for External Memory Controller. | 
|  | * | 
|  | * We don't want EMC clock to be disabled ever by gating its | 
|  | * parent and whatnot because system is busted immediately in that | 
|  | * case, hence the clock is marked as critical. | 
|  | */ | 
|  | init.name = "emc"; | 
|  | init.ops = &tegra_clk_emc_ops; | 
|  | init.flags = CLK_IS_CRITICAL; | 
|  | init.parent_names = emc_parent_clk_names; | 
|  | init.num_parents = ARRAY_SIZE(emc_parent_clk_names); | 
|  |  | 
|  | emc->reg = ioaddr; | 
|  | emc->hw.init = &init; | 
|  | emc->want_low_jitter = low_jitter; | 
|  |  | 
|  | clk = clk_register(NULL, &emc->hw); | 
|  | if (IS_ERR(clk)) { | 
|  | kfree(emc); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return clk; | 
|  | } | 
|  |  | 
|  | int tegra20_clk_prepare_emc_mc_same_freq(struct clk *emc_clk, bool same) | 
|  | { | 
|  | struct tegra_clk_emc *emc; | 
|  | struct clk_hw *hw; | 
|  |  | 
|  | if (!emc_clk) | 
|  | return -EINVAL; | 
|  |  | 
|  | hw = __clk_get_hw(emc_clk); | 
|  | emc = to_tegra_clk_emc(hw); | 
|  | emc->mc_same_freq = same; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(tegra20_clk_prepare_emc_mc_same_freq); |