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

Commit 32f610a2 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "clk: qcom: Add A7 CPU clock driver for sdxpoorwills"

parents c295e603 86a69554
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
Qualcomm Application A7 CPU clock driver
-------------------------------------

It is the clock controller driver which provides higher frequency
clocks and allows A7 CPU frequency scaling on sdxpoorwills based platforms.

Required properties:
- compatible : shall contain only one of the following:
		"qcom,cpu-sdxpoorwills",
- clocks : Phandle to the clock device.
- clock-names: Names of the used clocks.
- qcom,a7cc-init-rate = Initial rate which needs to be set from cpu driver.
- reg : shall contain base register offset and size.
- reg-names : Names of the bases for the above registers.
- vdd_dig_ao-supply : The regulator powering the APSS PLL.
- cpu-vdd-supply : The regulator powering the APSS RCG.
- qcom,rcg-reg-offset : Register offset for APSS RCG.
- qcom,speedX-bin-vZ :	A table of CPU frequency (Hz) to regulator voltage (uV) mapping.
			Format: <freq uV>
			This represents the max frequency possible for each possible
			power configuration for a CPU that's binned as speed bin X,
			speed bin revision Z. Speed bin values can be between [0-7]
			and the version can be between [0-3].
- #clock-cells : shall contain 1.

Optional properties :
- reg-names: "efuse",

Example:
	clock_cpu: qcom,clock-a7@17808100 {
		compatible = "qcom,cpu-sdxpoorwills";
		clocks = <&clock_rpmh RPMH_CXO_CLK_A>;
		clock-names = "xo_ao";
		qcom,a7cc-init-rate = <1497600000>;
		reg = <0x17808100 0x7F10>;
		reg-names = "apcs_pll";

		vdd_dig_ao-supply = <&pmxpoorwills_s5_level_ao>;
		cpu-vdd-supply = <&pmxpoorwills_s5_level_ao>;
		qcom,rcg-reg-offset = <0x7F08>;
		qcom,speed0-bin-v0 =
			<          0 RPMH_REGULATOR_LEVEL_OFF>,
			<  345600000 RPMH_REGULATOR_LEVEL_LOW_SVS>,
			<  576000000 RPMH_REGULATOR_LEVEL_SVS>,
			< 1094400000 RPMH_REGULATOR_LEVEL_NOM>,
			< 1497600000 RPMH_REGULATOR_LEVEL_TURBO>;
		#clock-cells = <1>;
	};
+9 −0
Original line number Diff line number Diff line
@@ -243,4 +243,13 @@ config MDM_GCC_SDXPOORWILLS
	  Say Y if you want to use peripheral devices such as UART, SPI,
	  i2c, USB, SD/eMMC, etc.

config MDM_CLOCK_CPU_SDXPOORWILLS
	tristate "SDXPOORWILLS CPU Clock Controller"
	depends on COMMON_CLK_QCOM
	help
	Support for the cpu clock controller on sdxpoorwills
	based devices.
	Say Y if you want to support CPU clock scaling using
	CPUfreq drivers for dyanmic power management.

source "drivers/clk/qcom/mdss/Kconfig"
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ obj-$(CONFIG_CLOCK_QPNP_DIV) += clk-qpnp-div.o
obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o
obj-$(CONFIG_IPQ_GCC_806X) += gcc-ipq806x.o
obj-$(CONFIG_IPQ_LCC_806X) += lcc-ipq806x.o
obj-$(CONFIG_MDM_CLOCK_CPU_SDXPOORWILLS) += clk-cpu-a7.o
obj-$(CONFIG_MDM_GCC_9615) += gcc-mdm9615.o
obj-$(CONFIG_MDM_GCC_SDXPOORWILLS) += gcc-sdxpoorwills.o
obj-$(CONFIG_MDM_LCC_9615) += lcc-mdm9615.o
+718 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 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/cpu.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <dt-bindings/clock/qcom,cpu-a7.h>

#include "clk-alpha-pll.h"
#include "clk-debug.h"
#include "clk-rcg.h"
#include "clk-regmap-mux-div.h"
#include "common.h"
#include "vdd-level-sdm845.h"

#define SYS_APC0_AUX_CLK_SRC	1

#define PLL_MODE_REG		0x0
#define PLL_OPMODE_RUN		0x1
#define PLL_OPMODE_REG		0x38
#define PLL_MODE_OUTCTRL	BIT(0)

#define to_clk_regmap_mux_div(_hw) \
	container_of(to_clk_regmap(_hw), struct clk_regmap_mux_div, clkr)

static DEFINE_VDD_REGULATORS(vdd_cx, VDD_CX_NUM, 1, vdd_corner);
static DEFINE_VDD_REGS_INIT(vdd_cpu, 1);

enum apcs_clk_parent_index {
	XO_AO_INDEX,
	SYS_APC0_AUX_CLK_INDEX,
	APCS_CPU_PLL_INDEX,
};

enum {
	P_SYS_APC0_AUX_CLK,
	P_APCS_CPU_PLL,
	P_BI_TCXO_AO,
};

static const struct parent_map apcs_clk_parent_map[] = {
	[XO_AO_INDEX] = { P_BI_TCXO_AO, 0 },
	[SYS_APC0_AUX_CLK_INDEX] = { P_SYS_APC0_AUX_CLK, 1 },
	[APCS_CPU_PLL_INDEX] = { P_APCS_CPU_PLL, 5 },
};

static const char *const apcs_clk_parent_name[] = {
	[XO_AO_INDEX] = "bi_tcxo_ao",
	[SYS_APC0_AUX_CLK_INDEX] = "sys_apc0_aux_clk",
	[APCS_CPU_PLL_INDEX] = "apcs_cpu_pll",
};

static int a7cc_clk_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
						unsigned long prate, u8 index)
{
	struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw);

	return __mux_div_set_src_div(cpuclk, cpuclk->parent_map[index].cfg,
					cpuclk->div);
}

static int a7cc_clk_set_parent(struct clk_hw *hw, u8 index)
{
	/*
	 * Since a7cc_clk_set_rate_and_parent() is defined and set_parent()
	 * will never gets called from clk_change_rate() so return 0.
	 */
	return 0;
}

static int a7cc_clk_set_rate(struct clk_hw *hw, unsigned long rate,
						unsigned long prate)
{
	struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw);

	/*
	 * Parent is same as the last rate.
	 * Here just configure new div.
	 */
	return __mux_div_set_src_div(cpuclk, cpuclk->src, cpuclk->div);
}

static int a7cc_clk_determine_rate(struct clk_hw *hw,
					struct clk_rate_request *req)
{
	int ret;
	u32 div = 1;
	struct clk_hw *xo, *apc0_auxclk_hw, *apcs_cpu_pll_hw;
	unsigned long apc0_auxclk_rate, rate = req->rate;
	struct clk_rate_request parent_req = { };
	struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw);
	unsigned long mask = BIT(cpuclk->hid_width) - 1;

	xo = clk_hw_get_parent_by_index(hw, XO_AO_INDEX);
	if (rate == clk_hw_get_rate(xo)) {
		req->best_parent_hw = xo;
		req->best_parent_rate = rate;
		cpuclk->div = div;
		cpuclk->src = cpuclk->parent_map[XO_AO_INDEX].cfg;
		return 0;
	}

	apc0_auxclk_hw = clk_hw_get_parent_by_index(hw, SYS_APC0_AUX_CLK_INDEX);
	apcs_cpu_pll_hw = clk_hw_get_parent_by_index(hw, APCS_CPU_PLL_INDEX);

	apc0_auxclk_rate = clk_hw_get_rate(apc0_auxclk_hw);
	if (rate <= apc0_auxclk_rate) {
		req->best_parent_hw = apc0_auxclk_hw;
		req->best_parent_rate = apc0_auxclk_rate;

		div = DIV_ROUND_UP((2 * req->best_parent_rate), rate) - 1;
		div = min_t(unsigned long, div, mask);

		req->rate = clk_rcg2_calc_rate(req->best_parent_rate, 0,
							0, 0, div);
		cpuclk->src = cpuclk->parent_map[SYS_APC0_AUX_CLK_INDEX].cfg;
	} else {
		parent_req.rate = rate;
		parent_req.best_parent_hw = apcs_cpu_pll_hw;

		req->best_parent_hw = apcs_cpu_pll_hw;
		ret = __clk_determine_rate(req->best_parent_hw, &parent_req);
		if (ret)
			return ret;

		req->best_parent_rate = parent_req.rate;
		cpuclk->src = cpuclk->parent_map[APCS_CPU_PLL_INDEX].cfg;
	}
	cpuclk->div = div;

	return 0;
}

static void a7cc_clk_list_registers(struct seq_file *f, struct clk_hw *hw)
{
	struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw);
	int i = 0, size = 0, val;

	static struct clk_register_data data[] = {
		{"CMD_RCGR", 0x0},
		{"CFG_RCGR", 0x4},
	};

	size = ARRAY_SIZE(data);
	for (i = 0; i < size; i++) {
		regmap_read(cpuclk->clkr.regmap,
				cpuclk->reg_offset + data[i].offset, &val);
		seq_printf(f, "%20s: 0x%.8x\n", data[i].name, val);
	}
}

static unsigned long a7cc_clk_recalc_rate(struct clk_hw *hw,
					unsigned long prate)
{
	struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw);
	const char *name = clk_hw_get_name(hw);
	struct clk_hw *parent;
	int ret = 0;
	unsigned long parent_rate;
	u32 i, div, src = 0;
	u32 num_parents = clk_hw_get_num_parents(hw);

	ret = mux_div_get_src_div(cpuclk, &src, &div);
	if (ret)
		return ret;

	for (i = 0; i < num_parents; i++) {
		if (src == cpuclk->parent_map[i].cfg) {
			parent = clk_hw_get_parent_by_index(hw, i);
			parent_rate = clk_hw_get_rate(parent);
			return clk_rcg2_calc_rate(parent_rate, 0, 0, 0, div);
		}
	}
	pr_err("%s: Can't find parent %d\n", name, src);
	return ret;
}

static int a7cc_clk_enable(struct clk_hw *hw)
{
	return clk_regmap_mux_div_ops.enable(hw);
}

static void a7cc_clk_disable(struct clk_hw *hw)
{
	clk_regmap_mux_div_ops.disable(hw);
}

static u8 a7cc_clk_get_parent(struct clk_hw *hw)
{
	return clk_regmap_mux_div_ops.get_parent(hw);
}

/*
 * We use the notifier function for switching to a temporary safe configuration
 * (mux and divider), while the APSS pll is reconfigured.
 */
static int a7cc_notifier_cb(struct notifier_block *nb, unsigned long event,
			     void *data)
{
	int ret = 0;
	struct clk_regmap_mux_div *cpuclk = container_of(nb,
					struct clk_regmap_mux_div, clk_nb);

	if (event == PRE_RATE_CHANGE)
		/* set the mux to safe source(sys_apc0_aux_clk) & div */
		ret = __mux_div_set_src_div(cpuclk, SYS_APC0_AUX_CLK_SRC, 1);

	if (event == ABORT_RATE_CHANGE)
		pr_err("Error in configuring PLL - stay at safe src only\n");

	return notifier_from_errno(ret);
}

static const struct clk_ops a7cc_clk_ops = {
	.enable = a7cc_clk_enable,
	.disable = a7cc_clk_disable,
	.get_parent = a7cc_clk_get_parent,
	.set_rate = a7cc_clk_set_rate,
	.set_parent = a7cc_clk_set_parent,
	.set_rate_and_parent = a7cc_clk_set_rate_and_parent,
	.determine_rate = a7cc_clk_determine_rate,
	.recalc_rate = a7cc_clk_recalc_rate,
	.debug_init = clk_debug_measure_add,
	.list_registers = a7cc_clk_list_registers,
};

/*
 * As per HW, sys_apc0_aux_clk runs at 300MHz and configured by BOOT
 * So adding it as dummy clock.
 */

static struct clk_dummy sys_apc0_aux_clk = {
	.rrate = 300000000,
	.hw.init = &(struct clk_init_data){
		.name = "sys_apc0_aux_clk",
		.ops = &clk_dummy_ops,
	},
};

/* Initial configuration for 1497.6MHz(Turbo) */
static const struct pll_config apcs_cpu_pll_config = {
	.l = 0x4E,
};

static struct pll_vco trion_vco[] = {
	{ 249600000, 2000000000, 0 },
};

static struct clk_alpha_pll apcs_cpu_pll = {
	.type = TRION_PLL,
	.vco_table = trion_vco,
	.num_vco = ARRAY_SIZE(trion_vco),
	.clkr.hw.init = &(struct clk_init_data){
		.name = "apcs_cpu_pll",
		.parent_names = (const char *[]){ "bi_tcxo_ao" },
		.num_parents = 1,
		.ops = &clk_trion_pll_ops,
		VDD_CX_FMAX_MAP4(LOWER, 345600000,
				LOW, 576000000,
				NOMINAL, 1094400000,
				HIGH, 1497600000),
	},
};

static struct clk_regmap_mux_div apcs_clk = {
	.hid_width  = 5,
	.hid_shift  = 0,
	.src_width  = 3,
	.src_shift  = 8,
	.safe_src = 1,
	.safe_div = 1,
	.parent_map = apcs_clk_parent_map,
	.clk_nb.notifier_call = a7cc_notifier_cb,
	.clkr.hw.init = &(struct clk_init_data) {
		.name = "apcs_clk",
		.parent_names = apcs_clk_parent_name,
		.num_parents = 3,
		.vdd_class = &vdd_cpu,
		.flags = CLK_SET_RATE_PARENT,
		.ops = &a7cc_clk_ops,
	},
};

static const struct of_device_id match_table[] = {
	{ .compatible = "qcom,cpu-sdxpoorwills" },
	{}
};

static const struct regmap_config cpu_regmap_config = {
	.reg_bits               = 32,
	.reg_stride             = 4,
	.val_bits               = 32,
	.max_register           = 0x7F10,
	.fast_io                = true,
};

static struct clk_hw *cpu_clks_hws[] = {
	[SYS_APC0_AUX_CLK] = &sys_apc0_aux_clk.hw,
	[APCS_CPU_PLL] = &apcs_cpu_pll.clkr.hw,
	[APCS_CLK] = &apcs_clk.clkr.hw,
};

static void a7cc_clk_get_speed_bin(struct platform_device *pdev, int *bin,
							int *version)
{
	struct resource *res;
	void __iomem *base;
	u32 pte_efuse, valid;

	*bin = 0;
	*version = 0;

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse");
	if (!res) {
		dev_info(&pdev->dev,
			"No speed/PVS binning available. Defaulting to 0!\n");
		return;
	}

	base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
	if (!base) {
		dev_info(&pdev->dev,
			"Unable to read efuse data. Defaulting to 0!\n");
		return;
	}

	pte_efuse = readl_relaxed(base);
	devm_iounmap(&pdev->dev, base);

	*bin = pte_efuse & 0x7;
	valid = (pte_efuse >> 3) & 0x1;
	*version = (pte_efuse >> 4) & 0x3;

	if (!valid) {
		dev_info(&pdev->dev, "Speed bin not set. Defaulting to 0!\n");
		*bin = 0;
	} else {
		dev_info(&pdev->dev, "Speed bin: %d\n", *bin);
	}

	dev_info(&pdev->dev, "PVS version: %d\n", *version);
}

static int a7cc_clk_get_fmax_vdd_class(struct platform_device *pdev,
			struct clk_init_data *clk_intd, char *prop_name)
{
	struct device_node *of = pdev->dev.of_node;
	int prop_len, i, j;
	struct clk_vdd_class *vdd = clk_intd->vdd_class;
	int num = vdd->num_regulators + 1;
	u32 *array;

	if (!of_find_property(of, prop_name, &prop_len)) {
		dev_err(&pdev->dev, "missing %s\n", prop_name);
		return -EINVAL;
	}

	prop_len /= sizeof(u32);
	if (prop_len % num) {
		dev_err(&pdev->dev, "bad length %d\n", prop_len);
		return -EINVAL;
	}

	prop_len /= num;
	vdd->level_votes = devm_kzalloc(&pdev->dev, prop_len * sizeof(int),
					GFP_KERNEL);
	if (!vdd->level_votes)
		return -ENOMEM;

	vdd->vdd_uv = devm_kzalloc(&pdev->dev,
				prop_len * sizeof(int) * (num - 1), GFP_KERNEL);
	if (!vdd->vdd_uv)
		return -ENOMEM;

	clk_intd->rate_max = devm_kzalloc(&pdev->dev,
				prop_len * sizeof(unsigned long), GFP_KERNEL);
	if (!clk_intd->rate_max)
		return -ENOMEM;

	array = devm_kzalloc(&pdev->dev,
			prop_len * sizeof(u32) * num, GFP_KERNEL);
	if (!array)
		return -ENOMEM;

	of_property_read_u32_array(of, prop_name, array, prop_len * num);
	for (i = 0; i < prop_len; i++) {
		clk_intd->rate_max[i] = array[num * i];
		for (j = 1; j < num; j++) {
			vdd->vdd_uv[(num - 1) * i + (j - 1)] =
					array[num * i + j];
		}
	}

	devm_kfree(&pdev->dev, array);
	vdd->num_levels = prop_len;
	vdd->cur_level = prop_len;
	clk_intd->num_rate_max = prop_len;

	return 0;
}

/*
 *  Find the voltage level required for a given clock rate.
 */
static int find_vdd_level(struct clk_init_data *clk_intd, unsigned long rate)
{
	int level;

	for (level = 0; level < clk_intd->num_rate_max; level++)
		if (rate <= clk_intd->rate_max[level])
			break;

	if (level == clk_intd->num_rate_max) {
		pr_err("Rate %lu for %s is greater than highest Fmax\n", rate,
				clk_intd->name);
		return -EINVAL;
	}

	return level;
}

static int
a7cc_clk_add_opp(struct clk_hw *hw, struct device *dev, unsigned long max_rate)
{
	unsigned long rate = 0;
	int level, uv, j = 1;
	long ret;
	struct clk_init_data *clk_intd =  (struct clk_init_data *)hw->init;
	struct clk_vdd_class *vdd = clk_intd->vdd_class;

	if (IS_ERR_OR_NULL(dev)) {
		pr_err("%s: Invalid parameters\n", __func__);
		return -EINVAL;
	}

	while (1) {
		rate = clk_intd->rate_max[j++];
		level = find_vdd_level(clk_intd, rate);
		if (level <= 0) {
			pr_warn("clock-cpu: no corner for %lu.\n", rate);
			return -EINVAL;
		}

		uv = vdd->vdd_uv[level];
		if (uv < 0) {
			pr_warn("clock-cpu: no uv for %lu.\n", rate);
			return -EINVAL;
		}

		ret = dev_pm_opp_add(dev, rate, uv);
		if (ret) {
			pr_warn("clock-cpu: failed to add OPP for %lu\n", rate);
			return rate;
		}

		if (rate >= max_rate)
			break;
	}

	return 0;
}

static void a7cc_clk_print_opp_table(int a7_cpu)
{
	struct dev_pm_opp *oppfmax, *oppfmin;
	unsigned long apc_fmax, apc_fmin;
	u32 max_a7ss_index = apcs_clk.clkr.hw.init->num_rate_max;

	apc_fmax = apcs_clk.clkr.hw.init->rate_max[max_a7ss_index - 1];
	apc_fmin = apcs_clk.clkr.hw.init->rate_max[1];

	rcu_read_lock();

	oppfmax = dev_pm_opp_find_freq_exact(get_cpu_device(a7_cpu),
					apc_fmax, true);
	oppfmin = dev_pm_opp_find_freq_exact(get_cpu_device(a7_cpu),
					apc_fmin, true);
	pr_info("Clock_cpu: OPP voltage for %lu: %ld\n", apc_fmin,
		dev_pm_opp_get_voltage(oppfmin));
	pr_info("Clock_cpu: OPP voltage for %lu: %ld\n", apc_fmax,
		dev_pm_opp_get_voltage(oppfmax));

	rcu_read_unlock();
}

static void a7cc_clk_populate_opp_table(struct platform_device *pdev)
{
	unsigned long apc_fmax;
	int cpu, a7_cpu = 0;
	u32 max_a7ss_index = apcs_clk.clkr.hw.init->num_rate_max;

	apc_fmax = apcs_clk.clkr.hw.init->rate_max[max_a7ss_index - 1];

	for_each_possible_cpu(cpu) {
		a7_cpu = cpu;
		WARN(a7cc_clk_add_opp(&apcs_clk.clkr.hw, get_cpu_device(cpu),
				apc_fmax),
				"Failed to add OPP levels for apcs_clk\n");
	}
	/* One time print during bootup */
	dev_info(&pdev->dev, "OPP tables populated (cpu %d)\n", a7_cpu);

	a7cc_clk_print_opp_table(a7_cpu);
}

static int a7cc_driver_probe(struct platform_device *pdev)
{
	struct clk *clk;
	void __iomem *base;
	u32 opmode_regval, mode_regval;
	struct resource *res;
	struct clk_onecell_data *data;
	struct device *dev = &pdev->dev;
	struct device_node *of = pdev->dev.of_node;
	int i, ret, speed_bin, version, cpu;
	int num_clks = ARRAY_SIZE(cpu_clks_hws);
	u32 a7cc_clk_init_rate = 0;
	char prop_name[] = "qcom,speedX-bin-vX";
	struct clk *ext_xo_clk;

	/* Require the RPMH-XO clock to be registered before */
	ext_xo_clk = devm_clk_get(dev, "xo_ao");
	if (IS_ERR(ext_xo_clk)) {
		if (PTR_ERR(ext_xo_clk) != -EPROBE_DEFER)
			dev_err(dev, "Unable to get xo clock\n");
		return PTR_ERR(ext_xo_clk);
	}

	/* Get speed bin information */
	a7cc_clk_get_speed_bin(pdev, &speed_bin, &version);

	/* Rail Regulator for apcs_pll */
	vdd_cx.regulator[0] = devm_regulator_get(&pdev->dev, "vdd_dig_ao");
	if (IS_ERR(vdd_cx.regulator[0])) {
		if (!(PTR_ERR(vdd_cx.regulator[0]) == -EPROBE_DEFER))
			dev_err(&pdev->dev,
				"Unable to get vdd_dig_ao regulator\n");
		return PTR_ERR(vdd_cx.regulator[0]);
	}

	/* Rail Regulator for APSS a7ss mux */
	vdd_cpu.regulator[0] = devm_regulator_get(&pdev->dev, "cpu-vdd");
	if (IS_ERR(vdd_cpu.regulator[0])) {
		if (!(PTR_ERR(vdd_cpu.regulator[0]) == -EPROBE_DEFER))
			dev_err(&pdev->dev,
				"Unable to get cpu-vdd regulator\n");
		return PTR_ERR(vdd_cpu.regulator[0]);
	}

	snprintf(prop_name, ARRAY_SIZE(prop_name),
			"qcom,speed%d-bin-v%d", speed_bin, version);

	ret = a7cc_clk_get_fmax_vdd_class(pdev,
		(struct clk_init_data *)apcs_clk.clkr.hw.init, prop_name);
	if (ret) {
		dev_err(&pdev->dev,
		"Can't get speed bin for apcs_clk. Falling back to zero\n");
		ret = a7cc_clk_get_fmax_vdd_class(pdev,
				(struct clk_init_data *)apcs_clk.clkr.hw.init,
				"qcom,speed0-bin-v0");
		if (ret) {
			dev_err(&pdev->dev,
			"Unable to get speed bin for apcs_clk freq-corner mapping info\n");
			return ret;
		}
	}

	ret = of_property_read_u32(of, "qcom,a7cc-init-rate",
						&a7cc_clk_init_rate);
	if (ret) {
		dev_err(&pdev->dev,
			"unable to find qcom,a7cc_clk_init_rate property,ret=%d\n",
			ret);
		return -EINVAL;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apcs_pll");
	base = devm_ioremap_resource(dev, res);
	if (IS_ERR(base)) {
		dev_err(&pdev->dev, "Failed to map apcs_cpu_pll register base\n");
		return PTR_ERR(base);
	}

	apcs_cpu_pll.clkr.regmap = devm_regmap_init_mmio(dev, base,
						&cpu_regmap_config);
	if (IS_ERR(apcs_cpu_pll.clkr.regmap)) {
		dev_err(&pdev->dev, "Couldn't get regmap for apcs_cpu_pll\n");
		return PTR_ERR(apcs_cpu_pll.clkr.regmap);
	}

	ret = of_property_read_u32(of, "qcom,rcg-reg-offset",
						&apcs_clk.reg_offset);
	if (ret) {
		dev_err(&pdev->dev,
			"unable to find qcom,rcg-reg-offset property,ret=%d\n",
			ret);
		return -EINVAL;
	}

	apcs_clk.clkr.regmap = apcs_cpu_pll.clkr.regmap;

	/* Read PLLs OPMODE and mode register */
	ret = regmap_read(apcs_cpu_pll.clkr.regmap, PLL_OPMODE_REG,
							&opmode_regval);
	if (ret)
		return ret;

	ret = regmap_read(apcs_cpu_pll.clkr.regmap, PLL_MODE_REG,
							&mode_regval);
	if (ret)
		return ret;

	/* Configure APSS PLL only if it is not enabled and running */
	if (!(opmode_regval & PLL_OPMODE_RUN) &&
				!(mode_regval & PLL_MODE_OUTCTRL))
		clk_trion_pll_configure(&apcs_cpu_pll,
			apcs_cpu_pll.clkr.regmap, &apcs_cpu_pll_config);

	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	data->clk_num = num_clks;

	data->clks = devm_kzalloc(dev, num_clks * sizeof(struct clk *),
					GFP_KERNEL);
	if (!data->clks)
		return -ENOMEM;

	/* Register clocks with clock framework */
	for (i = 0; i < num_clks; i++) {
		clk = devm_clk_register(dev, cpu_clks_hws[i]);
		if (IS_ERR(clk))
			return PTR_ERR(clk);
		data->clks[i] = clk;
	}

	ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, data);
	if (ret) {
		dev_err(&pdev->dev, "CPU clock driver registeration failed\n");
		return ret;
	}

	ret = clk_notifier_register(apcs_cpu_pll.clkr.hw.clk, &apcs_clk.clk_nb);
	if (ret) {
		dev_err(dev, "failed to register clock notifier: %d\n", ret);
		return ret;
	}

	/* Put proxy vote for APSS PLL */
	clk_prepare_enable(apcs_cpu_pll.clkr.hw.clk);

	/* Set to TURBO boot frequency */
	ret = clk_set_rate(apcs_clk.clkr.hw.clk, a7cc_clk_init_rate);
	if (ret)
		dev_err(&pdev->dev, "Unable to set init rate on apcs_clk\n");

	/*
	 * We don't want the CPU clocks to be turned off at late init
	 * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the
	 * refcount of these clocks. Any cpufreq/hotplug manager can assume
	 * that the clocks have already been prepared and enabled by the time
	 * they take over.
	 */

	get_online_cpus();
	for_each_online_cpu(cpu)
		WARN(clk_prepare_enable(apcs_clk.clkr.hw.clk),
			"Unable to turn on CPU clock\n");
	put_online_cpus();

	/* Remove proxy vote for APSS PLL */
	clk_disable_unprepare(apcs_cpu_pll.clkr.hw.clk);

	a7cc_clk_populate_opp_table(pdev);

	dev_info(dev, "CPU clock Driver probed successfully\n");

	return ret;
}

static struct platform_driver a7_clk_driver = {
	.probe = a7cc_driver_probe,
	.driver = {
		.name = "qcom-cpu-sdxpoorwills",
		.of_match_table = match_table,
	},
};

static int __init a7_clk_init(void)
{
	return platform_driver_register(&a7_clk_driver);
}
subsys_initcall(a7_clk_init);

static void __exit a7_clk_exit(void)
{
	platform_driver_unregister(&a7_clk_driver);
}
module_exit(a7_clk_exit);

MODULE_ALIAS("platform:cpu");
MODULE_DESCRIPTION("A7 CPU clock Driver");
MODULE_LICENSE("GPL v2");
+21 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 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 _DT_BINDINGS_CLK_MSM_CPU_A7_H
#define _DT_BINDINGS_CLK_MSM_CPU_A7_H

#define SYS_APC0_AUX_CLK	0
#define APCS_CPU_PLL		1
#define APCS_CLK		2

#endif