GNU Linux-libre 4.19.245-gnu1
[releases.git] / drivers / clk / qcom / clk-branch.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2013, The Linux Foundation. All rights reserved.
4  */
5
6 #include <linux/kernel.h>
7 #include <linux/bitops.h>
8 #include <linux/err.h>
9 #include <linux/delay.h>
10 #include <linux/export.h>
11 #include <linux/clk-provider.h>
12 #include <linux/regmap.h>
13
14 #include "clk-branch.h"
15
16 static bool clk_branch_in_hwcg_mode(const struct clk_branch *br)
17 {
18         u32 val;
19
20         if (!br->hwcg_reg)
21                 return 0;
22
23         regmap_read(br->clkr.regmap, br->hwcg_reg, &val);
24
25         return !!(val & BIT(br->hwcg_bit));
26 }
27
28 static bool clk_branch_check_halt(const struct clk_branch *br, bool enabling)
29 {
30         bool invert = (br->halt_check == BRANCH_HALT_ENABLE);
31         u32 val;
32
33         regmap_read(br->clkr.regmap, br->halt_reg, &val);
34
35         val &= BIT(br->halt_bit);
36         if (invert)
37                 val = !val;
38
39         return !!val == !enabling;
40 }
41
42 #define BRANCH_CLK_OFF                  BIT(31)
43 #define BRANCH_NOC_FSM_STATUS_SHIFT     28
44 #define BRANCH_NOC_FSM_STATUS_MASK      0x7
45 #define BRANCH_NOC_FSM_STATUS_ON        (0x2 << BRANCH_NOC_FSM_STATUS_SHIFT)
46
47 static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling)
48 {
49         u32 val;
50         u32 mask;
51
52         mask = BRANCH_NOC_FSM_STATUS_MASK << BRANCH_NOC_FSM_STATUS_SHIFT;
53         mask |= BRANCH_CLK_OFF;
54
55         regmap_read(br->clkr.regmap, br->halt_reg, &val);
56
57         if (enabling) {
58                 val &= mask;
59                 return (val & BRANCH_CLK_OFF) == 0 ||
60                         val == BRANCH_NOC_FSM_STATUS_ON;
61         } else {
62                 return val & BRANCH_CLK_OFF;
63         }
64 }
65
66 static int clk_branch_wait(const struct clk_branch *br, bool enabling,
67                 bool (check_halt)(const struct clk_branch *, bool))
68 {
69         bool voted = br->halt_check & BRANCH_VOTED;
70         const char *name = clk_hw_get_name(&br->clkr.hw);
71
72         /*
73          * Skip checking halt bit if we're explicitly ignoring the bit or the
74          * clock is in hardware gated mode
75          */
76         if (br->halt_check == BRANCH_HALT_SKIP || clk_branch_in_hwcg_mode(br))
77                 return 0;
78
79         if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) {
80                 udelay(10);
81         } else if (br->halt_check == BRANCH_HALT_ENABLE ||
82                    br->halt_check == BRANCH_HALT ||
83                    (enabling && voted)) {
84                 int count = 200;
85
86                 while (count-- > 0) {
87                         if (check_halt(br, enabling))
88                                 return 0;
89                         udelay(1);
90                 }
91                 WARN(1, "%s status stuck at 'o%s'", name,
92                                 enabling ? "ff" : "n");
93                 return -EBUSY;
94         }
95         return 0;
96 }
97
98 static int clk_branch_toggle(struct clk_hw *hw, bool en,
99                 bool (check_halt)(const struct clk_branch *, bool))
100 {
101         struct clk_branch *br = to_clk_branch(hw);
102         int ret;
103
104         if (en) {
105                 ret = clk_enable_regmap(hw);
106                 if (ret)
107                         return ret;
108         } else {
109                 clk_disable_regmap(hw);
110         }
111
112         return clk_branch_wait(br, en, check_halt);
113 }
114
115 static int clk_branch_enable(struct clk_hw *hw)
116 {
117         return clk_branch_toggle(hw, true, clk_branch_check_halt);
118 }
119
120 static void clk_branch_disable(struct clk_hw *hw)
121 {
122         clk_branch_toggle(hw, false, clk_branch_check_halt);
123 }
124
125 const struct clk_ops clk_branch_ops = {
126         .enable = clk_branch_enable,
127         .disable = clk_branch_disable,
128         .is_enabled = clk_is_enabled_regmap,
129 };
130 EXPORT_SYMBOL_GPL(clk_branch_ops);
131
132 static int clk_branch2_enable(struct clk_hw *hw)
133 {
134         return clk_branch_toggle(hw, true, clk_branch2_check_halt);
135 }
136
137 static void clk_branch2_disable(struct clk_hw *hw)
138 {
139         clk_branch_toggle(hw, false, clk_branch2_check_halt);
140 }
141
142 const struct clk_ops clk_branch2_ops = {
143         .enable = clk_branch2_enable,
144         .disable = clk_branch2_disable,
145         .is_enabled = clk_is_enabled_regmap,
146 };
147 EXPORT_SYMBOL_GPL(clk_branch2_ops);
148
149 const struct clk_ops clk_branch_simple_ops = {
150         .enable = clk_enable_regmap,
151         .disable = clk_disable_regmap,
152         .is_enabled = clk_is_enabled_regmap,
153 };
154 EXPORT_SYMBOL_GPL(clk_branch_simple_ops);