Linux-libre 4.9.189-gnu
[librecmc/linux-libre.git] / drivers / cpufreq / tegra124-cpufreq.c
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/regulator/consumer.h>
26 #include <linux/types.h>
27
28 struct tegra124_cpufreq_priv {
29         struct regulator *vdd_cpu_reg;
30         struct clk *cpu_clk;
31         struct clk *pllp_clk;
32         struct clk *pllx_clk;
33         struct clk *dfll_clk;
34         struct platform_device *cpufreq_dt_pdev;
35 };
36
37 static int tegra124_cpu_switch_to_dfll(struct tegra124_cpufreq_priv *priv)
38 {
39         struct clk *orig_parent;
40         int ret;
41
42         ret = clk_set_rate(priv->dfll_clk, clk_get_rate(priv->cpu_clk));
43         if (ret)
44                 return ret;
45
46         orig_parent = clk_get_parent(priv->cpu_clk);
47         clk_set_parent(priv->cpu_clk, priv->pllp_clk);
48
49         ret = clk_prepare_enable(priv->dfll_clk);
50         if (ret)
51                 goto out;
52
53         clk_set_parent(priv->cpu_clk, priv->dfll_clk);
54
55         return 0;
56
57 out:
58         clk_set_parent(priv->cpu_clk, orig_parent);
59
60         return ret;
61 }
62
63 static void tegra124_cpu_switch_to_pllx(struct tegra124_cpufreq_priv *priv)
64 {
65         clk_set_parent(priv->cpu_clk, priv->pllp_clk);
66         clk_disable_unprepare(priv->dfll_clk);
67         regulator_sync_voltage(priv->vdd_cpu_reg);
68         clk_set_parent(priv->cpu_clk, priv->pllx_clk);
69 }
70
71 static int tegra124_cpufreq_probe(struct platform_device *pdev)
72 {
73         struct tegra124_cpufreq_priv *priv;
74         struct device_node *np;
75         struct device *cpu_dev;
76         struct platform_device_info cpufreq_dt_devinfo = {};
77         int ret;
78
79         priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
80         if (!priv)
81                 return -ENOMEM;
82
83         cpu_dev = get_cpu_device(0);
84         if (!cpu_dev)
85                 return -ENODEV;
86
87         np = of_cpu_device_node_get(0);
88         if (!np)
89                 return -ENODEV;
90
91         priv->vdd_cpu_reg = regulator_get(cpu_dev, "vdd-cpu");
92         if (IS_ERR(priv->vdd_cpu_reg)) {
93                 ret = PTR_ERR(priv->vdd_cpu_reg);
94                 goto out_put_np;
95         }
96
97         priv->cpu_clk = of_clk_get_by_name(np, "cpu_g");
98         if (IS_ERR(priv->cpu_clk)) {
99                 ret = PTR_ERR(priv->cpu_clk);
100                 goto out_put_vdd_cpu_reg;
101         }
102
103         priv->dfll_clk = of_clk_get_by_name(np, "dfll");
104         if (IS_ERR(priv->dfll_clk)) {
105                 ret = PTR_ERR(priv->dfll_clk);
106                 goto out_put_cpu_clk;
107         }
108
109         priv->pllx_clk = of_clk_get_by_name(np, "pll_x");
110         if (IS_ERR(priv->pllx_clk)) {
111                 ret = PTR_ERR(priv->pllx_clk);
112                 goto out_put_dfll_clk;
113         }
114
115         priv->pllp_clk = of_clk_get_by_name(np, "pll_p");
116         if (IS_ERR(priv->pllp_clk)) {
117                 ret = PTR_ERR(priv->pllp_clk);
118                 goto out_put_pllx_clk;
119         }
120
121         ret = tegra124_cpu_switch_to_dfll(priv);
122         if (ret)
123                 goto out_put_pllp_clk;
124
125         cpufreq_dt_devinfo.name = "cpufreq-dt";
126         cpufreq_dt_devinfo.parent = &pdev->dev;
127
128         priv->cpufreq_dt_pdev =
129                 platform_device_register_full(&cpufreq_dt_devinfo);
130         if (IS_ERR(priv->cpufreq_dt_pdev)) {
131                 ret = PTR_ERR(priv->cpufreq_dt_pdev);
132                 goto out_switch_to_pllx;
133         }
134
135         platform_set_drvdata(pdev, priv);
136
137         of_node_put(np);
138
139         return 0;
140
141 out_switch_to_pllx:
142         tegra124_cpu_switch_to_pllx(priv);
143 out_put_pllp_clk:
144         clk_put(priv->pllp_clk);
145 out_put_pllx_clk:
146         clk_put(priv->pllx_clk);
147 out_put_dfll_clk:
148         clk_put(priv->dfll_clk);
149 out_put_cpu_clk:
150         clk_put(priv->cpu_clk);
151 out_put_vdd_cpu_reg:
152         regulator_put(priv->vdd_cpu_reg);
153 out_put_np:
154         of_node_put(np);
155
156         return ret;
157 }
158
159 static int tegra124_cpufreq_remove(struct platform_device *pdev)
160 {
161         struct tegra124_cpufreq_priv *priv = platform_get_drvdata(pdev);
162
163         platform_device_unregister(priv->cpufreq_dt_pdev);
164         tegra124_cpu_switch_to_pllx(priv);
165
166         clk_put(priv->pllp_clk);
167         clk_put(priv->pllx_clk);
168         clk_put(priv->dfll_clk);
169         clk_put(priv->cpu_clk);
170         regulator_put(priv->vdd_cpu_reg);
171
172         return 0;
173 }
174
175 static struct platform_driver tegra124_cpufreq_platdrv = {
176         .driver.name    = "cpufreq-tegra124",
177         .probe          = tegra124_cpufreq_probe,
178         .remove         = tegra124_cpufreq_remove,
179 };
180
181 static int __init tegra_cpufreq_init(void)
182 {
183         int ret;
184         struct platform_device *pdev;
185
186         if (!of_machine_is_compatible("nvidia,tegra124"))
187                 return -ENODEV;
188
189         /*
190          * Platform driver+device required for handling EPROBE_DEFER with
191          * the regulator and the DFLL clock
192          */
193         ret = platform_driver_register(&tegra124_cpufreq_platdrv);
194         if (ret)
195                 return ret;
196
197         pdev = platform_device_register_simple("cpufreq-tegra124", -1, NULL, 0);
198         if (IS_ERR(pdev)) {
199                 platform_driver_unregister(&tegra124_cpufreq_platdrv);
200                 return PTR_ERR(pdev);
201         }
202
203         return 0;
204 }
205 module_init(tegra_cpufreq_init);
206
207 MODULE_AUTHOR("Tuomas Tynkkynen <ttynkkynen@nvidia.com>");
208 MODULE_DESCRIPTION("cpufreq driver for NVIDIA Tegra124");
209 MODULE_LICENSE("GPL v2");