aw_gmacclk.c revision 297627
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: head/sys/arm/allwinner/clk/aw_gmacclk.c 297627 2016-04-06 23:11:03Z jmcneill $
27 */
28
29/*
30 * Allwinner GMAC clock
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: head/sys/arm/allwinner/clk/aw_gmacclk.c 297627 2016-04-06 23:11:03Z jmcneill $");
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	GMAC_CLK_PIT		(0x1 << 2)
54#define	GMAC_CLK_PIT_SHIFT	2
55#define	GMAC_CLK_PIT_MII	0
56#define	GMAC_CLK_PIT_RGMII	1
57#define	GMAC_CLK_SRC		(0x3 << 0)
58#define	GMAC_CLK_SRC_SHIFT	0
59#define	GMAC_CLK_SRC_MII	0
60#define	GMAC_CLK_SRC_EXT_RGMII	1
61#define	GMAC_CLK_SRC_RGMII	2
62
63#define	CLK_IDX_MII		0
64#define	CLK_IDX_RGMII		1
65#define	CLK_IDX_COUNT		2
66
67static struct ofw_compat_data compat_data[] = {
68	{ "allwinner,sun7i-a20-gmac-clk",	1 },
69	{ NULL, 0 }
70};
71
72struct aw_gmacclk_sc {
73	device_t	clkdev;
74	bus_addr_t	reg;
75};
76
77#define	GMACCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
78#define	GMACCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
79#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
80#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
81
82static int
83aw_gmacclk_init(struct clknode *clk, device_t dev)
84{
85	struct aw_gmacclk_sc *sc;
86	uint32_t val, index;
87
88	sc = clknode_get_softc(clk);
89
90	DEVICE_LOCK(sc);
91	GMACCLK_READ(sc, &val);
92	DEVICE_UNLOCK(sc);
93
94	switch ((val & GMAC_CLK_SRC) >> GMAC_CLK_SRC_SHIFT) {
95	case GMAC_CLK_SRC_MII:
96		index = CLK_IDX_MII;
97		break;
98	case GMAC_CLK_SRC_RGMII:
99		index = CLK_IDX_RGMII;
100		break;
101	default:
102		return (ENXIO);
103	}
104
105	clknode_init_parent_idx(clk, index);
106	return (0);
107}
108
109static int
110aw_gmacclk_set_mux(struct clknode *clk, int index)
111{
112	struct aw_gmacclk_sc *sc;
113	uint32_t val, clk_src, pit;
114	int error;
115
116	sc = clknode_get_softc(clk);
117	error = 0;
118
119	switch (index) {
120	case CLK_IDX_MII:
121		clk_src = GMAC_CLK_SRC_MII;
122		pit = GMAC_CLK_PIT_MII;
123		break;
124	case CLK_IDX_RGMII:
125		clk_src = GMAC_CLK_SRC_RGMII;
126		pit = GMAC_CLK_PIT_RGMII;
127		break;
128	default:
129		return (ENXIO);
130	}
131
132	DEVICE_LOCK(sc);
133	GMACCLK_READ(sc, &val);
134	val &= ~(GMAC_CLK_SRC | GMAC_CLK_PIT);
135	val |= (clk_src << GMAC_CLK_SRC_SHIFT);
136	val |= (pit << GMAC_CLK_PIT_SHIFT);
137	GMACCLK_WRITE(sc, val);
138	DEVICE_UNLOCK(sc);
139
140	return (0);
141}
142
143static clknode_method_t aw_gmacclk_clknode_methods[] = {
144	/* Device interface */
145	CLKNODEMETHOD(clknode_init,		aw_gmacclk_init),
146	CLKNODEMETHOD(clknode_set_mux,		aw_gmacclk_set_mux),
147	CLKNODEMETHOD_END
148};
149DEFINE_CLASS_1(aw_gmacclk_clknode, aw_gmacclk_clknode_class,
150    aw_gmacclk_clknode_methods, sizeof(struct aw_gmacclk_sc), clknode_class);
151
152static int
153aw_gmacclk_probe(device_t dev)
154{
155	if (!ofw_bus_status_okay(dev))
156		return (ENXIO);
157
158	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
159		return (ENXIO);
160
161	device_set_desc(dev, "Allwinner Module Clock");
162	return (BUS_PROBE_DEFAULT);
163}
164
165static int
166aw_gmacclk_attach(device_t dev)
167{
168	struct clknode_init_def def;
169	struct aw_gmacclk_sc *sc;
170	struct clkdom *clkdom;
171	struct clknode *clk;
172	clk_t clk_parent;
173	bus_addr_t paddr;
174	bus_size_t psize;
175	phandle_t node;
176	int error, ncells, i;
177
178	node = ofw_bus_get_node(dev);
179
180	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
181		device_printf(dev, "cannot parse 'reg' property\n");
182		return (ENXIO);
183	}
184
185	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
186	    "#clock-cells", &ncells);
187	if (error != 0 || ncells != CLK_IDX_COUNT) {
188		device_printf(dev, "couldn't find parent clocks\n");
189		return (ENXIO);
190	}
191
192	clkdom = clkdom_create(dev);
193
194	memset(&def, 0, sizeof(def));
195	error = clk_parse_ofw_clk_name(dev, node, &def.name);
196	if (error != 0) {
197		device_printf(dev, "cannot parse clock name\n");
198		error = ENXIO;
199		goto fail;
200	}
201	def.id = 1;
202	def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
203	for (i = 0; i < ncells; i++) {
204		error = clk_get_by_ofw_index(dev, i, &clk_parent);
205		if (error != 0) {
206			device_printf(dev, "cannot get clock %d\n", error);
207			goto fail;
208		}
209		def.parent_names[i] = clk_get_name(clk_parent);
210		clk_release(clk_parent);
211	}
212	def.parent_cnt = ncells;
213
214	clk = clknode_create(clkdom, &aw_gmacclk_clknode_class, &def);
215	if (clk == NULL) {
216		device_printf(dev, "cannot create clknode\n");
217		error = ENXIO;
218		goto fail;
219	}
220
221	sc = clknode_get_softc(clk);
222	sc->reg = paddr;
223	sc->clkdev = device_get_parent(dev);
224
225	clknode_register(clkdom, clk);
226
227	if (clkdom_finit(clkdom) != 0) {
228		device_printf(dev, "cannot finalize clkdom initialization\n");
229		error = ENXIO;
230		goto fail;
231	}
232
233	if (bootverbose)
234		clkdom_dump(clkdom);
235
236	return (0);
237
238fail:
239	return (error);
240}
241
242static device_method_t aw_gmacclk_methods[] = {
243	/* Device interface */
244	DEVMETHOD(device_probe,		aw_gmacclk_probe),
245	DEVMETHOD(device_attach,	aw_gmacclk_attach),
246
247	DEVMETHOD_END
248};
249
250static driver_t aw_gmacclk_driver = {
251	"aw_gmacclk",
252	aw_gmacclk_methods,
253	0
254};
255
256static devclass_t aw_gmacclk_devclass;
257
258EARLY_DRIVER_MODULE(aw_gmacclk, simplebus, aw_gmacclk_driver,
259    aw_gmacclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
260