1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/param.h>
29#include <sys/systm.h>
30#include <sys/bus.h>
31
32#include <dev/clk/clk.h>
33
34#include <dev/clk/allwinner/aw_clk.h>
35#include <dev/clk/allwinner/aw_clk_np.h>
36
37#include "clkdev_if.h"
38
39/*
40 * clknode for clocks matching the formula :
41 *
42 * clk = clkin * n / p
43 *
44 */
45
46struct aw_clk_np_sc {
47	uint32_t	offset;
48
49	struct aw_clk_factor	n;
50	struct aw_clk_factor	p;
51
52	uint32_t	gate_shift;
53	uint32_t	lock_shift;
54	uint32_t	lock_retries;
55
56	uint32_t	flags;
57};
58
59#define	WRITE4(_clk, off, val)						\
60	CLKDEV_WRITE_4(clknode_get_device(_clk), off, val)
61#define	READ4(_clk, off, val)						\
62	CLKDEV_READ_4(clknode_get_device(_clk), off, val)
63#define	DEVICE_LOCK(_clk)							\
64	CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
65#define	DEVICE_UNLOCK(_clk)						\
66	CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
67
68static int
69aw_clk_np_init(struct clknode *clk, device_t dev)
70{
71
72	clknode_init_parent_idx(clk, 0);
73	return (0);
74}
75
76static int
77aw_clk_np_set_gate(struct clknode *clk, bool enable)
78{
79	struct aw_clk_np_sc *sc;
80	uint32_t val;
81
82	sc = clknode_get_softc(clk);
83
84	if ((sc->flags & AW_CLK_HAS_GATE) == 0)
85		return (0);
86
87	DEVICE_LOCK(clk);
88	READ4(clk, sc->offset, &val);
89	if (enable)
90		val |= (1 << sc->gate_shift);
91	else
92		val &= ~(1 << sc->gate_shift);
93	WRITE4(clk, sc->offset, val);
94	DEVICE_UNLOCK(clk);
95
96	return (0);
97}
98
99static uint64_t
100aw_clk_np_find_best(struct aw_clk_np_sc *sc, uint64_t fparent, uint64_t *fout,
101    uint32_t *factor_n, uint32_t *factor_p)
102{
103	uint64_t cur, best;
104	uint32_t n, p, max_n, max_p, min_n, min_p;
105
106	*factor_n = *factor_p = 0;
107
108	max_n = aw_clk_factor_get_max(&sc->n);
109	max_p = aw_clk_factor_get_max(&sc->p);
110	min_n = aw_clk_factor_get_min(&sc->n);
111	min_p = aw_clk_factor_get_min(&sc->p);
112
113	for (p = min_p; p <= max_p; ) {
114		for (n = min_n; n <= max_n; ) {
115			cur = fparent * n / p;
116			if (abs(*fout - cur) < abs(*fout - best)) {
117				best = cur;
118				*factor_n = n;
119				*factor_p = p;
120			}
121
122			n++;
123		}
124		p++;
125	}
126
127	return (best);
128}
129
130static int
131aw_clk_np_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout,
132    int flags, int *stop)
133{
134	struct aw_clk_np_sc *sc;
135	uint64_t cur, best;
136	uint32_t val, n, p, best_n, best_p;
137	int retry;
138
139	sc = clknode_get_softc(clk);
140
141	best = cur = 0;
142
143	best = aw_clk_np_find_best(sc, fparent, fout,
144	    &best_n, &best_p);
145
146	if ((flags & CLK_SET_DRYRUN) != 0) {
147		*fout = best;
148		*stop = 1;
149		return (0);
150	}
151
152	if ((best < *fout) &&
153	  ((flags & CLK_SET_ROUND_DOWN) == 0)) {
154		*stop = 1;
155		return (ERANGE);
156	}
157	if ((best > *fout) &&
158	  ((flags & CLK_SET_ROUND_UP) == 0)) {
159		*stop = 1;
160		return (ERANGE);
161	}
162
163	DEVICE_LOCK(clk);
164	READ4(clk, sc->offset, &val);
165
166	n = aw_clk_factor_get_value(&sc->n, best_n);
167	p = aw_clk_factor_get_value(&sc->p, best_p);
168	val &= ~sc->n.mask;
169	val &= ~sc->p.mask;
170	val |= n << sc->n.shift;
171	val |= p << sc->p.shift;
172
173	WRITE4(clk, sc->offset, val);
174	DEVICE_UNLOCK(clk);
175
176	if ((sc->flags & AW_CLK_HAS_LOCK) != 0) {
177		for (retry = 0; retry < sc->lock_retries; retry++) {
178			READ4(clk, sc->offset, &val);
179			if ((val & (1 << sc->lock_shift)) != 0)
180				break;
181			DELAY(1000);
182		}
183	}
184
185	*fout = best;
186	*stop = 1;
187
188	return (0);
189}
190
191static int
192aw_clk_np_recalc(struct clknode *clk, uint64_t *freq)
193{
194	struct aw_clk_np_sc *sc;
195	uint32_t val, n, p;
196
197	sc = clknode_get_softc(clk);
198
199	DEVICE_LOCK(clk);
200	READ4(clk, sc->offset, &val);
201	DEVICE_UNLOCK(clk);
202
203	n = aw_clk_get_factor(val, &sc->n);
204	p = aw_clk_get_factor(val, &sc->p);
205
206	*freq = *freq * n / p;
207
208	return (0);
209}
210
211static clknode_method_t aw_np_clknode_methods[] = {
212	/* Device interface */
213	CLKNODEMETHOD(clknode_init,		aw_clk_np_init),
214	CLKNODEMETHOD(clknode_set_gate,		aw_clk_np_set_gate),
215	CLKNODEMETHOD(clknode_recalc_freq,	aw_clk_np_recalc),
216	CLKNODEMETHOD(clknode_set_freq,		aw_clk_np_set_freq),
217	CLKNODEMETHOD_END
218};
219
220DEFINE_CLASS_1(aw_np_clknode, aw_np_clknode_class, aw_np_clknode_methods,
221    sizeof(struct aw_clk_np_sc), clknode_class);
222
223int
224aw_clk_np_register(struct clkdom *clkdom, struct aw_clk_np_def *clkdef)
225{
226	struct clknode *clk;
227	struct aw_clk_np_sc *sc;
228
229	clk = clknode_create(clkdom, &aw_np_clknode_class, &clkdef->clkdef);
230	if (clk == NULL)
231		return (1);
232
233	sc = clknode_get_softc(clk);
234
235	sc->offset = clkdef->offset;
236
237	sc->n.shift = clkdef->n.shift;
238	sc->n.width = clkdef->n.width;
239	sc->n.mask = ((1 << sc->n.width) - 1) << sc->n.shift;
240	sc->n.value = clkdef->n.value;
241	sc->n.flags = clkdef->n.flags;
242
243	sc->p.shift = clkdef->p.shift;
244	sc->p.width = clkdef->p.width;
245	sc->p.mask = ((1 << sc->p.width) - 1) << sc->p.shift;
246	sc->p.value = clkdef->p.value;
247	sc->p.flags = clkdef->p.flags;
248
249	sc->gate_shift = clkdef->gate_shift;
250
251	sc->lock_shift = clkdef->lock_shift;
252	sc->lock_retries = clkdef->lock_retries;
253
254	sc->flags = clkdef->flags;
255
256	clknode_register(clkdom, clk);
257
258	return (0);
259}
260