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