GNU Linux-libre 5.10.215-gnu1
[releases.git] / drivers / clk / tegra / clk-tegra-super-cclk.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Based on clk-super.c
4  * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
5  *
6  * Based on older tegra20-cpufreq driver by Colin Cross <ccross@google.com>
7  * Copyright (C) 2010 Google, Inc.
8  *
9  * Author: Dmitry Osipenko <digetx@gmail.com>
10  * Copyright (C) 2019 GRATE-DRIVER project
11  */
12
13 #include <linux/bits.h>
14 #include <linux/clk-provider.h>
15 #include <linux/err.h>
16 #include <linux/io.h>
17 #include <linux/kernel.h>
18 #include <linux/slab.h>
19 #include <linux/types.h>
20
21 #include "clk.h"
22
23 #define PLLP_INDEX              4
24 #define PLLX_INDEX              8
25
26 #define SUPER_CDIV_ENB          BIT(31)
27
28 static struct tegra_clk_super_mux *cclk_super;
29 static bool cclk_on_pllx;
30
31 static u8 cclk_super_get_parent(struct clk_hw *hw)
32 {
33         return tegra_clk_super_ops.get_parent(hw);
34 }
35
36 static int cclk_super_set_parent(struct clk_hw *hw, u8 index)
37 {
38         return tegra_clk_super_ops.set_parent(hw, index);
39 }
40
41 static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate,
42                                unsigned long parent_rate)
43 {
44         return tegra_clk_super_ops.set_rate(hw, rate, parent_rate);
45 }
46
47 static unsigned long cclk_super_recalc_rate(struct clk_hw *hw,
48                                             unsigned long parent_rate)
49 {
50         if (cclk_super_get_parent(hw) == PLLX_INDEX)
51                 return parent_rate;
52
53         return tegra_clk_super_ops.recalc_rate(hw, parent_rate);
54 }
55
56 static int cclk_super_determine_rate(struct clk_hw *hw,
57                                      struct clk_rate_request *req)
58 {
59         struct clk_hw *pllp_hw = clk_hw_get_parent_by_index(hw, PLLP_INDEX);
60         struct clk_hw *pllx_hw = clk_hw_get_parent_by_index(hw, PLLX_INDEX);
61         struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
62         unsigned long pllp_rate;
63         long rate = req->rate;
64
65         if (WARN_ON_ONCE(!pllp_hw || !pllx_hw))
66                 return -EINVAL;
67
68         /*
69          * Switch parent to PLLP for all CCLK rates that are suitable for PLLP.
70          * PLLX will be disabled in this case, saving some power.
71          */
72         pllp_rate = clk_hw_get_rate(pllp_hw);
73
74         if (rate <= pllp_rate) {
75                 if (super->flags & TEGRA20_SUPER_CLK)
76                         rate = pllp_rate;
77                 else
78                         rate = tegra_clk_super_ops.round_rate(hw, rate,
79                                                               &pllp_rate);
80
81                 req->best_parent_rate = pllp_rate;
82                 req->best_parent_hw = pllp_hw;
83                 req->rate = rate;
84         } else {
85                 rate = clk_hw_round_rate(pllx_hw, rate);
86                 req->best_parent_rate = rate;
87                 req->best_parent_hw = pllx_hw;
88                 req->rate = rate;
89         }
90
91         if (WARN_ON_ONCE(rate <= 0))
92                 return -EINVAL;
93
94         return 0;
95 }
96
97 static const struct clk_ops tegra_cclk_super_ops = {
98         .get_parent = cclk_super_get_parent,
99         .set_parent = cclk_super_set_parent,
100         .set_rate = cclk_super_set_rate,
101         .recalc_rate = cclk_super_recalc_rate,
102         .determine_rate = cclk_super_determine_rate,
103 };
104
105 static const struct clk_ops tegra_cclk_super_mux_ops = {
106         .get_parent = cclk_super_get_parent,
107         .set_parent = cclk_super_set_parent,
108         .determine_rate = cclk_super_determine_rate,
109 };
110
111 struct clk *tegra_clk_register_super_cclk(const char *name,
112                 const char * const *parent_names, u8 num_parents,
113                 unsigned long flags, void __iomem *reg, u8 clk_super_flags,
114                 spinlock_t *lock)
115 {
116         struct tegra_clk_super_mux *super;
117         struct clk *clk;
118         struct clk_init_data init;
119         u32 val;
120
121         if (WARN_ON(cclk_super))
122                 return ERR_PTR(-EBUSY);
123
124         super = kzalloc(sizeof(*super), GFP_KERNEL);
125         if (!super)
126                 return ERR_PTR(-ENOMEM);
127
128         init.name = name;
129         init.flags = flags;
130         init.parent_names = parent_names;
131         init.num_parents = num_parents;
132
133         super->reg = reg;
134         super->lock = lock;
135         super->width = 4;
136         super->flags = clk_super_flags;
137         super->hw.init = &init;
138
139         if (super->flags & TEGRA20_SUPER_CLK) {
140                 init.ops = &tegra_cclk_super_mux_ops;
141         } else {
142                 init.ops = &tegra_cclk_super_ops;
143
144                 super->frac_div.reg = reg + 4;
145                 super->frac_div.shift = 16;
146                 super->frac_div.width = 8;
147                 super->frac_div.frac_width = 1;
148                 super->frac_div.lock = lock;
149                 super->div_ops = &tegra_clk_frac_div_ops;
150         }
151
152         /*
153          * Tegra30+ has the following CPUG clock topology:
154          *
155          *        +---+  +-------+  +-+            +-+                +-+
156          * PLLP+->+   +->+DIVIDER+->+0|  +-------->+0|  ------------->+0|
157          *        |   |  +-------+  | |  |  +---+  | |  |             | |
158          * PLLC+->+MUX|             | +->+  | S |  | +->+             | +->+CPU
159          *  ...   |   |             | |  |  | K |  | |  |  +-------+  | |
160          * PLLX+->+-->+------------>+1|  +->+ I +->+1|  +->+ DIV2  +->+1|
161          *        +---+             +++     | P |  +++     |SKIPPER|  +++
162          *                           ^      | P |   ^      +-------+   ^
163          *                           |      | E |   |                  |
164          *                PLLX_SEL+--+      | R |   |       OVERHEAT+--+
165          *                                  +---+   |
166          *                                          |
167          *                         SUPER_CDIV_ENB+--+
168          *
169          * Tegra20 is similar, but simpler. It doesn't have the divider and
170          * thermal DIV2 skipper.
171          *
172          * At least for now we're not going to use clock-skipper, hence let's
173          * ensure that it is disabled.
174          */
175         val = readl_relaxed(reg + 4);
176         val &= ~SUPER_CDIV_ENB;
177         writel_relaxed(val, reg + 4);
178
179         clk = clk_register(NULL, &super->hw);
180         if (IS_ERR(clk))
181                 kfree(super);
182         else
183                 cclk_super = super;
184
185         return clk;
186 }
187
188 int tegra_cclk_pre_pllx_rate_change(void)
189 {
190         if (IS_ERR_OR_NULL(cclk_super))
191                 return -EINVAL;
192
193         if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
194                 cclk_on_pllx = true;
195         else
196                 cclk_on_pllx = false;
197
198         /*
199          * CPU needs to be temporarily re-parented away from PLLX if PLLX
200          * changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs.
201          */
202         if (cclk_on_pllx)
203                 cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
204
205         return 0;
206 }
207
208 void tegra_cclk_post_pllx_rate_change(void)
209 {
210         if (cclk_on_pllx)
211                 cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);
212 }