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 false; |
22 | |
23 | regmap_read(map: br->clkr.regmap, reg: br->hwcg_reg, val: &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(map: br->clkr.regmap, reg: br->halt_reg, val: &val); |
34 | |
35 | val &= BIT(br->halt_bit); |
36 | if (invert) |
37 | val = !val; |
38 | |
39 | return !!val == !enabling; |
40 | } |
41 | |
42 | static bool clk_branch2_check_halt(const struct clk_branch *br, bool enabling) |
43 | { |
44 | u32 val; |
45 | u32 mask; |
46 | bool invert = (br->halt_check == BRANCH_HALT_ENABLE); |
47 | |
48 | mask = CBCR_NOC_FSM_STATUS; |
49 | mask |= CBCR_CLK_OFF; |
50 | |
51 | regmap_read(map: br->clkr.regmap, reg: br->halt_reg, val: &val); |
52 | |
53 | if (enabling) { |
54 | val &= mask; |
55 | return (val & CBCR_CLK_OFF) == (invert ? CBCR_CLK_OFF : 0) || |
56 | FIELD_GET(CBCR_NOC_FSM_STATUS, val) == FSM_STATUS_ON; |
57 | } |
58 | return (val & CBCR_CLK_OFF) == (invert ? 0 : CBCR_CLK_OFF); |
59 | } |
60 | |
61 | static int clk_branch_wait(const struct clk_branch *br, bool enabling, |
62 | bool (check_halt)(const struct clk_branch *, bool)) |
63 | { |
64 | bool voted = br->halt_check & BRANCH_VOTED; |
65 | const char *name = clk_hw_get_name(hw: &br->clkr.hw); |
66 | |
67 | /* |
68 | * Skip checking halt bit if we're explicitly ignoring the bit or the |
69 | * clock is in hardware gated mode |
70 | */ |
71 | if (br->halt_check == BRANCH_HALT_SKIP || clk_branch_in_hwcg_mode(br)) |
72 | return 0; |
73 | |
74 | if (br->halt_check == BRANCH_HALT_DELAY || (!enabling && voted)) { |
75 | udelay(10); |
76 | } else if (br->halt_check == BRANCH_HALT_ENABLE || |
77 | br->halt_check == BRANCH_HALT || |
78 | (enabling && voted)) { |
79 | int count = 200; |
80 | |
81 | while (count-- > 0) { |
82 | if (check_halt(br, enabling)) |
83 | return 0; |
84 | udelay(1); |
85 | } |
86 | WARN(1, "%s status stuck at 'o%s'" , name, |
87 | enabling ? "ff" : "n" ); |
88 | return -EBUSY; |
89 | } |
90 | return 0; |
91 | } |
92 | |
93 | static int clk_branch_toggle(struct clk_hw *hw, bool en, |
94 | bool (check_halt)(const struct clk_branch *, bool)) |
95 | { |
96 | struct clk_branch *br = to_clk_branch(hw); |
97 | int ret; |
98 | |
99 | if (en) { |
100 | ret = clk_enable_regmap(hw); |
101 | if (ret) |
102 | return ret; |
103 | } else { |
104 | clk_disable_regmap(hw); |
105 | } |
106 | |
107 | return clk_branch_wait(br, enabling: en, check_halt); |
108 | } |
109 | |
110 | static int clk_branch_enable(struct clk_hw *hw) |
111 | { |
112 | return clk_branch_toggle(hw, en: true, check_halt: clk_branch_check_halt); |
113 | } |
114 | |
115 | static void clk_branch_disable(struct clk_hw *hw) |
116 | { |
117 | clk_branch_toggle(hw, en: false, check_halt: clk_branch_check_halt); |
118 | } |
119 | |
120 | const struct clk_ops clk_branch_ops = { |
121 | .enable = clk_branch_enable, |
122 | .disable = clk_branch_disable, |
123 | .is_enabled = clk_is_enabled_regmap, |
124 | }; |
125 | EXPORT_SYMBOL_GPL(clk_branch_ops); |
126 | |
127 | static int clk_branch2_enable(struct clk_hw *hw) |
128 | { |
129 | return clk_branch_toggle(hw, en: true, check_halt: clk_branch2_check_halt); |
130 | } |
131 | |
132 | static void clk_branch2_disable(struct clk_hw *hw) |
133 | { |
134 | clk_branch_toggle(hw, en: false, check_halt: clk_branch2_check_halt); |
135 | } |
136 | |
137 | const struct clk_ops clk_branch2_ops = { |
138 | .enable = clk_branch2_enable, |
139 | .disable = clk_branch2_disable, |
140 | .is_enabled = clk_is_enabled_regmap, |
141 | }; |
142 | EXPORT_SYMBOL_GPL(clk_branch2_ops); |
143 | |
144 | const struct clk_ops clk_branch2_aon_ops = { |
145 | .enable = clk_branch2_enable, |
146 | .is_enabled = clk_is_enabled_regmap, |
147 | }; |
148 | EXPORT_SYMBOL_GPL(clk_branch2_aon_ops); |
149 | |
150 | const struct clk_ops clk_branch_simple_ops = { |
151 | .enable = clk_enable_regmap, |
152 | .disable = clk_disable_regmap, |
153 | .is_enabled = clk_is_enabled_regmap, |
154 | }; |
155 | EXPORT_SYMBOL_GPL(clk_branch_simple_ops); |
156 | |