Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*-
 * Copyright 2015 Alexander Kabaev <kan@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.
 */

/*
 * Ingenic JZ4780 generic CGU clock driver.
 *
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/resource.h>

#include <machine/bus.h>

#include <mips/ingenic/jz4780_clk.h>
#include <mips/ingenic/jz4780_regs.h>

/* JZ4780 generic mux and div clocks implementation */
static int jz4780_clk_gen_init(struct clknode *clk, device_t dev);
static int jz4780_clk_gen_recalc_freq(struct clknode *clk, uint64_t *freq);
static int jz4780_clk_gen_set_freq(struct clknode *clk, uint64_t fin,
    uint64_t *fout, int flags, int *stop);
static int jz4780_clk_gen_set_gate(struct clknode *clk, bool enable);
static int jz4780_clk_gen_set_mux(struct clknode *clk, int src);

struct jz4780_clk_gen_sc {
	struct mtx	*clk_mtx;
	struct resource *clk_res;
	int clk_reg;
	const struct jz4780_clk_descr *clk_descr;
};

/*
 * JZ4780 clock PLL clock methods
 */
static clknode_method_t jz4780_clk_gen_methods[] = {
	CLKNODEMETHOD(clknode_init,		jz4780_clk_gen_init),
	CLKNODEMETHOD(clknode_set_gate,		jz4780_clk_gen_set_gate),
	CLKNODEMETHOD(clknode_recalc_freq,	jz4780_clk_gen_recalc_freq),
	CLKNODEMETHOD(clknode_set_freq,		jz4780_clk_gen_set_freq),
	CLKNODEMETHOD(clknode_set_mux,		jz4780_clk_gen_set_mux),

	CLKNODEMETHOD_END
};
DEFINE_CLASS_1(jz4780_clk_pll, jz4780_clk_gen_class, jz4780_clk_gen_methods,
       sizeof(struct jz4780_clk_gen_sc), clknode_class);

static inline unsigned
mux_to_reg(unsigned src, unsigned map)
{
	unsigned ret, bit;

	bit = (1u << 3);
	for (ret = 0; bit; ret++, bit >>= 1) {
		if (map & bit) {
			if (src-- == 0)
				return (ret);
		}
	}
	panic("mux_to_reg");
}

static inline unsigned
reg_to_mux(unsigned reg, unsigned map)
{
	unsigned ret, bit;

	bit = (1u << 3);
	for (ret = 0; reg; reg--, bit >>= 1)
		if (map & bit)
			ret++;
	return (ret);
}

static int
jz4780_clk_gen_init(struct clknode *clk, device_t dev)
{
	struct jz4780_clk_gen_sc *sc;
	uint32_t reg, msk, parent_idx;

	sc = clknode_get_softc(clk);
	CLK_LOCK(sc);
	/* Figure our parent out */
	if (sc->clk_descr->clk_type & CLK_MASK_MUX) {
		msk = (1u << sc->clk_descr->clk_mux.mux_bits) - 1;
		reg = CLK_RD_4(sc, sc->clk_descr->clk_mux.mux_reg);
		reg = (reg >> sc->clk_descr->clk_mux.mux_shift) & msk;
		parent_idx = reg_to_mux(reg, sc->clk_descr->clk_mux.mux_map);
	} else
		parent_idx = 0;
	CLK_UNLOCK(sc);

	clknode_init_parent_idx(clk, parent_idx);
	return (0);
}

static int
jz4780_clk_gen_recalc_freq(struct clknode *clk, uint64_t *freq)
{
	struct jz4780_clk_gen_sc *sc;
	uint32_t reg;

	sc = clknode_get_softc(clk);

	/* Calculate divisor frequency */
	if (sc->clk_descr->clk_type & CLK_MASK_DIV) {
		uint32_t msk;

		msk = (1u << sc->clk_descr->clk_div.div_bits) - 1;
		reg = CLK_RD_4(sc, sc->clk_descr->clk_div.div_reg);
		reg = (reg >> sc->clk_descr->clk_div.div_shift) & msk;
		reg = (reg + 1) << sc->clk_descr->clk_div.div_lg;
		*freq /= reg;
	}
	return (0);
}

#define DIV_TIMEOUT	100

static int
jz4780_clk_gen_set_freq(struct clknode *clk, uint64_t fin,
    uint64_t *fout, int flags, int *stop)
{
	struct jz4780_clk_gen_sc *sc;
	uint64_t _fout;
	uint32_t divider, div_reg, div_msk, reg, div_l, div_h;
	int rv;

	sc = clknode_get_softc(clk);

	/* Find closest divider */
	div_l = howmany(fin, *fout);
	div_h = fin / *fout;
	divider = abs((int64_t)*fout - (fin / div_l)) <
	    abs((int64_t)*fout - (fin / div_h)) ? div_l : div_h;

	/* Adjust for divider multiplier */
	div_reg = divider >> sc->clk_descr->clk_div.div_lg;
	divider = div_reg << sc->clk_descr->clk_div.div_lg;
	if (divider == 0)
		divider = 1;

	_fout = fin / divider;

	/* Rounding */
	if ((flags & CLK_SET_ROUND_UP) && (*fout > _fout))
		div_reg--;
	else if ((flags & CLK_SET_ROUND_DOWN) && (*fout < _fout))
		div_reg++;
	if (div_reg == 0)
		div_reg = 1;

	div_msk = (1u << sc->clk_descr->clk_div.div_bits) - 1;

	*stop = 1;
	if (div_reg > div_msk + 1) {
		*stop = 0;
		div_reg = div_msk;
	}

	divider = (div_reg << sc->clk_descr->clk_div.div_lg);
	div_reg--;

	if ((flags & CLK_SET_DRYRUN) != 0) {
		if (*stop != 0 && *fout != fin / divider &&
		    (flags & (CLK_SET_ROUND_UP | CLK_SET_ROUND_DOWN)) == 0)
			return (ERANGE);
		*fout = fin / divider;
		return (0);
	}

	CLK_LOCK(sc);
	/* Apply the new divider value */
	reg = CLK_RD_4(sc, sc->clk_descr->clk_div.div_reg);
	reg &= ~(div_msk << sc->clk_descr->clk_div.div_shift);
	reg |= (div_reg << sc->clk_descr->clk_div.div_shift);
	/* Set the change enable bit, it present */
	if (sc->clk_descr->clk_div.div_ce_bit >= 0)
		reg |= (1u << sc->clk_descr->clk_div.div_ce_bit);
	/* Clear stop bit, it present */
	if (sc->clk_descr->clk_div.div_st_bit >= 0)
		reg &= ~(1u << sc->clk_descr->clk_div.div_st_bit);
	/* Initiate the change */
	CLK_WR_4(sc, sc->clk_descr->clk_div.div_reg, reg);

	/* Wait for busy bit to clear indicating the change is complete */
	rv = 0;
	if (sc->clk_descr->clk_div.div_busy_bit >= 0) {
		int i;

		for (i = 0;  i < DIV_TIMEOUT; i++) {
			reg = CLK_RD_4(sc, sc->clk_descr->clk_div.div_reg);
			if (!(reg & (1u << sc->clk_descr->clk_div.div_busy_bit)))
				break;
			DELAY(1000);
		}
		if (i == DIV_TIMEOUT)
			rv = ETIMEDOUT;
	}
	CLK_UNLOCK(sc);

	*fout = fin / divider;
	return (rv);
}

static int
jz4780_clk_gen_set_mux(struct clknode *clk, int src)
{
	struct jz4780_clk_gen_sc *sc;
	uint32_t reg, msk;

	sc = clknode_get_softc(clk);

	/* Only mux nodes are capable of being reparented */
	if (!(sc->clk_descr->clk_type & CLK_MASK_MUX))
		return (src ? EINVAL : 0);

	msk = (1u << sc->clk_descr->clk_mux.mux_bits) - 1;
	src = mux_to_reg(src & msk, sc->clk_descr->clk_mux.mux_map);

	CLK_LOCK(sc);
	reg = CLK_RD_4(sc, sc->clk_descr->clk_mux.mux_reg);
	reg &= ~(msk << sc->clk_descr->clk_mux.mux_shift);
	reg |=  (src << sc->clk_descr->clk_mux.mux_shift);
	CLK_WR_4(sc, sc->clk_descr->clk_mux.mux_reg, reg);
	CLK_UNLOCK(sc);

	return (0);
}

static int
jz4780_clk_gen_set_gate(struct clknode *clk, bool enable)
{
	struct jz4780_clk_gen_sc *sc;
	uint32_t off, reg, bit;

	sc = clknode_get_softc(clk);

	/* Check is clock can be gated */
	if (sc->clk_descr->clk_gate_bit < 0)
		return 0;

	bit = sc->clk_descr->clk_gate_bit;
	if (bit < 32) {
		off = JZ_CLKGR0;
	} else {
		off = JZ_CLKGR1;
		bit -= 32;
	}

	CLK_LOCK(sc);
	reg = CLK_RD_4(sc, off);
	if (enable)
		reg &= ~(1u << bit);
	else
		reg |= (1u << bit);
	CLK_WR_4(sc, off, reg);
	CLK_UNLOCK(sc);

	return (0);
}

int jz4780_clk_gen_register(struct clkdom *clkdom,
    const struct jz4780_clk_descr *descr, struct mtx *dev_mtx,
    struct resource *mem_res)
{
	struct clknode_init_def clkdef;
	struct clknode *clk;
	struct jz4780_clk_gen_sc *sc;

	clkdef.id = descr->clk_id;
	clkdef.name = __DECONST(char *, descr->clk_name);
	/* Silly const games to work around API deficiency */
	clkdef.parent_names = (const char **)(uintptr_t)&descr->clk_pnames[0];
	clkdef.flags = CLK_NODE_STATIC_STRINGS;
	if (descr->clk_type & CLK_MASK_MUX)
		clkdef.parent_cnt = __bitcount16(descr->clk_mux.mux_map);
	else
		clkdef.parent_cnt = 1;

	clk = clknode_create(clkdom, &jz4780_clk_gen_class, &clkdef);
	if (clk == NULL)
		return (1);

	sc = clknode_get_softc(clk);
	sc->clk_mtx = dev_mtx;
	sc->clk_res = mem_res;
	sc->clk_descr = descr;
	clknode_register(clkdom, clk);

	return (0);
}