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: releng/11.0/sys/arm/allwinner/clk/aw_usbclk.c 299688 2016-05-13 18:20:54Z manu $
27 */
28
29/*
30 * Allwinner USB clocks
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: releng/11.0/sys/arm/allwinner/clk/aw_usbclk.c 299688 2016-05-13 18:20:54Z 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_gate.h>
49#include <dev/extres/hwreset/hwreset.h>
50
51#include "clkdev_if.h"
52#include "hwreset_if.h"
53
54#define	A10_SCLK_GATING_USBPHY	(1 << 8)
55#define	A10_SCLK_GATING_OHCI1	(1 << 7)
56#define	A10_SCLK_GATING_OHCI0	(1 << 6)
57
58#define	USBPHY2_RST		(1 << 2)
59#define	USBPHY1_RST		(1 << 1)
60#define	USBPHY0_RST		(1 << 0)
61
62enum aw_usbclk_type {
63	AW_A10_USBCLK = 1,
64	AW_A31_USBCLK,
65	AW_A83T_USBCLK,
66	AW_H3_USBCLK,
67};
68
69static struct ofw_compat_data compat_data[] = {
70	{ "allwinner,sun4i-a10-usb-clk",	AW_A10_USBCLK },
71	{ "allwinner,sun6i-a31-usb-clk",	AW_A31_USBCLK },
72	{ "allwinner,sun8i-a83t-usb-clk",	AW_A83T_USBCLK },
73	{ "allwinner,sun8i-h3-usb-clk",		AW_H3_USBCLK },
74	{ NULL, 0 }
75};
76
77/* Clock indices for A10, as there is no clock-indices property in the DT */
78static uint32_t aw_usbclk_indices_a10[] = { 6, 7, 8 };
79/* Clock indices for H3, as there is no clock-indices property in the DT */
80static uint32_t aw_usbclk_indices_h3[] = { 8, 9, 10, 11, 16, 17, 18, 19 };
81
82struct aw_usbclk_softc {
83	bus_addr_t	reg;
84};
85
86static int
87aw_usbclk_hwreset_assert(device_t dev, intptr_t id, bool value)
88{
89	struct aw_usbclk_softc *sc;
90	uint32_t mask;
91	device_t pdev;
92	int error;
93
94	sc = device_get_softc(dev);
95	pdev = device_get_parent(dev);
96
97	mask = USBPHY0_RST << id;
98
99	CLKDEV_DEVICE_LOCK(pdev);
100	error = CLKDEV_MODIFY_4(pdev, sc->reg, mask, value ? 0 : mask);
101	CLKDEV_DEVICE_UNLOCK(pdev);
102
103	return (error);
104}
105
106static int
107aw_usbclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value)
108{
109	struct aw_usbclk_softc *sc;
110	uint32_t mask, val;
111	device_t pdev;
112	int error;
113
114	sc = device_get_softc(dev);
115	pdev = device_get_parent(dev);
116
117	mask = USBPHY0_RST << id;
118
119	CLKDEV_DEVICE_LOCK(pdev);
120	error = CLKDEV_READ_4(pdev, sc->reg, &val);
121	CLKDEV_DEVICE_UNLOCK(pdev);
122
123	if (error)
124		return (error);
125
126	*value = (val & mask) != 0 ? false : true;
127
128	return (0);
129}
130
131static int
132aw_usbclk_create(device_t dev, bus_addr_t paddr, struct clkdom *clkdom,
133    const char *pclkname, const char *clkname, int index)
134{
135	const char *parent_names[1] = { pclkname };
136	struct clk_gate_def def;
137
138	memset(&def, 0, sizeof(def));
139	def.clkdef.id = index;
140	def.clkdef.name = clkname;
141	def.clkdef.parent_names = parent_names;
142	def.clkdef.parent_cnt = 1;
143	def.offset = paddr;
144	def.shift = index;
145	def.mask = 1;
146	def.on_value = 1;
147	def.off_value = 0;
148
149	return (clknode_gate_register(clkdom, &def));
150}
151
152static int
153aw_usbclk_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 USB Clocks");
162	return (BUS_PROBE_DEFAULT);
163}
164
165static int
166aw_usbclk_attach(device_t dev)
167{
168	struct aw_usbclk_softc *sc;
169	struct clkdom *clkdom;
170	const char **names;
171	const char *pname;
172	int index, nout, error;
173	enum aw_usbclk_type type;
174	uint32_t *indices;
175	clk_t clk_parent, clk_parent_pll;
176	bus_size_t psize;
177	phandle_t node;
178
179	sc = device_get_softc(dev);
180	node = ofw_bus_get_node(dev);
181	indices = NULL;
182	type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
183
184	if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) {
185		device_printf(dev, "cannot parse 'reg' property\n");
186		return (ENXIO);
187	}
188
189	clkdom = clkdom_create(dev);
190
191	nout = clk_parse_ofw_out_names(dev, node, &names, &indices);
192	if (nout == 0) {
193		device_printf(dev, "no clock outputs found\n");
194		error = ENOENT;
195		goto fail;
196	}
197
198	if (indices == NULL && type == AW_A10_USBCLK)
199		indices = aw_usbclk_indices_a10;
200	else if (indices == NULL && type == AW_H3_USBCLK)
201		indices = aw_usbclk_indices_h3;
202
203	error = clk_get_by_ofw_index(dev, 0, &clk_parent);
204	if (error != 0) {
205		device_printf(dev, "cannot parse clock parent\n");
206		return (ENXIO);
207	}
208	if (type == AW_A83T_USBCLK) {
209		error = clk_get_by_ofw_index(dev, 1, &clk_parent_pll);
210		if (error != 0) {
211			device_printf(dev, "cannot parse pll clock parent\n");
212			return (ENXIO);
213		}
214	}
215
216	for (index = 0; index < nout; index++) {
217		if (strcmp(names[index], "usb_hsic_pll") == 0)
218			pname = clk_get_name(clk_parent_pll);
219		else
220			pname = clk_get_name(clk_parent);
221		error = aw_usbclk_create(dev, sc->reg, clkdom, pname,
222		    names[index], indices != NULL ? indices[index] : index);
223		if (error)
224			goto fail;
225	}
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	hwreset_register_ofw_provider(dev);
237
238	return (0);
239
240fail:
241	return (error);
242}
243
244static device_method_t aw_usbclk_methods[] = {
245	/* Device interface */
246	DEVMETHOD(device_probe,		aw_usbclk_probe),
247	DEVMETHOD(device_attach,	aw_usbclk_attach),
248
249	/* Reset interface */
250	DEVMETHOD(hwreset_assert,	aw_usbclk_hwreset_assert),
251	DEVMETHOD(hwreset_is_asserted,	aw_usbclk_hwreset_is_asserted),
252
253	DEVMETHOD_END
254};
255
256static driver_t aw_usbclk_driver = {
257	"aw_usbclk",
258	aw_usbclk_methods,
259	sizeof(struct aw_usbclk_softc)
260};
261
262static devclass_t aw_usbclk_devclass;
263
264EARLY_DRIVER_MODULE(aw_usbclk, simplebus, aw_usbclk_driver,
265    aw_usbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
266