linux/drivers/clk/tegra/clk-tegra20-emc.c

294 lines
6.3 KiB
C

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