8ee54cadae
frequency >= target. Fix inverted rounding logic for CLK_SET_ROUND_UP/DOWN. Reviewed by: kan Differential Revision: https://reviews.freebsd.org/D8784
324 lines
8.5 KiB
C
324 lines
8.5 KiB
C
/*-
|
|
* 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);
|
|
}
|