aw_axiclk.c revision 302408
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_axiclk.c 297627 2016-04-06 23:11:03Z jmcneill $
27 */
28
29/*
30 * Allwinner AXI clock
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_axiclk.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	AXI_CLK_DIV_RATIO	(0x3 << 0)
54
55static struct ofw_compat_data compat_data[] = {
56	{ "allwinner,sun4i-a10-axi-clk",	1 },
57	{ NULL, 0 }
58};
59
60struct aw_axiclk_sc {
61	device_t	clkdev;
62	bus_addr_t	reg;
63};
64
65#define	AXICLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
66#define	AXICLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
67#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
68#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
69
70static int
71aw_axiclk_init(struct clknode *clk, device_t dev)
72{
73	clknode_init_parent_idx(clk, 0);
74	return (0);
75}
76
77static int
78aw_axiclk_recalc_freq(struct clknode *clk, uint64_t *freq)
79{
80	struct aw_axiclk_sc *sc;
81	uint32_t val;
82
83	sc = clknode_get_softc(clk);
84
85	DEVICE_LOCK(sc);
86	AXICLK_READ(sc, &val);
87	DEVICE_UNLOCK(sc);
88
89	*freq = *freq / ((val & AXI_CLK_DIV_RATIO) + 1);
90
91	return (0);
92}
93
94static clknode_method_t aw_axiclk_clknode_methods[] = {
95	/* Device interface */
96	CLKNODEMETHOD(clknode_init,		aw_axiclk_init),
97	CLKNODEMETHOD(clknode_recalc_freq,	aw_axiclk_recalc_freq),
98	CLKNODEMETHOD_END
99};
100DEFINE_CLASS_1(aw_axiclk_clknode, aw_axiclk_clknode_class,
101    aw_axiclk_clknode_methods, sizeof(struct aw_axiclk_sc), clknode_class);
102
103static int
104aw_axiclk_probe(device_t dev)
105{
106	if (!ofw_bus_status_okay(dev))
107		return (ENXIO);
108
109	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
110		return (ENXIO);
111
112	device_set_desc(dev, "Allwinner AXI Clock");
113	return (BUS_PROBE_DEFAULT);
114}
115
116static int
117aw_axiclk_attach(device_t dev)
118{
119	struct clknode_init_def def;
120	struct aw_axiclk_sc *sc;
121	struct clkdom *clkdom;
122	struct clknode *clk;
123	clk_t clk_parent;
124	bus_addr_t paddr;
125	bus_size_t psize;
126	phandle_t node;
127	int error;
128
129	node = ofw_bus_get_node(dev);
130
131	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
132		device_printf(dev, "cannot parse 'reg' property\n");
133		return (ENXIO);
134	}
135
136	clkdom = clkdom_create(dev);
137
138	error = clk_get_by_ofw_index(dev, 0, &clk_parent);
139	if (error != 0) {
140		device_printf(dev, "cannot parse clock parent\n");
141		return (ENXIO);
142	}
143
144	memset(&def, 0, sizeof(def));
145	error = clk_parse_ofw_clk_name(dev, node, &def.name);
146	if (error != 0) {
147		device_printf(dev, "cannot parse clock name\n");
148		error = ENXIO;
149		goto fail;
150	}
151	def.id = 1;
152	def.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK);
153	def.parent_names[0] = clk_get_name(clk_parent);
154	def.parent_cnt = 1;
155
156	clk = clknode_create(clkdom, &aw_axiclk_clknode_class, &def);
157	if (clk == NULL) {
158		device_printf(dev, "cannot create clknode\n");
159		error = ENXIO;
160		goto fail;
161	}
162
163	sc = clknode_get_softc(clk);
164	sc->reg = paddr;
165	sc->clkdev = device_get_parent(dev);
166
167	clknode_register(clkdom, clk);
168
169	if (clkdom_finit(clkdom) != 0) {
170		device_printf(dev, "cannot finalize clkdom initialization\n");
171		error = ENXIO;
172		goto fail;
173	}
174
175	if (bootverbose)
176		clkdom_dump(clkdom);
177
178	return (0);
179
180fail:
181	return (error);
182}
183
184static device_method_t aw_axiclk_methods[] = {
185	/* Device interface */
186	DEVMETHOD(device_probe,		aw_axiclk_probe),
187	DEVMETHOD(device_attach,	aw_axiclk_attach),
188
189	DEVMETHOD_END
190};
191
192static driver_t aw_axiclk_driver = {
193	"aw_axiclk",
194	aw_axiclk_methods,
195	0
196};
197
198static devclass_t aw_axiclk_devclass;
199
200EARLY_DRIVER_MODULE(aw_axiclk, simplebus, aw_axiclk_driver,
201    aw_axiclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
202