lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1609842147-8161-3-git-send-email-claudiu.beznea@microchip.com>
Date:   Tue, 5 Jan 2021 12:22:26 +0200
From:   Claudiu Beznea <claudiu.beznea@...rochip.com>
To:     <rjw@...ysocki.net>, <viresh.kumar@...aro.org>,
        <robh+dt@...nel.org>
CC:     <linux-pm@...r.kernel.org>, <devicetree@...r.kernel.org>,
        <linux-kernel@...r.kernel.org>,
        Claudiu Beznea <claudiu.beznea@...rochip.com>
Subject: [PATCH 2/3] cpufreq: sama7g5: add cpufreq driver

Microchip SAMA7G5 devices supports runtime changes of CPU frequency.
This is doable by changing CPUPLL frequency along with MCK0 frequency.
Along with this CPU's regulator must be changed to accommodate new
frequencies.

Signed-off-by: Claudiu Beznea <claudiu.beznea@...rochip.com>
---
 drivers/cpufreq/Kconfig.arm          |   9 +
 drivers/cpufreq/Makefile             |   1 +
 drivers/cpufreq/cpufreq-dt-platdev.c |   2 +
 drivers/cpufreq/sama7g5-cpufreq.c    | 371 +++++++++++++++++++++++++++++++++++
 4 files changed, 383 insertions(+)
 create mode 100644 drivers/cpufreq/sama7g5-cpufreq.c

diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 1f73fa75b1a0..22ebd7c988e8 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -261,6 +261,15 @@ config ARM_SA1100_CPUFREQ
 config ARM_SA1110_CPUFREQ
 	bool
 
+config ARM_SAMA7G5_CPUFREQ
+	bool "Microchip SAMA7G5 CPUFreq support"
+	depends on SOC_SAMA7G5
+	depends on COMMON_CLK
+	select PM_OPP
+	default y
+	help
+	  This adds the CPUFreq driver support for Microchip SAMA7G5 platforms.
+
 config ARM_SCMI_CPUFREQ
 	tristate "SCMI based CPUfreq driver"
 	depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index f1b7e3dd6e5d..3900f3507f72 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
 obj-$(CONFIG_ARM_S5PV210_CPUFREQ)	+= s5pv210-cpufreq.o
 obj-$(CONFIG_ARM_SA1100_CPUFREQ)	+= sa1100-cpufreq.o
 obj-$(CONFIG_ARM_SA1110_CPUFREQ)	+= sa1110-cpufreq.o
+obj-$(CONFIG_ARM_SAMA7G5_CPUFREQ)	+= sama7g5-cpufreq.o
 obj-$(CONFIG_ARM_SCMI_CPUFREQ)		+= scmi-cpufreq.o
 obj-$(CONFIG_ARM_SCPI_CPUFREQ)		+= scpi-cpufreq.o
 obj-$(CONFIG_ARM_SPEAR_CPUFREQ)		+= spear-cpufreq.o
diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index bd2db0188cbb..61834e307236 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -153,6 +153,8 @@ static const struct of_device_id blacklist[] __initconst = {
 	{ .compatible = "qcom,msm8974", },
 	{ .compatible = "qcom,msm8960", },
 
+	{ .compatible = "microchip,sama7g5", },
+
 	{ }
 };
 
diff --git a/drivers/cpufreq/sama7g5-cpufreq.c b/drivers/cpufreq/sama7g5-cpufreq.c
new file mode 100644
index 000000000000..f961758f4ac3
--- /dev/null
+++ b/drivers/cpufreq/sama7g5-cpufreq.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SAMA7G5 CPUFREQ driver.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@...rochip.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/regulator/consumer.h>
+
+#define TOL (25000)
+
+/**
+ * struct sama7g5_cpufreq_info - sama7g5 CPUFreq driver data
+ * @cpu: CPU device
+ * @cpu: CPU clock
+ * @mck: master clock
+ * @reg: CPU regulator
+ */
+static struct sama7g5_cpufreq_info {
+	struct device *cpudev;
+	struct clk *cpuck;
+	struct clk *mck;
+	struct regulator *reg;
+} sama7g5_cpufreq;
+
+static int sama7g5_cpufreq_set_target(struct cpufreq_policy *policy,
+				      unsigned int index)
+{
+	struct cpufreq_frequency_table *freq_table = policy->freq_table;
+	struct clk *cpuck = policy->clk;
+	struct clk *mck = sama7g5_cpufreq.mck;
+	struct dev_pm_opp *opp;
+	long cur_cpuck_freq_hz, new_cpuck_freq_hz, mck_freq_hz;
+	long new_voltage, cur_voltage;
+	unsigned long tmp;
+	u32 cur_div, new_div, diff_div;
+	int ret;
+
+	cur_cpuck_freq_hz = clk_get_rate(cpuck);
+	mck_freq_hz = clk_get_rate(mck);
+	new_cpuck_freq_hz = freq_table[index].frequency * 1000;
+
+	cur_voltage = regulator_get_voltage(sama7g5_cpufreq.reg);
+	if (cur_voltage < 0) {
+		dev_err(sama7g5_cpufreq.cpudev, "Invalid voltage: %ld\n",
+			cur_voltage);
+		return cur_voltage;
+	}
+
+	opp = dev_pm_opp_find_freq_ceil(sama7g5_cpufreq.cpudev,
+					&new_cpuck_freq_hz);
+	if (IS_ERR(opp)) {
+		dev_err(sama7g5_cpufreq.cpudev, "Failed to find OPP for %lu\n",
+			new_cpuck_freq_hz);
+		return PTR_ERR(opp);
+	}
+	new_voltage = dev_pm_opp_get_voltage(opp);
+	dev_pm_opp_put(opp);
+
+	/* Scale up volgage. */
+	if (cur_voltage < new_voltage) {
+		ret = regulator_set_voltage(sama7g5_cpufreq.reg, new_voltage,
+					    new_voltage + TOL);
+		if (ret) {
+			dev_err(sama7g5_cpufreq.cpudev,
+				"Failed to scale up voltage!\n");
+			return ret;
+		}
+	}
+
+	/*
+	 * The relation b/w mck and cpuck is as follows:
+	 *
+	 *                         +--------------> cpuck
+	 *                         |
+	 *               +------+  |    +-----+
+	 *  cpupllck --> | pres |--+--> | div |---> mck
+	 *               +------+       +-----+
+	 *
+	 * So we need to do the changes as follows:
+	 *     1/ change mck to avoid its underclocking/overclocking
+	 *     2/ change the cpuck: this will lead to changing the cpupllck
+	 *        as well as changing the pres to accommodate cpuck requested
+	 *        frequency
+	 *     3/ change mck to preserve its initial frequency
+	 */
+
+	/* Scale up/down MCK. */
+	cur_div = DIV_ROUND_CLOSEST(cur_cpuck_freq_hz, mck_freq_hz);
+	new_div = DIV_ROUND_CLOSEST(new_cpuck_freq_hz, mck_freq_hz);
+	diff_div = abs(cur_div - new_div);
+
+	tmp = DIV_ROUND_CLOSEST_ULL(cur_cpuck_freq_hz, (cur_div + diff_div));
+	ret = clk_set_rate(mck, tmp);
+	if (ret) {
+		dev_err(sama7g5_cpufreq.cpudev,
+			"Failed to set - intermediary - MCK to %lu\n", tmp);
+		goto restore_regulator;
+	}
+
+	/* Set cpuck. */
+	ret = clk_set_rate(cpuck, new_cpuck_freq_hz);
+	if (ret) {
+		dev_err(sama7g5_cpufreq.cpudev,
+			"Failed to scale cpu clk frequency!\n");
+		goto restore_mck;
+	}
+
+	/* Scale down/up MCK. */
+	tmp = DIV_ROUND_CLOSEST_ULL(new_cpuck_freq_hz, new_div);
+	ret = clk_set_rate(mck, tmp);
+	if (ret) {
+		dev_err(sama7g5_cpufreq.cpudev,
+			"Failed to set - final - MCK to %lu\n", tmp);
+		goto restore_cpuck;
+	}
+
+	if (cur_voltage > new_voltage) {
+		ret = regulator_set_voltage(sama7g5_cpufreq.reg, new_voltage,
+					    new_voltage + TOL);
+		if (ret) {
+			dev_err(sama7g5_cpufreq.cpudev,
+				"Failed to scale down voltage!\n");
+			goto restore_cpuck;
+		}
+	}
+
+	return 0;
+
+restore_cpuck:
+	/* Scale up/down MCK. */
+	cur_div = DIV_ROUND_CLOSEST(new_cpuck_freq_hz, mck_freq_hz);
+	new_div = DIV_ROUND_CLOSEST(cur_cpuck_freq_hz, mck_freq_hz);
+	diff_div = abs(cur_div - new_div);
+
+	tmp = DIV_ROUND_CLOSEST_ULL(new_cpuck_freq_hz, (cur_div + diff_div));
+	clk_set_rate(mck, tmp);
+	/* Set cpuck. */
+	clk_set_rate(cpuck, cur_cpuck_freq_hz);
+restore_mck:
+	/* Scale down/up MCK. */
+	clk_set_rate(mck, mck_freq_hz);
+restore_regulator:
+	if (cur_voltage < new_voltage)
+		regulator_set_voltage(sama7g5_cpufreq.reg, cur_voltage,
+				      cur_voltage + TOL);
+
+	return ret;
+}
+
+static int sama7g5_cpufreq_drvinit(struct cpufreq_policy *policy)
+{
+	struct cpufreq_frequency_table *freq_table;
+	struct dev_pm_opp *opp;
+	unsigned long uV_min = INT_MIN, uV_max = 0;
+	unsigned long uV, freq_hz, latency;
+	int ret, i;
+
+	ret = dev_pm_opp_init_cpufreq_table(sama7g5_cpufreq.cpudev,
+					    &freq_table);
+	if (ret) {
+		dev_err(sama7g5_cpufreq.cpudev,
+			"Failed to init cpufreq table for cpu%d: %d\n",
+			policy->cpu, ret);
+		return ret;
+	}
+
+	for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+		freq_hz = freq_table[i].frequency * 1000;
+		opp = dev_pm_opp_find_freq_ceil(sama7g5_cpufreq.cpudev,
+						&freq_hz);
+		if (IS_ERR(opp))
+			continue;
+		uV = dev_pm_opp_get_voltage(opp);
+		dev_pm_opp_put(opp);
+
+		if (uV < uV_min)
+			uV_min = uV;
+		else if (uV > uV_max)
+			uV_max = uV;
+	}
+
+	if (uV_min == INT_MIN || !uV_max) {
+		dev_err(sama7g5_cpufreq.cpudev,
+			"Failed to locate min, max voltages for regulator\n");
+		dev_pm_opp_free_cpufreq_table(sama7g5_cpufreq.cpudev,
+					      &freq_table);
+		return -EINVAL;
+	}
+
+	latency = regulator_set_voltage_time(sama7g5_cpufreq.reg, uV_min,
+					     uV_max);
+	if (latency >= 0) {
+		latency *= 1000;
+		latency += dev_pm_opp_get_max_clock_latency(sama7g5_cpufreq.cpudev);
+		/* Measuements for regulator updates via i2c @1MHz. */
+		latency += 540 * 1000;
+	} else {
+		latency = 0;
+	}
+
+	if (!latency)
+		latency = CPUFREQ_ETERNAL;
+
+	policy->freq_table = freq_table;
+	policy->clk = sama7g5_cpufreq.cpuck;
+	policy->cpuinfo.transition_latency = latency;
+
+	return 0;
+}
+
+static int sama7g5_cpufreq_drvexit(struct cpufreq_policy *policy)
+{
+	dev_pm_opp_free_cpufreq_table(sama7g5_cpufreq.cpudev,
+				      &policy->freq_table);
+
+	return 0;
+}
+
+static struct cpufreq_driver sama7g5_cpufreq_driver = {
+	.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+	.verify = cpufreq_generic_frequency_table_verify,
+	.target_index = sama7g5_cpufreq_set_target,
+	.get = cpufreq_generic_get,
+	.init = sama7g5_cpufreq_drvinit,
+	.exit = sama7g5_cpufreq_drvexit,
+	.name = "sama7g5-cpufreq",
+	.attr = cpufreq_generic_attr,
+};
+
+static int sama7g5_cpufreq_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	sama7g5_cpufreq.cpudev = get_cpu_device(0);
+	if (!sama7g5_cpufreq.cpudev) {
+		dev_err(&pdev->dev, "Failed to get cpu0 device\n");
+		return -ENODEV;
+	}
+
+	sama7g5_cpufreq.cpuck = clk_get(sama7g5_cpufreq.cpudev, "cpu");
+	if (IS_ERR(sama7g5_cpufreq.cpuck)) {
+		ret = PTR_ERR(sama7g5_cpufreq.cpuck);
+		dev_err(&pdev->dev, "Failed to get cpu clock: %d\n", ret);
+		return ret;
+	}
+
+	sama7g5_cpufreq.mck = clk_get(sama7g5_cpufreq.cpudev, "mck");
+	if (IS_ERR(sama7g5_cpufreq.mck)) {
+		ret = PTR_ERR(sama7g5_cpufreq.mck);
+		dev_err(&pdev->dev, "Failed to get master clock: %d\n", ret);
+		goto free_resources;
+	}
+
+	sama7g5_cpufreq.reg = regulator_get(sama7g5_cpufreq.cpudev, "cpu");
+	if (IS_ERR(sama7g5_cpufreq.reg)) {
+		ret = PTR_ERR(sama7g5_cpufreq.reg);
+		dev_err(&pdev->dev, "Failed to get cpu regulator: %d\n", ret);
+		goto free_resources;
+	}
+
+	ret = clk_prepare_enable(sama7g5_cpufreq.cpuck);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to enable cpu clk: %d\n", ret);
+		goto free_resources;
+	}
+
+	ret = clk_prepare_enable(sama7g5_cpufreq.mck);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to enable MCK clk: %d\n", ret);
+		goto unprepare_cpuck;
+	}
+
+	ret = dev_pm_opp_of_add_table(sama7g5_cpufreq.cpudev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to init OPP table: %d\n", ret);
+		goto unprepare_mck;
+	}
+
+	ret = cpufreq_register_driver(&sama7g5_cpufreq_driver);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register sama7g5-cpufreq driver: %d\n",
+			ret);
+		goto remove_table;
+	}
+
+	return 0;
+
+remove_table:
+	dev_pm_opp_of_remove_table(sama7g5_cpufreq.cpudev);
+unprepare_mck:
+	clk_disable_unprepare(sama7g5_cpufreq.mck);
+unprepare_cpuck:
+	clk_disable_unprepare(sama7g5_cpufreq.cpuck);
+free_resources:
+	if (!IS_ERR_OR_NULL(sama7g5_cpufreq.cpuck))
+		clk_put(sama7g5_cpufreq.cpuck);
+	if (!IS_ERR_OR_NULL(sama7g5_cpufreq.mck))
+		clk_put(sama7g5_cpufreq.mck);
+	if (!IS_ERR_OR_NULL(sama7g5_cpufreq.reg))
+		regulator_put(sama7g5_cpufreq.reg);
+
+	return ret;
+}
+
+static struct platform_driver sama7g5_cpufreq_platdrv = {
+	.driver = {
+		.name	= "sama7g5-cpufreq",
+	},
+	.probe		= sama7g5_cpufreq_probe,
+};
+
+static const struct of_device_id sama7g5_cpufreq_dt_ids[] __initconst = {
+	{ .compatible = "microchip,sama7g5", },
+	{ },
+};
+
+static int __init sama7g5_cpufreq_driver_init(void)
+{
+	const struct of_device_id *match;
+	struct platform_device *pdev;
+	struct device_node *np;
+	int ret;
+
+	np = of_find_node_by_path("/");
+	if (!np)
+		return -ENODEV;
+
+	match = of_match_node(sama7g5_cpufreq_dt_ids, np);
+	of_node_put(np);
+	if (!match) {
+		pr_err("Couldn't find sama7g5-cpufreq compatible machine\n");
+		ret = -ENODEV;
+		goto put_node;
+	}
+
+	ret = platform_driver_register(&sama7g5_cpufreq_platdrv);
+	if (ret)
+		goto put_node;
+
+	pdev = platform_device_register_simple("sama7g5-cpufreq", -1, NULL, 0);
+	if (IS_ERR_OR_NULL(pdev)) {
+		pr_err("failed to register sama7g5-cpufreq platform device\n");
+		ret = PTR_ERR(pdev);
+		goto driver_unregister;
+	}
+
+	return 0;
+
+driver_unregister:
+	platform_driver_unregister(&sama7g5_cpufreq_platdrv);
+put_node:
+	of_node_put(np);
+
+	return ret;
+}
+device_initcall(sama7g5_cpufreq_driver_init);
+
+MODULE_ALIAS("platform:sama7g5-cpufreq");
+MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@...rochip.com>");
+MODULE_DESCRIPTION("Microchip SAMA7G5 CPUFreq driver");
+MODULE_LICENSE("GPL");
-- 
2.7.4

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ