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
+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
 
--- /dev/null
+// 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/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;
+       }
+}
+
+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;
+}
 
 static void __iomem *clk_base;
 static void __iomem *pmc_base;
 
-static DEFINE_SPINLOCK(emc_lock);
-
 #define TEGRA_INIT_DATA_MUX(_name, _parents, _offset,  \
                            _clk_num, _gate_flags, _clk_id)     \
        TEGRA_INIT_DATA(_name, NULL, NULL, _parents, _offset,   \
 static const char *mux_pllpcm_clkm[] = { "pll_p", "pll_c", "pll_m", "clk_m" };
 static const char *mux_pllpdc_clkm[] = { "pll_p", "pll_d_out0", "pll_c",
                                         "clk_m" };
-static const char *mux_pllmcp_clkm[] = { "pll_m", "pll_c", "pll_p", "clk_m" };
 
 static struct tegra_periph_init_data tegra_periph_clk_list[] = {
        TEGRA_INIT_DATA_MUX("i2s1", i2s1_parents,     CLK_SOURCE_I2S1,   11, TEGRA_PERIPH_ON_APB, TEGRA20_CLK_I2S1),
        TEGRA_INIT_DATA_NODIV("disp2",  mux_pllpdc_clkm, CLK_SOURCE_DISP2, 30, 2, 26,  0, TEGRA20_CLK_DISP2),
 };
 
-static void __init tegra20_emc_clk_init(void)
-{
-       const u32 use_pllm_ud = BIT(29);
-       struct clk *clk;
-       u32 emc_reg;
-
-       clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm,
-                              ARRAY_SIZE(mux_pllmcp_clkm),
-                              CLK_SET_RATE_NO_REPARENT,
-                              clk_base + CLK_SOURCE_EMC,
-                              30, 2, 0, &emc_lock);
-
-       clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC,
-                                   &emc_lock);
-       clks[TEGRA20_CLK_MC] = clk;
-
-       /* un-divided pll_m_out0 is currently unsupported */
-       emc_reg = readl_relaxed(clk_base + CLK_SOURCE_EMC);
-       if (emc_reg & use_pllm_ud) {
-               pr_err("%s: un-divided PllM_out0 used as clock source\n",
-                      __func__);
-               return;
-       }
-
-       /*
-        * Note that 'emc_mux' source and 'emc' rate shouldn't be changed at
-        * the same time due to a HW bug, this won't happen because we're
-        * defining 'emc_mux' and 'emc' as distinct clocks.
-        */
-       clk = tegra_clk_register_divider("emc", "emc_mux",
-                               clk_base + CLK_SOURCE_EMC, CLK_IS_CRITICAL,
-                               TEGRA_DIVIDER_INT, 0, 8, 1, &emc_lock);
-       clks[TEGRA20_CLK_EMC] = clk;
-}
-
 static void __init tegra20_periph_clk_init(void)
 {
        struct tegra_periph_init_data *data;
        clks[TEGRA20_CLK_AC97] = clk;
 
        /* emc */
-       tegra20_emc_clk_init();
+       clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, false);
+
+       clks[TEGRA20_CLK_EMC] = clk;
+
+       clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC,
+                                   NULL);
+       clks[TEGRA20_CLK_MC] = clk;
 
        /* dsi */
        clk = tegra_clk_register_periph_gate("dsi", "pll_d", 0, clk_base, 0,
        if (IS_ERR(clk))
                return clk;
 
+       hw = __clk_get_hw(clk);
+
        /*
         * Tegra20 CDEV1 and CDEV2 clocks are a bit special case, their parent
         * clock is created by the pinctrl driver. It is possible for clk user
         */
        if (clkspec->args[0] == TEGRA20_CLK_CDEV1 ||
            clkspec->args[0] == TEGRA20_CLK_CDEV2) {
-               hw = __clk_get_hw(clk);
-
                parent_hw = clk_hw_get_parent(hw);
                if (!parent_hw)
                        return ERR_PTR(-EPROBE_DEFER);
        }
 
+       if (clkspec->args[0] == TEGRA20_CLK_EMC) {
+               if (!tegra20_clk_emc_driver_available(hw))
+                       return ERR_PTR(-EPROBE_DEFER);
+       }
+
        return clk;
 }
 
 
 
 static DEFINE_SPINLOCK(cml_lock);
 static DEFINE_SPINLOCK(pll_d_lock);
-static DEFINE_SPINLOCK(emc_lock);
 
 #define TEGRA_INIT_DATA_MUX(_name, _parents, _offset,  \
                            _clk_num, _gate_flags, _clk_id)     \
        [tegra_clk_pll_a] = { .dt_id = TEGRA30_CLK_PLL_A, .present = true },
        [tegra_clk_pll_a_out0] = { .dt_id = TEGRA30_CLK_PLL_A_OUT0, .present = true },
        [tegra_clk_cec] = { .dt_id = TEGRA30_CLK_CEC, .present = true },
-       [tegra_clk_emc] = { .dt_id = TEGRA30_CLK_EMC, .present = true },
+       [tegra_clk_emc] = { .dt_id = TEGRA30_CLK_EMC, .present = false },
 };
 
 static const char *pll_e_parents[] = { "pll_ref", "pll_p" };
 static const char *mux_pllacp_clkm[] = { "pll_a_out0", "unused", "pll_p",
                                         "clk_m" };
 static const char *mux_pllpcm_clkm[] = { "pll_p", "pll_c", "pll_m", "clk_m" };
-static const char *mux_pllmcp_clkm[] = { "pll_m", "pll_c", "pll_p", "clk_m" };
 static const char *spdif_out_parents[] = { "pll_a_out0", "spdif_2x", "pll_p",
                                           "clk_m" };
 static const char *mux_pllmcpa[] = { "pll_m", "pll_c", "pll_p", "pll_a_out0" };
        clks[TEGRA30_CLK_AFI] = clk;
 
        /* emc */
-       clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm,
-                              ARRAY_SIZE(mux_pllmcp_clkm),
-                              CLK_SET_RATE_NO_REPARENT,
-                              clk_base + CLK_SOURCE_EMC,
-                              30, 2, 0, &emc_lock);
+       clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, true);
+
+       clks[TEGRA30_CLK_EMC] = clk;
 
-       clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC,
-                                   &emc_lock);
+       clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC,
+                                   NULL);
        clks[TEGRA30_CLK_MC] = clk;
 
        /* cml0 */
        { "pll_a", &pll_a_params, tegra_clk_pll_a, "pll_p_out1" },
 };
 
+static struct clk *tegra30_clk_src_onecell_get(struct of_phandle_args *clkspec,
+                                              void *data)
+{
+       struct clk_hw *hw;
+       struct clk *clk;
+
+       clk = of_clk_src_onecell_get(clkspec, data);
+       if (IS_ERR(clk))
+               return clk;
+
+       hw = __clk_get_hw(clk);
+
+       if (clkspec->args[0] == TEGRA30_CLK_EMC) {
+               if (!tegra20_clk_emc_driver_available(hw))
+                       return ERR_PTR(-EPROBE_DEFER);
+       }
+
+       return clk;
+}
+
 static void __init tegra30_clock_init(struct device_node *np)
 {
        struct device_node *node;
 
        tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA30_CLK_CLK_MAX);
 
-       tegra_add_of_provider(np, of_clk_src_onecell_get);
+       tegra_add_of_provider(np, tegra30_clk_src_onecell_get);
        tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
 
        tegra_clk_apply_init_table = tegra30_clock_apply_init_table;
 
                udelay(delay);          \
        } while (0)
 
+bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw);
+struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter);
+
 #endif /* TEGRA_CLK_H */
 
 extern void tegra210_put_utmipll_out_iddq(void);
 extern int tegra210_clk_handle_mbist_war(unsigned int id);
 
+struct clk;
+
+typedef long (tegra20_clk_emc_round_cb)(unsigned long rate,
+                                       unsigned long min_rate,
+                                       unsigned long max_rate,
+                                       void *arg);
+
+void tegra20_clk_set_emc_round_callback(tegra20_clk_emc_round_cb *round_cb,
+                                       void *cb_arg);
+int tegra20_clk_prepare_emc_mc_same_freq(struct clk *emc_clk, bool same);
+
 #endif /* __LINUX_CLK_TEGRA_H_ */