1294660Smmel/*-
2294660Smmel * Copyright 2016 Michal Meloun <mmel@FreeBSD.org>
3294660Smmel * All rights reserved.
4294660Smmel *
5294660Smmel * Redistribution and use in source and binary forms, with or without
6294660Smmel * modification, are permitted provided that the following conditions
7294660Smmel * are met:
8294660Smmel * 1. Redistributions of source code must retain the above copyright
9294660Smmel *    notice, this list of conditions and the following disclaimer.
10294660Smmel * 2. Redistributions in binary form must reproduce the above copyright
11294660Smmel *    notice, this list of conditions and the following disclaimer in the
12294660Smmel *    documentation and/or other materials provided with the distribution.
13294660Smmel *
14294660Smmel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15294660Smmel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16294660Smmel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17294660Smmel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18294660Smmel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19294660Smmel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20294660Smmel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21294660Smmel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22294660Smmel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23294660Smmel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24294660Smmel * SUCH DAMAGE.
25294660Smmel */
26294660Smmel
27294660Smmel#include <sys/cdefs.h>
28294660Smmel__FBSDID("$FreeBSD: stable/11/sys/dev/extres/clk/clk_fixed.c 308324 2016-11-05 04:17:32Z mmel $");
29294660Smmel
30294660Smmel#include <sys/param.h>
31294660Smmel#include <sys/conf.h>
32294660Smmel#include <sys/bus.h>
33294660Smmel#include <sys/kernel.h>
34294660Smmel#include <sys/kobj.h>
35294660Smmel#include <sys/malloc.h>
36294660Smmel#include <sys/mutex.h>
37296904Smmel#include <sys/module.h>
38294660Smmel#include <sys/rman.h>
39294660Smmel#include <sys/systm.h>
40294660Smmel
41294660Smmel#include <machine/bus.h>
42294660Smmel
43296904Smmel#include <dev/ofw/ofw_bus.h>
44296904Smmel#include <dev/ofw/ofw_bus_subr.h>
45294660Smmel#include <dev/extres/clk/clk_fixed.h>
46294660Smmel
47296904Smmel#define	CLK_TYPE_FIXED		1
48296904Smmel#define	CLK_TYPE_FIXED_FACTOR	2
49296904Smmel
50294660Smmelstatic int clknode_fixed_init(struct clknode *clk, device_t dev);
51294660Smmelstatic int clknode_fixed_recalc(struct clknode *clk, uint64_t *freq);
52296904Smmelstatic int clknode_fixed_set_freq(struct clknode *clk, uint64_t fin,
53296904Smmel    uint64_t *fout, int flags, int *stop);
54296904Smmel
55294660Smmelstruct clknode_fixed_sc {
56294660Smmel	int		fixed_flags;
57294660Smmel	uint64_t	freq;
58294660Smmel	uint32_t	mult;
59294660Smmel	uint32_t	div;
60294660Smmel};
61294660Smmel
62294660Smmelstatic clknode_method_t clknode_fixed_methods[] = {
63294660Smmel	/* Device interface */
64294660Smmel	CLKNODEMETHOD(clknode_init,	   clknode_fixed_init),
65294660Smmel	CLKNODEMETHOD(clknode_recalc_freq, clknode_fixed_recalc),
66296904Smmel	CLKNODEMETHOD(clknode_set_freq,    clknode_fixed_set_freq),
67294660Smmel	CLKNODEMETHOD_END
68294660Smmel};
69294660SmmelDEFINE_CLASS_1(clknode_fixed, clknode_fixed_class, clknode_fixed_methods,
70294660Smmel   sizeof(struct clknode_fixed_sc), clknode_class);
71294660Smmel
72294660Smmelstatic int
73294660Smmelclknode_fixed_init(struct clknode *clk, device_t dev)
74294660Smmel{
75294660Smmel	struct clknode_fixed_sc *sc;
76294660Smmel
77294660Smmel	sc = clknode_get_softc(clk);
78294660Smmel	if (sc->freq == 0)
79294660Smmel		clknode_init_parent_idx(clk, 0);
80294660Smmel	return(0);
81294660Smmel}
82296903Smmel
83294660Smmelstatic int
84294660Smmelclknode_fixed_recalc(struct clknode *clk, uint64_t *freq)
85294660Smmel{
86294660Smmel	struct clknode_fixed_sc *sc;
87294660Smmel
88294660Smmel	sc = clknode_get_softc(clk);
89296904Smmel
90296904Smmel	if ((sc->mult != 0) && (sc->div != 0))
91294660Smmel		*freq = (*freq / sc->div) * sc->mult;
92294660Smmel	else
93296904Smmel		*freq = sc->freq;
94294660Smmel	return (0);
95294660Smmel}
96294660Smmel
97296904Smmelstatic int
98296904Smmelclknode_fixed_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
99296904Smmel    int flags, int *stop)
100296904Smmel{
101296904Smmel	struct clknode_fixed_sc *sc;
102296904Smmel
103296904Smmel	sc = clknode_get_softc(clk);
104296904Smmel	if (sc->mult == 0 || sc->div == 0) {
105296904Smmel		/* Fixed frequency clock. */
106296904Smmel		*stop = 1;
107296904Smmel		if (*fout != sc->freq)
108296904Smmel			return (ERANGE);
109296904Smmel		return (0);
110296904Smmel	}
111296904Smmel	/* Fixed factor clock. */
112296904Smmel	*stop = 0;
113296904Smmel	*fout = (*fout / sc->mult) *  sc->div;
114296904Smmel	return (0);
115296904Smmel}
116296904Smmel
117294660Smmelint
118296903Smmelclknode_fixed_register(struct clkdom *clkdom, struct clk_fixed_def *clkdef)
119294660Smmel{
120294660Smmel	struct clknode *clk;
121294660Smmel	struct clknode_fixed_sc *sc;
122294660Smmel
123294660Smmel	clk = clknode_create(clkdom, &clknode_fixed_class, &clkdef->clkdef);
124294660Smmel	if (clk == NULL)
125294660Smmel		return (1);
126294660Smmel
127294660Smmel	sc = clknode_get_softc(clk);
128294660Smmel	sc->fixed_flags = clkdef->fixed_flags;
129294660Smmel	sc->freq = clkdef->freq;
130294660Smmel	sc->mult = clkdef->mult;
131294660Smmel	sc->div = clkdef->div;
132294660Smmel
133294660Smmel	clknode_register(clkdom, clk);
134294660Smmel	return (0);
135294660Smmel}
136296904Smmel
137296904Smmel#ifdef FDT
138296904Smmel
139296904Smmelstatic struct ofw_compat_data compat_data[] = {
140296904Smmel	{"fixed-clock",		CLK_TYPE_FIXED},
141296904Smmel	{"fixed-factor-clock",  CLK_TYPE_FIXED_FACTOR},
142296904Smmel	{NULL,		 	0},
143296904Smmel};
144296904Smmel
145296904Smmelstruct clk_fixed_softc {
146296904Smmel	device_t	dev;
147296904Smmel	struct clkdom	*clkdom;
148296904Smmel};
149296904Smmel
150296904Smmelstatic int
151296904Smmelclk_fixed_probe(device_t dev)
152296904Smmel{
153297215Sjmcneill	intptr_t clk_type;
154296904Smmel
155297215Sjmcneill	clk_type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
156297215Sjmcneill	switch (clk_type) {
157297215Sjmcneill	case CLK_TYPE_FIXED:
158296904Smmel		device_set_desc(dev, "Fixed clock");
159296904Smmel		return (BUS_PROBE_DEFAULT);
160297215Sjmcneill	case CLK_TYPE_FIXED_FACTOR:
161297215Sjmcneill		device_set_desc(dev, "Fixed factor clock");
162297215Sjmcneill		return (BUS_PROBE_DEFAULT);
163297215Sjmcneill	default:
164297215Sjmcneill		return (ENXIO);
165296904Smmel	}
166296904Smmel}
167296904Smmel
168296904Smmelstatic int
169296904Smmelclk_fixed_init_fixed(struct clk_fixed_softc *sc, phandle_t node,
170296904Smmel    struct clk_fixed_def *def)
171296904Smmel{
172296904Smmel	uint32_t freq;
173296904Smmel	int rv;
174296904Smmel
175296904Smmel	def->clkdef.id = 1;
176296904Smmel	rv = OF_getencprop(node, "clock-frequency", &freq,  sizeof(freq));
177296904Smmel	if (rv <= 0)
178296904Smmel		return (ENXIO);
179296904Smmel	def->freq = freq;
180296904Smmel	return (0);
181296904Smmel}
182296904Smmel
183296904Smmelstatic int
184296904Smmelclk_fixed_init_fixed_factor(struct clk_fixed_softc *sc, phandle_t node,
185296904Smmel    struct clk_fixed_def *def)
186296904Smmel{
187296904Smmel	int rv;
188296904Smmel	clk_t  parent;
189296904Smmel
190296904Smmel	def->clkdef.id = 1;
191296904Smmel	rv = OF_getencprop(node, "clock-mult", &def->mult,  sizeof(def->mult));
192296904Smmel	if (rv <= 0)
193296904Smmel		return (ENXIO);
194297215Sjmcneill	rv = OF_getencprop(node, "clock-div", &def->div,  sizeof(def->div));
195296904Smmel	if (rv <= 0)
196296904Smmel		return (ENXIO);
197296904Smmel	/* Get name of parent clock */
198308324Smmel	rv = clk_get_by_ofw_index(sc->dev, 0, 0, &parent);
199296904Smmel	if (rv != 0)
200296904Smmel		return (ENXIO);
201296904Smmel	def->clkdef.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK);
202296904Smmel	def->clkdef.parent_names[0] = clk_get_name(parent);
203296904Smmel	def->clkdef.parent_cnt  = 1;
204296904Smmel	clk_release(parent);
205296904Smmel	return (0);
206296904Smmel}
207296904Smmel
208296904Smmelstatic int
209296904Smmelclk_fixed_attach(device_t dev)
210296904Smmel{
211296904Smmel	struct clk_fixed_softc *sc;
212296904Smmel	intptr_t clk_type;
213296904Smmel	phandle_t node;
214296904Smmel	struct clk_fixed_def def;
215296904Smmel	int rv;
216296904Smmel
217296904Smmel	sc = device_get_softc(dev);
218296904Smmel	sc->dev = dev;
219296904Smmel	node  = ofw_bus_get_node(dev);
220296904Smmel	clk_type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
221296904Smmel
222296904Smmel	bzero(&def, sizeof(def));
223296904Smmel	if (clk_type == CLK_TYPE_FIXED)
224296904Smmel		rv = clk_fixed_init_fixed(sc, node, &def);
225296904Smmel	else if (clk_type == CLK_TYPE_FIXED_FACTOR)
226296904Smmel		rv = clk_fixed_init_fixed_factor(sc, node, &def);
227296904Smmel	else
228296904Smmel		rv = ENXIO;
229296904Smmel	if (rv != 0) {
230296904Smmel		device_printf(sc->dev, "Cannot FDT parameters.\n");
231296904Smmel		goto fail;
232296904Smmel	}
233296904Smmel	rv = clk_parse_ofw_clk_name(dev, node, &def.clkdef.name);
234296904Smmel	if (rv != 0) {
235296904Smmel		device_printf(sc->dev, "Cannot parse clock name.\n");
236296904Smmel		goto fail;
237296904Smmel	}
238296904Smmel	sc->clkdom = clkdom_create(dev);
239296904Smmel	KASSERT(sc->clkdom != NULL, ("Clock domain is NULL"));
240296904Smmel
241296904Smmel	rv = clknode_fixed_register(sc->clkdom, &def);
242296904Smmel	if (rv != 0) {
243296904Smmel		device_printf(sc->dev, "Cannot register fixed clock.\n");
244296904Smmel		rv = ENXIO;
245296904Smmel		goto fail;
246296904Smmel	}
247296904Smmel
248296904Smmel	rv = clkdom_finit(sc->clkdom);
249296904Smmel	if (rv != 0) {
250296904Smmel		device_printf(sc->dev, "Clk domain finit fails.\n");
251296904Smmel		rv = ENXIO;
252296904Smmel		goto fail;
253296904Smmel	}
254296904Smmel#ifdef CLK_DEBUG
255296904Smmel	clkdom_dump(sc->clkdom);
256296904Smmel#endif
257299714Sgonzo	OF_prop_free(__DECONST(char *, def.clkdef.name));
258299714Sgonzo	OF_prop_free(def.clkdef.parent_names);
259296904Smmel	return (bus_generic_attach(dev));
260296904Smmel
261296904Smmelfail:
262299714Sgonzo	OF_prop_free(__DECONST(char *, def.clkdef.name));
263299714Sgonzo	OF_prop_free(def.clkdef.parent_names);
264296904Smmel	return (rv);
265296904Smmel}
266296904Smmel
267296904Smmelstatic device_method_t clk_fixed_methods[] = {
268296904Smmel	/* Device interface */
269296904Smmel	DEVMETHOD(device_probe,		clk_fixed_probe),
270296904Smmel	DEVMETHOD(device_attach,	clk_fixed_attach),
271296904Smmel
272296904Smmel	DEVMETHOD_END
273296904Smmel};
274296904Smmel
275296904SmmelDEFINE_CLASS_0(clk_fixed, clk_fixed_driver, clk_fixed_methods,
276296904Smmel    sizeof(struct clk_fixed_softc));
277296904Smmelstatic devclass_t clk_fixed_devclass;
278296904SmmelEARLY_DRIVER_MODULE(clk_fixed, simplebus, clk_fixed_driver,
279296904Smmel    clk_fixed_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
280296904SmmelMODULE_VERSION(clk_fixed, 1);
281296904Smmel
282296904Smmel#endif
283