From 151d7e91baaa4016ba687b80e8f7ccead62d6c72 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 25 Mar 2014 13:37:55 -0700 Subject: [PATCH 165/182] clk: qcom: Add support for muxes, dividers, and mux dividers The Krait CPU clocks are made up of muxes and dividers with a handful of sources. Add a set of clk_ops that allow us to configure these clocks so we can support CPU frequency scaling on Krait CPUs. Based on code originally written by Saravana Kannan. Cc: Saravana Kannan Signed-off-by: Stephen Boyd --- drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clk-generic.c | 405 +++++++++++++++++++++++++++++++++++ include/linux/clk/msm-clk-generic.h | 208 ++++++++++++++++++ 3 files changed, 614 insertions(+) create mode 100644 drivers/clk/qcom/clk-generic.c create mode 100644 include/linux/clk/msm-clk-generic.h diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index df2a1b3..2cc6039 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o clk-qcom-y += clk-rcg.o clk-qcom-y += clk-rcg2.o clk-qcom-y += clk-branch.o +clk-qcom-y += clk-generic.o clk-qcom-y += reset.o obj-$(CONFIG_IPQ_GCC_806X) += gcc-ipq806x.o diff --git a/drivers/clk/qcom/clk-generic.c b/drivers/clk/qcom/clk-generic.c new file mode 100644 index 0000000..a0d778b --- /dev/null +++ b/drivers/clk/qcom/clk-generic.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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. + */ + +#include +#include +#include +#include +#include +#include + + +/* ==================== Mux clock ==================== */ + +static int mux_set_parent(struct clk_hw *hw, u8 sel) +{ + struct mux_clk *mux = to_mux_clk(hw); + + if (mux->parent_map) + sel = mux->parent_map[sel]; + + return mux->ops->set_mux_sel(mux, sel); +} + +static u8 mux_get_parent(struct clk_hw *hw) +{ + struct mux_clk *mux = to_mux_clk(hw); + int num_parents = __clk_get_num_parents(hw->clk); + int i; + u8 sel; + + sel = mux->ops->get_mux_sel(mux); + if (mux->parent_map) { + for (i = 0; i < num_parents; i++) + if (sel == mux->parent_map[i]) + return i; + WARN(1, "Can't find parent\n"); + return -EINVAL; + } + + return sel; +} + +static int mux_enable(struct clk_hw *hw) +{ + struct mux_clk *mux = to_mux_clk(hw); + if (mux->ops->enable) + return mux->ops->enable(mux); + return 0; +} + +static void mux_disable(struct clk_hw *hw) +{ + struct mux_clk *mux = to_mux_clk(hw); + if (mux->ops->disable) + return mux->ops->disable(mux); +} + +static struct clk *mux_get_safe_parent(struct clk_hw *hw) +{ + int i; + struct mux_clk *mux = to_mux_clk(hw); + int num_parents = __clk_get_num_parents(hw->clk); + + if (!mux->has_safe_parent) + return NULL; + + i = mux->safe_sel; + if (mux->parent_map) + for (i = 0; i < num_parents; i++) + if (mux->safe_sel == mux->parent_map[i]) + break; + + return clk_get_parent_by_index(hw->clk, i); +} + +const struct clk_ops clk_ops_gen_mux = { + .enable = mux_enable, + .disable = mux_disable, + .set_parent = mux_set_parent, + .get_parent = mux_get_parent, + .determine_rate = __clk_mux_determine_rate, + .get_safe_parent = mux_get_safe_parent, +}; +EXPORT_SYMBOL_GPL(clk_ops_gen_mux); + +/* ==================== Divider clock ==================== */ + +static long __div_round_rate(struct div_data *data, unsigned long rate, + struct clk *parent, unsigned int *best_div, unsigned long *best_prate, + bool set_parent) +{ + unsigned int div, min_div, max_div, _best_div = 1; + unsigned long prate, _best_prate = 0, rrate = 0, req_prate, actual_rate; + unsigned int numer; + + rate = max(rate, 1UL); + + min_div = max(data->min_div, 1U); + max_div = min(data->max_div, (unsigned int) (ULONG_MAX / rate)); + + /* + * div values are doubled for half dividers. + * Adjust for that by picking a numer of 2. + */ + numer = data->is_half_divider ? 2 : 1; + + if (!set_parent) { + prate = *best_prate * numer; + div = DIV_ROUND_UP(prate, rate); + div = clamp(1U, div, max_div); + if (best_div) + *best_div = div; + return mult_frac(*best_prate, numer, div); + } + + for (div = min_div; div <= max_div; div++) { + req_prate = mult_frac(rate, div, numer); + prate = __clk_round_rate(parent, req_prate); + if (IS_ERR_VALUE(prate)) + break; + + actual_rate = mult_frac(prate, numer, div); + if (is_better_rate(rate, rrate, actual_rate)) { + rrate = actual_rate; + _best_div = div; + _best_prate = prate; + } + + /* + * Trying higher dividers is only going to ask the parent for + * a higher rate. If it can't even output a rate higher than + * the one we request for this divider, the parent is not + * going to be able to output an even higher rate required + * for a higher divider. So, stop trying higher dividers. + */ + if (actual_rate < rate) + break; + + if (rrate <= rate) + break; + } + + if (!rrate) + return -EINVAL; + if (best_div) + *best_div = _best_div; + if (best_prate) + *best_prate = _best_prate; + + return rrate; +} + +static long div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct div_clk *d = to_div_clk(hw); + bool set_parent = __clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT; + + return __div_round_rate(&d->data, rate, __clk_get_parent(hw->clk), + NULL, parent_rate, set_parent); +} + +static int div_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long + parent_rate) +{ + struct div_clk *d = to_div_clk(hw); + int div, rc = 0; + struct div_data *data = &d->data; + + div = parent_rate / rate; + if (div != data->div) + rc = d->ops->set_div(d, div); + data->div = div; + + return rc; +} + +static int div_enable(struct clk_hw *hw) +{ + struct div_clk *d = to_div_clk(hw); + if (d->ops && d->ops->enable) + return d->ops->enable(d); + return 0; +} + +static void div_disable(struct clk_hw *hw) +{ + struct div_clk *d = to_div_clk(hw); + if (d->ops && d->ops->disable) + return d->ops->disable(d); +} + +static unsigned long div_recalc_rate(struct clk_hw *hw, unsigned long prate) +{ + struct div_clk *d = to_div_clk(hw); + unsigned int div = d->data.div; + + if (d->ops && d->ops->get_div) + div = max(d->ops->get_div(d), 1); + div = max(div, 1U); + + if (!d->ops || !d->ops->set_div) + d->data.min_div = d->data.max_div = div; + d->data.div = div; + + return prate / div; +} + +const struct clk_ops clk_ops_div = { + .enable = div_enable, + .disable = div_disable, + .round_rate = div_round_rate, + .set_rate = div_set_rate, + .recalc_rate = div_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_ops_div); + +/* ==================== Mux_div clock ==================== */ + +static int mux_div_clk_enable(struct clk_hw *hw) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + + if (md->ops->enable) + return md->ops->enable(md); + return 0; +} + +static void mux_div_clk_disable(struct clk_hw *hw) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + + if (md->ops->disable) + return md->ops->disable(md); +} + +static long __mux_div_round_rate(struct clk_hw *hw, unsigned long rate, + struct clk **best_parent, int *best_div, unsigned long *best_prate) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + unsigned int i; + unsigned long rrate, best = 0, _best_div = 0, _best_prate = 0; + struct clk *_best_parent = 0; + int num_parents = __clk_get_num_parents(hw->clk); + bool set_parent = __clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT; + + for (i = 0; i < num_parents; i++) { + int div; + unsigned long prate; + struct clk *p = clk_get_parent_by_index(hw->clk, i); + + rrate = __div_round_rate(&md->data, rate, p, &div, &prate, + set_parent); + + if (is_better_rate(rate, best, rrate)) { + best = rrate; + _best_div = div; + _best_prate = prate; + _best_parent = p; + } + + if (rate <= rrate) + break; + } + + if (best_div) + *best_div = _best_div; + if (best_prate) + *best_prate = _best_prate; + if (best_parent) + *best_parent = _best_parent; + + if (best) + return best; + return -EINVAL; +} + +static long mux_div_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return __mux_div_round_rate(hw, rate, NULL, NULL, parent_rate); +} + +/* requires enable lock to be held */ +static int __set_src_div(struct mux_div_clk *md, u8 src_sel, u32 div) +{ + int rc; + + rc = md->ops->set_src_div(md, src_sel, div); + if (!rc) { + md->data.div = div; + md->src_sel = src_sel; + } + + return rc; +} + +/* Must be called after handoff to ensure parent clock rates are initialized */ +static int safe_parent_init_once(struct clk_hw *hw) +{ + unsigned long rrate; + u32 best_div; + struct clk *best_parent; + struct mux_div_clk *md = to_mux_div_clk(hw); + + if (IS_ERR(md->safe_parent)) + return -EINVAL; + if (!md->safe_freq || md->safe_parent) + return 0; + + rrate = __mux_div_round_rate(hw, md->safe_freq, &best_parent, + &best_div, NULL); + + if (rrate == md->safe_freq) { + md->safe_div = best_div; + md->safe_parent = best_parent; + } else { + md->safe_parent = ERR_PTR(-EINVAL); + return -EINVAL; + } + return 0; +} + +static int +__mux_div_clk_set_rate_and_parent(struct clk_hw *hw, u8 index, u32 div) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + int rc; + + rc = safe_parent_init_once(hw); + if (rc) + return rc; + + return __set_src_div(md, index, div); +} + +static int mux_div_clk_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate, u8 index) +{ + return __mux_div_clk_set_rate_and_parent(hw, index, parent_rate / rate); +} + +static int mux_div_clk_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + return __mux_div_clk_set_rate_and_parent(hw, md->src_sel, + parent_rate / rate); +} + +static int mux_div_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + return __mux_div_clk_set_rate_and_parent(hw, md->parent_map[index], + md->data.div); +} + +static u8 mux_div_clk_get_parent(struct clk_hw *hw) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + int num_parents = __clk_get_num_parents(hw->clk); + u32 i, div, sel; + + md->ops->get_src_div(md, &sel, &div); + md->src_sel = sel; + + for (i = 0; i < num_parents; i++) + if (sel == md->parent_map[i]) + return i; + WARN(1, "Can't find parent\n"); + return -EINVAL; +} + +static unsigned long +mux_div_clk_recalc_rate(struct clk_hw *hw, unsigned long prate) +{ + struct mux_div_clk *md = to_mux_div_clk(hw); + u32 div, sel; + + md->ops->get_src_div(md, &sel, &div); + + return prate / div; +} + +const struct clk_ops clk_ops_mux_div_clk = { + .enable = mux_div_clk_enable, + .disable = mux_div_clk_disable, + .set_rate_and_parent = mux_div_clk_set_rate_and_parent, + .set_rate = mux_div_clk_set_rate, + .set_parent = mux_div_clk_set_parent, + .round_rate = mux_div_clk_round_rate, + .get_parent = mux_div_clk_get_parent, + .recalc_rate = mux_div_clk_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_ops_mux_div_clk); diff --git a/include/linux/clk/msm-clk-generic.h b/include/linux/clk/msm-clk-generic.h new file mode 100644 index 0000000..cee3863 --- /dev/null +++ b/include/linux/clk/msm-clk-generic.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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. + */ + +#ifndef __QCOM_CLK_GENERIC_H__ +#define __QCOM_CLK_GENERIC_H__ + +#include +#include + +static inline bool is_better_rate(unsigned long req, unsigned long best, + unsigned long new) +{ + if (IS_ERR_VALUE(new)) + return false; + + return (req <= new && new < best) || (best < req && best < new); +} + +/* ==================== Mux clock ==================== */ + +struct mux_clk; + +struct clk_mux_ops { + int (*set_mux_sel)(struct mux_clk *clk, int sel); + int (*get_mux_sel)(struct mux_clk *clk); + + /* Optional */ + bool (*is_enabled)(struct mux_clk *clk); + int (*enable)(struct mux_clk *clk); + void (*disable)(struct mux_clk *clk); +}; + +struct mux_clk { + /* Parents in decreasing order of preference for obtaining rates. */ + u8 *parent_map; + bool has_safe_parent; + u8 safe_sel; + const struct clk_mux_ops *ops; + + /* Fields not used by helper function. */ + void __iomem *base; + u32 offset; + u32 en_offset; + int en_reg; + u32 mask; + u32 shift; + u32 en_mask; + void *priv; + + struct clk_hw hw; +}; + +static inline struct mux_clk *to_mux_clk(struct clk_hw *hw) +{ + return container_of(hw, struct mux_clk, hw); +} + +extern const struct clk_ops clk_ops_gen_mux; + +/* ==================== Divider clock ==================== */ + +struct div_clk; + +struct clk_div_ops { + int (*set_div)(struct div_clk *clk, int div); + int (*get_div)(struct div_clk *clk); + bool (*is_enabled)(struct div_clk *clk); + int (*enable)(struct div_clk *clk); + void (*disable)(struct div_clk *clk); +}; + +struct div_data { + unsigned int div; + unsigned int min_div; + unsigned int max_div; + /* + * Indicate whether this divider clock supports half-interger divider. + * If it is, all the min_div and max_div have been doubled. It means + * they are 2*N. + */ + bool is_half_divider; +}; + +struct div_clk { + struct div_data data; + + /* Optional */ + const struct clk_div_ops *ops; + + /* Fields not used by helper function. */ + void __iomem *base; + u32 offset; + u32 mask; + u32 shift; + u32 en_mask; + void *priv; + struct clk_hw hw; +}; + +static inline struct div_clk *to_div_clk(struct clk_hw *hw) +{ + return container_of(hw, struct div_clk, hw); +} + +extern const struct clk_ops clk_ops_div; + +#define DEFINE_FIXED_DIV_CLK(clk_name, _div, _parent) \ +static struct div_clk clk_name = { \ + .data = { \ + .max_div = _div, \ + .min_div = _div, \ + .div = _div, \ + }, \ + .hw.init = &(struct clk_init_data){ \ + .parent_names = (const char *[]){ _parent }, \ + .num_parents = 1, \ + .name = #clk_name, \ + .ops = &clk_ops_div, \ + .flags = CLK_SET_RATE_PARENT, \ + } \ +} + +/* ==================== Mux Div clock ==================== */ + +struct mux_div_clk; + +/* + * struct mux_div_ops + * the enable and disable ops are optional. + */ + +struct mux_div_ops { + int (*set_src_div)(struct mux_div_clk *, u32 src_sel, u32 div); + void (*get_src_div)(struct mux_div_clk *, u32 *src_sel, u32 *div); + int (*enable)(struct mux_div_clk *); + void (*disable)(struct mux_div_clk *); + bool (*is_enabled)(struct mux_div_clk *); +}; + +/* + * struct mux_div_clk - combined mux/divider clock + * @priv + parameters needed by ops + * @safe_freq + when switching rates from A to B, the mux div clock will + instead switch from A -> safe_freq -> B. This allows the + mux_div clock to change rates while enabled, even if this + behavior is not supported by the parent clocks. + + If changing the rate of parent A also causes the rate of + parent B to change, then safe_freq must be defined. + + safe_freq is expected to have a source clock which is always + on and runs at only one rate. + * @parents + list of parents and mux indicies + * @ops + function pointers for hw specific operations + * @src_sel + the mux index which will be used if the clock is enabled. + */ + +struct mux_div_clk { + /* Required parameters */ + const struct mux_div_ops *ops; + struct div_data data; + u8 *parent_map; + + struct clk_hw hw; + + /* Internal */ + u32 src_sel; + + /* Optional parameters */ + void *priv; + void __iomem *base; + u32 div_mask; + u32 div_offset; + u32 div_shift; + u32 src_mask; + u32 src_offset; + u32 src_shift; + u32 en_mask; + u32 en_offset; + + u32 safe_div; + struct clk *safe_parent; + unsigned long safe_freq; +}; + +static inline struct mux_div_clk *to_mux_div_clk(struct clk_hw *hw) +{ + return container_of(hw, struct mux_div_clk, hw); +} + +extern const struct clk_ops clk_ops_mux_div_clk; + +#endif -- 1.7.10.4