1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2019 Axiado Corporation
5 * All rights reserved.
6 *
7 * This software was developed in part by Kristof Provost under contract for
8 * Axiado Corporation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/bus.h>
38#include <sys/kernel.h>
39#include <sys/lock.h>
40#include <sys/module.h>
41#include <sys/mutex.h>
42#include <sys/rman.h>
43
44#include <machine/bus.h>
45#include <machine/cpu.h>
46
47#include <dev/extres/clk/clk.h>
48#include <dev/extres/clk/clk_fixed.h>
49
50#include <dev/ofw/ofw_bus.h>
51#include <dev/ofw/ofw_bus_subr.h>
52#include <dev/ofw/openfirm.h>
53
54#include <dt-bindings/clock/sifive-fu540-prci.h>
55
56static struct ofw_compat_data compat_data[] = {
57	{ "sifive,aloeprci0",		1 },
58	{ "sifive,ux00prci0",		1 },
59	{ "sifive,fu540-c000-prci",	1 },
60	{ NULL,				0 },
61};
62
63static struct resource_spec prci_spec[] = {
64	{ SYS_RES_MEMORY, 0, RF_ACTIVE },
65	RESOURCE_SPEC_END
66};
67
68struct prci_softc {
69	device_t		dev;
70
71	struct mtx		mtx;
72
73	struct clkdom		*clkdom;
74	struct resource		*res;
75	bus_space_tag_t		bst;
76	bus_space_handle_t	bsh;
77};
78
79struct prci_clk_pll_sc {
80	struct prci_softc	*parent_sc;
81	uint32_t		reg;
82};
83
84#define	PRCI_LOCK(sc)			mtx_lock(&(sc)->mtx)
85#define	PRCI_UNLOCK(sc)			mtx_unlock(&(sc)->mtx)
86#define	PRCI_ASSERT_LOCKED(sc)		mtx_assert(&(sc)->mtx, MA_OWNED);
87#define	PRCI_ASSERT_UNLOCKED(sc)	mtx_assert(&(sc)->mtx, MA_NOTOWNED);
88
89#define	PRCI_COREPLL_CFG0		0x4
90#define	PRCI_DDRPLL_CFG0		0xC
91#define	PRCI_GEMGXLPLL_CFG0		0x1C
92
93#define	PRCI_PLL_DIVR_MASK		0x3f
94#define	PRCI_PLL_DIVR_SHIFT		0
95#define	PRCI_PLL_DIVF_MASK		0x7fc0
96#define	PRCI_PLL_DIVF_SHIFT		6
97#define	PRCI_PLL_DIVQ_MASK		0x38000
98#define	PRCI_PLL_DIVQ_SHIFT		15
99
100#define	PRCI_READ(_sc, _reg)		\
101    bus_space_read_4((_sc)->bst, (_sc)->bsh, (_reg))
102
103struct prci_pll_def {
104	uint32_t	id;
105	const char	*name;
106	uint32_t	reg;
107};
108
109#define PLL(_id, _name, _base)					\
110{								\
111	.id = (_id),						\
112	.name = (_name),					\
113	.reg = (_base),						\
114}
115
116/* PLL Clocks */
117struct prci_pll_def pll_clks[] = {
118	PLL(PRCI_CLK_COREPLL, "coreclk",  PRCI_COREPLL_CFG0),
119	PLL(PRCI_CLK_DDRPLL, "ddrclk",   PRCI_DDRPLL_CFG0),
120	PLL(PRCI_CLK_GEMGXLPLL, "gemgxclk", PRCI_GEMGXLPLL_CFG0),
121};
122
123/* Fixed divisor clock TLCLK. */
124struct clk_fixed_def tlclk_def = {
125	.clkdef.id = PRCI_CLK_TLCLK,
126	.clkdef.name = "prci_tlclk",
127	.clkdef.parent_names = (const char *[]){"coreclk"},
128	.clkdef.parent_cnt = 1,
129	.clkdef.flags = CLK_NODE_STATIC_STRINGS,
130	.mult = 1,
131	.div = 2,
132};
133
134static int
135prci_clk_pll_init(struct clknode *clk, device_t dev)
136{
137
138	clknode_init_parent_idx(clk, 0);
139
140	return (0);
141}
142
143static int
144prci_clk_pll_recalc(struct clknode *clk, uint64_t *freq)
145{
146	struct prci_clk_pll_sc *sc;
147	struct clknode *parent_clk;
148	uint32_t val;
149	uint64_t refclk, divf, divq, divr;
150	int err;
151
152	KASSERT(freq != NULL, ("freq cannot be NULL"));
153
154	sc = clknode_get_softc(clk);
155
156	PRCI_LOCK(sc->parent_sc);
157
158	/* Get refclock frequency. */
159	parent_clk = clknode_get_parent(clk);
160	err = clknode_get_freq(parent_clk, &refclk);
161	if (err) {
162		device_printf(sc->parent_sc->dev,
163		    "Failed to get refclk frequency\n");
164		PRCI_UNLOCK(sc->parent_sc);
165		return (err);
166	}
167
168	/* Calculate the PLL output */
169	val = PRCI_READ(sc->parent_sc, sc->reg);
170
171	divf = (val & PRCI_PLL_DIVF_MASK) >> PRCI_PLL_DIVF_SHIFT;
172	divq = (val & PRCI_PLL_DIVQ_MASK) >> PRCI_PLL_DIVQ_SHIFT;
173	divr = (val & PRCI_PLL_DIVR_MASK) >> PRCI_PLL_DIVR_SHIFT;
174
175	*freq = refclk / (divr + 1) * (2 * (divf + 1)) / (1 << divq);
176
177	PRCI_UNLOCK(sc->parent_sc);
178
179	return (0);
180}
181
182static clknode_method_t prci_clk_pll_clknode_methods[] = {
183	CLKNODEMETHOD(clknode_init,		prci_clk_pll_init),
184	CLKNODEMETHOD(clknode_recalc_freq,	prci_clk_pll_recalc),
185	CLKNODEMETHOD_END
186};
187
188DEFINE_CLASS_1(prci_clk_pll_clknode, prci_clk_pll_clknode_class,
189    prci_clk_pll_clknode_methods, sizeof(struct prci_clk_pll_sc),
190    clknode_class);
191
192static int
193prci_probe(device_t dev)
194{
195
196	if (!ofw_bus_status_okay(dev))
197		return (ENXIO);
198
199	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
200		return (ENXIO);
201
202	device_set_desc(dev, "SiFive FU540 Power Reset Clocking Interrupt");
203
204	return (BUS_PROBE_DEFAULT);
205}
206
207static void
208prci_pll_register(struct prci_softc *parent_sc, struct clknode_init_def *clkdef,
209	uint32_t reg)
210{
211	struct clknode *clk;
212	struct prci_clk_pll_sc *sc;
213
214	clk = clknode_create(parent_sc->clkdom, &prci_clk_pll_clknode_class,
215	    clkdef);
216	if (clk == NULL)
217		panic("Failed to create clknode");
218
219	sc = clknode_get_softc(clk);
220	sc->parent_sc = parent_sc;
221	sc->reg = reg;
222
223	clknode_register(parent_sc->clkdom, clk);
224}
225
226static int
227prci_attach(device_t dev)
228{
229	struct clknode_init_def clkdef;
230	struct prci_softc *sc;
231	clk_t clk_parent;
232	phandle_t node;
233	int i, ncells, error;
234
235	sc = device_get_softc(dev);
236	sc->dev = dev;
237
238	mtx_init(&sc->mtx, device_get_nameunit(sc->dev), NULL, MTX_DEF);
239
240	error = bus_alloc_resources(dev, prci_spec, &sc->res);
241	if (error) {
242		device_printf(dev, "Couldn't allocate resources\n");
243		goto fail;
244	}
245	sc->bst = rman_get_bustag(sc->res);
246	sc->bsh = rman_get_bushandle(sc->res);
247
248	node = ofw_bus_get_node(dev);
249	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
250	    "#clock-cells", &ncells);
251	if (error != 0 || ncells < 1) {
252		device_printf(dev, "couldn't find parent clock\n");
253		goto fail;
254	}
255
256	bzero(&clkdef, sizeof(clkdef));
257	clkdef.parent_names = mallocarray(ncells, sizeof(char *), M_OFWPROP,
258	    M_WAITOK);
259	for (i = 0; i < ncells; i++) {
260		error = clk_get_by_ofw_index(dev, 0, i, &clk_parent);
261		if (error != 0) {
262			device_printf(dev, "cannot get clock %d\n", error);
263			goto fail1;
264		}
265		clkdef.parent_names[i] = clk_get_name(clk_parent);
266		if (bootverbose)
267			device_printf(dev, "clk parent: %s\n",
268			    clkdef.parent_names[i]);
269		clk_release(clk_parent);
270	}
271	clkdef.parent_cnt = ncells;
272
273	sc->clkdom = clkdom_create(dev);
274	if (sc->clkdom == NULL) {
275		device_printf(dev, "Couldn't create clock domain\n");
276		goto fail;
277	}
278
279	/* We can't free a clkdom, so from now on we cannot fail. */
280	for (i = 0; i < nitems(pll_clks); i++) {
281		clkdef.id = pll_clks[i].id;
282		clkdef.name = pll_clks[i].name;
283		prci_pll_register(sc, &clkdef, pll_clks[i].reg);
284	}
285
286	/*
287	 * Register the fixed clock "tlclk".
288	 *
289	 * If an older device tree is being used, tlclk may appear as its own
290	 * entity in the device tree, under soc/tlclk. If this is the case it
291	 * will be registered automatically by the fixed_clk driver, and the
292	 * version we register here will be an unreferenced duplicate.
293	 */
294	clknode_fixed_register(sc->clkdom, &tlclk_def);
295
296	error = clkdom_finit(sc->clkdom);
297	if (error)
298		panic("Couldn't finalise clock domain");
299
300	return (0);
301
302fail1:
303	free(clkdef.parent_names, M_OFWPROP);
304
305fail:
306	bus_release_resources(dev, prci_spec, &sc->res);
307	mtx_destroy(&sc->mtx);
308	return (error);
309}
310
311static device_method_t prci_methods[] = {
312	DEVMETHOD(device_probe,		prci_probe),
313	DEVMETHOD(device_attach,	prci_attach),
314
315	DEVMETHOD_END
316};
317
318static driver_t prci_driver = {
319	"fu540prci",
320	prci_methods,
321	sizeof(struct prci_softc)
322};
323
324static devclass_t prci_devclass;
325
326EARLY_DRIVER_MODULE(fu540prci, simplebus, prci_driver, prci_devclass, 0, 0,
327    BUS_PASS_BUS);
328