diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-26 23:42:29 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-26 23:42:29 +0400 |
commit | 27953437059c64d14086196eb96f43c78caa9db3 (patch) | |
tree | 0cfd5fb21262a6db3de0c64462847b4c0c43e9df /drivers/clk/spear/clk-aux-synth.c | |
parent | 2c757fd5d1a92086f225a75a8fac7cab242d11b0 (diff) | |
parent | 3c0dec5f58b3c7b3627715126d1bf9b030a076f0 (diff) | |
download | linux-27953437059c64d14086196eb96f43c78caa9db3.tar.xz |
Merge tag 'clock' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull arm-soc clock driver changes from Olof Johansson:
"The new clock subsystem was merged in linux-3.4 without any users,
this now moves the first three platforms over to it: imx, mxs and
spear.
The series also contains the changes for the clock subsystem itself,
since Mike preferred to have it together with the platforms that
require these changes, in order to avoid interdependencies and
conflicts."
Fix up trivial conflicts in arch/arm/mach-kirkwood/common.c (code
removed in one branch, added OF support in another) and
drivers/dma/imx-sdma.c (independent changes next to each other).
* tag 'clock' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (97 commits)
clk: Fix CLK_SET_RATE_GATE flag validation in clk_set_rate().
clk: Provide dummy clk_unregister()
SPEAr: Update defconfigs
SPEAr: Add SMI NOR partition info in dts files
SPEAr: Switch to common clock framework
SPEAr: Call clk_prepare() before calling clk_enable
SPEAr: clk: Add General Purpose Timer Synthesizer clock
SPEAr: clk: Add Fractional Synthesizer clock
SPEAr: clk: Add Auxiliary Synthesizer clock
SPEAr: clk: Add VCO-PLL Synthesizer clock
SPEAr: Add DT bindings for SPEAr's timer
ARM i.MX: remove now unused clock files
ARM: i.MX6: implement clocks using common clock framework
ARM i.MX35: implement clocks using common clock framework
ARM i.MX5: implement clocks using common clock framework
ARM: Kirkwood: Replace clock gating
ARM: Orion: Audio: Add clk/clkdev support
ARM: Orion: PCIE: Add support for clk
ARM: Orion: XOR: Add support for clk
ARM: Orion: CESA: Add support for clk
...
Diffstat (limited to 'drivers/clk/spear/clk-aux-synth.c')
-rw-r--r-- | drivers/clk/spear/clk-aux-synth.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/drivers/clk/spear/clk-aux-synth.c b/drivers/clk/spear/clk-aux-synth.c new file mode 100644 index 000000000000..af34074e702b --- /dev/null +++ b/drivers/clk/spear/clk-aux-synth.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2012 ST Microelectronics + * Viresh Kumar <viresh.kumar@st.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * Auxiliary Synthesizer clock implementation + */ + +#define pr_fmt(fmt) "clk-aux-synth: " fmt + +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> +#include "clk.h" + +/* + * DOC: Auxiliary Synthesizer clock + * + * Aux synth gives rate for different values of eq, x and y + * + * Fout from synthesizer can be given from two equations: + * Fout1 = (Fin * X/Y)/2 EQ1 + * Fout2 = Fin * X/Y EQ2 + */ + +#define to_clk_aux(_hw) container_of(_hw, struct clk_aux, hw) + +static struct aux_clk_masks default_aux_masks = { + .eq_sel_mask = AUX_EQ_SEL_MASK, + .eq_sel_shift = AUX_EQ_SEL_SHIFT, + .eq1_mask = AUX_EQ1_SEL, + .eq2_mask = AUX_EQ2_SEL, + .xscale_sel_mask = AUX_XSCALE_MASK, + .xscale_sel_shift = AUX_XSCALE_SHIFT, + .yscale_sel_mask = AUX_YSCALE_MASK, + .yscale_sel_shift = AUX_YSCALE_SHIFT, + .enable_bit = AUX_SYNT_ENB, +}; + +static unsigned long aux_calc_rate(struct clk_hw *hw, unsigned long prate, + int index) +{ + struct clk_aux *aux = to_clk_aux(hw); + struct aux_rate_tbl *rtbl = aux->rtbl; + u8 eq = rtbl[index].eq ? 1 : 2; + + return (((prate / 10000) * rtbl[index].xscale) / + (rtbl[index].yscale * eq)) * 10000; +} + +static long clk_aux_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *prate) +{ + struct clk_aux *aux = to_clk_aux(hw); + int unused; + + return clk_round_rate_index(hw, drate, *prate, aux_calc_rate, + aux->rtbl_cnt, &unused); +} + +static unsigned long clk_aux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_aux *aux = to_clk_aux(hw); + unsigned int num = 1, den = 1, val, eqn; + unsigned long flags = 0; + + if (aux->lock) + spin_lock_irqsave(aux->lock, flags); + + val = readl_relaxed(aux->reg); + + if (aux->lock) + spin_unlock_irqrestore(aux->lock, flags); + + eqn = (val >> aux->masks->eq_sel_shift) & aux->masks->eq_sel_mask; + if (eqn == aux->masks->eq1_mask) + den = 2; + + /* calculate numerator */ + num = (val >> aux->masks->xscale_sel_shift) & + aux->masks->xscale_sel_mask; + + /* calculate denominator */ + den *= (val >> aux->masks->yscale_sel_shift) & + aux->masks->yscale_sel_mask; + + if (!den) + return 0; + + return (((parent_rate / 10000) * num) / den) * 10000; +} + +/* Configures new clock rate of aux */ +static int clk_aux_set_rate(struct clk_hw *hw, unsigned long drate, + unsigned long prate) +{ + struct clk_aux *aux = to_clk_aux(hw); + struct aux_rate_tbl *rtbl = aux->rtbl; + unsigned long val, flags = 0; + int i; + + clk_round_rate_index(hw, drate, prate, aux_calc_rate, aux->rtbl_cnt, + &i); + + if (aux->lock) + spin_lock_irqsave(aux->lock, flags); + + val = readl_relaxed(aux->reg) & + ~(aux->masks->eq_sel_mask << aux->masks->eq_sel_shift); + val |= (rtbl[i].eq & aux->masks->eq_sel_mask) << + aux->masks->eq_sel_shift; + val &= ~(aux->masks->xscale_sel_mask << aux->masks->xscale_sel_shift); + val |= (rtbl[i].xscale & aux->masks->xscale_sel_mask) << + aux->masks->xscale_sel_shift; + val &= ~(aux->masks->yscale_sel_mask << aux->masks->yscale_sel_shift); + val |= (rtbl[i].yscale & aux->masks->yscale_sel_mask) << + aux->masks->yscale_sel_shift; + writel_relaxed(val, aux->reg); + + if (aux->lock) + spin_unlock_irqrestore(aux->lock, flags); + + return 0; +} + +static struct clk_ops clk_aux_ops = { + .recalc_rate = clk_aux_recalc_rate, + .round_rate = clk_aux_round_rate, + .set_rate = clk_aux_set_rate, +}; + +struct clk *clk_register_aux(const char *aux_name, const char *gate_name, + const char *parent_name, unsigned long flags, void __iomem *reg, + struct aux_clk_masks *masks, struct aux_rate_tbl *rtbl, + u8 rtbl_cnt, spinlock_t *lock, struct clk **gate_clk) +{ + struct clk_aux *aux; + struct clk_init_data init; + struct clk *clk; + + if (!aux_name || !parent_name || !reg || !rtbl || !rtbl_cnt) { + pr_err("Invalid arguments passed"); + return ERR_PTR(-EINVAL); + } + + aux = kzalloc(sizeof(*aux), GFP_KERNEL); + if (!aux) { + pr_err("could not allocate aux clk\n"); + return ERR_PTR(-ENOMEM); + } + + /* struct clk_aux assignments */ + if (!masks) + aux->masks = &default_aux_masks; + else + aux->masks = masks; + + aux->reg = reg; + aux->rtbl = rtbl; + aux->rtbl_cnt = rtbl_cnt; + aux->lock = lock; + aux->hw.init = &init; + + init.name = aux_name; + init.ops = &clk_aux_ops; + init.flags = flags; + init.parent_names = &parent_name; + init.num_parents = 1; + + clk = clk_register(NULL, &aux->hw); + if (IS_ERR_OR_NULL(clk)) + goto free_aux; + + if (gate_name) { + struct clk *tgate_clk; + + tgate_clk = clk_register_gate(NULL, gate_name, aux_name, 0, reg, + aux->masks->enable_bit, 0, lock); + if (IS_ERR_OR_NULL(tgate_clk)) + goto free_aux; + + if (gate_clk) + *gate_clk = tgate_clk; + } + + return clk; + +free_aux: + kfree(aux); + pr_err("clk register failed\n"); + + return NULL; +} |