// 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 = { .determine_rate = clk_hw_determine_rate_no_reparent, .get_parent = clk_super_get_parent, .set_parent = clk_super_set_parent, .restore_context = clk_super_mux_restore_context, }; static int clk_super_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct tegra_clk_super_mux *super = to_clk_super_mux(hw); struct clk_hw *div_hw = &super->frac_div.hw; unsigned long rate; __clk_hw_set_clk(div_hw, hw); rate = super->div_ops->round_rate(div_hw, req->rate, &req->best_parent_rate); if (rate < 0) return rate; req->rate = rate; return 0; } 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, .determine_rate = clk_super_determine_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; }