1 // SPDX-License-Identifier: GPL-2.0-only OR MIT
3 * Driver for an SoC block (Numerically Controlled Oscillator)
4 * found on t8103 (M1) and other Apple chips
6 * Copyright (C) The Asahi Linux Contributors
9 #include <linux/bits.h>
10 #include <linux/bitfield.h>
11 #include <linux/clk-provider.h>
13 #include <linux/kernel.h>
14 #include <linux/math64.h>
15 #include <linux/module.h>
17 #include <linux/platform_device.h>
18 #include <linux/spinlock.h>
20 #define NCO_CHANNEL_STRIDE 0x4000
21 #define NCO_CHANNEL_REGSIZE 20
24 #define CTRL_ENABLE BIT(31)
26 #define DIV_FINE GENMASK(1, 0)
27 #define DIV_COARSE GENMASK(12, 2)
30 #define REG_ACCINIT 16
33 * Theory of operation (postulated)
35 * The REG_DIV register indirectly expresses a base integer divisor, roughly
36 * corresponding to twice the desired ratio of input to output clock. This
37 * base divisor is adjusted on a cycle-by-cycle basis based on the state of a
38 * 32-bit phase accumulator to achieve a desired precise clock ratio over the
41 * Specifically an output clock cycle is produced after (REG_DIV divisor)/2
42 * or (REG_DIV divisor + 1)/2 input cycles, the latter taking effect when top
43 * bit of the 32-bit accumulator is set. The accumulator is incremented each
44 * produced output cycle, by the value from either REG_INC1 or REG_INC2, which
45 * of the two is selected depending again on the accumulator's current top bit.
47 * Because the NCO hardware implements counting of input clock cycles in part
48 * in a Galois linear-feedback shift register, the higher bits of divisor
49 * are programmed into REG_DIV by picking an appropriate LFSR state. See
50 * applnco_compute_tables/applnco_div_translate for details on this.
53 #define LFSR_POLY 0xa01
54 #define LFSR_INIT 0x7ff
56 #define LFSR_PERIOD ((1 << LFSR_LEN) - 1)
57 #define LFSR_TBLSIZE (1 << LFSR_LEN)
59 /* The minimal attainable coarse divisor (first value in table) */
60 #define COARSE_DIV_OFFSET 2
62 struct applnco_tables
{
63 u16 fwd
[LFSR_TBLSIZE
];
64 u16 inv
[LFSR_TBLSIZE
];
67 struct applnco_channel
{
69 struct applnco_tables
*tbl
;
75 #define to_applnco_channel(_hw) container_of(_hw, struct applnco_channel, hw)
77 static void applnco_enable_nolock(struct clk_hw
*hw
)
79 struct applnco_channel
*chan
= to_applnco_channel(hw
);
82 val
= readl_relaxed(chan
->base
+ REG_CTRL
);
83 writel_relaxed(val
| CTRL_ENABLE
, chan
->base
+ REG_CTRL
);
86 static void applnco_disable_nolock(struct clk_hw
*hw
)
88 struct applnco_channel
*chan
= to_applnco_channel(hw
);
91 val
= readl_relaxed(chan
->base
+ REG_CTRL
);
92 writel_relaxed(val
& ~CTRL_ENABLE
, chan
->base
+ REG_CTRL
);
95 static int applnco_is_enabled(struct clk_hw
*hw
)
97 struct applnco_channel
*chan
= to_applnco_channel(hw
);
99 return (readl_relaxed(chan
->base
+ REG_CTRL
) & CTRL_ENABLE
) != 0;
102 static void applnco_compute_tables(struct applnco_tables
*tbl
)
105 u32 state
= LFSR_INIT
;
108 * Go through the states of a Galois LFSR and build
109 * a coarse divisor translation table.
111 for (i
= LFSR_PERIOD
; i
> 0; i
--) {
113 state
= (state
>> 1) ^ (LFSR_POLY
>> 1);
115 state
= (state
>> 1);
120 /* Zero value is special-cased */
125 static bool applnco_div_out_of_range(unsigned int div
)
127 unsigned int coarse
= div
/ 4;
129 return coarse
< COARSE_DIV_OFFSET
||
130 coarse
>= COARSE_DIV_OFFSET
+ LFSR_TBLSIZE
;
133 static u32
applnco_div_translate(struct applnco_tables
*tbl
, unsigned int div
)
135 unsigned int coarse
= div
/ 4;
137 if (WARN_ON(applnco_div_out_of_range(div
)))
140 return FIELD_PREP(DIV_COARSE
, tbl
->fwd
[coarse
- COARSE_DIV_OFFSET
]) |
141 FIELD_PREP(DIV_FINE
, div
% 4);
144 static unsigned int applnco_div_translate_inv(struct applnco_tables
*tbl
, u32 regval
)
146 unsigned int coarse
, fine
;
148 coarse
= tbl
->inv
[FIELD_GET(DIV_COARSE
, regval
)] + COARSE_DIV_OFFSET
;
149 fine
= FIELD_GET(DIV_FINE
, regval
);
151 return coarse
* 4 + fine
;
154 static int applnco_set_rate(struct clk_hw
*hw
, unsigned long rate
,
155 unsigned long parent_rate
)
157 struct applnco_channel
*chan
= to_applnco_channel(hw
);
162 div
= 2 * parent_rate
/ rate
;
163 inc1
= 2 * parent_rate
- div
* rate
;
166 if (applnco_div_out_of_range(div
))
169 div
= applnco_div_translate(chan
->tbl
, div
);
171 spin_lock_irqsave(&chan
->lock
, flags
);
172 was_enabled
= applnco_is_enabled(hw
);
173 applnco_disable_nolock(hw
);
175 writel_relaxed(div
, chan
->base
+ REG_DIV
);
176 writel_relaxed(inc1
, chan
->base
+ REG_INC1
);
177 writel_relaxed(inc2
, chan
->base
+ REG_INC2
);
179 /* Presumably a neutral initial value for accumulator */
180 writel_relaxed(1 << 31, chan
->base
+ REG_ACCINIT
);
183 applnco_enable_nolock(hw
);
184 spin_unlock_irqrestore(&chan
->lock
, flags
);
189 static unsigned long applnco_recalc_rate(struct clk_hw
*hw
,
190 unsigned long parent_rate
)
192 struct applnco_channel
*chan
= to_applnco_channel(hw
);
193 u32 div
, inc1
, inc2
, incbase
;
195 div
= applnco_div_translate_inv(chan
->tbl
,
196 readl_relaxed(chan
->base
+ REG_DIV
));
198 inc1
= readl_relaxed(chan
->base
+ REG_INC1
);
199 inc2
= readl_relaxed(chan
->base
+ REG_INC2
);
202 * We don't support wraparound of accumulator
203 * nor the edge case of both increments being zero
205 if (inc1
>= (1 << 31) || inc2
< (1 << 31) || (inc1
== 0 && inc2
== 0))
208 /* Scale both sides of division by incbase to maintain precision */
209 incbase
= inc1
- inc2
;
211 return div64_u64(((u64
) parent_rate
) * 2 * incbase
,
212 ((u64
) div
) * incbase
+ inc1
);
215 static long applnco_round_rate(struct clk_hw
*hw
, unsigned long rate
,
216 unsigned long *parent_rate
)
218 unsigned long lo
= *parent_rate
/ (COARSE_DIV_OFFSET
+ LFSR_TBLSIZE
) + 1;
219 unsigned long hi
= *parent_rate
/ COARSE_DIV_OFFSET
;
221 return clamp(rate
, lo
, hi
);
224 static int applnco_enable(struct clk_hw
*hw
)
226 struct applnco_channel
*chan
= to_applnco_channel(hw
);
229 spin_lock_irqsave(&chan
->lock
, flags
);
230 applnco_enable_nolock(hw
);
231 spin_unlock_irqrestore(&chan
->lock
, flags
);
236 static void applnco_disable(struct clk_hw
*hw
)
238 struct applnco_channel
*chan
= to_applnco_channel(hw
);
241 spin_lock_irqsave(&chan
->lock
, flags
);
242 applnco_disable_nolock(hw
);
243 spin_unlock_irqrestore(&chan
->lock
, flags
);
246 static const struct clk_ops applnco_ops
= {
247 .set_rate
= applnco_set_rate
,
248 .recalc_rate
= applnco_recalc_rate
,
249 .round_rate
= applnco_round_rate
,
250 .enable
= applnco_enable
,
251 .disable
= applnco_disable
,
252 .is_enabled
= applnco_is_enabled
,
255 static int applnco_probe(struct platform_device
*pdev
)
257 struct device_node
*np
= pdev
->dev
.of_node
;
258 struct clk_parent_data pdata
= { .index
= 0 };
259 struct clk_init_data init
;
260 struct clk_hw_onecell_data
*onecell_data
;
262 struct resource
*res
;
263 struct applnco_tables
*tbl
;
264 unsigned int nchannels
;
267 base
= devm_platform_get_and_ioremap_resource(pdev
, 0, &res
);
269 return PTR_ERR(base
);
271 if (resource_size(res
) < NCO_CHANNEL_REGSIZE
)
273 nchannels
= (resource_size(res
) - NCO_CHANNEL_REGSIZE
)
274 / NCO_CHANNEL_STRIDE
+ 1;
276 onecell_data
= devm_kzalloc(&pdev
->dev
, struct_size(onecell_data
, hws
,
277 nchannels
), GFP_KERNEL
);
280 onecell_data
->num
= nchannels
;
282 tbl
= devm_kzalloc(&pdev
->dev
, sizeof(*tbl
), GFP_KERNEL
);
285 applnco_compute_tables(tbl
);
287 for (i
= 0; i
< nchannels
; i
++) {
288 struct applnco_channel
*chan
;
290 chan
= devm_kzalloc(&pdev
->dev
, sizeof(*chan
), GFP_KERNEL
);
293 chan
->base
= base
+ NCO_CHANNEL_STRIDE
* i
;
295 spin_lock_init(&chan
->lock
);
297 memset(&init
, 0, sizeof(init
));
298 init
.name
= devm_kasprintf(&pdev
->dev
, GFP_KERNEL
,
299 "%s-%d", np
->name
, i
);
300 init
.ops
= &applnco_ops
;
301 init
.parent_data
= &pdata
;
302 init
.num_parents
= 1;
305 chan
->hw
.init
= &init
;
306 ret
= devm_clk_hw_register(&pdev
->dev
, &chan
->hw
);
310 onecell_data
->hws
[i
] = &chan
->hw
;
313 return devm_of_clk_add_hw_provider(&pdev
->dev
, of_clk_hw_onecell_get
,
317 static const struct of_device_id applnco_ids
[] = {
318 { .compatible
= "apple,nco" },
321 MODULE_DEVICE_TABLE(of
, applnco_ids
);
323 static struct platform_driver applnco_driver
= {
326 .of_match_table
= applnco_ids
,
328 .probe
= applnco_probe
,
330 module_platform_driver(applnco_driver
);
332 MODULE_AUTHOR("Martin PoviĊĦer <povik+lin@cutebit.org>");
333 MODULE_DESCRIPTION("Clock driver for NCO blocks on Apple SoCs");
334 MODULE_LICENSE("GPL");