]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/riscv/sifive/fu540_prci.c
Import tzdata 2020c
[FreeBSD/FreeBSD.git] / sys / riscv / sifive / fu540_prci.c
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 <gnu/dts/include/dt-bindings/clock/sifive-fu540-prci.h>
55
56 static 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
63 static struct resource_spec prci_spec[] = {
64         { SYS_RES_MEMORY, 0, RF_ACTIVE },
65         RESOURCE_SPEC_END
66 };
67
68 struct 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
79 struct 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
103 struct 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 */
117 struct 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. */
124 struct 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
134 static int
135 prci_clk_pll_init(struct clknode *clk, device_t dev)
136 {
137
138         clknode_init_parent_idx(clk, 0);
139
140         return (0);
141 }
142
143 static int
144 prci_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
182 static 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
188 DEFINE_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
192 static int
193 prci_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
207 static void
208 prci_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
226 static int
227 prci_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
302 fail1:
303         free(clkdef.parent_names, M_OFWPROP);
304
305 fail:
306         bus_release_resources(dev, prci_spec, &sc->res);
307         mtx_destroy(&sc->mtx);
308         return (error);
309 }
310
311 static device_method_t prci_methods[] = {
312         DEVMETHOD(device_probe,         prci_probe),
313         DEVMETHOD(device_attach,        prci_attach),
314
315         DEVMETHOD_END
316 };
317
318 static driver_t prci_driver = {
319         "fu540prci",
320         prci_methods,
321         sizeof(struct prci_softc)
322 };
323
324 static devclass_t prci_devclass;
325
326 EARLY_DRIVER_MODULE(fu540prci, simplebus, prci_driver, prci_devclass, 0, 0,
327     BUS_PASS_BUS);