lkml.org 
[lkml]   [2014]   [Jun]   [24]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[RFC/PATCH 03/12] clk: qcom: Add support for muxes, dividers, and mux dividers
    Date
    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 <skannan@codeaurora.org>
    Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
    ---
    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 689e05bf4f95..8684ff004be6 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_MSM_GCC_8660) += gcc-msm8660.o
    diff --git a/drivers/clk/qcom/clk-generic.c b/drivers/clk/qcom/clk-generic.c
    new file mode 100644
    index 000000000000..a0d778ba7394
    --- /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 <linux/kernel.h>
    +#include <linux/export.h>
    +#include <linux/bug.h>
    +#include <linux/err.h>
    +#include <linux/clk-provider.h>
    +#include <linux/clk/msm-clk-generic.h>
    +
    +
    +/* ==================== 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 000000000000..cee38636fe0f
    --- /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 <linux/err.h>
    +#include <linux/clk-provider.h>
    +
    +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
    --
    The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
    hosted by The Linux Foundation


    \
     
     \ /
      Last update: 2014-06-25 02:41    [W:2.746 / U:0.368 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site