162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Tegra 124 cpufreq driver
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/clk.h>
962306a36Sopenharmony_ci#include <linux/cpufreq.h>
1062306a36Sopenharmony_ci#include <linux/err.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/pm_opp.h>
1762306a36Sopenharmony_ci#include <linux/types.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct tegra124_cpufreq_priv {
2062306a36Sopenharmony_ci	struct clk *cpu_clk;
2162306a36Sopenharmony_ci	struct clk *pllp_clk;
2262306a36Sopenharmony_ci	struct clk *pllx_clk;
2362306a36Sopenharmony_ci	struct clk *dfll_clk;
2462306a36Sopenharmony_ci	struct platform_device *cpufreq_dt_pdev;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic int tegra124_cpu_switch_to_dfll(struct tegra124_cpufreq_priv *priv)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	struct clk *orig_parent;
3062306a36Sopenharmony_ci	int ret;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	ret = clk_set_rate(priv->dfll_clk, clk_get_rate(priv->cpu_clk));
3362306a36Sopenharmony_ci	if (ret)
3462306a36Sopenharmony_ci		return ret;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	orig_parent = clk_get_parent(priv->cpu_clk);
3762306a36Sopenharmony_ci	clk_set_parent(priv->cpu_clk, priv->pllp_clk);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	ret = clk_prepare_enable(priv->dfll_clk);
4062306a36Sopenharmony_ci	if (ret)
4162306a36Sopenharmony_ci		goto out;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	clk_set_parent(priv->cpu_clk, priv->dfll_clk);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	return 0;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ciout:
4862306a36Sopenharmony_ci	clk_set_parent(priv->cpu_clk, orig_parent);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return ret;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int tegra124_cpufreq_probe(struct platform_device *pdev)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct tegra124_cpufreq_priv *priv;
5662306a36Sopenharmony_ci	struct device_node *np;
5762306a36Sopenharmony_ci	struct device *cpu_dev;
5862306a36Sopenharmony_ci	struct platform_device_info cpufreq_dt_devinfo = {};
5962306a36Sopenharmony_ci	int ret;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
6262306a36Sopenharmony_ci	if (!priv)
6362306a36Sopenharmony_ci		return -ENOMEM;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	cpu_dev = get_cpu_device(0);
6662306a36Sopenharmony_ci	if (!cpu_dev)
6762306a36Sopenharmony_ci		return -ENODEV;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	np = of_cpu_device_node_get(0);
7062306a36Sopenharmony_ci	if (!np)
7162306a36Sopenharmony_ci		return -ENODEV;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	priv->cpu_clk = of_clk_get_by_name(np, "cpu_g");
7462306a36Sopenharmony_ci	if (IS_ERR(priv->cpu_clk)) {
7562306a36Sopenharmony_ci		ret = PTR_ERR(priv->cpu_clk);
7662306a36Sopenharmony_ci		goto out_put_np;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	priv->dfll_clk = of_clk_get_by_name(np, "dfll");
8062306a36Sopenharmony_ci	if (IS_ERR(priv->dfll_clk)) {
8162306a36Sopenharmony_ci		ret = PTR_ERR(priv->dfll_clk);
8262306a36Sopenharmony_ci		goto out_put_cpu_clk;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	priv->pllx_clk = of_clk_get_by_name(np, "pll_x");
8662306a36Sopenharmony_ci	if (IS_ERR(priv->pllx_clk)) {
8762306a36Sopenharmony_ci		ret = PTR_ERR(priv->pllx_clk);
8862306a36Sopenharmony_ci		goto out_put_dfll_clk;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	priv->pllp_clk = of_clk_get_by_name(np, "pll_p");
9262306a36Sopenharmony_ci	if (IS_ERR(priv->pllp_clk)) {
9362306a36Sopenharmony_ci		ret = PTR_ERR(priv->pllp_clk);
9462306a36Sopenharmony_ci		goto out_put_pllx_clk;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	ret = tegra124_cpu_switch_to_dfll(priv);
9862306a36Sopenharmony_ci	if (ret)
9962306a36Sopenharmony_ci		goto out_put_pllp_clk;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	cpufreq_dt_devinfo.name = "cpufreq-dt";
10262306a36Sopenharmony_ci	cpufreq_dt_devinfo.parent = &pdev->dev;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	priv->cpufreq_dt_pdev =
10562306a36Sopenharmony_ci		platform_device_register_full(&cpufreq_dt_devinfo);
10662306a36Sopenharmony_ci	if (IS_ERR(priv->cpufreq_dt_pdev)) {
10762306a36Sopenharmony_ci		ret = PTR_ERR(priv->cpufreq_dt_pdev);
10862306a36Sopenharmony_ci		goto out_put_pllp_clk;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	of_node_put(np);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ciout_put_pllp_clk:
11862306a36Sopenharmony_ci	clk_put(priv->pllp_clk);
11962306a36Sopenharmony_ciout_put_pllx_clk:
12062306a36Sopenharmony_ci	clk_put(priv->pllx_clk);
12162306a36Sopenharmony_ciout_put_dfll_clk:
12262306a36Sopenharmony_ci	clk_put(priv->dfll_clk);
12362306a36Sopenharmony_ciout_put_cpu_clk:
12462306a36Sopenharmony_ci	clk_put(priv->cpu_clk);
12562306a36Sopenharmony_ciout_put_np:
12662306a36Sopenharmony_ci	of_node_put(np);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return ret;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic int __maybe_unused tegra124_cpufreq_suspend(struct device *dev)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct tegra124_cpufreq_priv *priv = dev_get_drvdata(dev);
13462306a36Sopenharmony_ci	int err;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/*
13762306a36Sopenharmony_ci	 * PLLP rate 408Mhz is below the CPU Fmax at Vmin and is safe to
13862306a36Sopenharmony_ci	 * use during suspend and resume. So, switch the CPU clock source
13962306a36Sopenharmony_ci	 * to PLLP and disable DFLL.
14062306a36Sopenharmony_ci	 */
14162306a36Sopenharmony_ci	err = clk_set_parent(priv->cpu_clk, priv->pllp_clk);
14262306a36Sopenharmony_ci	if (err < 0) {
14362306a36Sopenharmony_ci		dev_err(dev, "failed to reparent to PLLP: %d\n", err);
14462306a36Sopenharmony_ci		return err;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	clk_disable_unprepare(priv->dfll_clk);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic int __maybe_unused tegra124_cpufreq_resume(struct device *dev)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct tegra124_cpufreq_priv *priv = dev_get_drvdata(dev);
15562306a36Sopenharmony_ci	int err;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	/*
15862306a36Sopenharmony_ci	 * Warmboot code powers up the CPU with PLLP clock source.
15962306a36Sopenharmony_ci	 * Enable DFLL clock and switch CPU clock source back to DFLL.
16062306a36Sopenharmony_ci	 */
16162306a36Sopenharmony_ci	err = clk_prepare_enable(priv->dfll_clk);
16262306a36Sopenharmony_ci	if (err < 0) {
16362306a36Sopenharmony_ci		dev_err(dev, "failed to enable DFLL clock for CPU: %d\n", err);
16462306a36Sopenharmony_ci		goto disable_cpufreq;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	err = clk_set_parent(priv->cpu_clk, priv->dfll_clk);
16862306a36Sopenharmony_ci	if (err < 0) {
16962306a36Sopenharmony_ci		dev_err(dev, "failed to reparent to DFLL clock: %d\n", err);
17062306a36Sopenharmony_ci		goto disable_dfll;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	return 0;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cidisable_dfll:
17662306a36Sopenharmony_ci	clk_disable_unprepare(priv->dfll_clk);
17762306a36Sopenharmony_cidisable_cpufreq:
17862306a36Sopenharmony_ci	disable_cpufreq();
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	return err;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic const struct dev_pm_ops tegra124_cpufreq_pm_ops = {
18462306a36Sopenharmony_ci	SET_SYSTEM_SLEEP_PM_OPS(tegra124_cpufreq_suspend,
18562306a36Sopenharmony_ci				tegra124_cpufreq_resume)
18662306a36Sopenharmony_ci};
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic struct platform_driver tegra124_cpufreq_platdrv = {
18962306a36Sopenharmony_ci	.driver.name	= "cpufreq-tegra124",
19062306a36Sopenharmony_ci	.driver.pm	= &tegra124_cpufreq_pm_ops,
19162306a36Sopenharmony_ci	.probe		= tegra124_cpufreq_probe,
19262306a36Sopenharmony_ci};
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic int __init tegra_cpufreq_init(void)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	int ret;
19762306a36Sopenharmony_ci	struct platform_device *pdev;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	if (!(of_machine_is_compatible("nvidia,tegra124") ||
20062306a36Sopenharmony_ci		of_machine_is_compatible("nvidia,tegra210")))
20162306a36Sopenharmony_ci		return -ENODEV;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	/*
20462306a36Sopenharmony_ci	 * Platform driver+device required for handling EPROBE_DEFER with
20562306a36Sopenharmony_ci	 * the regulator and the DFLL clock
20662306a36Sopenharmony_ci	 */
20762306a36Sopenharmony_ci	ret = platform_driver_register(&tegra124_cpufreq_platdrv);
20862306a36Sopenharmony_ci	if (ret)
20962306a36Sopenharmony_ci		return ret;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	pdev = platform_device_register_simple("cpufreq-tegra124", -1, NULL, 0);
21262306a36Sopenharmony_ci	if (IS_ERR(pdev)) {
21362306a36Sopenharmony_ci		platform_driver_unregister(&tegra124_cpufreq_platdrv);
21462306a36Sopenharmony_ci		return PTR_ERR(pdev);
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	return 0;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_cimodule_init(tegra_cpufreq_init);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ciMODULE_AUTHOR("Tuomas Tynkkynen <ttynkkynen@nvidia.com>");
22262306a36Sopenharmony_ciMODULE_DESCRIPTION("cpufreq driver for NVIDIA Tegra124");
223