Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 14b40d78 authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "clk: qcom: Add support for debugfs measure clock"

parents 675cce74 bcc43f7a
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -787,9 +787,12 @@
		#reset-cells = <1>;
	};

	clock_gpucc: qcom,gpucc {
		compatible = "qcom,dummycc";
		clock-output-names = "gpucc_clocks";
	clock_gpucc: qcom,gpucc@3d90000 {
		compatible = "qcom,gpucc-kona", "syscon";
		reg = <0x3d90000 0x9000>;
		reg-names = "cc_base";
		vdd_cx-supply = <&VDD_CX_LEVEL>;
		vdd_mx-supply = <&VDD_MX_LEVEL>;
		#clock-cells = <1>;
		#reset-cells = <1>;
	};
+3 −3
Original line number Diff line number Diff line
@@ -62,8 +62,8 @@ struct clk_core {
	struct clk_core		*parent;
	const char		**parent_names;
	struct clk_core		**parents;
	u8			num_parents;
	u8			new_parent_index;
	unsigned int		num_parents;
	unsigned int		new_parent_index;
	unsigned long		rate;
	unsigned long		req_rate;
	unsigned long		new_rate;
@@ -2563,7 +2563,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 */
+1 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ clk-qcom-y += clk-regmap-divider.o
clk-qcom-y += clk-regmap-mux.o
clk-qcom-y += clk-regmap-mux-div.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-y += gdsc-regulator.o
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o

+2 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@

#include "clk-branch.h"
#include "clk-regmap.h"
#include "clk-debug.h"

static bool clk_branch_in_hwcg_mode(const struct clk_branch *br)
{
@@ -363,6 +364,7 @@ const struct clk_ops clk_branch2_ops = {
	.recalc_rate = clk_branch2_recalc_rate,
	.set_flags = clk_branch_set_flags,
	.list_registers = clk_branch2_list_registers,
	.debug_init = clk_debug_measure_add,
};
EXPORT_SYMBOL_GPL(clk_branch2_ops);

+290 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2016, The Linux Foundation. All rights reserved. */

#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 <linux/bitops.h>

#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, &regval);

	/* Wait for timer to become ready. */
	while ((regval & XO_DIV4_CNT_DONE) != 0) {
		cpu_relax();
		regmap_read(regmap, status_reg, &regval);
	}

	/* Run measurement and wait for completion. */
	regmap_write(regmap, ctl_reg, (CNT_EN|ticks));

	regmap_read(regmap, status_reg, &regval);

	while ((regval & XO_DIV4_CNT_DONE) == 0) {
		cpu_relax();
		regmap_read(regmap, status_reg, &regval);
	}

	/* Return measured ticks. */
	regmap_read(regmap, status_reg, &regval);
	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, &regval);
		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, &regval);
		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, &regval);
			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, &regval);
	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, &regval);
	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, &regval);
	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);
Loading