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
14struct clk_divider_gate {
15 struct clk_divider divider;
16 u32 cached_val;
17};
18
19static 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
26static 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
41static 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
67static 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
73static 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
103static 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
126static 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
144static 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
155static 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
160static 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 */
176struct 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

source code of linux/drivers/clk/imx/clk-divider-gate.c