// 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);