forked from Minki/linux
b1bc04a2ac
The Clock-and-Reset controller resides in a core power domain on NVIDIA Tegra SoCs. In order to support voltage scaling of the core power domain, we hook up DVFS-capable clocks to the core GENPD for managing of the GENPD's performance state based on the clock changes. Some clocks don't have any specific physical hardware unit that backs them, like root PLLs and system clock and they have theirs own voltage requirements. This patch adds new clk-device driver that backs the clocks and provides runtime PM functionality for them. A virtual clk-device is created for each such DVFS-capable clock at the clock's registration time by the new tegra_clk_register() helper. Driver changes clock's device GENPD performance state based on clk-rate notifications. In result we have this sequence of events: 1. Clock driver creates virtual device for selective clocks, enables runtime PM for the created device and registers the clock. 2. Clk-device driver starts to listen to clock rate changes. 3. Something changes clk rate or enables/disables clk. 4. CCF core propagates the change through the clk tree. 5. Clk-device driver gets clock rate-change notification or GENPD core handles prepare/unprepare of the clock. 6. Clk-device driver changes GENPD performance state on clock rate change. 7. GENPD driver changes voltage regulator state change. 8. The regulator state is committed to hardware via I2C. We rely on fact that DVFS is not needed for Tegra I2C and that Tegra I2C driver already keeps clock always-prepared. Hence I2C subsystem stays independent from the clk power management and there are no deadlock spots in the sequence. Currently all clocks are registered very early during kernel boot when the device driver core isn't available yet. The clk-device can't be created at that time. This patch splits the registration of the clocks in two phases: 1. Register all essential clocks which don't use RPM and are needed during early boot. 2. Register at a later boot time the rest of clocks. This patch adds power management support for Tegra20 and Tegra30 clocks. Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org> Tested-by: Peter Geis <pgwipeout@gmail.com> # Ouya T30 Tested-by: Paul Fertser <fercerpav@gmail.com> # PAZ00 T20 Tested-by: Nicolas Chauvet <kwizart@gmail.com> # PAZ00 T20 and TK1 T124 Tested-by: Matt Merhar <mattmerhar@protonmail.com> # Ouya T30 Signed-off-by: Dmitry Osipenko <digetx@gmail.com> Signed-off-by: Thierry Reding <treding@nvidia.com>
275 lines
7.1 KiB
C
275 lines
7.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk-provider.h>
|
|
|
|
#include "clk.h"
|
|
|
|
#define SUPER_STATE_IDLE 0
|
|
#define SUPER_STATE_RUN 1
|
|
#define SUPER_STATE_IRQ 2
|
|
#define SUPER_STATE_FIQ 3
|
|
|
|
#define SUPER_STATE_SHIFT 28
|
|
#define SUPER_STATE_MASK ((BIT(SUPER_STATE_IDLE) | BIT(SUPER_STATE_RUN) | \
|
|
BIT(SUPER_STATE_IRQ) | BIT(SUPER_STATE_FIQ)) \
|
|
<< SUPER_STATE_SHIFT)
|
|
|
|
#define SUPER_LP_DIV2_BYPASS (1 << 16)
|
|
|
|
#define super_state(s) (BIT(s) << SUPER_STATE_SHIFT)
|
|
#define super_state_to_src_shift(m, s) ((m->width * s))
|
|
#define super_state_to_src_mask(m) (((1 << m->width) - 1))
|
|
|
|
#define CCLK_SRC_PLLP_OUT0 4
|
|
#define CCLK_SRC_PLLP_OUT4 5
|
|
|
|
static u8 clk_super_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct tegra_clk_super_mux *mux = to_clk_super_mux(hw);
|
|
u32 val, state;
|
|
u8 source, shift;
|
|
|
|
val = readl_relaxed(mux->reg);
|
|
|
|
state = val & SUPER_STATE_MASK;
|
|
|
|
BUG_ON((state != super_state(SUPER_STATE_RUN)) &&
|
|
(state != super_state(SUPER_STATE_IDLE)));
|
|
shift = (state == super_state(SUPER_STATE_IDLE)) ?
|
|
super_state_to_src_shift(mux, SUPER_STATE_IDLE) :
|
|
super_state_to_src_shift(mux, SUPER_STATE_RUN);
|
|
|
|
source = (val >> shift) & super_state_to_src_mask(mux);
|
|
|
|
/*
|
|
* If LP_DIV2_BYPASS is not set and PLLX is current parent then
|
|
* PLLX/2 is the input source to CCLKLP.
|
|
*/
|
|
if ((mux->flags & TEGRA_DIVIDER_2) && !(val & SUPER_LP_DIV2_BYPASS) &&
|
|
(source == mux->pllx_index))
|
|
source = mux->div2_index;
|
|
|
|
return source;
|
|
}
|
|
|
|
static int clk_super_set_parent(struct clk_hw *hw, u8 index)
|
|
{
|
|
struct tegra_clk_super_mux *mux = to_clk_super_mux(hw);
|
|
u32 val, state;
|
|
int err = 0;
|
|
u8 parent_index, shift;
|
|
unsigned long flags = 0;
|
|
|
|
if (mux->lock)
|
|
spin_lock_irqsave(mux->lock, flags);
|
|
|
|
val = readl_relaxed(mux->reg);
|
|
state = val & SUPER_STATE_MASK;
|
|
BUG_ON((state != super_state(SUPER_STATE_RUN)) &&
|
|
(state != super_state(SUPER_STATE_IDLE)));
|
|
shift = (state == super_state(SUPER_STATE_IDLE)) ?
|
|
super_state_to_src_shift(mux, SUPER_STATE_IDLE) :
|
|
super_state_to_src_shift(mux, SUPER_STATE_RUN);
|
|
|
|
/*
|
|
* For LP mode super-clock switch between PLLX direct
|
|
* and divided-by-2 outputs is allowed only when other
|
|
* than PLLX clock source is current parent.
|
|
*/
|
|
if ((mux->flags & TEGRA_DIVIDER_2) && ((index == mux->div2_index) ||
|
|
(index == mux->pllx_index))) {
|
|
parent_index = clk_super_get_parent(hw);
|
|
if ((parent_index == mux->div2_index) ||
|
|
(parent_index == mux->pllx_index)) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
val ^= SUPER_LP_DIV2_BYPASS;
|
|
writel_relaxed(val, mux->reg);
|
|
udelay(2);
|
|
|
|
if (index == mux->div2_index)
|
|
index = mux->pllx_index;
|
|
}
|
|
|
|
/* enable PLLP branches to CPU before selecting PLLP source */
|
|
if ((mux->flags & TEGRA210_CPU_CLK) &&
|
|
(index == CCLK_SRC_PLLP_OUT0 || index == CCLK_SRC_PLLP_OUT4))
|
|
tegra_clk_set_pllp_out_cpu(true);
|
|
|
|
val &= ~((super_state_to_src_mask(mux)) << shift);
|
|
val |= (index & (super_state_to_src_mask(mux))) << shift;
|
|
|
|
writel_relaxed(val, mux->reg);
|
|
udelay(2);
|
|
|
|
/* disable PLLP branches to CPU if not used */
|
|
if ((mux->flags & TEGRA210_CPU_CLK) &&
|
|
index != CCLK_SRC_PLLP_OUT0 && index != CCLK_SRC_PLLP_OUT4)
|
|
tegra_clk_set_pllp_out_cpu(false);
|
|
|
|
out:
|
|
if (mux->lock)
|
|
spin_unlock_irqrestore(mux->lock, flags);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void clk_super_mux_restore_context(struct clk_hw *hw)
|
|
{
|
|
int parent_id;
|
|
|
|
parent_id = clk_hw_get_parent_index(hw);
|
|
if (WARN_ON(parent_id < 0))
|
|
return;
|
|
|
|
clk_super_set_parent(hw, parent_id);
|
|
}
|
|
|
|
static const struct clk_ops tegra_clk_super_mux_ops = {
|
|
.get_parent = clk_super_get_parent,
|
|
.set_parent = clk_super_set_parent,
|
|
.restore_context = clk_super_mux_restore_context,
|
|
};
|
|
|
|
static long clk_super_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
|
|
struct clk_hw *div_hw = &super->frac_div.hw;
|
|
|
|
__clk_hw_set_clk(div_hw, hw);
|
|
|
|
return super->div_ops->round_rate(div_hw, rate, parent_rate);
|
|
}
|
|
|
|
static unsigned long clk_super_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
|
|
struct clk_hw *div_hw = &super->frac_div.hw;
|
|
|
|
__clk_hw_set_clk(div_hw, hw);
|
|
|
|
return super->div_ops->recalc_rate(div_hw, parent_rate);
|
|
}
|
|
|
|
static int clk_super_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
|
|
struct clk_hw *div_hw = &super->frac_div.hw;
|
|
|
|
__clk_hw_set_clk(div_hw, hw);
|
|
|
|
return super->div_ops->set_rate(div_hw, rate, parent_rate);
|
|
}
|
|
|
|
static void clk_super_restore_context(struct clk_hw *hw)
|
|
{
|
|
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
|
|
struct clk_hw *div_hw = &super->frac_div.hw;
|
|
int parent_id;
|
|
|
|
parent_id = clk_hw_get_parent_index(hw);
|
|
if (WARN_ON(parent_id < 0))
|
|
return;
|
|
|
|
super->div_ops->restore_context(div_hw);
|
|
clk_super_set_parent(hw, parent_id);
|
|
}
|
|
|
|
const struct clk_ops tegra_clk_super_ops = {
|
|
.get_parent = clk_super_get_parent,
|
|
.set_parent = clk_super_set_parent,
|
|
.set_rate = clk_super_set_rate,
|
|
.round_rate = clk_super_round_rate,
|
|
.recalc_rate = clk_super_recalc_rate,
|
|
.restore_context = clk_super_restore_context,
|
|
};
|
|
|
|
struct clk *tegra_clk_register_super_mux(const char *name,
|
|
const char **parent_names, u8 num_parents,
|
|
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
|
|
u8 width, u8 pllx_index, u8 div2_index, spinlock_t *lock)
|
|
{
|
|
struct tegra_clk_super_mux *super;
|
|
struct clk *clk;
|
|
struct clk_init_data init;
|
|
|
|
super = kzalloc(sizeof(*super), GFP_KERNEL);
|
|
if (!super)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
init.name = name;
|
|
init.ops = &tegra_clk_super_mux_ops;
|
|
init.flags = flags;
|
|
init.parent_names = parent_names;
|
|
init.num_parents = num_parents;
|
|
|
|
super->reg = reg;
|
|
super->pllx_index = pllx_index;
|
|
super->div2_index = div2_index;
|
|
super->lock = lock;
|
|
super->width = width;
|
|
super->flags = clk_super_flags;
|
|
|
|
/* Data in .init is copied by clk_register(), so stack variable OK */
|
|
super->hw.init = &init;
|
|
|
|
clk = tegra_clk_dev_register(&super->hw);
|
|
if (IS_ERR(clk))
|
|
kfree(super);
|
|
|
|
return clk;
|
|
}
|
|
|
|
struct clk *tegra_clk_register_super_clk(const char *name,
|
|
const char * const *parent_names, u8 num_parents,
|
|
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
|
|
spinlock_t *lock)
|
|
{
|
|
struct tegra_clk_super_mux *super;
|
|
struct clk *clk;
|
|
struct clk_init_data init;
|
|
|
|
super = kzalloc(sizeof(*super), GFP_KERNEL);
|
|
if (!super)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
init.name = name;
|
|
init.ops = &tegra_clk_super_ops;
|
|
init.flags = flags;
|
|
init.parent_names = parent_names;
|
|
init.num_parents = num_parents;
|
|
|
|
super->reg = reg;
|
|
super->lock = lock;
|
|
super->width = 4;
|
|
super->flags = clk_super_flags;
|
|
super->frac_div.reg = reg + 4;
|
|
super->frac_div.shift = 16;
|
|
super->frac_div.width = 8;
|
|
super->frac_div.frac_width = 1;
|
|
super->frac_div.lock = lock;
|
|
super->div_ops = &tegra_clk_frac_div_ops;
|
|
|
|
/* Data in .init is copied by clk_register(), so stack variable OK */
|
|
super->hw.init = &init;
|
|
|
|
clk = clk_register(NULL, &super->hw);
|
|
if (IS_ERR(clk))
|
|
kfree(super);
|
|
|
|
return clk;
|
|
}
|