mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
7.1 KiB
274 lines
7.1 KiB
// 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 = clk_register(NULL, &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; |
|
}
|
|
|