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_gate.c 309762 2016-12-09 20:35:01Z manu $
27 */
28
29/*
30 * Allwinner clock gates
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_gate.c 309762 2016-12-09 20:35: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#include <dev/fdt/fdt_common.h>
48
49#include <dev/extres/clk/clk_gate.h>
50
51#define	GATE_OFFSET(index)	((index / 32) * 4)
52#define	GATE_SHIFT(index)	(index % 32)
53
54static struct ofw_compat_data compat_data[] = {
55	{ "allwinner,sun4i-a10-dram-gates-clk",
56	  (uintptr_t)"Allwinner DRAM Clock Gates" },
57	{ "allwinner,sun4i-a10-ahb-gates-clk",
58	  (uintptr_t)"Allwinner AHB Clock Gates" },
59	{ "allwinner,sun4i-a10-apb0-gates-clk",
60	  (uintptr_t)"Allwinner APB0 Clock Gates" },
61	{ "allwinner,sun4i-a10-apb1-gates-clk",
62	  (uintptr_t)"Allwinner APB1 Clock Gates" },
63
64	{ "allwinner,sun5i-a13-ahb-gates-clk",
65	  (uintptr_t)"Allwinner AHB Clock Gates" },
66	{ "allwinner,sun5i-a13-apb0-gates-clk",
67	  (uintptr_t)"Allwinner APB0 Clock Gates" },
68	{ "allwinner,sun5i-a13-apb1-gates-clk",
69	  (uintptr_t)"Allwinner APB1 Clock Gates" },
70
71	{ "allwinner,sun7i-a20-ahb-gates-clk",
72	  (uintptr_t)"Allwinner AHB Clock Gates" },
73	{ "allwinner,sun7i-a20-apb0-gates-clk",
74	  (uintptr_t)"Allwinner APB0 Clock Gates" },
75	{ "allwinner,sun7i-a20-apb1-gates-clk",
76	  (uintptr_t)"Allwinner APB1 Clock Gates" },
77
78	{ "allwinner,sun6i-a31-ahb1-gates-clk",
79	  (uintptr_t)"Allwinner AHB1 Clock Gates" },
80	{ "allwinner,sun6i-a31-apb0-gates-clk",
81	  (uintptr_t)"Allwinner APB0 Clock Gates" },
82	{ "allwinner,sun6i-a31-apb1-gates-clk",
83	  (uintptr_t)"Allwinner APB1 Clock Gates" },
84	{ "allwinner,sun6i-a31-apb2-gates-clk",
85	  (uintptr_t)"Allwinner APB2 Clock Gates" },
86
87	{ "allwinner,sun8i-a83t-bus-gates-clk",
88	  (uintptr_t)"Allwinner Bus Clock Gates" },
89	{ "allwinner,sun8i-a83t-apb0-gates-clk",
90	  (uintptr_t)"Allwinner APB0 Clock Gates" },
91
92	{ "allwinner,sun8i-h3-bus-gates-clk",
93	  (uintptr_t)"Allwinner Bus Clock Gates" },
94	{ "allwinner,sun8i-h3-apb0-gates-clk",
95	  (uintptr_t)"Allwinner APB0 Clock Gates" },
96
97	{ "allwinner,sun9i-a80-apbs-gates-clk",
98	  (uintptr_t)"Allwinner APBS Clock Gates" },
99
100	{ "allwinner,sunxi-multi-bus-gates-clk",
101	  (uintptr_t)"Allwinner Multi Bus Clock Gates" },
102
103	{ NULL, 0 }
104};
105
106static int
107aw_gate_create(device_t dev, bus_addr_t paddr, struct clkdom *clkdom,
108    const char *pclkname, const char *clkname, int index)
109{
110	const char *parent_names[1] = { pclkname };
111	struct clk_gate_def def;
112
113	memset(&def, 0, sizeof(def));
114	def.clkdef.id = index;
115	def.clkdef.name = clkname;
116	def.clkdef.parent_names = parent_names;
117	def.clkdef.parent_cnt = 1;
118	def.offset = paddr + GATE_OFFSET(index);
119	def.shift = GATE_SHIFT(index);
120	def.mask = 1;
121	def.on_value = 1;
122	def.off_value = 0;
123
124	return (clknode_gate_register(clkdom, &def));
125}
126
127static int
128aw_gate_add(device_t dev, struct clkdom *clkdom, phandle_t node,
129    bus_addr_t paddr)
130{
131	const char **names;
132	uint32_t *indices;
133	clk_t clk_parent;
134	int index, nout, error;
135
136	indices = NULL;
137
138	nout = clk_parse_ofw_out_names(dev, node, &names, &indices);
139	if (nout == 0) {
140		device_printf(dev, "no clock outputs found\n");
141		return (ENOENT);
142	}
143	if (indices == NULL) {
144		device_printf(dev, "no clock-indices property\n");
145		return (ENXIO);
146	}
147
148	error = clk_get_by_ofw_index(dev, node, 0, &clk_parent);
149	if (error != 0) {
150		device_printf(dev, "cannot parse clock parent\n");
151		return (ENXIO);
152	}
153
154	for (index = 0; index < nout; index++) {
155		error = aw_gate_create(dev, paddr, clkdom,
156		    clk_get_name(clk_parent), names[index], indices[index]);
157		if (error)
158			return (error);
159	}
160
161	return (0);
162}
163
164static int
165aw_gate_probe(device_t dev)
166{
167	const char *d;
168
169	if (!ofw_bus_status_okay(dev))
170		return (ENXIO);
171
172	d = (const char *)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
173	if (d == NULL)
174		return (ENXIO);
175
176	device_set_desc(dev, d);
177	return (BUS_PROBE_DEFAULT);
178}
179
180static int
181aw_gate_attach(device_t dev)
182{
183	struct clkdom *clkdom;
184	bus_addr_t paddr;
185	bus_size_t psize;
186	phandle_t node, child;
187
188	node = ofw_bus_get_node(dev);
189
190	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
191		device_printf(dev, "cannot parse 'reg' property\n");
192		return (ENXIO);
193	}
194
195	clkdom = clkdom_create(dev);
196
197	if (ofw_bus_is_compatible(dev, "allwinner,sunxi-multi-bus-gates-clk")) {
198		for (child = OF_child(node); child > 0; child = OF_peer(child))
199			aw_gate_add(dev, clkdom, child, paddr);
200	} else
201		aw_gate_add(dev, clkdom, node, paddr);
202
203	if (clkdom_finit(clkdom) != 0) {
204		device_printf(dev, "cannot finalize clkdom initialization\n");
205		return (ENXIO);
206	}
207
208	if (bootverbose)
209		clkdom_dump(clkdom);
210
211	return (0);
212}
213
214static device_method_t aw_gate_methods[] = {
215	/* Device interface */
216	DEVMETHOD(device_probe,		aw_gate_probe),
217	DEVMETHOD(device_attach,	aw_gate_attach),
218
219	DEVMETHOD_END
220};
221
222static driver_t aw_gate_driver = {
223	"aw_gate",
224	aw_gate_methods,
225	0
226};
227
228static devclass_t aw_gate_devclass;
229
230EARLY_DRIVER_MODULE(aw_gate, simplebus, aw_gate_driver,
231    aw_gate_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
232