1/*-
2 * Copyright 2014-2015 John Wehle <john@feith.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28/*
29 * Amlogic aml8726 clock measurement driver.
30 *
31 * This allows various clock rates to be determine at runtime.
32 * The measurements are done once and are not expected to change
33 * (i.e. FDT fixup provides clk81 as bus-frequency to the MMC
34 * and UART drivers which use the value when programming the
35 * hardware).
36 */
37
38#include <sys/cdefs.h>
39__FBSDID("$FreeBSD$");
40
41#include <sys/param.h>
42#include <sys/systm.h>
43#include <sys/bus.h>
44#include <sys/kernel.h>
45#include <sys/module.h>
46#include <sys/malloc.h>
47#include <sys/rman.h>
48#include <sys/timetc.h>
49#include <sys/timeet.h>
50
51#include <machine/bus.h>
52#include <machine/cpu.h>
53#include <machine/fdt.h>
54
55#include <dev/fdt/fdt_common.h>
56#include <dev/ofw/ofw_bus.h>
57#include <dev/ofw/ofw_bus_subr.h>
58
59#include <arm/amlogic/aml8726/aml8726_soc.h>
60#include <arm/amlogic/aml8726/aml8726_clkmsr.h>
61
62
63static struct aml8726_clkmsr_clk {
64	const char *		name;
65	uint32_t		mux;
66} aml8726_clkmsr_clks[] = {
67	{ "clk81", 7 },
68};
69
70#define	AML_CLKMSR_CLK81	0
71
72#define	AML_CLKMSR_NCLKS	nitems(aml8726_clkmsr_clks)
73
74struct aml8726_clkmsr_softc {
75	device_t		dev;
76	struct resource	*	res[1];
77};
78
79static struct resource_spec aml8726_clkmsr_spec[] = {
80	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
81	{ -1, 0 }
82};
83
84/*
85 * Duration can range from 1uS to 65535 uS and should be chosen
86 * based on the expected frequency result so to maximize resolution
87 * and avoid overflowing the 16 bit result counter.
88 */
89#define	AML_CLKMSR_DURATION		32
90
91#define	AML_CLKMSR_DUTY_REG		0
92#define	AML_CLKMSR_0_REG		4
93#define	AML_CLKMSR_0_BUSY		(1U << 31)
94#define	AML_CLKMSR_0_MUX_MASK		(0x3f << 20)
95#define	AML_CLKMSR_0_MUX_SHIFT		20
96#define	AML_CLKMSR_0_MUX_EN		(1 << 19)
97#define	AML_CLKMSR_0_MEASURE		(1 << 16)
98#define	AML_CLKMSR_0_DURATION_MASK	0xffff
99#define	AML_CLKMSR_0_DURATION_SHIFT	0
100#define	AML_CLKMSR_1_REG		8
101#define	AML_CLKMSR_2_REG		12
102#define	AML_CLKMSR_2_RESULT_MASK	0xffff
103#define	AML_CLKMSR_2_RESULT_SHIFT	0
104
105#define CSR_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
106#define CSR_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
107#define CSR_BARRIER(sc, reg)		bus_barrier((sc)->res[0], reg, 4, \
108    (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE))
109
110static int
111aml8726_clkmsr_clock_frequency(struct aml8726_clkmsr_softc *sc, unsigned clock)
112{
113	uint32_t value;
114
115	if (clock >= AML_CLKMSR_NCLKS)
116		return (0);
117
118	/*
119	 * Locking is not used as this is only expected to be called from
120	 * FDT fixup (which occurs prior to driver initialization) or attach.
121	 */
122
123	CSR_WRITE_4(sc, AML_CLKMSR_0_REG, 0);
124
125	CSR_BARRIER(sc, AML_CLKMSR_0_REG);
126
127	value = (aml8726_clkmsr_clks[clock].mux << AML_CLKMSR_0_MUX_SHIFT)
128	    | ((AML_CLKMSR_DURATION - 1) << AML_CLKMSR_0_DURATION_SHIFT)
129	    | AML_CLKMSR_0_MUX_EN
130	    | AML_CLKMSR_0_MEASURE;
131	CSR_WRITE_4(sc, AML_CLKMSR_0_REG, value);
132
133	CSR_BARRIER(sc, AML_CLKMSR_0_REG);
134
135	while ((CSR_READ_4(sc, AML_CLKMSR_0_REG) & AML_CLKMSR_0_BUSY) != 0)
136		cpu_spinwait();
137
138	value &= ~AML_CLKMSR_0_MEASURE;
139	CSR_WRITE_4(sc, AML_CLKMSR_0_REG, value);
140
141	CSR_BARRIER(sc, AML_CLKMSR_0_REG);
142
143	value = (((CSR_READ_4(sc, AML_CLKMSR_2_REG) & AML_CLKMSR_2_RESULT_MASK)
144	    >> AML_CLKMSR_2_RESULT_SHIFT) + AML_CLKMSR_DURATION / 2) /
145	    AML_CLKMSR_DURATION;
146
147	return value;
148}
149
150static void
151aml8726_clkmsr_fixup_clk81(struct aml8726_clkmsr_softc *sc, int freq)
152{
153	pcell_t prop;
154	ssize_t len;
155	phandle_t clk_node;
156	phandle_t node;
157
158	node = ofw_bus_get_node(sc->dev);
159
160	len = OF_getencprop(node, "clocks", &prop, sizeof(prop));
161	if ((len / sizeof(prop)) != 1 || prop == 0 ||
162	    (clk_node = OF_node_from_xref(prop)) == 0)
163		return;
164
165	len = OF_getencprop(clk_node, "clock-frequency", &prop, sizeof(prop));
166	if ((len / sizeof(prop)) != 1 || prop != 0)
167		return;
168
169	freq = cpu_to_fdt32(freq);
170
171	OF_setprop(clk_node, "clock-frequency", (void *)&freq, sizeof(freq));
172}
173
174static int
175aml8726_clkmsr_probe(device_t dev)
176{
177
178	if (!ofw_bus_status_okay(dev))
179		return (ENXIO);
180
181	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-clkmsr"))
182		return (ENXIO);
183
184	device_set_desc(dev, "Amlogic aml8726 clkmsr");
185
186	return (BUS_PROBE_DEFAULT);
187}
188
189static int
190aml8726_clkmsr_attach(device_t dev)
191{
192	struct aml8726_clkmsr_softc *sc = device_get_softc(dev);
193	int freq;
194
195	sc->dev = dev;
196
197	if (bus_alloc_resources(dev, aml8726_clkmsr_spec, sc->res)) {
198		device_printf(dev, "can not allocate resources for device\n");
199		return (ENXIO);
200	}
201
202	freq = aml8726_clkmsr_clock_frequency(sc, AML_CLKMSR_CLK81);
203	device_printf(sc->dev, "bus clock %u MHz\n", freq);
204
205	aml8726_clkmsr_fixup_clk81(sc, freq * 1000000);
206
207	return (0);
208}
209
210static int
211aml8726_clkmsr_detach(device_t dev)
212{
213	struct aml8726_clkmsr_softc *sc = device_get_softc(dev);
214
215	bus_release_resources(dev, aml8726_clkmsr_spec, sc->res);
216
217	return (0);
218}
219
220
221static device_method_t aml8726_clkmsr_methods[] = {
222	/* Device interface */
223	DEVMETHOD(device_probe,		aml8726_clkmsr_probe),
224	DEVMETHOD(device_attach,	aml8726_clkmsr_attach),
225	DEVMETHOD(device_detach,	aml8726_clkmsr_detach),
226
227	DEVMETHOD_END
228};
229
230static driver_t aml8726_clkmsr_driver = {
231	"clkmsr",
232	aml8726_clkmsr_methods,
233	sizeof(struct aml8726_clkmsr_softc),
234};
235
236static devclass_t aml8726_clkmsr_devclass;
237
238EARLY_DRIVER_MODULE(clkmsr, simplebus, aml8726_clkmsr_driver,
239    aml8726_clkmsr_devclass, 0, 0,  BUS_PASS_CPU + BUS_PASS_ORDER_EARLY);
240
241int
242aml8726_clkmsr_bus_frequency()
243{
244	struct resource mem;
245	struct aml8726_clkmsr_softc sc;
246	phandle_t node;
247	u_long pbase, psize;
248	u_long start, size;
249	int freq;
250
251	KASSERT(aml8726_soc_hw_rev != AML_SOC_HW_REV_UNKNOWN,
252		("aml8726_soc_hw_rev isn't initialized"));
253
254	/*
255	 * Try to access the clkmsr node directly i.e. through /aliases/.
256	 */
257
258	if ((node = OF_finddevice("clkmsr")) != -1)
259		if (fdt_is_compatible_strict(node, "amlogic,aml8726-clkmsr"))
260			 goto moveon;
261
262	/*
263	 * Find the node the long way.
264	 */
265	if ((node = OF_finddevice("/soc")) == -1)
266		return (0);
267
268	if ((node = fdt_find_compatible(node,
269	    "amlogic,aml8726-clkmsr", 1)) == 0)
270		return (0);
271
272moveon:
273	if (fdt_get_range(OF_parent(node), 0, &pbase, &psize) != 0
274	    || fdt_regsize(node, &start, &size) != 0)
275		return (0);
276
277	start += pbase;
278
279	memset(&mem, 0, sizeof(mem));
280
281	mem.r_bustag = fdtbus_bs_tag;
282
283	if (bus_space_map(mem.r_bustag, start, size, 0, &mem.r_bushandle) != 0)
284		return (0);
285
286	/*
287	 * Build an incomplete (however sufficient for the purpose
288	 * of calling aml8726_clkmsr_clock_frequency) softc.
289	 */
290
291	memset(&sc, 0, sizeof(sc));
292
293	sc.res[0] = &mem;
294
295	freq = aml8726_clkmsr_clock_frequency(&sc, AML_CLKMSR_CLK81) * 1000000;
296
297	bus_space_unmap(mem.r_bustag, mem.r_bushandle, size);
298
299	return (freq);
300}
301