1/*-
2 * Copyright (c) 2016 Michal Meloun <mmel@FreeBSD.org>
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 AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, 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
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/bus.h>
33#include <sys/lock.h>
34#include <sys/mutex.h>
35#include <sys/rman.h>
36
37#include <machine/bus.h>
38
39#include <dev/extres/clk/clk.h>
40
41#include <dt-bindings/clock/tegra124-car.h>
42#include "tegra124_car.h"
43
44/* Flags */
45#define	SMF_HAVE_DIVIDER_2	1
46
47struct super_mux_def {
48	struct clknode_init_def	clkdef;
49	uint32_t		base_reg;
50	uint32_t		flags;
51	int			src_pllx;
52	int			src_div2;
53};
54
55#define	PLIST(x) static const char *x[]
56#define	SM(_id, cn, pl, r, x, d, f)				\
57{									\
58	.clkdef.id = _id,						\
59	.clkdef.name = cn,						\
60	.clkdef.parent_names = pl,					\
61	.clkdef.parent_cnt = nitems(pl),				\
62	.clkdef.flags = CLK_NODE_STATIC_STRINGS,				\
63	.base_reg = r,							\
64	.src_pllx = x,							\
65	.src_div2 = d,							\
66	.flags = f,							\
67}
68
69PLIST(cclk_g_parents) = {
70	"clk_m", "pllC_out0", "clk_s", "pllM_out0",
71	"pllP_out0", "pllP_out4", "pllC2_out0", "pllC3_out0",
72	"pllX_out", NULL, NULL, NULL,
73	NULL, NULL, NULL,NULL, // "dfllCPU_out0"
74};
75
76PLIST(cclk_lp_parents) = {
77	"clk_m", "pllC_out0", "clk_s", "pllM_out0",
78	"pllP_out0", "pllP_out4", "pllC2_out0", "pllC3_out0",
79	"pllX_out", NULL, NULL, NULL,
80	NULL, NULL, NULL, NULL,
81	"pllX_out0"
82};
83
84PLIST(sclk_parents) = {
85	"clk_m", "pllC_out1", "pllP_out4", "pllP_out0",
86	"pllP_out2", "pllC_out0", "clk_s", "pllM_out1",
87};
88
89static struct super_mux_def super_mux_def[] = {
90 SM(TEGRA124_CLK_CCLK_G, "cclk_g", cclk_g_parents, CCLKG_BURST_POLICY, 0, 0, 0),
91 SM(TEGRA124_CLK_CCLK_LP, "cclk_lp", cclk_lp_parents, CCLKLP_BURST_POLICY, 8, 16, SMF_HAVE_DIVIDER_2),
92 SM(TEGRA124_CLK_SCLK, "sclk", sclk_parents, SCLK_BURST_POLICY, 0, 0, 0),
93};
94
95static int super_mux_init(struct clknode *clk, device_t dev);
96static int super_mux_set_mux(struct clknode *clk, int idx);
97
98struct super_mux_sc {
99	device_t		clkdev;
100	uint32_t		base_reg;
101	int			src_pllx;
102	int			src_div2;
103	uint32_t		flags;
104
105	int 			mux;
106};
107
108static clknode_method_t super_mux_methods[] = {
109	/* Device interface */
110	CLKNODEMETHOD(clknode_init,		super_mux_init),
111	CLKNODEMETHOD(clknode_set_mux, 		super_mux_set_mux),
112	CLKNODEMETHOD_END
113};
114DEFINE_CLASS_1(tegra124_super_mux, tegra124_super_mux_class, super_mux_methods,
115   sizeof(struct super_mux_sc), clknode_class);
116
117/* Mux status. */
118#define	SUPER_MUX_STATE_STDBY		0
119#define	SUPER_MUX_STATE_IDLE		1
120#define	SUPER_MUX_STATE_RUN		2
121#define	SUPER_MUX_STATE_IRQ		3
122#define	SUPER_MUX_STATE_FIQ		4
123
124/* Mux register bits. */
125#define	SUPER_MUX_STATE_BIT_SHIFT	28
126#define	SUPER_MUX_STATE_BIT_MASK	0xF
127/* State is Priority encoded */
128#define	SUPER_MUX_STATE_BIT_STDBY	0x00
129#define	SUPER_MUX_STATE_BIT_IDLE	0x01
130#define	SUPER_MUX_STATE_BIT_RUN		0x02
131#define	SUPER_MUX_STATE_BIT_IRQ		0x04
132#define	SUPER_MUX_STATE_BIT_FIQ		0x08
133
134#define	SUPER_MUX_MUX_WIDTH		4
135#define	SUPER_MUX_LP_DIV2_BYPASS	(1 << 16)
136
137static uint32_t
138super_mux_get_state(uint32_t reg)
139{
140	reg = (reg >> SUPER_MUX_STATE_BIT_SHIFT) & SUPER_MUX_STATE_BIT_MASK;
141	if (reg & SUPER_MUX_STATE_BIT_FIQ)
142		 return (SUPER_MUX_STATE_FIQ);
143	if (reg & SUPER_MUX_STATE_BIT_IRQ)
144		 return (SUPER_MUX_STATE_IRQ);
145	if (reg & SUPER_MUX_STATE_BIT_RUN)
146		 return (SUPER_MUX_STATE_RUN);
147	if (reg & SUPER_MUX_STATE_BIT_IDLE)
148		 return (SUPER_MUX_STATE_IDLE);
149	return (SUPER_MUX_STATE_STDBY);
150}
151
152static int
153super_mux_init(struct clknode *clk, device_t dev)
154{
155	struct super_mux_sc *sc;
156	uint32_t reg;
157	int shift, state;
158
159	sc = clknode_get_softc(clk);
160
161	DEVICE_LOCK(sc);
162	RD4(sc, sc->base_reg, &reg);
163	DEVICE_UNLOCK(sc);
164	state = super_mux_get_state(reg);
165
166	if ((state != SUPER_MUX_STATE_RUN) &&
167	    (state != SUPER_MUX_STATE_IDLE)) {
168		panic("Unexpected super mux state: %u", state);
169	}
170
171	shift = state * SUPER_MUX_MUX_WIDTH;
172
173	sc->mux = (reg >> shift) & ((1 << SUPER_MUX_MUX_WIDTH) - 1);
174
175	/*
176	 * CCLKLP uses PLLX/2 as source if LP_DIV2_BYPASS isn't set
177	 * and source mux is set to PLLX.
178	 */
179	if (sc->flags & SMF_HAVE_DIVIDER_2) {
180		if (((reg & SUPER_MUX_LP_DIV2_BYPASS) == 0) &&
181		    (sc->mux == sc->src_pllx))
182		sc->mux = sc->src_div2;
183	}
184	clknode_init_parent_idx(clk, sc->mux);
185
186	return(0);
187}
188
189static int
190super_mux_set_mux(struct clknode *clk, int idx)
191{
192
193	struct super_mux_sc *sc;
194	int shift, state;
195	uint32_t reg, dummy;
196
197	sc = clknode_get_softc(clk);
198
199	DEVICE_LOCK(sc);
200	RD4(sc, sc->base_reg, &reg);
201	state = super_mux_get_state(reg);
202
203	if ((state != SUPER_MUX_STATE_RUN) &&
204	    (state != SUPER_MUX_STATE_IDLE)) {
205		panic("Unexpected super mux state: %u", state);
206	}
207	shift = (state - 1) * SUPER_MUX_MUX_WIDTH;
208	sc->mux = idx;
209	if (sc->flags & SMF_HAVE_DIVIDER_2) {
210		if (idx == sc->src_div2) {
211			idx = sc->src_pllx;
212			reg &= ~SUPER_MUX_LP_DIV2_BYPASS;
213			WR4(sc, sc->base_reg, reg);
214			RD4(sc, sc->base_reg, &dummy);
215		} else if (idx == sc->src_pllx) {
216			reg = SUPER_MUX_LP_DIV2_BYPASS;
217			WR4(sc, sc->base_reg, reg);
218			RD4(sc, sc->base_reg, &dummy);
219		}
220	}
221	reg &= ~(((1 << SUPER_MUX_MUX_WIDTH) - 1) << shift);
222	reg |= idx << shift;
223
224	WR4(sc, sc->base_reg, reg);
225	RD4(sc, sc->base_reg, &dummy);
226	DEVICE_UNLOCK(sc);
227
228	return(0);
229}
230
231static int
232super_mux_register(struct clkdom *clkdom, struct super_mux_def *clkdef)
233{
234	struct clknode *clk;
235	struct super_mux_sc *sc;
236
237	clk = clknode_create(clkdom, &tegra124_super_mux_class,
238	    &clkdef->clkdef);
239	if (clk == NULL)
240		return (1);
241
242	sc = clknode_get_softc(clk);
243	sc->clkdev = clknode_get_device(clk);
244	sc->base_reg = clkdef->base_reg;
245	sc->src_pllx = clkdef->src_pllx;
246	sc->src_div2 = clkdef->src_div2;
247	sc->flags = clkdef->flags;
248
249	clknode_register(clkdom, clk);
250	return (0);
251}
252
253void
254tegra124_super_mux_clock(struct tegra124_car_softc *sc)
255{
256	int i, rv;
257
258	for (i = 0; i <  nitems(super_mux_def); i++) {
259		rv = super_mux_register(sc->clkdom, &super_mux_def[i]);
260		if (rv != 0)
261			panic("super_mux_register failed");
262	}
263
264}
265