1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org>
5 * Copyright (c) 2022 Mitchell Horne <mhorne@FreeBSD.org>
6 * Copyright (c) 2024 Jari Sihvola <jsihv@gmx.com>
7 */
8
9#include <sys/param.h>
10#include <sys/systm.h>
11#include <sys/bus.h>
12#include <sys/mutex.h>
13#include <sys/rman.h>
14
15#include <machine/bus.h>
16#include <machine/intr.h>
17#include <machine/resource.h>
18
19#include <dev/clk/clk.h>
20#include <dev/hwreset/hwreset.h>
21
22#include <dt-bindings/clock/starfive,jh7110-crg.h>
23
24#include <dev/clk/starfive/jh7110_clk.h>
25
26#include "clkdev_if.h"
27#include "hwreset_if.h"
28
29#define	JH7110_DIV_MASK		0xffffff
30#define	JH7110_MUX_SHIFT	24
31#define	JH7110_MUX_MASK		0x3f000000
32#define	JH7110_ENABLE_SHIFT	31
33
34#define	REG_SIZE		4
35
36struct jh7110_clk_sc {
37	uint32_t	offset;
38	uint32_t	flags;
39	uint64_t	d_max;
40	int		id;
41};
42
43#define DIV_ROUND_CLOSEST(n, d)  (((n) + (d) / 2) / (d))
44
45#define	READ4(_sc, _off)				\
46	bus_read_4(_sc->mem_res, _off)
47#define WRITE4(_sc, _off, _val)				\
48	bus_write_4(_sc->mem_res, _off, _val)
49
50#define	DEVICE_LOCK(_clk)				\
51	CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
52#define	DEVICE_UNLOCK(_clk)				\
53	CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
54
55/* Reset functions */
56
57int
58jh7110_reset_assert(device_t dev, intptr_t id, bool assert)
59{
60	struct jh7110_clkgen_softc *sc;
61	uint32_t regvalue, offset, bitmask = 1UL << id % 32;
62
63	sc = device_get_softc(dev);
64	offset = sc->reset_selector_offset + id / 32 * 4;
65
66	mtx_lock(&sc->mtx);
67
68	regvalue = READ4(sc, offset);
69
70	if (assert)
71		regvalue |= bitmask;
72	else
73		regvalue &= ~bitmask;
74	WRITE4(sc, offset, regvalue);
75
76	mtx_unlock(&sc->mtx);
77
78	return (0);
79}
80
81int
82jh7110_reset_is_asserted(device_t dev, intptr_t id, bool *reset)
83{
84	struct jh7110_clkgen_softc *sc;
85	uint32_t regvalue, offset, bitmask;
86
87	sc = device_get_softc(dev);
88	offset = sc->reset_status_offset + id / 32 * 4;
89
90	mtx_lock(&sc->mtx);
91
92	regvalue = READ4(sc, offset);
93	bitmask = 1UL << id % 32;
94
95	mtx_unlock(&sc->mtx);
96
97	*reset = (regvalue & bitmask) == 0;
98
99	return (0);
100}
101
102/* Clock functions */
103
104static int
105jh7110_clk_init(struct clknode *clk, device_t dev)
106{
107	struct jh7110_clkgen_softc *sc;
108	struct jh7110_clk_sc *sc_clk;
109	uint32_t reg;
110	int idx = 0;
111
112	sc = device_get_softc(clknode_get_device(clk));
113	sc_clk = clknode_get_softc(clk);
114
115	if (sc_clk->flags & JH7110_CLK_HAS_MUX) {
116		DEVICE_LOCK(clk);
117		reg = READ4(sc, sc_clk->offset);
118		DEVICE_UNLOCK(clk);
119		idx = (reg & JH7110_MUX_MASK) >> JH7110_MUX_SHIFT;
120	}
121
122	clknode_init_parent_idx(clk, idx);
123
124	return (0);
125}
126
127static int
128jh7110_clk_set_gate(struct clknode *clk, bool enable)
129{
130	struct jh7110_clkgen_softc *sc;
131	struct jh7110_clk_sc *sc_clk;
132	uint32_t reg;
133
134	sc = device_get_softc(clknode_get_device(clk));
135	sc_clk = clknode_get_softc(clk);
136
137	if ((sc_clk->flags & JH7110_CLK_HAS_GATE) == 0)
138		return (0);
139
140	DEVICE_LOCK(clk);
141
142	reg = READ4(sc, sc_clk->offset);
143	if (enable)
144		reg |= (1 << JH7110_ENABLE_SHIFT);
145	else
146		reg &= ~(1 << JH7110_ENABLE_SHIFT);
147	WRITE4(sc, sc_clk->offset, reg);
148
149	DEVICE_UNLOCK(clk);
150
151	return (0);
152}
153
154static int
155jh7110_clk_set_mux(struct clknode *clk, int idx)
156{
157	struct jh7110_clkgen_softc *sc;
158	struct jh7110_clk_sc *sc_clk;
159	uint32_t reg;
160
161	sc = device_get_softc(clknode_get_device(clk));
162	sc_clk = clknode_get_softc(clk);
163
164	if ((sc_clk->flags & JH7110_CLK_HAS_MUX) == 0)
165		return (ENXIO);
166
167	/* Checking index size */
168	if ((idx & (JH7110_MUX_MASK >> JH7110_MUX_SHIFT)) != idx)
169		return (EINVAL);
170
171	DEVICE_LOCK(clk);
172
173	reg = READ4(sc, sc_clk->offset) & ~JH7110_MUX_MASK;
174	reg |= idx << JH7110_MUX_SHIFT;
175	WRITE4(sc, sc_clk->offset, reg);
176
177	DEVICE_UNLOCK(clk);
178
179	return (0);
180}
181
182static int
183jh7110_clk_recalc_freq(struct clknode *clk, uint64_t *freq)
184{
185	struct jh7110_clkgen_softc *sc;
186	struct jh7110_clk_sc *sc_clk;
187	uint32_t divisor;
188
189	sc = device_get_softc(clknode_get_device(clk));
190	sc_clk = clknode_get_softc(clk);
191
192	/* Returning error here causes panic */
193	if ((sc_clk->flags & JH7110_CLK_HAS_DIV) == 0)
194		return (0);
195
196	DEVICE_LOCK(clk);
197
198	divisor = READ4(sc, sc_clk->offset) & JH7110_DIV_MASK;
199
200	DEVICE_UNLOCK(clk);
201
202	if (divisor)
203		*freq = *freq / divisor;
204	else
205		*freq = 0;
206
207	return (0);
208}
209
210static int
211jh7110_clk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
212    int flags, int *done)
213{
214	struct jh7110_clkgen_softc *sc;
215	struct jh7110_clk_sc *sc_clk;
216	uint32_t divisor;
217
218	sc = device_get_softc(clknode_get_device(clk));
219	sc_clk = clknode_get_softc(clk);
220
221	if ((sc_clk->flags & JH7110_CLK_HAS_DIV) == 0)
222		return (0);
223
224	divisor = MIN(MAX(DIV_ROUND_CLOSEST(fin, *fout), 1UL), sc_clk->d_max);
225
226	if (flags & CLK_SET_DRYRUN)
227		goto done;
228
229	DEVICE_LOCK(clk);
230
231	divisor |= READ4(sc, sc_clk->offset) & ~JH7110_DIV_MASK;
232	WRITE4(sc, sc_clk->offset, divisor);
233
234	DEVICE_UNLOCK(clk);
235
236done:
237	*fout = divisor;
238	*done = 1;
239
240	return (0);
241}
242
243static clknode_method_t jh7110_clknode_methods[] = {
244	/* Device interface */
245	CLKNODEMETHOD(clknode_init,		jh7110_clk_init),
246	CLKNODEMETHOD(clknode_set_gate,		jh7110_clk_set_gate),
247	CLKNODEMETHOD(clknode_set_mux,		jh7110_clk_set_mux),
248	CLKNODEMETHOD(clknode_recalc_freq,	jh7110_clk_recalc_freq),
249	CLKNODEMETHOD(clknode_set_freq,		jh7110_clk_set_freq),
250	CLKNODEMETHOD_END
251};
252
253DEFINE_CLASS_1(jh7110_clknode, jh7110_clknode_class, jh7110_clknode_methods,
254    sizeof(struct jh7110_clk_sc), clknode_class);
255
256int
257jh7110_clk_register(struct clkdom *clkdom, const struct jh7110_clk_def *clkdef)
258{
259	struct clknode *clk;
260	struct jh7110_clk_sc *sc;
261
262	clk = clknode_create(clkdom, &jh7110_clknode_class, &clkdef->clkdef);
263	if (clk == NULL)
264		return (-1);
265
266	sc = clknode_get_softc(clk);
267
268	sc->offset = clkdef->clkdef.id * REG_SIZE;
269
270	sc->flags = clkdef->flags;
271	sc->id = clkdef->clkdef.id;
272	sc->d_max = clkdef->d_max;
273
274	clknode_register(clkdom, clk);
275
276	return (0);
277}
278