1 /*
2  * Tegra 124 cpufreq driver
3  *
4  * This software is licensed under the terms of the GNU General Public
5  * License version 2, as published by the Free Software Foundation, and
6  * may be copied, distributed, and modified under those terms.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13 
14 #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
15 
16 #include <linux/clk.h>
17 #include <linux/err.h>
18 #include <linux/init.h>
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/of_device.h>
22 #include <linux/of.h>
23 #include <linux/platform_device.h>
24 #include <linux/pm_opp.h>
25 #include <linux/types.h>
26 
27 struct tegra124_cpufreq_priv {
28 	struct clk *cpu_clk;
29 	struct clk *pllp_clk;
30 	struct clk *pllx_clk;
31 	struct clk *dfll_clk;
32 	struct platform_device *cpufreq_dt_pdev;
33 };
34 
35 static int tegra124_cpu_switch_to_dfll(struct tegra124_cpufreq_priv *priv)
36 {
37 	struct clk *orig_parent;
38 	int ret;
39 
40 	ret = clk_set_rate(priv->dfll_clk, clk_get_rate(priv->cpu_clk));
41 	if (ret)
42 		return ret;
43 
44 	orig_parent = clk_get_parent(priv->cpu_clk);
45 	clk_set_parent(priv->cpu_clk, priv->pllp_clk);
46 
47 	ret = clk_prepare_enable(priv->dfll_clk);
48 	if (ret)
49 		goto out;
50 
51 	clk_set_parent(priv->cpu_clk, priv->dfll_clk);
52 
53 	return 0;
54 
55 out:
56 	clk_set_parent(priv->cpu_clk, orig_parent);
57 
58 	return ret;
59 }
60 
61 static int tegra124_cpufreq_probe(struct platform_device *pdev)
62 {
63 	struct tegra124_cpufreq_priv *priv;
64 	struct device_node *np;
65 	struct device *cpu_dev;
66 	struct platform_device_info cpufreq_dt_devinfo = {};
67 	int ret;
68 
69 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
70 	if (!priv)
71 		return -ENOMEM;
72 
73 	cpu_dev = get_cpu_device(0);
74 	if (!cpu_dev)
75 		return -ENODEV;
76 
77 	np = of_cpu_device_node_get(0);
78 	if (!np)
79 		return -ENODEV;
80 
81 	priv->cpu_clk = of_clk_get_by_name(np, "cpu_g");
82 	if (IS_ERR(priv->cpu_clk)) {
83 		ret = PTR_ERR(priv->cpu_clk);
84 		goto out_put_np;
85 	}
86 
87 	priv->dfll_clk = of_clk_get_by_name(np, "dfll");
88 	if (IS_ERR(priv->dfll_clk)) {
89 		ret = PTR_ERR(priv->dfll_clk);
90 		goto out_put_cpu_clk;
91 	}
92 
93 	priv->pllx_clk = of_clk_get_by_name(np, "pll_x");
94 	if (IS_ERR(priv->pllx_clk)) {
95 		ret = PTR_ERR(priv->pllx_clk);
96 		goto out_put_dfll_clk;
97 	}
98 
99 	priv->pllp_clk = of_clk_get_by_name(np, "pll_p");
100 	if (IS_ERR(priv->pllp_clk)) {
101 		ret = PTR_ERR(priv->pllp_clk);
102 		goto out_put_pllx_clk;
103 	}
104 
105 	ret = tegra124_cpu_switch_to_dfll(priv);
106 	if (ret)
107 		goto out_put_pllp_clk;
108 
109 	cpufreq_dt_devinfo.name = "cpufreq-dt";
110 	cpufreq_dt_devinfo.parent = &pdev->dev;
111 
112 	priv->cpufreq_dt_pdev =
113 		platform_device_register_full(&cpufreq_dt_devinfo);
114 	if (IS_ERR(priv->cpufreq_dt_pdev)) {
115 		ret = PTR_ERR(priv->cpufreq_dt_pdev);
116 		goto out_put_pllp_clk;
117 	}
118 
119 	platform_set_drvdata(pdev, priv);
120 
121 	of_node_put(np);
122 
123 	return 0;
124 
125 out_put_pllp_clk:
126 	clk_put(priv->pllp_clk);
127 out_put_pllx_clk:
128 	clk_put(priv->pllx_clk);
129 out_put_dfll_clk:
130 	clk_put(priv->dfll_clk);
131 out_put_cpu_clk:
132 	clk_put(priv->cpu_clk);
133 out_put_np:
134 	of_node_put(np);
135 
136 	return ret;
137 }
138 
139 static struct platform_driver tegra124_cpufreq_platdrv = {
140 	.driver.name	= "cpufreq-tegra124",
141 	.probe		= tegra124_cpufreq_probe,
142 };
143 
144 static int __init tegra_cpufreq_init(void)
145 {
146 	int ret;
147 	struct platform_device *pdev;
148 
149 	if (!(of_machine_is_compatible("nvidia,tegra124") ||
150 		of_machine_is_compatible("nvidia,tegra210")))
151 		return -ENODEV;
152 
153 	/*
154 	 * Platform driver+device required for handling EPROBE_DEFER with
155 	 * the regulator and the DFLL clock
156 	 */
157 	ret = platform_driver_register(&tegra124_cpufreq_platdrv);
158 	if (ret)
159 		return ret;
160 
161 	pdev = platform_device_register_simple("cpufreq-tegra124", -1, NULL, 0);
162 	if (IS_ERR(pdev)) {
163 		platform_driver_unregister(&tegra124_cpufreq_platdrv);
164 		return PTR_ERR(pdev);
165 	}
166 
167 	return 0;
168 }
169 module_init(tegra_cpufreq_init);
170 
171 MODULE_AUTHOR("Tuomas Tynkkynen <ttynkkynen@nvidia.com>");
172 MODULE_DESCRIPTION("cpufreq driver for NVIDIA Tegra124");
173 MODULE_LICENSE("GPL v2");
174