From fbadd521a2bbec09a5e663142c281fcb1e24479e Mon Sep 17 00:00:00 2001 From: Deepak Katragadda Date: Mon, 21 Nov 2016 17:50:13 +0530 Subject: [PATCH] clk: qcom: Add support for debugfs measure clock Introduce the clk_debug_mux which would support clock frequency measurement from debugfs. Change-Id: I81c32a876b33f5a7773485a76897ff9cbed45a76 Signed-off-by: Taniya Das Signed-off-by: Deepak Katragadda Signed-off-by: David Dai Signed-off-by: David Collins --- drivers/clk/clk.c | 2 +- drivers/clk/qcom/Makefile | 2 +- drivers/clk/qcom/clk-branch.c | 2 + drivers/clk/qcom/clk-debug.c | 291 ++++++++++++++++++++++++++++++++++ drivers/clk/qcom/clk-debug.h | 126 +++++++++++++++ drivers/clk/qcom/clk-dummy.c | 2 + include/linux/clk-provider.h | 1 + 7 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 drivers/clk/qcom/clk-debug.c create mode 100644 drivers/clk/qcom/clk-debug.h diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index c0990703ce54..6a09e76b4895 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -2434,7 +2434,7 @@ static int clk_core_set_parent_nolock(struct clk_core *core, if (!core) return 0; - if (core->parent == parent) + if (core->parent == parent && !(core->flags & CLK_IS_MEASURE)) return 0; /* verify ops for for multi-parent clks */ diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index bc840e48de8f..bf92d3c44fe9 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -14,7 +14,7 @@ clk-qcom-y += clk-regmap-mux-div.o clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o clk-qcom-y += clk-hfpll.o clk-qcom-y += reset.o -clk-qcom-y += clk-dummy.o +clk-qcom-y += clk-dummy.o clk-debug.o clk-qcom-y += gdsc-regulator.o clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o diff --git a/drivers/clk/qcom/clk-branch.c b/drivers/clk/qcom/clk-branch.c index 43ad05b584d7..07f91876a250 100644 --- a/drivers/clk/qcom/clk-branch.c +++ b/drivers/clk/qcom/clk-branch.c @@ -13,6 +13,7 @@ #include #include "clk-branch.h" +#include "clk-debug.h" static bool clk_branch_in_hwcg_mode(const struct clk_branch *br) { @@ -144,6 +145,7 @@ const struct clk_ops clk_branch2_ops = { .enable = clk_branch2_enable, .disable = clk_branch2_disable, .is_enabled = clk_is_enabled_regmap, + .debug_init = clk_debug_measure_add, }; EXPORT_SYMBOL_GPL(clk_branch2_ops); diff --git a/drivers/clk/qcom/clk-debug.c b/drivers/clk/qcom/clk-debug.c new file mode 100644 index 000000000000..0baab4d37ef3 --- /dev/null +++ b/drivers/clk/qcom/clk-debug.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-regmap.h" +#include "clk-debug.h" + +static struct clk_hw *measure; + +static DEFINE_SPINLOCK(clk_reg_lock); +static DEFINE_MUTEX(clk_debug_lock); + +#define TCXO_DIV_4_HZ 4800000 +#define SAMPLE_TICKS_1_MS 0x1000 +#define SAMPLE_TICKS_14_MS 0x10000 + +#define XO_DIV4_CNT_DONE BIT(25) +#define CNT_EN BIT(20) +#define MEASURE_CNT GENMASK(24, 0) + +/* Sample clock for 'ticks' reference clock ticks. */ +static u32 run_measurement(unsigned int ticks, struct regmap *regmap, + u32 ctl_reg, u32 status_reg) +{ + u32 regval; + + /* Stop counters and set the XO4 counter start value. */ + regmap_write(regmap, ctl_reg, ticks); + + regmap_read(regmap, status_reg, ®val); + + /* Wait for timer to become ready. */ + while ((regval & XO_DIV4_CNT_DONE) != 0) { + cpu_relax(); + regmap_read(regmap, status_reg, ®val); + } + + /* Run measurement and wait for completion. */ + regmap_write(regmap, ctl_reg, (CNT_EN|ticks)); + + regmap_read(regmap, status_reg, ®val); + + while ((regval & XO_DIV4_CNT_DONE) == 0) { + cpu_relax(); + regmap_read(regmap, status_reg, ®val); + } + + /* Return measured ticks. */ + regmap_read(regmap, status_reg, ®val); + regval &= MEASURE_CNT; + + /* Stop the counters */ + regmap_write(regmap, ctl_reg, ticks); + + return regval; +} + +/* + * Perform a hardware rate measurement for a given clock. + * FOR DEBUG USE ONLY: Measurements take ~15 ms! + */ +static unsigned long clk_debug_mux_measure_rate(struct clk_hw *hw) +{ + unsigned long flags, ret = 0; + u32 gcc_xo4_reg, multiplier = 1; + u64 raw_count_short, raw_count_full; + struct clk_debug_mux *meas = to_clk_measure(hw); + struct measure_clk_data *data = meas->priv; + + clk_prepare_enable(data->cxo); + + spin_lock_irqsave(&clk_reg_lock, flags); + + /* Enable CXO/4 and RINGOSC branch. */ + regmap_read(meas->regmap[GCC], data->xo_div4_cbcr, &gcc_xo4_reg); + gcc_xo4_reg |= BIT(0); + regmap_write(meas->regmap[GCC], data->xo_div4_cbcr, gcc_xo4_reg); + + /* + * The ring oscillator counter will not reset if the measured clock + * is not running. To detect this, run a short measurement before + * the full measurement. If the raw results of the two are the same + * then the clock must be off. + */ + + /* Run a short measurement. (~1 ms) */ + raw_count_short = run_measurement(SAMPLE_TICKS_1_MS, meas->regmap[GCC], + data->ctl_reg, data->status_reg); + + /* Run a full measurement. (~14 ms) */ + raw_count_full = run_measurement(SAMPLE_TICKS_14_MS, meas->regmap[GCC], + data->ctl_reg, data->status_reg); + + gcc_xo4_reg &= ~BIT(0); + regmap_write(meas->regmap[GCC], data->xo_div4_cbcr, gcc_xo4_reg); + + /* Return 0 if the clock is off. */ + if (raw_count_full == raw_count_short) + ret = 0; + else { + /* Compute rate in Hz. */ + raw_count_full = ((raw_count_full * 10) + 15) * TCXO_DIV_4_HZ; + do_div(raw_count_full, ((SAMPLE_TICKS_14_MS * 10) + 35)); + ret = (raw_count_full * multiplier); + } + + spin_unlock_irqrestore(&clk_reg_lock, flags); + + clk_disable_unprepare(data->cxo); + + return ret; +} + +static u8 clk_debug_mux_get_parent(struct clk_hw *hw) +{ + struct clk_debug_mux *meas = to_clk_measure(hw); + int i, num_parents = clk_hw_get_num_parents(hw); + struct clk_hw *hw_clk = clk_hw_get_parent(hw); + + if (!hw_clk) + return 0; + + for (i = 0; i < num_parents; i++) { + if (!strcmp(meas->parent[i].parents, + clk_hw_get_name(hw_clk))) { + pr_debug("%s: clock parent - %s, index %d\n", __func__, + meas->parent[i].parents, i); + return i; + } + } + + return 0; +} + +static int clk_debug_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_debug_mux *meas = to_clk_measure(hw); + u32 regval = 0; + int dbg_cc = 0; + + dbg_cc = meas->parent[index].dbg_cc; + + if (dbg_cc != GCC) { + /* Update the recursive debug mux */ + regmap_read(meas->regmap[dbg_cc], + meas->parent[index].mux_offset, ®val); + regval &= ~(meas->parent[index].mux_sel_mask << + meas->parent[index].mux_sel_shift); + regval |= (meas->parent[index].dbg_cc_mux_sel & + meas->parent[index].mux_sel_mask) << + meas->parent[index].mux_sel_shift; + regmap_write(meas->regmap[dbg_cc], + meas->parent[index].mux_offset, regval); + + regmap_read(meas->regmap[dbg_cc], + meas->parent[index].post_div_offset, ®val); + regval &= ~(meas->parent[index].post_div_mask << + meas->parent[index].post_div_shift); + regval |= ((meas->parent[index].post_div_val - 1) & + meas->parent[index].post_div_mask) << + meas->parent[index].post_div_shift; + regmap_write(meas->regmap[dbg_cc], + meas->parent[index].post_div_offset, regval); + + /* Not all recursive muxes have a DEBUG clock. */ + if (meas->parent[index].cbcr_offset != U32_MAX) { + regmap_read(meas->regmap[dbg_cc], + meas->parent[index].cbcr_offset, ®val); + regval |= BIT(0); + regmap_write(meas->regmap[dbg_cc], + meas->parent[index].cbcr_offset, regval); + } + } + + /* Update the debug sel for GCC */ + regmap_read(meas->regmap[GCC], meas->debug_offset, ®val); + regval &= ~(meas->src_sel_mask << meas->src_sel_shift); + regval |= (meas->parent[index].prim_mux_sel & meas->src_sel_mask) << + meas->src_sel_shift; + regmap_write(meas->regmap[GCC], meas->debug_offset, regval); + + /* Set the GCC mux's post divider bits */ + regmap_read(meas->regmap[GCC], meas->post_div_offset, ®val); + regval &= ~(meas->post_div_mask << meas->post_div_shift); + regval |= ((meas->parent[index].prim_mux_div_val - 1) & + meas->post_div_mask) << meas->post_div_shift; + regmap_write(meas->regmap[GCC], meas->post_div_offset, regval); + + /* Turn on the GCC_DEBUG_CBCR */ + regmap_read(meas->regmap[GCC], meas->cbcr_offset, ®val); + regval |= BIT(0); + regmap_write(meas->regmap[GCC], meas->cbcr_offset, regval); + + return 0; +} + +const struct clk_ops clk_debug_mux_ops = { + .get_parent = clk_debug_mux_get_parent, + .set_parent = clk_debug_mux_set_parent, +}; +EXPORT_SYMBOL(clk_debug_mux_ops); + +static int clk_debug_measure_get(void *data, u64 *val) +{ + struct clk_hw *hw = data, *par; + struct clk_debug_mux *meas = to_clk_measure(measure); + int index; + int ret = 0; + unsigned long meas_rate, sw_rate; + + mutex_lock(&clk_debug_lock); + + ret = clk_set_parent(measure->clk, hw->clk); + if (!ret) { + par = measure; + index = clk_debug_mux_get_parent(measure); + while (par && par != hw) { + if (par->init->ops->enable) + par->init->ops->enable(par); + par = clk_hw_get_parent(par); + } + *val = clk_debug_mux_measure_rate(measure); + if (meas->parent[index].dbg_cc != GCC) + *val *= meas->parent[index].post_div_val; + *val *= meas->parent[index].prim_mux_div_val; + + /* Accommodate for any pre-set dividers */ + if (meas->parent[index].misc_div_val) + *val *= meas->parent[index].misc_div_val; + } + + meas_rate = clk_get_rate(hw->clk); + par = clk_hw_get_parent(measure); + if (!par) + return -EINVAL; + + sw_rate = clk_get_rate(par->clk); + if (sw_rate && meas_rate >= (sw_rate * 2)) + *val *= DIV_ROUND_CLOSEST(meas_rate, sw_rate); + mutex_unlock(&clk_debug_lock); + + return ret; +} + +DEFINE_DEBUGFS_ATTRIBUTE(clk_measure_fops, clk_debug_measure_get, + NULL, "%lld\n"); + +void clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry) +{ + int ret; + + if (IS_ERR_OR_NULL(measure)) { + pr_err_once("Please check if `measure` clk is registered.\n"); + return; + } + + ret = clk_set_parent(measure->clk, hw->clk); + if (ret) { + pr_debug("Unable to set %s as %s's parent, ret=%d\n", + clk_hw_get_name(hw), clk_hw_get_name(measure), ret); + return; + } + + debugfs_create_file("clk_measure", 0x444, dentry, hw, + &clk_measure_fops); +} +EXPORT_SYMBOL(clk_debug_measure_add); + +int clk_debug_measure_register(struct clk_hw *hw) +{ + if (IS_ERR_OR_NULL(measure)) { + if (hw->init->flags & CLK_IS_MEASURE) { + measure = hw; + return 0; + } + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(clk_debug_measure_register); + diff --git a/drivers/clk/qcom/clk-debug.h b/drivers/clk/qcom/clk-debug.h new file mode 100644 index 000000000000..eb40b32b66bf --- /dev/null +++ b/drivers/clk/qcom/clk-debug.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. */ + +#ifndef __QCOM_CLK_DEBUG_H__ +#define __QCOM_CLK_DEBUG_H__ + +#include "../clk.h" + +/* Debugfs Measure Clocks */ + +/** + * struct measure_clk_data - Structure of clk measure + * + * @cxo: XO clock. + * @xo_div4_cbcr: offset of debug XO/4 div register. + * @ctl_reg: offset of debug control register. + * @status_reg: offset of debug status register. + * @cbcr_offset: branch register to turn on debug mux. + */ +struct measure_clk_data { + struct clk *cxo; + u32 ctl_reg; + u32 status_reg; + u32 xo_div4_cbcr; +}; + +/** + * List of Debug clock controllers. + */ +enum debug_cc { + GCC, + CAM_CC, + DISP_CC, + NPU_CC, + GPU_CC, + VIDEO_CC, + CPU_CC, + MAX_NUM_CC, +}; + +/** + * struct clk_src - Structure of clock source for debug mux + * + * @parents: clock name to be used as parent for debug mux. + * @prim_mux_sel: debug mux index at global clock controller. + * @prim_mux_div_val: PLL post-divider setting for the primary mux. + * @dbg_cc: indicates the clock controller for recursive debug + * clock controllers. + * @dbg_cc_mux_sel: indicates the debug mux index at recursive debug mux. + * @mux_sel_mask: indicates the mask for the mux selection. + * @mux_sel_shift: indicates the shift required for mux selection. + * @post_div_mask: indicates the post div mask to be used at recursive + * debug mux. + * @post_div_shift: indicates the shift required for post divider + * configuration. + * @post_div_val: indicates the post div value to be used at recursive + * debug mux. + * @mux_offset: the debug mux offset. + * @post_div_offset: register with post-divider settings for the debug mux. + * @cbcr_offset: branch register to turn on debug mux. + * @misc_div_val: includes any pre-set dividers in the measurement logic. + */ +struct clk_src { + const char *parents; + int prim_mux_sel; + u32 prim_mux_div_val; + enum debug_cc dbg_cc; + int dbg_cc_mux_sel; + u32 mux_sel_mask; + u32 mux_sel_shift; + u32 post_div_mask; + u32 post_div_shift; + u32 post_div_val; + u32 mux_offset; + u32 post_div_offset; + u32 cbcr_offset; + u32 misc_div_val; +}; + +#define MUX_SRC_LIST(...) \ + .parent = (struct clk_src[]){__VA_ARGS__}, \ + .num_parents = ARRAY_SIZE(((struct clk_src[]){__VA_ARGS__})) + +/** + * struct clk_debug_mux - Structure of clock debug mux + * + * @parent: structure of clk_src + * @num_parents: number of parents + * @regmap: regmaps of debug mux + * @priv: private measure_clk_data to be used by debug mux + * @debug_offset: debug mux offset. + * @post_div_offset: register with post-divider settings for the debug mux. + * @cbcr_offset: branch register to turn on debug mux. + * @src_sel_mask: indicates the mask to be used for src selection in + primary mux. + * @src_sel_shift: indicates the shift required for source selection in + primary mux. + * @post_div_mask: indicates the post div mask to be used for the primary + mux. + * @post_div_shift: indicates the shift required for post divider + selection in primary mux. + * @hw: handle between common and hardware-specific interfaces. + */ +struct clk_debug_mux { + struct clk_src *parent; + int num_parents; + struct regmap **regmap; + void *priv; + u32 debug_offset; + u32 post_div_offset; + u32 cbcr_offset; + u32 src_sel_mask; + u32 src_sel_shift; + u32 post_div_mask; + u32 post_div_shift; + struct clk_hw hw; +}; + +#define to_clk_measure(_hw) container_of((_hw), struct clk_debug_mux, hw) + +extern const struct clk_ops clk_debug_mux_ops; + +int clk_debug_measure_register(struct clk_hw *hw); +void clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry); + +#endif diff --git a/drivers/clk/qcom/clk-dummy.c b/drivers/clk/qcom/clk-dummy.c index 32c430fe2f1a..c782643f0206 100644 --- a/drivers/clk/qcom/clk-dummy.c +++ b/drivers/clk/qcom/clk-dummy.c @@ -9,6 +9,7 @@ #include #include "common.h" +#include "clk-debug.h" #define to_clk_dummy(_hw) container_of(_hw, struct clk_dummy, hw) @@ -47,6 +48,7 @@ const struct clk_ops clk_dummy_ops = { .set_rate = dummy_clk_set_rate, .round_rate = dummy_clk_round_rate, .recalc_rate = dummy_clk_recalc_rate, + .debug_init = clk_debug_measure_add, }; EXPORT_SYMBOL(clk_dummy_ops); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 2ae7604783dd..7fcc7eee89ad 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -32,6 +32,7 @@ #define CLK_OPS_PARENT_ENABLE BIT(12) /* duty cycle call may be forwarded to the parent clock */ #define CLK_DUTY_CYCLE_PARENT BIT(13) +#define CLK_IS_MEASURE BIT(14) /* clock used for measurement only */ struct clk; struct clk_hw;