1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2017 Poul-Henning Kamp <phk@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 AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, 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
29#include <sys/param.h>
30#include <sys/systm.h>
31#include <sys/bus.h>
32#include <sys/cpu.h>
33#include <sys/kernel.h>
34#include <sys/lock.h>
35#include <sys/malloc.h>
36#include <sys/module.h>
37#include <sys/mutex.h>
38#include <sys/rman.h>
39#include <sys/sema.h>
40#include <sys/sysctl.h>
41
42#include <machine/bus.h>
43#include <machine/cpu.h>
44
45#include <dev/ofw/ofw_bus.h>
46#include <dev/ofw/ofw_bus_subr.h>
47
48#include <arm/broadcom/bcm2835/bcm2835_clkman.h>
49
50static struct ofw_compat_data compat_data[] = {
51	{"brcm,bcm2711-cprman",		1},
52	{"brcm,bcm2835-cprman",		1},
53	{"broadcom,bcm2835-cprman",	1},
54	{NULL,				0}
55};
56
57struct bcm2835_clkman_softc {
58	device_t		sc_dev;
59
60	struct resource *	sc_m_res;
61	bus_space_tag_t		sc_m_bst;
62	bus_space_handle_t	sc_m_bsh;
63};
64
65#define BCM_CLKMAN_WRITE(_sc, _off, _val)              \
66    bus_space_write_4(_sc->sc_m_bst, _sc->sc_m_bsh, _off, _val)
67#define BCM_CLKMAN_READ(_sc, _off)                     \
68    bus_space_read_4(_sc->sc_m_bst, _sc->sc_m_bsh, _off)
69
70#define W_CMCLK(_sc, unit, _val) BCM_CLKMAN_WRITE(_sc, unit, 0x5a000000 | (_val))
71#define R_CMCLK(_sc, unit) BCM_CLKMAN_READ(_sc, unit)
72#define W_CMDIV(_sc, unit, _val) BCM_CLKMAN_WRITE(_sc, (unit) + 4, 0x5a000000 | (_val))
73#define R_CMDIV(_sc,  unit) BCM_CLKMAN_READ(_sc, (unit) + 4)
74
75static int
76bcm2835_clkman_probe(device_t dev)
77{
78
79	if (!ofw_bus_status_okay(dev))
80		return (ENXIO);
81
82	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
83		return (ENXIO);
84
85	device_set_desc(dev, "BCM283x Clock Manager");
86
87	return (BUS_PROBE_DEFAULT);
88}
89
90static int
91bcm2835_clkman_attach(device_t dev)
92{
93	struct bcm2835_clkman_softc *sc;
94	int rid;
95
96	if (device_get_unit(dev) != 0) {
97		device_printf(dev, "only one clk manager supported\n");
98		return (ENXIO);
99	}
100
101	sc = device_get_softc(dev);
102	sc->sc_dev = dev;
103
104	rid = 0;
105	sc->sc_m_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
106	    RF_ACTIVE);
107	if (!sc->sc_m_res) {
108		device_printf(dev, "cannot allocate memory window\n");
109		return (ENXIO);
110	}
111
112	sc->sc_m_bst = rman_get_bustag(sc->sc_m_res);
113	sc->sc_m_bsh = rman_get_bushandle(sc->sc_m_res);
114
115	return (bus_generic_attach(dev));
116}
117
118uint32_t
119bcm2835_clkman_set_frequency(device_t dev, uint32_t unit, uint32_t hz)
120{
121	struct bcm2835_clkman_softc *sc;
122	int i;
123	uint32_t u;
124
125	sc = device_get_softc(dev);
126
127	if (unit != BCM_PWM_CLKSRC) {
128		device_printf(sc->sc_dev,
129		    "Unsupported unit 0x%x", unit);
130		return (0);
131	}
132
133	W_CMCLK(sc, unit, 6);
134	for (i = 0; i < 10; i++) {
135		u = R_CMCLK(sc, unit);
136		if (!(u&0x80))
137			break;
138		DELAY(1000);
139	}
140	if (u & 0x80) {
141		device_printf(sc->sc_dev,
142		    "Failed to stop clock for unit 0x%x", unit);
143		return (0);
144	}
145	if (hz == 0)
146		return (0);
147
148	u = 500000000/hz;
149	if (u < 4) {
150		device_printf(sc->sc_dev,
151		    "Frequency too high for unit 0x%x (max: 125 MHz)",
152		    unit);
153		return (0);
154	}
155	if (u > 0xfff) {
156		device_printf(sc->sc_dev,
157		    "Frequency too low for unit 0x%x (min: 123 kHz)",
158		    unit);
159		return (0);
160	}
161	hz = 500000000/u;
162	W_CMDIV(sc, unit, u << 12);
163
164	W_CMCLK(sc, unit, 0x16);
165	for (i = 0; i < 10; i++) {
166		u = R_CMCLK(sc, unit);
167		if ((u&0x80))
168			break;
169		DELAY(1000);
170	}
171	if (!(u & 0x80)) {
172		device_printf(sc->sc_dev,
173		    "Failed to start clock for unit 0x%x", unit);
174		return (0);
175	}
176	return (hz);
177}
178
179static int
180bcm2835_clkman_detach(device_t dev)
181{
182	struct bcm2835_clkman_softc *sc;
183
184	bus_generic_detach(dev);
185
186	sc = device_get_softc(dev);
187	if (sc->sc_m_res)
188		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_m_res);
189
190	return (0);
191}
192
193static device_method_t bcm2835_clkman_methods[] = {
194	/* Device interface */
195	DEVMETHOD(device_probe,		bcm2835_clkman_probe),
196	DEVMETHOD(device_attach,	bcm2835_clkman_attach),
197	DEVMETHOD(device_detach,	bcm2835_clkman_detach),
198
199	DEVMETHOD_END
200};
201
202static driver_t bcm2835_clkman_driver = {
203	"bcm2835_clkman",
204	bcm2835_clkman_methods,
205	sizeof(struct bcm2835_clkman_softc),
206};
207
208DRIVER_MODULE(bcm2835_clkman, simplebus, bcm2835_clkman_driver, 0, 0);
209MODULE_VERSION(bcm2835_clkman, 1);
210