aw_mmcclk.c revision 309756
1/*-
2 * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
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 ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * 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 * $FreeBSD: stable/11/sys/arm/allwinner/clk/aw_mmcclk.c 309756 2016-12-09 20:07:01Z manu $
27 */
28
29/*
30 * Allwinner MMC clocks
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_mmcclk.c 309756 2016-12-09 20:07:01Z manu $");
35
36#include <sys/param.h>
37#include <sys/systm.h>
38#include <sys/bus.h>
39#include <sys/rman.h>
40#include <sys/kernel.h>
41#include <sys/module.h>
42#include <machine/bus.h>
43
44#include <dev/ofw/ofw_bus.h>
45#include <dev/ofw/ofw_bus_subr.h>
46#include <dev/ofw/ofw_subr.h>
47
48#include <dev/extres/clk/clk_mux.h>
49#include <dev/extres/clk/clk_gate.h>
50
51#include "clkdev_if.h"
52
53#define	SCLK_GATING			(1 << 31)
54#define	CLK_SRC_SEL			(0x3 << 24)
55#define	CLK_SRC_SEL_SHIFT		24
56#define	CLK_SRC_SEL_MAX			0x3
57#define	CLK_SRC_SEL_OSC24M		0
58#define	CLK_SRC_SEL_PLL6		1
59#define	CLK_PHASE_CTR			(0x7 << 20)
60#define	CLK_PHASE_CTR_SHIFT		20
61#define	CLK_RATIO_N			(0x3 << 16)
62#define	CLK_RATIO_N_SHIFT		16
63#define	CLK_RATIO_N_MAX			0x3
64#define	OUTPUT_CLK_PHASE_CTR		(0x7 << 8)
65#define	OUTPUT_CLK_PHASE_CTR_SHIFT	8
66#define	CLK_RATIO_M			(0xf << 0)
67#define	CLK_RATIO_M_SHIFT		0
68#define	CLK_RATIO_M_MAX			0xf
69
70static struct ofw_compat_data compat_data[] = {
71	{ "allwinner,sun4i-a10-mmc-clk",	1 },
72	{ NULL, 0 }
73};
74
75struct aw_mmcclk_sc {
76	device_t	clkdev;
77	bus_addr_t	reg;
78};
79
80#define	MODCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
81#define	MODCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
82#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
83#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
84
85static int
86aw_mmcclk_init(struct clknode *clk, device_t dev)
87{
88	struct aw_mmcclk_sc *sc;
89	uint32_t val, index;
90
91	sc = clknode_get_softc(clk);
92
93	DEVICE_LOCK(sc);
94	MODCLK_READ(sc, &val);
95	DEVICE_UNLOCK(sc);
96
97	index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT;
98
99	clknode_init_parent_idx(clk, index);
100	return (0);
101}
102
103static int
104aw_mmcclk_set_mux(struct clknode *clk, int index)
105{
106	struct aw_mmcclk_sc *sc;
107	uint32_t val;
108
109	sc = clknode_get_softc(clk);
110
111	if (index < 0 || index > CLK_SRC_SEL_MAX)
112		return (ERANGE);
113
114	DEVICE_LOCK(sc);
115	MODCLK_READ(sc, &val);
116	val &= ~CLK_SRC_SEL;
117	val |= (index << CLK_SRC_SEL_SHIFT);
118	MODCLK_WRITE(sc, val);
119	DEVICE_UNLOCK(sc);
120
121	return (0);
122}
123
124static int
125aw_mmcclk_set_gate(struct clknode *clk, bool enable)
126{
127	struct aw_mmcclk_sc *sc;
128	uint32_t val;
129
130	sc = clknode_get_softc(clk);
131
132	DEVICE_LOCK(sc);
133	MODCLK_READ(sc, &val);
134	if (enable)
135		val |= SCLK_GATING;
136	else
137		val &= ~SCLK_GATING;
138	MODCLK_WRITE(sc, val);
139	DEVICE_UNLOCK(sc);
140
141	return (0);
142}
143
144static int
145aw_mmcclk_recalc_freq(struct clknode *clk, uint64_t *freq)
146{
147	struct aw_mmcclk_sc *sc;
148	uint32_t val, m, n;
149
150	sc = clknode_get_softc(clk);
151
152	DEVICE_LOCK(sc);
153	MODCLK_READ(sc, &val);
154	DEVICE_UNLOCK(sc);
155
156	n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT);
157	m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1;
158
159	*freq = *freq / n / m;
160
161	return (0);
162}
163
164static int
165aw_mmcclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
166    int flags, int *stop)
167{
168	struct aw_mmcclk_sc *sc;
169	uint32_t val, m, n, phase, ophase;
170	int parent_idx, error;
171
172	sc = clknode_get_softc(clk);
173
174	/* XXX
175	 * The ophase/phase values should be set by the MMC driver, but
176	 * there is currently no way to do this with the clk API
177	 */
178	if (*fout <= 400000) {
179		parent_idx = CLK_SRC_SEL_OSC24M;
180		ophase = 0;
181		phase = 0;
182		n = 2;
183	} else if (*fout <= 25000000) {
184		parent_idx = CLK_SRC_SEL_PLL6;
185		ophase = 0;
186		phase = 5;
187		n = 2;
188	} else if (*fout <= 52000000) {
189		parent_idx = CLK_SRC_SEL_PLL6;
190		ophase = 3;
191		phase = 5;
192		n = 0;
193	} else
194		return (ERANGE);
195
196	/* Switch parent clock, if necessary */
197	if (parent_idx != clknode_get_parent_idx(clk)) {
198		error = clknode_set_parent_by_idx(clk, parent_idx);
199		if (error != 0)
200			return (error);
201
202		/* Fetch new input frequency */
203		error = clknode_get_freq(clknode_get_parent(clk), &fin);
204		if (error != 0)
205			return (error);
206	}
207
208	m = ((fin / (1 << n)) / *fout) - 1;
209
210	DEVICE_LOCK(sc);
211	MODCLK_READ(sc, &val);
212	val &= ~(CLK_RATIO_N | CLK_RATIO_M | CLK_PHASE_CTR |
213	    OUTPUT_CLK_PHASE_CTR);
214	val |= (n << CLK_RATIO_N_SHIFT);
215	val |= (m << CLK_RATIO_M_SHIFT);
216	val |= (phase << CLK_PHASE_CTR_SHIFT);
217	val |= (ophase << OUTPUT_CLK_PHASE_CTR_SHIFT);
218	MODCLK_WRITE(sc, val);
219	DEVICE_UNLOCK(sc);
220
221	*fout = fin / (1 << n) / (m + 1);
222	*stop = 1;
223
224	return (0);
225}
226
227static clknode_method_t aw_mmcclk_clknode_methods[] = {
228	/* Device interface */
229	CLKNODEMETHOD(clknode_init,		aw_mmcclk_init),
230	CLKNODEMETHOD(clknode_set_gate,		aw_mmcclk_set_gate),
231	CLKNODEMETHOD(clknode_set_mux,		aw_mmcclk_set_mux),
232	CLKNODEMETHOD(clknode_recalc_freq,	aw_mmcclk_recalc_freq),
233	CLKNODEMETHOD(clknode_set_freq,		aw_mmcclk_set_freq),
234	CLKNODEMETHOD_END
235};
236DEFINE_CLASS_1(aw_mmcclk_clknode, aw_mmcclk_clknode_class,
237    aw_mmcclk_clknode_methods, sizeof(struct aw_mmcclk_sc), clknode_class);
238
239static int
240aw_mmcclk_probe(device_t dev)
241{
242	if (!ofw_bus_status_okay(dev))
243		return (ENXIO);
244
245	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
246		return (ENXIO);
247
248	device_set_desc(dev, "Allwinner MMC Clock");
249	return (BUS_PROBE_DEFAULT);
250}
251
252static int
253aw_mmcclk_attach(device_t dev)
254{
255	struct clknode_init_def def;
256	struct aw_mmcclk_sc *sc;
257	struct clkdom *clkdom;
258	struct clknode *clk;
259	const char **names;
260	uint32_t *indices;
261	clk_t clk_parent;
262	bus_addr_t paddr;
263	bus_size_t psize;
264	phandle_t node;
265	int error, nout, ncells, i;
266
267	node = ofw_bus_get_node(dev);
268
269	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
270		device_printf(dev, "cannot parse 'reg' property\n");
271		return (ENXIO);
272	}
273
274	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
275	    "#clock-cells", &ncells);
276	if (error != 0 || ncells == 0) {
277		device_printf(dev, "couldn't find parent clocks\n");
278		return (ENXIO);
279	}
280
281	clkdom = clkdom_create(dev);
282
283	nout = clk_parse_ofw_out_names(dev, node, &names, &indices);
284	if (nout == 0) {
285		device_printf(dev, "no output clocks found\n");
286		error = ENXIO;
287		goto fail;
288	}
289
290	memset(&def, 0, sizeof(def));
291	def.name = names[0];
292	def.id = 0;
293	def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
294	for (i = 0; i < ncells; i++) {
295		error = clk_get_by_ofw_index(dev, 0, i, &clk_parent);
296		if (error != 0) {
297			device_printf(dev, "cannot get clock %d\n", i);
298			goto fail;
299		}
300		def.parent_names[i] = clk_get_name(clk_parent);
301		clk_release(clk_parent);
302	}
303	def.parent_cnt = ncells;
304	def.flags = CLK_NODE_GLITCH_FREE;
305
306	clk = clknode_create(clkdom, &aw_mmcclk_clknode_class, &def);
307	if (clk == NULL) {
308		device_printf(dev, "cannot create clknode\n");
309		error = ENXIO;
310		goto fail;
311	}
312
313	sc = clknode_get_softc(clk);
314	sc->reg = paddr;
315	sc->clkdev = device_get_parent(dev);
316
317	clknode_register(clkdom, clk);
318
319	if (clkdom_finit(clkdom) != 0) {
320		device_printf(dev, "cannot finalize clkdom initialization\n");
321		error = ENXIO;
322		goto fail;
323	}
324
325	if (bootverbose)
326		clkdom_dump(clkdom);
327
328	return (0);
329
330fail:
331	return (error);
332}
333
334static device_method_t aw_mmcclk_methods[] = {
335	/* Device interface */
336	DEVMETHOD(device_probe,		aw_mmcclk_probe),
337	DEVMETHOD(device_attach,	aw_mmcclk_attach),
338
339	DEVMETHOD_END
340};
341
342static driver_t aw_mmcclk_driver = {
343	"aw_mmcclk",
344	aw_mmcclk_methods,
345	0
346};
347
348static devclass_t aw_mmcclk_devclass;
349
350EARLY_DRIVER_MODULE(aw_mmcclk, simplebus, aw_mmcclk_driver,
351    aw_mmcclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
352