aboutsummaryrefslogtreecommitdiff
path: root/sys/arm/nvidia/tegra124/tegra124_clk_super.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arm/nvidia/tegra124/tegra124_clk_super.c')
-rw-r--r--sys/arm/nvidia/tegra124/tegra124_clk_super.c265
1 files changed, 265 insertions, 0 deletions
diff --git a/sys/arm/nvidia/tegra124/tegra124_clk_super.c b/sys/arm/nvidia/tegra124/tegra124_clk_super.c
new file mode 100644
index 000000000000..1ff81797ed6c
--- /dev/null
+++ b/sys/arm/nvidia/tegra124/tegra124_clk_super.c
@@ -0,0 +1,265 @@
+/*-
+ * Copyright (c) 2016 Michal Meloun <mmel@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+
+#include <gnu/dts/include/dt-bindings/clock/tegra124-car.h>
+#include "tegra124_car.h"
+
+
+/* Flags */
+#define SMF_HAVE_DIVIDER_2 1
+
+struct super_mux_def {
+ struct clknode_init_def clkdef;
+ uint32_t base_reg;
+ uint32_t flags;
+ int src_pllx;
+ int src_div2;
+};
+
+#define PLIST(x) static const char *x[]
+#define SM(_id, cn, pl, r, x, d, f) \
+{ \
+ .clkdef.id = _id, \
+ .clkdef.name = cn, \
+ .clkdef.parent_names = pl, \
+ .clkdef.parent_cnt = nitems(pl), \
+ .clkdef.flags = CLK_NODE_STATIC_STRINGS, \
+ .base_reg = r, \
+ .src_pllx = x, \
+ .src_div2 = d, \
+ .flags = f, \
+}
+
+PLIST(cclk_g_parents) = {
+ "clk_m", "pllC_out0", "clk_s", "pllM_out0",
+ "pllP_out0", "pllP_out4", "pllC2_out0", "pllC3_out0",
+ "pllX_out", NULL, NULL, NULL,
+ NULL, NULL, NULL,NULL, // "dfllCPU_out0"
+};
+
+PLIST(cclk_lp_parents) = {
+ "clk_m", "pllC_out0", "clk_s", "pllM_out0",
+ "pllP_out0", "pllP_out4", "pllC2_out0", "pllC3_out0",
+ "pllX_out", NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ "pllX_out0"
+};
+
+PLIST(sclk_parents) = {
+ "clk_m", "pllC_out1", "pllP_out4", "pllP_out0",
+ "pllP_out2", "pllC_out0", "clk_s", "pllM_out1",
+};
+
+static struct super_mux_def super_mux_def[] = {
+ SM(TEGRA124_CLK_CCLK_G, "cclk_g", cclk_g_parents, CCLKG_BURST_POLICY, 0, 0, 0),
+ SM(TEGRA124_CLK_CCLK_LP, "cclk_lp", cclk_lp_parents, CCLKLP_BURST_POLICY, 8, 16, SMF_HAVE_DIVIDER_2),
+ SM(TEGRA124_CLK_SCLK, "sclk", sclk_parents, SCLK_BURST_POLICY, 0, 0, 0),
+};
+
+static int super_mux_init(struct clknode *clk, device_t dev);
+static int super_mux_set_mux(struct clknode *clk, int idx);
+
+struct super_mux_sc {
+ device_t clkdev;
+ uint32_t base_reg;
+ int src_pllx;
+ int src_div2;
+ uint32_t flags;
+
+ int mux;
+};
+
+static clknode_method_t super_mux_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, super_mux_init),
+ CLKNODEMETHOD(clknode_set_mux, super_mux_set_mux),
+ CLKNODEMETHOD_END
+};
+DEFINE_CLASS_1(tegra124_super_mux, tegra124_super_mux_class, super_mux_methods,
+ sizeof(struct super_mux_sc), clknode_class);
+
+/* Mux status. */
+#define SUPER_MUX_STATE_STDBY 0
+#define SUPER_MUX_STATE_IDLE 1
+#define SUPER_MUX_STATE_RUN 2
+#define SUPER_MUX_STATE_IRQ 3
+#define SUPER_MUX_STATE_FIQ 4
+
+/* Mux register bits. */
+#define SUPER_MUX_STATE_BIT_SHIFT 28
+#define SUPER_MUX_STATE_BIT_MASK 0xF
+/* State is Priority encoded */
+#define SUPER_MUX_STATE_BIT_STDBY 0x00
+#define SUPER_MUX_STATE_BIT_IDLE 0x01
+#define SUPER_MUX_STATE_BIT_RUN 0x02
+#define SUPER_MUX_STATE_BIT_IRQ 0x04
+#define SUPER_MUX_STATE_BIT_FIQ 0x08
+
+#define SUPER_MUX_MUX_WIDTH 4
+#define SUPER_MUX_LP_DIV2_BYPASS (1 << 16)
+
+static uint32_t
+super_mux_get_state(uint32_t reg)
+{
+ reg = (reg >> SUPER_MUX_STATE_BIT_SHIFT) & SUPER_MUX_STATE_BIT_MASK;
+ if (reg & SUPER_MUX_STATE_BIT_FIQ)
+ return (SUPER_MUX_STATE_FIQ);
+ if (reg & SUPER_MUX_STATE_BIT_IRQ)
+ return (SUPER_MUX_STATE_IRQ);
+ if (reg & SUPER_MUX_STATE_BIT_RUN)
+ return (SUPER_MUX_STATE_RUN);
+ if (reg & SUPER_MUX_STATE_BIT_IDLE)
+ return (SUPER_MUX_STATE_IDLE);
+ return (SUPER_MUX_STATE_STDBY);
+}
+
+static int
+super_mux_init(struct clknode *clk, device_t dev)
+{
+ struct super_mux_sc *sc;
+ uint32_t reg;
+ int shift, state;
+
+ sc = clknode_get_softc(clk);
+
+ DEVICE_LOCK(sc);
+ RD4(sc, sc->base_reg, &reg);
+ DEVICE_UNLOCK(sc);
+ state = super_mux_get_state(reg);
+
+ if ((state != SUPER_MUX_STATE_RUN) &&
+ (state != SUPER_MUX_STATE_IDLE)) {
+ panic("Unexpected super mux state: %u", state);
+ }
+
+ shift = state * SUPER_MUX_MUX_WIDTH;
+
+ sc->mux = (reg >> shift) & ((1 << SUPER_MUX_MUX_WIDTH) - 1);
+
+ /*
+ * CCLKLP uses PLLX/2 as source if LP_DIV2_BYPASS isn't set
+ * and source mux is set to PLLX.
+ */
+ if (sc->flags & SMF_HAVE_DIVIDER_2) {
+ if (((reg & SUPER_MUX_LP_DIV2_BYPASS) == 0) &&
+ (sc->mux == sc->src_pllx))
+ sc->mux = sc->src_div2;
+ }
+ clknode_init_parent_idx(clk, sc->mux);
+
+ return(0);
+}
+
+static int
+super_mux_set_mux(struct clknode *clk, int idx)
+{
+
+ struct super_mux_sc *sc;
+ int shift, state;
+ uint32_t reg, dummy;
+
+ sc = clknode_get_softc(clk);
+
+ DEVICE_LOCK(sc);
+ RD4(sc, sc->base_reg, &reg);
+ state = super_mux_get_state(reg);
+
+ if ((state != SUPER_MUX_STATE_RUN) &&
+ (state != SUPER_MUX_STATE_IDLE)) {
+ panic("Unexpected super mux state: %u", state);
+ }
+ shift = (state - 1) * SUPER_MUX_MUX_WIDTH;
+ sc->mux = idx;
+ if (sc->flags & SMF_HAVE_DIVIDER_2) {
+ if (idx == sc->src_div2) {
+ idx = sc->src_pllx;
+ reg &= ~SUPER_MUX_LP_DIV2_BYPASS;
+ WR4(sc, sc->base_reg, reg);
+ RD4(sc, sc->base_reg, &dummy);
+ } else if (idx == sc->src_pllx) {
+ reg = SUPER_MUX_LP_DIV2_BYPASS;
+ WR4(sc, sc->base_reg, reg);
+ RD4(sc, sc->base_reg, &dummy);
+ }
+ }
+ reg &= ~(((1 << SUPER_MUX_MUX_WIDTH) - 1) << shift);
+ reg |= idx << shift;
+
+ WR4(sc, sc->base_reg, reg);
+ RD4(sc, sc->base_reg, &dummy);
+ DEVICE_UNLOCK(sc);
+
+ return(0);
+}
+
+static int
+super_mux_register(struct clkdom *clkdom, struct super_mux_def *clkdef)
+{
+ struct clknode *clk;
+ struct super_mux_sc *sc;
+
+ clk = clknode_create(clkdom, &tegra124_super_mux_class,
+ &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clkdev = clknode_get_device(clk);
+ sc->base_reg = clkdef->base_reg;
+ sc->src_pllx = clkdef->src_pllx;
+ sc->src_div2 = clkdef->src_div2;
+ sc->flags = clkdef->flags;
+
+ clknode_register(clkdom, clk);
+ return (0);
+}
+
+void
+tegra124_super_mux_clock(struct tegra124_car_softc *sc)
+{
+ int i, rv;
+
+ for (i = 0; i < nitems(super_mux_def); i++) {
+ rv = super_mux_register(sc->clkdom, &super_mux_def[i]);
+ if (rv != 0)
+ panic("super_mux_register failed");
+ }
+
+}