Merge branch 'parisc-4.9-1' of git://git.kernel.org/pub/scm/linux/kernel/git/deller...
[cascardo/linux.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         return 0;
138
139 out_switch_to_pllx:
140         tegra124_cpu_switch_to_pllx(priv);
141 out_put_pllp_clk:
142         clk_put(priv->pllp_clk);
143 out_put_pllx_clk:
144         clk_put(priv->pllx_clk);
145 out_put_dfll_clk:
146         clk_put(priv->dfll_clk);
147 out_put_cpu_clk:
148         clk_put(priv->cpu_clk);
149 out_put_vdd_cpu_reg:
150         regulator_put(priv->vdd_cpu_reg);
151 out_put_np:
152         of_node_put(np);
153
154         return ret;
155 }
156
157 static int tegra124_cpufreq_remove(struct platform_device *pdev)
158 {
159         struct tegra124_cpufreq_priv *priv = platform_get_drvdata(pdev);
160
161         platform_device_unregister(priv->cpufreq_dt_pdev);
162         tegra124_cpu_switch_to_pllx(priv);
163
164         clk_put(priv->pllp_clk);
165         clk_put(priv->pllx_clk);
166         clk_put(priv->dfll_clk);
167         clk_put(priv->cpu_clk);
168         regulator_put(priv->vdd_cpu_reg);
169
170         return 0;
171 }
172
173 static struct platform_driver tegra124_cpufreq_platdrv = {
174         .driver.name    = "cpufreq-tegra124",
175         .probe          = tegra124_cpufreq_probe,
176         .remove         = tegra124_cpufreq_remove,
177 };
178
179 static int __init tegra_cpufreq_init(void)
180 {
181         int ret;
182         struct platform_device *pdev;
183
184         if (!of_machine_is_compatible("nvidia,tegra124"))
185                 return -ENODEV;
186
187         /*
188          * Platform driver+device required for handling EPROBE_DEFER with
189          * the regulator and the DFLL clock
190          */
191         ret = platform_driver_register(&tegra124_cpufreq_platdrv);
192         if (ret)
193                 return ret;
194
195         pdev = platform_device_register_simple("cpufreq-tegra124", -1, NULL, 0);
196         if (IS_ERR(pdev)) {
197                 platform_driver_unregister(&tegra124_cpufreq_platdrv);
198                 return PTR_ERR(pdev);
199         }
200
201         return 0;
202 }
203 module_init(tegra_cpufreq_init);
204
205 MODULE_AUTHOR("Tuomas Tynkkynen <ttynkkynen@nvidia.com>");
206 MODULE_DESCRIPTION("cpufreq driver for NVIDIA Tegra124");
207 MODULE_LICENSE("GPL v2");