1*7fead5f1SAdrian Chadd /*-
2*7fead5f1SAdrian Chadd * SPDX-License-Identifier: BSD-2-Clause
3*7fead5f1SAdrian Chadd *
4*7fead5f1SAdrian Chadd * Copyright (c) 2025, Adrian Chadd <adrian@FreeBSD.org>
5*7fead5f1SAdrian Chadd *
6*7fead5f1SAdrian Chadd * Redistribution and use in source and binary forms, with or without
7*7fead5f1SAdrian Chadd * modification, are permitted provided that the following conditions
8*7fead5f1SAdrian Chadd * are met:
9*7fead5f1SAdrian Chadd * 1. Redistributions of source code must retain the above copyright
10*7fead5f1SAdrian Chadd * notice unmodified, this list of conditions, and the following
11*7fead5f1SAdrian Chadd * disclaimer.
12*7fead5f1SAdrian Chadd * 2. Redistributions in binary form must reproduce the above copyright
13*7fead5f1SAdrian Chadd * notice, this list of conditions and the following disclaimer in the
14*7fead5f1SAdrian Chadd * documentation and/or other materials provided with the distribution.
15*7fead5f1SAdrian Chadd *
16*7fead5f1SAdrian Chadd * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17*7fead5f1SAdrian Chadd * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18*7fead5f1SAdrian Chadd * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19*7fead5f1SAdrian Chadd * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20*7fead5f1SAdrian Chadd * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21*7fead5f1SAdrian Chadd * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22*7fead5f1SAdrian Chadd * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23*7fead5f1SAdrian Chadd * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24*7fead5f1SAdrian Chadd * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25*7fead5f1SAdrian Chadd * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*7fead5f1SAdrian Chadd */
27*7fead5f1SAdrian Chadd
28*7fead5f1SAdrian Chadd /* Driver for Qualcomm clock/reset trees */
29*7fead5f1SAdrian Chadd
30*7fead5f1SAdrian Chadd #include <sys/param.h>
31*7fead5f1SAdrian Chadd #include <sys/kernel.h>
32*7fead5f1SAdrian Chadd #include <sys/malloc.h>
33*7fead5f1SAdrian Chadd #include <sys/module.h>
34*7fead5f1SAdrian Chadd #include <sys/sglist.h>
35*7fead5f1SAdrian Chadd #include <sys/random.h>
36*7fead5f1SAdrian Chadd #include <sys/stdatomic.h>
37*7fead5f1SAdrian Chadd #include <sys/mutex.h>
38*7fead5f1SAdrian Chadd
39*7fead5f1SAdrian Chadd #include <machine/bus.h>
40*7fead5f1SAdrian Chadd #include <machine/resource.h>
41*7fead5f1SAdrian Chadd #include <sys/bus.h>
42*7fead5f1SAdrian Chadd
43*7fead5f1SAdrian Chadd #include <dev/fdt/fdt_common.h>
44*7fead5f1SAdrian Chadd #include <dev/ofw/ofw_bus.h>
45*7fead5f1SAdrian Chadd #include <dev/ofw/ofw_bus_subr.h>
46*7fead5f1SAdrian Chadd
47*7fead5f1SAdrian Chadd #include <dev/hwreset/hwreset.h>
48*7fead5f1SAdrian Chadd
49*7fead5f1SAdrian Chadd #include "clkdev_if.h"
50*7fead5f1SAdrian Chadd #include "hwreset_if.h"
51*7fead5f1SAdrian Chadd
52*7fead5f1SAdrian Chadd #include "qcom_gcc_var.h"
53*7fead5f1SAdrian Chadd #include "qcom_gcc_ipq4018.h"
54*7fead5f1SAdrian Chadd
55*7fead5f1SAdrian Chadd static int qcom_gcc_modevent(module_t, int, void *);
56*7fead5f1SAdrian Chadd
57*7fead5f1SAdrian Chadd static int qcom_gcc_probe(device_t);
58*7fead5f1SAdrian Chadd static int qcom_gcc_attach(device_t);
59*7fead5f1SAdrian Chadd static int qcom_gcc_detach(device_t);
60*7fead5f1SAdrian Chadd
61*7fead5f1SAdrian Chadd struct qcom_gcc_chipset_list_entry {
62*7fead5f1SAdrian Chadd const char *ofw;
63*7fead5f1SAdrian Chadd const char *desc;
64*7fead5f1SAdrian Chadd qcom_gcc_chipset_t chipset;
65*7fead5f1SAdrian Chadd };
66*7fead5f1SAdrian Chadd
67*7fead5f1SAdrian Chadd static struct qcom_gcc_chipset_list_entry qcom_gcc_chipset_list[] = {
68*7fead5f1SAdrian Chadd { "qcom,gcc-ipq4019", "Qualcomm IPQ4018 Clock/Reset Controller",
69*7fead5f1SAdrian Chadd QCOM_GCC_CHIPSET_IPQ4018 },
70*7fead5f1SAdrian Chadd { NULL, NULL, 0 },
71*7fead5f1SAdrian Chadd };
72*7fead5f1SAdrian Chadd
73*7fead5f1SAdrian Chadd static int
qcom_gcc_modevent(module_t mod,int type,void * unused)74*7fead5f1SAdrian Chadd qcom_gcc_modevent(module_t mod, int type, void *unused)
75*7fead5f1SAdrian Chadd {
76*7fead5f1SAdrian Chadd int error;
77*7fead5f1SAdrian Chadd
78*7fead5f1SAdrian Chadd switch (type) {
79*7fead5f1SAdrian Chadd case MOD_LOAD:
80*7fead5f1SAdrian Chadd case MOD_QUIESCE:
81*7fead5f1SAdrian Chadd case MOD_UNLOAD:
82*7fead5f1SAdrian Chadd case MOD_SHUTDOWN:
83*7fead5f1SAdrian Chadd error = 0;
84*7fead5f1SAdrian Chadd break;
85*7fead5f1SAdrian Chadd default:
86*7fead5f1SAdrian Chadd error = EOPNOTSUPP;
87*7fead5f1SAdrian Chadd break;
88*7fead5f1SAdrian Chadd }
89*7fead5f1SAdrian Chadd
90*7fead5f1SAdrian Chadd return (error);
91*7fead5f1SAdrian Chadd }
92*7fead5f1SAdrian Chadd
93*7fead5f1SAdrian Chadd static int
qcom_gcc_probe(device_t dev)94*7fead5f1SAdrian Chadd qcom_gcc_probe(device_t dev)
95*7fead5f1SAdrian Chadd {
96*7fead5f1SAdrian Chadd struct qcom_gcc_softc *sc;
97*7fead5f1SAdrian Chadd int i;
98*7fead5f1SAdrian Chadd
99*7fead5f1SAdrian Chadd sc = device_get_softc(dev);
100*7fead5f1SAdrian Chadd
101*7fead5f1SAdrian Chadd if (! ofw_bus_status_okay(dev))
102*7fead5f1SAdrian Chadd return (ENXIO);
103*7fead5f1SAdrian Chadd
104*7fead5f1SAdrian Chadd for (i = 0; qcom_gcc_chipset_list[i].ofw != NULL; i++) {
105*7fead5f1SAdrian Chadd const struct qcom_gcc_chipset_list_entry *ce;
106*7fead5f1SAdrian Chadd
107*7fead5f1SAdrian Chadd ce = &qcom_gcc_chipset_list[i];
108*7fead5f1SAdrian Chadd if (ofw_bus_is_compatible(dev, ce->ofw) == 0)
109*7fead5f1SAdrian Chadd continue;
110*7fead5f1SAdrian Chadd device_set_desc(dev, ce->desc);
111*7fead5f1SAdrian Chadd sc->sc_chipset = ce->chipset;
112*7fead5f1SAdrian Chadd return (0);
113*7fead5f1SAdrian Chadd }
114*7fead5f1SAdrian Chadd
115*7fead5f1SAdrian Chadd return (ENXIO);
116*7fead5f1SAdrian Chadd }
117*7fead5f1SAdrian Chadd
118*7fead5f1SAdrian Chadd static int
qcom_gcc_attach(device_t dev)119*7fead5f1SAdrian Chadd qcom_gcc_attach(device_t dev)
120*7fead5f1SAdrian Chadd {
121*7fead5f1SAdrian Chadd struct qcom_gcc_softc *sc;
122*7fead5f1SAdrian Chadd size_t mem_sz;
123*7fead5f1SAdrian Chadd
124*7fead5f1SAdrian Chadd sc = device_get_softc(dev);
125*7fead5f1SAdrian Chadd
126*7fead5f1SAdrian Chadd /* Found a compatible device! */
127*7fead5f1SAdrian Chadd sc->dev = dev;
128*7fead5f1SAdrian Chadd
129*7fead5f1SAdrian Chadd /*
130*7fead5f1SAdrian Chadd * Setup the hardware callbacks, before any further initialisation
131*7fead5f1SAdrian Chadd * is performed.
132*7fead5f1SAdrian Chadd */
133*7fead5f1SAdrian Chadd switch (sc->sc_chipset) {
134*7fead5f1SAdrian Chadd case QCOM_GCC_CHIPSET_IPQ4018:
135*7fead5f1SAdrian Chadd qcom_gcc_ipq4018_hwreset_init(sc);
136*7fead5f1SAdrian Chadd mem_sz = 0x60000;
137*7fead5f1SAdrian Chadd break;
138*7fead5f1SAdrian Chadd case QCOM_GCC_CHIPSET_NONE:
139*7fead5f1SAdrian Chadd device_printf(dev, "Invalid chipset (%d)\n", sc->sc_chipset);
140*7fead5f1SAdrian Chadd return (ENXIO);
141*7fead5f1SAdrian Chadd }
142*7fead5f1SAdrian Chadd
143*7fead5f1SAdrian Chadd sc->reg_rid = 0;
144*7fead5f1SAdrian Chadd
145*7fead5f1SAdrian Chadd sc->reg = bus_alloc_resource_anywhere(dev, SYS_RES_MEMORY,
146*7fead5f1SAdrian Chadd &sc->reg_rid, mem_sz, RF_ACTIVE);
147*7fead5f1SAdrian Chadd if (sc->reg == NULL) {
148*7fead5f1SAdrian Chadd device_printf(dev, "Couldn't allocate memory resource!\n");
149*7fead5f1SAdrian Chadd return (ENXIO);
150*7fead5f1SAdrian Chadd }
151*7fead5f1SAdrian Chadd
152*7fead5f1SAdrian Chadd mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
153*7fead5f1SAdrian Chadd
154*7fead5f1SAdrian Chadd /*
155*7fead5f1SAdrian Chadd * Register as a reset provider.
156*7fead5f1SAdrian Chadd */
157*7fead5f1SAdrian Chadd hwreset_register_ofw_provider(dev);
158*7fead5f1SAdrian Chadd
159*7fead5f1SAdrian Chadd /*
160*7fead5f1SAdrian Chadd * Setup and register as a clock provider.
161*7fead5f1SAdrian Chadd */
162*7fead5f1SAdrian Chadd switch (sc->sc_chipset) {
163*7fead5f1SAdrian Chadd case QCOM_GCC_CHIPSET_IPQ4018:
164*7fead5f1SAdrian Chadd qcom_gcc_ipq4018_clock_setup(sc);
165*7fead5f1SAdrian Chadd break;
166*7fead5f1SAdrian Chadd case QCOM_GCC_CHIPSET_NONE:
167*7fead5f1SAdrian Chadd device_printf(dev, "Invalid chipset (%d)\n", sc->sc_chipset);
168*7fead5f1SAdrian Chadd return (ENXIO);
169*7fead5f1SAdrian Chadd }
170*7fead5f1SAdrian Chadd
171*7fead5f1SAdrian Chadd return (0);
172*7fead5f1SAdrian Chadd }
173*7fead5f1SAdrian Chadd
174*7fead5f1SAdrian Chadd static int
qcom_gcc_detach(device_t dev)175*7fead5f1SAdrian Chadd qcom_gcc_detach(device_t dev)
176*7fead5f1SAdrian Chadd {
177*7fead5f1SAdrian Chadd struct qcom_gcc_softc *sc;
178*7fead5f1SAdrian Chadd
179*7fead5f1SAdrian Chadd sc = device_get_softc(dev);
180*7fead5f1SAdrian Chadd
181*7fead5f1SAdrian Chadd /*
182*7fead5f1SAdrian Chadd * TBD - deregistering reset/clock resources.
183*7fead5f1SAdrian Chadd */
184*7fead5f1SAdrian Chadd
185*7fead5f1SAdrian Chadd if (sc->reg != NULL) {
186*7fead5f1SAdrian Chadd bus_release_resource(sc->dev, SYS_RES_MEMORY,
187*7fead5f1SAdrian Chadd sc->reg_rid, sc->reg);
188*7fead5f1SAdrian Chadd }
189*7fead5f1SAdrian Chadd return (0);
190*7fead5f1SAdrian Chadd }
191*7fead5f1SAdrian Chadd
192*7fead5f1SAdrian Chadd static device_method_t qcom_gcc_methods[] = {
193*7fead5f1SAdrian Chadd /* Device methods. */
194*7fead5f1SAdrian Chadd DEVMETHOD(device_probe, qcom_gcc_probe),
195*7fead5f1SAdrian Chadd DEVMETHOD(device_attach, qcom_gcc_attach),
196*7fead5f1SAdrian Chadd DEVMETHOD(device_detach, qcom_gcc_detach),
197*7fead5f1SAdrian Chadd
198*7fead5f1SAdrian Chadd /* Reset interface */
199*7fead5f1SAdrian Chadd DEVMETHOD(hwreset_assert, qcom_gcc_hwreset_assert),
200*7fead5f1SAdrian Chadd DEVMETHOD(hwreset_is_asserted, qcom_gcc_hwreset_is_asserted),
201*7fead5f1SAdrian Chadd
202*7fead5f1SAdrian Chadd /* Clock interface */
203*7fead5f1SAdrian Chadd DEVMETHOD(clkdev_read_4, qcom_gcc_clock_read),
204*7fead5f1SAdrian Chadd DEVMETHOD(clkdev_write_4, qcom_gcc_clock_write),
205*7fead5f1SAdrian Chadd DEVMETHOD(clkdev_modify_4, qcom_gcc_clock_modify),
206*7fead5f1SAdrian Chadd DEVMETHOD(clkdev_device_lock, qcom_gcc_clock_lock),
207*7fead5f1SAdrian Chadd DEVMETHOD(clkdev_device_unlock, qcom_gcc_clock_unlock),
208*7fead5f1SAdrian Chadd
209*7fead5f1SAdrian Chadd DEVMETHOD_END
210*7fead5f1SAdrian Chadd };
211*7fead5f1SAdrian Chadd
212*7fead5f1SAdrian Chadd static driver_t qcom_gcc_driver = {
213*7fead5f1SAdrian Chadd "qcom_gcc",
214*7fead5f1SAdrian Chadd qcom_gcc_methods,
215*7fead5f1SAdrian Chadd sizeof(struct qcom_gcc_softc)
216*7fead5f1SAdrian Chadd };
217*7fead5f1SAdrian Chadd
218*7fead5f1SAdrian Chadd EARLY_DRIVER_MODULE(qcom_gcc, simplebus, qcom_gcc_driver,
219*7fead5f1SAdrian Chadd qcom_gcc_modevent, NULL, BUS_PASS_CPU + BUS_PASS_ORDER_EARLY);
220*7fead5f1SAdrian Chadd EARLY_DRIVER_MODULE(qcom_gcc, ofwbus, qcom_gcc_driver,
221*7fead5f1SAdrian Chadd qcom_gcc_modevent, NULL, BUS_PASS_CPU + BUS_PASS_ORDER_EARLY);
222*7fead5f1SAdrian Chadd MODULE_VERSION(qcom_gcc, 1);
223