Merge "clk: qcom: Move clock debug measure support from common code"
This commit is contained in:
commit
f9cf02e447
7 changed files with 405 additions and 339 deletions
|
@ -3006,7 +3006,7 @@ static int __init clk_debug_init(void)
|
||||||
|
|
||||||
mutex_lock(&clk_debug_lock);
|
mutex_lock(&clk_debug_lock);
|
||||||
hlist_for_each_entry(core, &clk_debug_list, debug_node) {
|
hlist_for_each_entry(core, &clk_debug_list, debug_node) {
|
||||||
clk_register_debug(core->hw, core->dentry);
|
clk_register_debug(core->hw);
|
||||||
clk_debug_create_one(core, rootdir);
|
clk_debug_create_one(core, rootdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@ void __clk_free_clk(struct clk *clk);
|
||||||
|
|
||||||
/* Debugfs API to print the enabled clocks */
|
/* Debugfs API to print the enabled clocks */
|
||||||
void clock_debug_print_enabled(void);
|
void clock_debug_print_enabled(void);
|
||||||
int clk_register_debug(struct clk_hw *hw, struct dentry *dentry);
|
int clk_register_debug(struct clk_hw *hw);
|
||||||
void clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry);
|
int clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry);
|
||||||
void clk_debug_print_hw(struct clk_core *clk, struct seq_file *f);
|
void clk_debug_print_hw(struct clk_core *clk, struct seq_file *f);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -12,7 +12,7 @@ clk-qcom-y += clk-regmap-mux.o
|
||||||
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||||
clk-qcom-y += clk-hfpll.o
|
clk-qcom-y += clk-hfpll.o
|
||||||
clk-qcom-y += reset.o clk-voter.o
|
clk-qcom-y += reset.o clk-voter.o
|
||||||
clk-qcom-y += clk-dummy.o
|
clk-qcom-y += clk-dummy.o clk-debug.o
|
||||||
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o gdsc-regulator.o
|
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o gdsc-regulator.o
|
||||||
|
|
||||||
obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
|
obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
|
||||||
|
|
277
drivers/clk/qcom/clk-debug.c
Normal file
277
drivers/clk/qcom/clk-debug.c
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2014, 2016-2017,
|
||||||
|
*
|
||||||
|
* The Linux Foundation. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 and
|
||||||
|
* only version 2 as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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/clk.h>
|
||||||
|
#include <linux/export.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "clk-regmap.h"
|
||||||
|
#include "clk-debug.h"
|
||||||
|
#include "common.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 BM(24, 0)
|
||||||
|
|
||||||
|
/* Sample clock for 'ticks' reference clock ticks. */
|
||||||
|
static u32 run_measurement(unsigned 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
|
||||||
|
multiplier = meas->multiplier + 1;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
for (i = 0; i < num_parents; i++) {
|
||||||
|
if (!strcmp(meas->parent[i].parents,
|
||||||
|
hw->init->parent_names[i])) {
|
||||||
|
pr_debug("%s: Clock name %s index %d\n", __func__,
|
||||||
|
hw->init->name, 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);
|
||||||
|
unsigned long lsb = 0;
|
||||||
|
u32 regval = 0;
|
||||||
|
int dbg_cc = 0;
|
||||||
|
|
||||||
|
dbg_cc = meas->parent[index].dbg_cc;
|
||||||
|
|
||||||
|
if (dbg_cc != GCC) {
|
||||||
|
regmap_read(meas->regmap[dbg_cc], 0x0, ®val);
|
||||||
|
|
||||||
|
/* Clear & Set post divider bits */
|
||||||
|
if (meas->parent[index].post_div_mask) {
|
||||||
|
regval &= ~meas->parent[index].post_div_mask;
|
||||||
|
lsb = find_first_bit((unsigned long *)
|
||||||
|
&meas->parent[index].post_div_mask, 32);
|
||||||
|
regval |= (meas->parent[index].post_div_val << lsb) &
|
||||||
|
meas->parent[index].post_div_mask;
|
||||||
|
meas->multiplier = meas->parent[index].post_div_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meas->parent[index].mask)
|
||||||
|
regval &= ~meas->parent[index].mask <<
|
||||||
|
meas->parent[index].shift;
|
||||||
|
else
|
||||||
|
regval &= ~meas->mask;
|
||||||
|
|
||||||
|
regval |= (meas->parent[index].next_sel & meas->mask);
|
||||||
|
|
||||||
|
if (meas->parent[index].en_mask == 0xFF)
|
||||||
|
/* Skip en_mask */
|
||||||
|
regval = regval;
|
||||||
|
else if (meas->parent[index].en_mask)
|
||||||
|
regval |= meas->parent[index].en_mask;
|
||||||
|
else
|
||||||
|
regval |= meas->en_mask;
|
||||||
|
|
||||||
|
regmap_write(meas->regmap[dbg_cc], 0x0, regval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update the debug sel for GCC */
|
||||||
|
regmap_read(meas->regmap[GCC], meas->debug_offset, ®val);
|
||||||
|
|
||||||
|
/* clear post divider bits */
|
||||||
|
regval &= ~BM(15, 12);
|
||||||
|
lsb = find_first_bit((unsigned long *)
|
||||||
|
&meas->parent[index].post_div_mask, 32);
|
||||||
|
regval |= (meas->parent[index].post_div_val << lsb) &
|
||||||
|
meas->parent[index].post_div_mask;
|
||||||
|
meas->multiplier = meas->parent[index].post_div_val;
|
||||||
|
regval &= ~meas->mask;
|
||||||
|
regval |= (meas->parent[index].sel & meas->mask);
|
||||||
|
regval |= meas->en_mask;
|
||||||
|
|
||||||
|
regmap_write(meas->regmap[GCC], meas->debug_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;
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
meas_rate = clk_get_rate(hw->clk);
|
||||||
|
sw_rate = clk_get_rate(clk_hw_get_parent(measure)->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_SIMPLE_ATTRIBUTE(clk_measure_fops, clk_debug_measure_get,
|
||||||
|
NULL, "%lld\n");
|
||||||
|
|
||||||
|
int clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry)
|
||||||
|
{
|
||||||
|
if (IS_ERR_OR_NULL(measure)) {
|
||||||
|
pr_err_once("Please check if `measure` clk is registered!!!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clk_set_parent(measure->clk, hw->clk))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
debugfs_create_file("clk_measure", S_IRUGO, dentry, hw,
|
||||||
|
&clk_measure_fops);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(clk_debug_measure_add);
|
||||||
|
|
||||||
|
int clk_register_debug(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_register_debug);
|
||||||
|
|
120
drivers/clk/qcom/clk-debug.h
Normal file
120
drivers/clk/qcom/clk-debug.h
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014, 2016-2017, The Linux Foundation. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 and
|
||||||
|
* only version 2 as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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_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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct measure_clk_data {
|
||||||
|
struct clk *cxo;
|
||||||
|
u32 xo_div4_cbcr;
|
||||||
|
u32 ctl_reg;
|
||||||
|
u32 status_reg;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Debug clock controllers.
|
||||||
|
*/
|
||||||
|
enum debug_cc {
|
||||||
|
GCC,
|
||||||
|
MMCC,
|
||||||
|
GPU,
|
||||||
|
CPU,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct clk_src - Struture of clock source for debug mux
|
||||||
|
*
|
||||||
|
* @parents: clock name to be used as parent for debug mux.
|
||||||
|
* @sel: debug mux index at global clock controller.
|
||||||
|
* @dbg_cc: indicates the clock controller for recursive debug clock
|
||||||
|
* controllers.
|
||||||
|
* @next_sel: indicates the debug mux index at recursive debug mux.
|
||||||
|
* @mask: indicates the mask required at recursive debug mux.
|
||||||
|
* @shift: indicates the shift required at recursive debug mux.
|
||||||
|
* @en_mask: indicates the enable bit mask at recursive debug mux.
|
||||||
|
* Incase the recursive debug mux does not have a enable bit,
|
||||||
|
* 0xFF should be used to indicate the same, otherwise global
|
||||||
|
* enable bit would be used.
|
||||||
|
* @post_div_mask: indicates the post div mask to be used at debug/recursive
|
||||||
|
* debug mux.
|
||||||
|
* @post_div_val: indicates the post div value to be used at debug/recursive
|
||||||
|
* debug mux.
|
||||||
|
*/
|
||||||
|
struct clk_src {
|
||||||
|
const char *parents;
|
||||||
|
int sel;
|
||||||
|
enum debug_cc dbg_cc;
|
||||||
|
int next_sel;
|
||||||
|
u32 mask;
|
||||||
|
u32 shift;
|
||||||
|
u32 en_mask;
|
||||||
|
u32 post_div_mask;
|
||||||
|
u32 post_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 - Struture of clock debug mux
|
||||||
|
*
|
||||||
|
* @parent: structure of clk_src
|
||||||
|
* @num_parents: number of parents
|
||||||
|
* @regmap: regmaps of debug mux
|
||||||
|
* @num_parent_regmap: number of regmap of debug mux
|
||||||
|
* @priv: private measure_clk_data to be used by debug mux
|
||||||
|
* @en_mask: indicates the enable bit mask at global clock
|
||||||
|
* controller debug mux.
|
||||||
|
* @mask: indicates the mask to be used at global clock
|
||||||
|
* controller debug mux.
|
||||||
|
* @debug_offset: debug mux offset.
|
||||||
|
* @hw: handle between common and hardware-specific interfaces.
|
||||||
|
* @multiplier: internally used by debug mux as post div multiplier.
|
||||||
|
*/
|
||||||
|
struct clk_debug_mux {
|
||||||
|
struct clk_src *parent;
|
||||||
|
int num_parents;
|
||||||
|
struct regmap **regmap;
|
||||||
|
int num_parent_regmap;
|
||||||
|
void *priv;
|
||||||
|
u32 en_mask;
|
||||||
|
u32 mask;
|
||||||
|
u32 debug_offset;
|
||||||
|
struct clk_hw hw;
|
||||||
|
|
||||||
|
/* internal */
|
||||||
|
u32 multiplier;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_clk_measure(_hw) container_of((_hw), struct clk_debug_mux, hw)
|
||||||
|
|
||||||
|
extern const struct clk_ops clk_debug_mux_ops;
|
||||||
|
|
||||||
|
int clk_register_debug(struct clk_hw *hw);
|
||||||
|
int clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry);
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013-2014, 2016-2017, The Linux Foundation. All rights reserved.
|
* Copyright (c) 2013-2014, 2016-2017,
|
||||||
|
*
|
||||||
|
* The Linux Foundation. All rights reserved.
|
||||||
*
|
*
|
||||||
* This software is licensed under the terms of the GNU General Public
|
* This software is licensed under the terms of the GNU General Public
|
||||||
* License version 2, as published by the Free Software Foundation, and
|
* License version 2, as published by the Free Software Foundation, and
|
||||||
|
@ -287,239 +289,4 @@ int qcom_cc_probe(struct platform_device *pdev, const struct qcom_cc_desc *desc)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(qcom_cc_probe);
|
EXPORT_SYMBOL_GPL(qcom_cc_probe);
|
||||||
|
|
||||||
/* Debugfs Support */
|
|
||||||
static struct clk_hw *measure;
|
|
||||||
|
|
||||||
DEFINE_SPINLOCK(clk_reg_lock);
|
|
||||||
|
|
||||||
/* Sample clock for 'ticks' reference clock ticks. */
|
|
||||||
static u32 run_measurement(unsigned 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 & BIT(25)) != 0) {
|
|
||||||
cpu_relax();
|
|
||||||
regmap_read(regmap, status_reg, ®val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run measurement and wait for completion. */
|
|
||||||
regmap_write(regmap, ctl_reg, (BIT(20)|ticks));
|
|
||||||
regmap_read(regmap, ctl_reg, ®val);
|
|
||||||
|
|
||||||
regmap_read(regmap, status_reg, ®val);
|
|
||||||
|
|
||||||
while ((regval & BIT(25)) == 0) {
|
|
||||||
cpu_relax();
|
|
||||||
regmap_read(regmap, status_reg, ®val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return measured ticks. */
|
|
||||||
regmap_read(regmap, status_reg, ®val);
|
|
||||||
regval &= BM(24, 0);
|
|
||||||
|
|
||||||
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, sample_ticks = 0x10000, multiplier;
|
|
||||||
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);
|
|
||||||
|
|
||||||
multiplier = meas->multiplier + 1;
|
|
||||||
|
|
||||||
/* 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(0x1000, meas->regmap[GCC],
|
|
||||||
data->ctl_reg, data->status_reg);
|
|
||||||
|
|
||||||
/* Run a full measurement. (~14 ms) */
|
|
||||||
raw_count_full = run_measurement(sample_ticks, 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) * 4800000;
|
|
||||||
do_div(raw_count_full, ((sample_ticks * 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);
|
|
||||||
|
|
||||||
for (i = 0; i < num_parents; i++) {
|
|
||||||
if (!strcmp(meas->parent[i].parents,
|
|
||||||
hw->init->parent_names[i])) {
|
|
||||||
pr_debug("%s :Clock name %s index %d\n", __func__,
|
|
||||||
hw->init->name, 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);
|
|
||||||
unsigned long lsb = 0;
|
|
||||||
u32 regval = 0;
|
|
||||||
int dbg_cc = 0;
|
|
||||||
|
|
||||||
dbg_cc = meas->parent[index].dbg_cc;
|
|
||||||
|
|
||||||
if (dbg_cc != GCC) {
|
|
||||||
regmap_read(meas->regmap[dbg_cc], 0x0, ®val);
|
|
||||||
|
|
||||||
/* Clear & Set post divider bits */
|
|
||||||
if (meas->parent[index].post_div_mask) {
|
|
||||||
regval &= ~meas->parent[index].post_div_mask;
|
|
||||||
lsb = find_first_bit((unsigned long *)
|
|
||||||
&meas->parent[index].post_div_mask, 32);
|
|
||||||
regval |= (meas->parent[index].post_div_val << lsb) &
|
|
||||||
meas->parent[index].post_div_mask;
|
|
||||||
meas->multiplier = meas->parent[index].post_div_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meas->parent[index].mask)
|
|
||||||
regval &= ~meas->parent[index].mask <<
|
|
||||||
meas->parent[index].shift;
|
|
||||||
else
|
|
||||||
regval &= ~meas->mask;
|
|
||||||
|
|
||||||
regval |= (meas->parent[index].next_sel & meas->mask);
|
|
||||||
|
|
||||||
if (meas->parent[index].en_mask == 0xFF)
|
|
||||||
/* Skip en_mask */
|
|
||||||
regval = regval;
|
|
||||||
else if (meas->parent[index].en_mask)
|
|
||||||
regval |= meas->parent[index].en_mask;
|
|
||||||
else
|
|
||||||
regval |= meas->en_mask;
|
|
||||||
|
|
||||||
regmap_write(meas->regmap[dbg_cc], 0x0, regval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* update the debug sel for GCC */
|
|
||||||
regmap_read(meas->regmap[GCC], meas->debug_offset, ®val);
|
|
||||||
|
|
||||||
/* clear post divider bits */
|
|
||||||
regval &= ~BM(15, 12);
|
|
||||||
lsb = find_first_bit((unsigned long *)
|
|
||||||
&meas->parent[index].post_div_mask, 32);
|
|
||||||
regval |= (meas->parent[index].post_div_val << lsb) &
|
|
||||||
meas->parent[index].post_div_mask;
|
|
||||||
meas->multiplier = meas->parent[index].post_div_val;
|
|
||||||
regval &= ~meas->mask;
|
|
||||||
regval |= (meas->parent[index].sel & meas->mask);
|
|
||||||
regval |= meas->en_mask;
|
|
||||||
|
|
||||||
regmap_write(meas->regmap[GCC], meas->debug_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_GPL(clk_debug_mux_ops);
|
|
||||||
|
|
||||||
static int clk_debug_measure_get(void *data, u64 *val)
|
|
||||||
{
|
|
||||||
struct clk_hw *hw = data, *par;
|
|
||||||
int ret = 0;
|
|
||||||
unsigned long meas_rate, sw_rate;
|
|
||||||
|
|
||||||
ret = clk_set_parent(measure->clk, hw->clk);
|
|
||||||
if (!ret) {
|
|
||||||
par = 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
meas_rate = clk_get_rate(hw->clk);
|
|
||||||
sw_rate = clk_get_rate(clk_hw_get_parent(measure)->clk);
|
|
||||||
if (sw_rate && meas_rate >= (sw_rate * 2))
|
|
||||||
*val *= DIV_ROUND_CLOSEST(meas_rate, sw_rate);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFINE_SIMPLE_ATTRIBUTE(clk_measure_fops, clk_debug_measure_get,
|
|
||||||
NULL, "%lld\n");
|
|
||||||
|
|
||||||
void clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry)
|
|
||||||
{
|
|
||||||
if (IS_ERR_OR_NULL(measure))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (clk_set_parent(measure->clk, hw->clk))
|
|
||||||
return;
|
|
||||||
|
|
||||||
debugfs_create_file("measure", S_IRUGO, dentry, hw,
|
|
||||||
&clk_measure_fops);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(clk_debug_measure_add);
|
|
||||||
|
|
||||||
int clk_register_debug(struct clk_hw *hw, struct dentry *dentry)
|
|
||||||
{
|
|
||||||
if (IS_ERR_OR_NULL(measure)) {
|
|
||||||
if (hw->init->flags & CLK_IS_MEASURE)
|
|
||||||
measure = hw;
|
|
||||||
if (!IS_ERR_OR_NULL(measure))
|
|
||||||
clk_debug_measure_add(hw, dentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(clk_register_debug);
|
|
||||||
|
|
||||||
MODULE_LICENSE("GPL v2");
|
MODULE_LICENSE("GPL v2");
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#ifndef __QCOM_CLK_COMMON_H__
|
#ifndef __QCOM_CLK_COMMON_H__
|
||||||
#define __QCOM_CLK_COMMON_H__
|
#define __QCOM_CLK_COMMON_H__
|
||||||
|
|
||||||
#include "../clk.h"
|
#include "clk-debug.h"
|
||||||
|
|
||||||
struct platform_device;
|
struct platform_device;
|
||||||
struct regmap_config;
|
struct regmap_config;
|
||||||
|
@ -54,107 +54,9 @@ extern int qcom_cc_probe(struct platform_device *pdev,
|
||||||
const struct qcom_cc_desc *desc);
|
const struct qcom_cc_desc *desc);
|
||||||
extern struct clk_ops clk_dummy_ops;
|
extern struct clk_ops clk_dummy_ops;
|
||||||
|
|
||||||
/* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
struct measure_clk_data {
|
|
||||||
struct clk *cxo;
|
|
||||||
u32 xo_div4_cbcr;
|
|
||||||
u32 ctl_reg;
|
|
||||||
u32 status_reg;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of Debug clock controllers.
|
|
||||||
*/
|
|
||||||
enum debug_cc {
|
|
||||||
GCC,
|
|
||||||
MMCC,
|
|
||||||
GPU,
|
|
||||||
CPU,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* struct clk_src - Struture of clock source for debug mux
|
|
||||||
*
|
|
||||||
* @parents: clock name to be used as parent for debug mux.
|
|
||||||
* @sel: debug mux index at global clock controller.
|
|
||||||
* @dbg_cc: indicates the clock controller for recursive debug clock
|
|
||||||
* controllers.
|
|
||||||
* @next_sel: indicates the debug mux index at recursive debug mux.
|
|
||||||
* @mask: indicates the mask required at recursive debug mux.
|
|
||||||
* @shift: indicates the shift required at recursive debug mux.
|
|
||||||
* @en_mask: indicates the enable bit mask at recursive debug mux.
|
|
||||||
* Incase the recursive debug mux does not have a enable bit,
|
|
||||||
* 0xFF should be used to indicate the same, otherwise global
|
|
||||||
* enable bit would be used.
|
|
||||||
* @post_div_mask: indicates the post div mask to be used at debug/recursive
|
|
||||||
* debug mux.
|
|
||||||
* @post_div_val: indicates the post div value to be used at debug/recursive
|
|
||||||
* debug mux.
|
|
||||||
*/
|
|
||||||
struct clk_src {
|
|
||||||
const char *parents;
|
|
||||||
int sel;
|
|
||||||
enum debug_cc dbg_cc;
|
|
||||||
int next_sel;
|
|
||||||
u32 mask;
|
|
||||||
u32 shift;
|
|
||||||
u32 en_mask;
|
|
||||||
u32 post_div_mask;
|
|
||||||
u32 post_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 - Struture of clock debug mux
|
|
||||||
*
|
|
||||||
* @parent: structure of clk_src
|
|
||||||
* @num_parents: number of parents
|
|
||||||
* @regmap: regmaps of debug mux
|
|
||||||
* @num_parent_regmap: number of regmap of debug mux
|
|
||||||
* @priv: private measure_clk_data to be used by debug mux
|
|
||||||
* @en_mask: indicates the enable bit mask at global clock
|
|
||||||
* controller debug mux.
|
|
||||||
* @mask: indicates the mask to be used at global clock
|
|
||||||
* controller debug mux.
|
|
||||||
* @debug_offset: Start of debug mux offset.
|
|
||||||
* @hw: handle between common and hardware-specific interfaces.
|
|
||||||
* @multiplier: internally used by debug mux as post div multiplier.
|
|
||||||
*/
|
|
||||||
struct clk_debug_mux {
|
|
||||||
struct clk_src *parent;
|
|
||||||
int num_parents;
|
|
||||||
struct regmap **regmap;
|
|
||||||
int num_parent_regmap;
|
|
||||||
void *priv;
|
|
||||||
u32 en_mask;
|
|
||||||
u32 mask;
|
|
||||||
u32 debug_offset;
|
|
||||||
struct clk_hw hw;
|
|
||||||
|
|
||||||
/* internal */
|
|
||||||
u32 multiplier;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb)
|
#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb)
|
||||||
#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb))
|
#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb))
|
||||||
|
|
||||||
#define to_clk_measure(_hw) container_of((_hw), struct clk_debug_mux, hw)
|
|
||||||
|
|
||||||
extern const struct clk_ops clk_debug_mux_ops;
|
|
||||||
|
|
||||||
#define WARN_CLK(core, name, cond, fmt, ...) do { \
|
#define WARN_CLK(core, name, cond, fmt, ...) do { \
|
||||||
clk_debug_print_hw(core, NULL); \
|
clk_debug_print_hw(core, NULL); \
|
||||||
WARN(cond, "%s: " fmt, name, ##__VA_ARGS__); \
|
WARN(cond, "%s: " fmt, name, ##__VA_ARGS__); \
|
||||||
|
|
Loading…
Add table
Reference in a new issue