1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2018 NXP. |
4 | * Dong Aisheng <aisheng.dong@nxp.com> |
5 | */ |
6 | |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/err.h> |
9 | #include <linux/io.h> |
10 | #include <linux/slab.h> |
11 | |
12 | #include "clk.h" |
13 | |
14 | struct clk_divider_gate { |
15 | struct clk_divider divider; |
16 | u32 cached_val; |
17 | }; |
18 | |
19 | static inline struct clk_divider_gate *to_clk_divider_gate(struct clk_hw *hw) |
20 | { |
21 | struct clk_divider *div = to_clk_divider(hw); |
22 | |
23 | return container_of(div, struct clk_divider_gate, divider); |
24 | } |
25 | |
26 | static unsigned long clk_divider_gate_recalc_rate_ro(struct clk_hw *hw, |
27 | unsigned long parent_rate) |
28 | { |
29 | struct clk_divider *div = to_clk_divider(hw); |
30 | unsigned int val; |
31 | |
32 | val = readl(addr: div->reg) >> div->shift; |
33 | val &= clk_div_mask(div->width); |
34 | if (!val) |
35 | return 0; |
36 | |
37 | return divider_recalc_rate(hw, parent_rate, val, table: div->table, |
38 | flags: div->flags, width: div->width); |
39 | } |
40 | |
41 | static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw, |
42 | unsigned long parent_rate) |
43 | { |
44 | struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); |
45 | struct clk_divider *div = to_clk_divider(hw); |
46 | unsigned long flags; |
47 | unsigned int val; |
48 | |
49 | spin_lock_irqsave(div->lock, flags); |
50 | |
51 | if (!clk_hw_is_enabled(hw)) { |
52 | val = div_gate->cached_val; |
53 | } else { |
54 | val = readl(addr: div->reg) >> div->shift; |
55 | val &= clk_div_mask(div->width); |
56 | } |
57 | |
58 | spin_unlock_irqrestore(lock: div->lock, flags); |
59 | |
60 | if (!val) |
61 | return 0; |
62 | |
63 | return divider_recalc_rate(hw, parent_rate, val, table: div->table, |
64 | flags: div->flags, width: div->width); |
65 | } |
66 | |
67 | static int clk_divider_determine_rate(struct clk_hw *hw, |
68 | struct clk_rate_request *req) |
69 | { |
70 | return clk_divider_ops.determine_rate(hw, req); |
71 | } |
72 | |
73 | static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate, |
74 | unsigned long parent_rate) |
75 | { |
76 | struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); |
77 | struct clk_divider *div = to_clk_divider(hw); |
78 | unsigned long flags; |
79 | int value; |
80 | u32 val; |
81 | |
82 | value = divider_get_val(rate, parent_rate, table: div->table, |
83 | width: div->width, flags: div->flags); |
84 | if (value < 0) |
85 | return value; |
86 | |
87 | spin_lock_irqsave(div->lock, flags); |
88 | |
89 | if (clk_hw_is_enabled(hw)) { |
90 | val = readl(addr: div->reg); |
91 | val &= ~(clk_div_mask(div->width) << div->shift); |
92 | val |= (u32)value << div->shift; |
93 | writel(val, addr: div->reg); |
94 | } else { |
95 | div_gate->cached_val = value; |
96 | } |
97 | |
98 | spin_unlock_irqrestore(lock: div->lock, flags); |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static int clk_divider_enable(struct clk_hw *hw) |
104 | { |
105 | struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); |
106 | struct clk_divider *div = to_clk_divider(hw); |
107 | unsigned long flags; |
108 | u32 val; |
109 | |
110 | if (!div_gate->cached_val) { |
111 | pr_err("%s: no valid preset rate\n" , clk_hw_get_name(hw)); |
112 | return -EINVAL; |
113 | } |
114 | |
115 | spin_lock_irqsave(div->lock, flags); |
116 | /* restore div val */ |
117 | val = readl(addr: div->reg); |
118 | val |= div_gate->cached_val << div->shift; |
119 | writel(val, addr: div->reg); |
120 | |
121 | spin_unlock_irqrestore(lock: div->lock, flags); |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static void clk_divider_disable(struct clk_hw *hw) |
127 | { |
128 | struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); |
129 | struct clk_divider *div = to_clk_divider(hw); |
130 | unsigned long flags; |
131 | u32 val; |
132 | |
133 | spin_lock_irqsave(div->lock, flags); |
134 | |
135 | /* store the current div val */ |
136 | val = readl(addr: div->reg) >> div->shift; |
137 | val &= clk_div_mask(div->width); |
138 | div_gate->cached_val = val; |
139 | writel(val: 0, addr: div->reg); |
140 | |
141 | spin_unlock_irqrestore(lock: div->lock, flags); |
142 | } |
143 | |
144 | static int clk_divider_is_enabled(struct clk_hw *hw) |
145 | { |
146 | struct clk_divider *div = to_clk_divider(hw); |
147 | u32 val; |
148 | |
149 | val = readl(addr: div->reg) >> div->shift; |
150 | val &= clk_div_mask(div->width); |
151 | |
152 | return val ? 1 : 0; |
153 | } |
154 | |
155 | static const struct clk_ops clk_divider_gate_ro_ops = { |
156 | .recalc_rate = clk_divider_gate_recalc_rate_ro, |
157 | .determine_rate = clk_divider_determine_rate, |
158 | }; |
159 | |
160 | static const struct clk_ops clk_divider_gate_ops = { |
161 | .recalc_rate = clk_divider_gate_recalc_rate, |
162 | .determine_rate = clk_divider_determine_rate, |
163 | .set_rate = clk_divider_gate_set_rate, |
164 | .enable = clk_divider_enable, |
165 | .disable = clk_divider_disable, |
166 | .is_enabled = clk_divider_is_enabled, |
167 | }; |
168 | |
169 | /* |
170 | * NOTE: In order to reuse the most code from the common divider, |
171 | * we also design our divider following the way that provids an extra |
172 | * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by |
173 | * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY |
174 | * flag which can be specified by user flexibly. |
175 | */ |
176 | struct clk_hw *imx_clk_hw_divider_gate(const char *name, const char *parent_name, |
177 | unsigned long flags, void __iomem *reg, |
178 | u8 shift, u8 width, u8 clk_divider_flags, |
179 | const struct clk_div_table *table, |
180 | spinlock_t *lock) |
181 | { |
182 | struct clk_init_data init; |
183 | struct clk_divider_gate *div_gate; |
184 | struct clk_hw *hw; |
185 | u32 val; |
186 | int ret; |
187 | |
188 | div_gate = kzalloc(size: sizeof(*div_gate), GFP_KERNEL); |
189 | if (!div_gate) |
190 | return ERR_PTR(error: -ENOMEM); |
191 | |
192 | init.name = name; |
193 | if (clk_divider_flags & CLK_DIVIDER_READ_ONLY) |
194 | init.ops = &clk_divider_gate_ro_ops; |
195 | else |
196 | init.ops = &clk_divider_gate_ops; |
197 | init.flags = flags; |
198 | init.parent_names = parent_name ? &parent_name : NULL; |
199 | init.num_parents = parent_name ? 1 : 0; |
200 | |
201 | div_gate->divider.reg = reg; |
202 | div_gate->divider.shift = shift; |
203 | div_gate->divider.width = width; |
204 | div_gate->divider.lock = lock; |
205 | div_gate->divider.table = table; |
206 | div_gate->divider.hw.init = &init; |
207 | div_gate->divider.flags = CLK_DIVIDER_ONE_BASED | clk_divider_flags; |
208 | /* cache gate status */ |
209 | val = readl(addr: reg) >> shift; |
210 | val &= clk_div_mask(width); |
211 | div_gate->cached_val = val; |
212 | |
213 | hw = &div_gate->divider.hw; |
214 | ret = clk_hw_register(NULL, hw); |
215 | if (ret) { |
216 | kfree(objp: div_gate); |
217 | hw = ERR_PTR(error: ret); |
218 | } |
219 | |
220 | return hw; |
221 | } |
222 | |