clk: osm: Make OSM clk driver into a cpufreq driver

Hook the OSM clks for the CPUs directly into the CPUFreq
framework. This allows us to bypass the clk framework when doing
frequency transitions.

Change-Id: If9947f6217e1f43992ba26c5dae0e4db32dcde26
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index 843e326..ecb62df 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -755,63 +755,6 @@
 		};
 	};
 
-	msm_cpufreq: qcom,msm-cpufreq {
-		compatible = "qcom,msm-cpufreq";
-		clock-names = "cpu0_clk", "cpu4_clk";
-		clocks = <&clock_cpucc CPU0_PWRCL_CLK>,
-			 <&clock_cpucc CPU4_PERFCL_CLK>;
-
-		qcom,governor-per-policy;
-
-		qcom,cpufreq-table-0 =
-			<  300000 >,
-			<  422400 >,
-			<  499200 >,
-			<  576000 >,
-			<  652800 >,
-			<  748800 >,
-			<  825600 >,
-			<  902400 >,
-			<  979200 >,
-			< 1056000 >,
-			< 1132800 >,
-			< 1209600 >,
-			< 1286400 >,
-			< 1363200 >,
-			< 1440000 >,
-			< 1516800 >,
-			< 1593600 >,
-			< 1651200 >,
-			< 1708800 >;
-
-		qcom,cpufreq-table-4 =
-			<  300000 >,
-			<  422400 >,
-			<  499200 >,
-			<  576000 >,
-			<  652800 >,
-			<  729600 >,
-			<  806400 >,
-			<  883200 >,
-			<  960000 >,
-			< 1036800 >,
-			< 1113600 >,
-			< 1190400 >,
-			< 1267200 >,
-			< 1344000 >,
-			< 1420800 >,
-			< 1497600 >,
-			< 1574400 >,
-			< 1651200 >,
-			< 1728000 >,
-			< 1804800 >,
-			< 1881600 >,
-			< 1958400 >,
-			< 2035200 >,
-			< 2092800 >,
-			< 2208000 >;
-	};
-
 	cpubw: qcom,cpubw {
 		compatible = "qcom,devbw";
 		governor = "performance";
diff --git a/drivers/clk/qcom/clk-cpu-osm.c b/drivers/clk/qcom/clk-cpu-osm.c
index 1e71204..2902f87 100644
--- a/drivers/clk/qcom/clk-cpu-osm.c
+++ b/drivers/clk/qcom/clk-cpu-osm.c
@@ -33,6 +33,8 @@
 #include <linux/regmap.h>
 #include <linux/uaccess.h>
 #include <linux/sched.h>
+#include <linux/cpufreq.h>
+#include <linux/slab.h>
 #include <soc/qcom/scm.h>
 #include <dt-bindings/clock/qcom,cpucc-sdm845.h>
 
@@ -42,6 +44,7 @@
 #include "clk-voter.h"
 #include "clk-debug.h"
 
+#define OSM_INIT_RATE			300000000UL
 #define OSM_TABLE_SIZE			40
 #define SINGLE_CORE			1
 #define MAX_CLUSTER_CNT			3
@@ -923,6 +926,193 @@
 	return cpu_clk_map[cpu];
 }
 
+static struct clk_osm *osm_configure_policy(struct cpufreq_policy *policy)
+{
+	int cpu;
+	struct clk_hw *parent, *c_parent;
+	struct clk_osm *first;
+	struct clk_osm *c, *n;
+
+	c = logical_cpu_to_clk(policy->cpu);
+	if (!c)
+		return NULL;
+
+	c_parent = clk_hw_get_parent(&c->hw);
+	if (!c_parent)
+		return NULL;
+
+	/*
+	 * Don't put any other CPUs into the policy if we're doing
+	 * per_core_dcvs
+	 */
+	if (to_clk_osm(c_parent)->per_core_dcvs)
+		return c;
+
+	first = c;
+	/* Find CPUs that share the same clock domain */
+	for_each_possible_cpu(cpu) {
+		n = logical_cpu_to_clk(cpu);
+		if (!n)
+			continue;
+
+		parent = clk_hw_get_parent(&n->hw);
+		if (!parent)
+			return NULL;
+		if (parent != c_parent)
+			continue;
+
+		cpumask_set_cpu(cpu, policy->cpus);
+		if (n->core_num == 0)
+			first = n;
+	}
+
+	return first;
+}
+
+static void
+osm_set_index(struct clk_osm *c, unsigned int index, unsigned int num)
+{
+	clk_osm_write_reg(c, index, DCVS_PERF_STATE_DESIRED_REG(num), OSM_BASE);
+
+	/* Make sure the write goes through before proceeding */
+	clk_osm_mb(c, OSM_BASE);
+}
+
+static int
+osm_cpufreq_target_index(struct cpufreq_policy *policy, unsigned int index)
+{
+	struct clk_osm *c = policy->driver_data;
+
+	osm_set_index(c, index, c->core_num);
+	return 0;
+}
+
+static unsigned int osm_cpufreq_get(unsigned int cpu)
+{
+	struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
+	struct clk_osm *c;
+	u32 index;
+
+	if (!policy)
+		return 0;
+
+	c = policy->driver_data;
+	index = clk_osm_read_reg(c, DCVS_PERF_STATE_DESIRED_REG(c->core_num));
+
+	return policy->freq_table[index].frequency;
+}
+
+static int osm_cpufreq_cpu_init(struct cpufreq_policy *policy)
+{
+	struct cpufreq_frequency_table *table;
+	struct clk_osm *c, *parent;
+	struct clk_hw *p_hw;
+	int ret;
+	unsigned int i;
+	unsigned int xo_kHz;
+
+	c = osm_configure_policy(policy);
+	if (!c) {
+		pr_err("no clock for CPU%d\n", policy->cpu);
+		return -ENODEV;
+	}
+
+	p_hw = clk_hw_get_parent(&c->hw);
+	if (!p_hw) {
+		pr_err("no parent clock for CPU%d\n", policy->cpu);
+		return -ENODEV;
+	}
+
+	parent = to_clk_osm(p_hw);
+	c->vbases[OSM_BASE] = parent->vbases[OSM_BASE];
+
+	p_hw = clk_hw_get_parent(p_hw);
+	if (!p_hw) {
+		pr_err("no xo clock for CPU%d\n", policy->cpu);
+		return -ENODEV;
+	}
+	xo_kHz = clk_hw_get_rate(p_hw) / 1000;
+
+	table = kcalloc(OSM_TABLE_SIZE + 1, sizeof(*table), GFP_KERNEL);
+	if (!table)
+		return -ENOMEM;
+
+	for (i = 0; i < OSM_TABLE_SIZE; i++) {
+		u32 data, src, div, lval, core_count;
+
+		data = clk_osm_read_reg(c, FREQ_REG + i * OSM_REG_SIZE);
+		src = (data & GENMASK(31, 30)) >> 30;
+		div = (data & GENMASK(29, 28)) >> 28;
+		lval = data & GENMASK(7, 0);
+		core_count = CORE_COUNT_VAL(data);
+
+		if (!src)
+			table[i].frequency = OSM_INIT_RATE / 1000;
+		else
+			table[i].frequency = xo_kHz * lval;
+		table[i].driver_data = table[i].frequency;
+
+		if (core_count != MAX_CORE_COUNT)
+			table[i].frequency = CPUFREQ_ENTRY_INVALID;
+
+		/* Two of the same frequencies means end of table */
+		if (i > 0 && table[i - 1].driver_data == table[i].driver_data) {
+			struct cpufreq_frequency_table *prev = &table[i - 1];
+
+			if (prev->frequency == CPUFREQ_ENTRY_INVALID) {
+				prev->flags = CPUFREQ_BOOST_FREQ;
+				prev->frequency = prev->driver_data;
+			}
+
+			break;
+		}
+	}
+	table[i].frequency = CPUFREQ_TABLE_END;
+
+	ret = cpufreq_table_validate_and_show(policy, table);
+	if (ret) {
+		pr_err("%s: invalid frequency table: %d\n", __func__, ret);
+		goto err;
+	}
+
+	policy->driver_data = c;
+
+	clk_osm_enable(&parent->hw);
+	udelay(300);
+
+	return 0;
+
+err:
+	kfree(table);
+	return ret;
+}
+
+static int osm_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+{
+	kfree(policy->freq_table);
+	policy->freq_table = NULL;
+	return 0;
+}
+
+static struct freq_attr *osm_cpufreq_attr[] = {
+	&cpufreq_freq_attr_scaling_available_freqs,
+	&cpufreq_freq_attr_scaling_boost_freqs,
+	NULL
+};
+
+static struct cpufreq_driver qcom_osm_cpufreq_driver = {
+	.flags		= CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK |
+			  CPUFREQ_HAVE_GOVERNOR_PER_POLICY,
+	.verify		= cpufreq_generic_frequency_table_verify,
+	.target_index	= osm_cpufreq_target_index,
+	.get		= osm_cpufreq_get,
+	.init		= osm_cpufreq_cpu_init,
+	.exit		= osm_cpufreq_cpu_exit,
+	.name		= "osm-cpufreq",
+	.attr		= osm_cpufreq_attr,
+	.boost_enabled	= true,
+};
+
 static inline int clk_osm_count_ns(struct clk_osm *c, u64 nsec)
 {
 	u64 temp;
@@ -2890,16 +3080,13 @@
 	return 0;
 }
 
-static unsigned long init_rate = 300000000;
-
 static int clk_cpu_osm_driver_probe(struct platform_device *pdev)
 {
-	int rc = 0, cpu, i;
+	int rc = 0, i;
 	int pvs_ver = 0;
 	u32 pte_efuse, val;
 	int num_clks = ARRAY_SIZE(osm_qcom_clk_hws);
 	struct clk *ext_xo_clk, *clk;
-	struct clk_osm *c, *parent;
 	struct device *dev = &pdev->dev;
 	struct clk_onecell_data *clk_data;
 	char l3speedbinstr[] = "qcom,l3-speedbin0-v0";
@@ -3207,7 +3394,7 @@
 	get_online_cpus();
 
 	/* Set the L3 clock to run off GPLL0 and enable OSM for the domain */
-	rc = clk_set_rate(l3_clk.hw.clk, init_rate);
+	rc = clk_set_rate(l3_clk.hw.clk, OSM_INIT_RATE);
 	if (rc) {
 		dev_err(&pdev->dev, "Unable to set init rate on L3 cluster, rc=%d\n",
 			rc);
@@ -3217,43 +3404,12 @@
 		     "Failed to enable clock for L3\n");
 	udelay(300);
 
-	/* Set CPU clocks to run off GPLL0 and enable OSM for both domains */
-	for_each_online_cpu(cpu) {
-		c = logical_cpu_to_clk(cpu);
-		if (!c) {
-			pr_err("no clock device for CPU=%d\n", cpu);
-			return -EINVAL;
-		}
-
-		parent = to_clk_osm(clk_hw_get_parent(&c->hw));
-		if (!parent->per_core_dcvs) {
-			if (cpu >= 0 && cpu <= 3)
-				c = logical_cpu_to_clk(0);
-			else if (cpu >= 4 && cpu <= 7)
-				c = logical_cpu_to_clk(4);
-			if (!c)
-				return -EINVAL;
-		}
-
-		rc = clk_set_rate(c->hw.clk, init_rate);
-		if (rc) {
-			dev_err(&pdev->dev, "Unable to set init rate on %s, rc=%d\n",
-					clk_hw_get_name(&parent->hw), rc);
-			goto provider_err;
-		}
-		WARN(clk_prepare_enable(c->hw.clk),
-					"Failed to enable OSM for %s\n",
-					clk_hw_get_name(&parent->hw));
-		udelay(300);
+	/* Configure default rate to lowest frequency */
+	for (i = 0; i < MAX_CORE_COUNT; i++) {
+		osm_set_index(&pwrcl_clk, 0, i);
+		osm_set_index(&perfcl_clk, 0, i);
 	}
 
-	/*
-	 * Add always-on votes for the CPU cluster clocks since we do not want
-	 * to re-enable OSM at any point.
-	 */
-	clk_prepare_enable(pwrcl_clk.hw.clk);
-	clk_prepare_enable(perfcl_clk.hw.clk);
-
 	populate_opp_table(pdev);
 	populate_debugfs_dir(&l3_clk);
 	populate_debugfs_dir(&pwrcl_clk);
@@ -3261,18 +3417,24 @@
 
 	of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
 	register_cpu_cycle_counter_cb(&cb);
-	pr_info("OSM driver inited\n");
 	put_online_cpus();
 
+	rc = cpufreq_register_driver(&qcom_osm_cpufreq_driver);
+	if (rc)
+		goto provider_err;
+
+	pr_info("OSM CPUFreq driver inited\n");
 	return 0;
+
 provider_err:
 	if (clk_data)
 		devm_kfree(&pdev->dev, clk_data->clks);
 clk_err:
 	devm_kfree(&pdev->dev, clk_data);
 exit:
-	dev_err(&pdev->dev, "OSM driver failed to initialize, rc=%d\n", rc);
-	panic("Unable to Setup OSM");
+	dev_err(&pdev->dev, "OSM CPUFreq driver failed to initialize, rc=%d\n",
+		rc);
+	panic("Unable to Setup OSM CPUFreq");
 }
 
 static const struct of_device_id match_table[] = {