|  | /* | 
|  | * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms and conditions of the GNU General Public License, | 
|  | * version 2, as published by the Free Software Foundation. | 
|  | * | 
|  | * This program is distributed in the hope it will be useful, but WITHOUT | 
|  | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|  | * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
|  | * more details. | 
|  | * | 
|  | * You should have received a copy of the GNU General Public License | 
|  | * along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
|  | */ | 
|  |  | 
|  | #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)) | 
|  |  | 
|  | 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; | 
|  | } | 
|  | val &= ~((super_state_to_src_mask(mux)) << shift); | 
|  | val |= (index & (super_state_to_src_mask(mux))) << shift; | 
|  |  | 
|  | writel_relaxed(val, mux->reg); | 
|  | udelay(2); | 
|  |  | 
|  | out: | 
|  | if (mux->lock) | 
|  | spin_unlock_irqrestore(mux->lock, flags); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | const struct clk_ops tegra_clk_super_mux_ops = { | 
|  | .get_parent = clk_super_get_parent, | 
|  | .set_parent = clk_super_set_parent, | 
|  | }; | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | 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, | 
|  | }; | 
|  |  | 
|  | 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; | 
|  | } |