/* $NetBSD: jh71x0_clkc.c,v 1.4 2024/09/18 08:31:50 skrll Exp $ */
/*-
* Copyright (c) 2023 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Nick Hudson
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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>
__KERNEL_RCSID(0, "$NetBSD: jh71x0_clkc.c,v 1.4 2024/09/18 08:31:50 skrll Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <dev/clk/clk_backend.h>
#include <dev/fdt/fdtvar.h>
#include <riscv/starfive/jh71x0_clkc.h>
#define RD4(sc, reg) \
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
#define WR4(sc, reg, val) \
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
static void
jh71x0_clkc_update(struct jh71x0_clkc_softc * const sc,
struct jh71x0_clkc_clk *jcc, uint32_t set, uint32_t clr)
{
// lock
uint32_t val = RD4(sc, jcc->jcc_reg);
val &= ~clr;
val |= set;
WR4(sc, jcc->jcc_reg, val);
}
/*
* FIXED_FACTOR operations
*/
static u_int
jh71x0_clkc_fixed_factor_get_parent_rate(struct clk *clk)
{
struct clk *clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return 0;
return clk_get_rate(clk_parent);
}
u_int
jh71x0_clkc_fixed_factor_get_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_FIXED_FACTOR);
struct jh71x0_clkc_fixed_factor * const jcff = &jcc->jcc_ffactor;
struct clk *clk = &jcc->jcc_clk;
uint64_t rate = jh71x0_clkc_fixed_factor_get_parent_rate(clk);
if (rate == 0)
return 0;
rate *= jcff->jcff_mult;
rate /= jcff->jcff_div;
return rate;
}
static int
jh71x0_clkc_fixed_factor_set_parent_rate(struct clk *clk, u_int rate)
{
struct clk *clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return ENXIO;
return clk_set_rate(clk_parent, rate);
}
int
jh71x0_clkc_fixed_factor_set_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, u_int rate)
{
KASSERT(jcc->jcc_type == JH71X0CLK_FIXED_FACTOR);
struct jh71x0_clkc_fixed_factor * const jcff = &jcc->jcc_ffactor;
struct clk *clk = &jcc->jcc_clk;
uint64_t tmp = rate;
tmp *= jcff->jcff_div;
tmp /= jcff->jcff_mult;
return jh71x0_clkc_fixed_factor_set_parent_rate(clk, tmp);
}
const char *
jh71x0_clkc_fixed_factor_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_FIXED_FACTOR);
struct jh71x0_clkc_fixed_factor * const jcff = &jcc->jcc_ffactor;
return jcff->jcff_parent;
}
/*
* MUX operations
*/
int
jh71x0_clkc_mux_set_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, const char *name)
{
KASSERT(jcc->jcc_type == JH71X0CLK_MUX);
struct jh71x0_clkc_mux * const jcm = &jcc->jcc_mux;
size_t i;
for (i = 0; i < jcm->jcm_nparents; i++) {
if (jcm->jcm_parents[i] != NULL &&
strcmp(jcm->jcm_parents[i], name) == 0)
break;
}
if (i >= jcm->jcm_nparents)
return EINVAL;
KASSERT(i <= __SHIFTOUT_MASK(JH71X0_CLK_MUX_MASK));
uint32_t val = RD4(sc, jcc->jcc_reg);
val &= ~JH71X0_CLK_MUX_MASK;
val |= __SHIFTIN(i, JH71X0_CLK_MUX_MASK);
WR4(sc, jcc->jcc_reg, val);
return 0;
}
const char *
jh71x0_clkc_mux_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_MUX);
uint32_t val = RD4(sc, jcc->jcc_reg);
size_t pindex = __SHIFTOUT(val, JH71X0_CLK_MUX_MASK);
if (pindex >= jcc->jcc_mux.jcm_nparents)
return NULL;
return jcc->jcc_mux.jcm_parents[pindex];
}
/*
* GATE operations
*/
int
jh71x0_clkc_gate_enable(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, int enable)
{
KASSERT(jcc->jcc_type == JH71X0CLK_GATE);
jh71x0_clkc_update(sc, jcc,
(enable ? JH71X0_CLK_ENABLE : 0), JH71X0_CLK_ENABLE);
return 0;
}
const char *
jh71x0_clkc_gate_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_GATE);
struct jh71x0_clkc_gate *jcc_gate = &jcc->jcc_gate;
return jcc_gate->jcg_parent;
}
/*
* DIVIDER operations
*/
u_int
jh71x0_clkc_div_get_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_DIV);
struct clk * const clk = &jcc->jcc_clk;
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return 0;
u_int rate = clk_get_rate(clk_parent);
if (rate == 0)
return 0;
uint32_t val = RD4(sc, jcc->jcc_reg);
uint32_t div = __SHIFTOUT(val, JH71X0_CLK_DIV_MASK);
return rate / div;
}
int
jh71x0_clkc_div_set_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, u_int new_rate)
{
KASSERT(jcc->jcc_type == JH71X0CLK_DIV);
struct jh71x0_clkc_div * const jcc_div = &jcc->jcc_div;
struct clk * const clk = &jcc->jcc_clk;
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return ENXIO;
if (jcc_div->jcd_maxdiv == 0)
return ENXIO;
u_int parent_rate = clk_get_rate(clk_parent);
if (parent_rate == 0) {
return (new_rate == 0) ? 0 : ERANGE;
}
u_int ratio = howmany(parent_rate, new_rate);
u_int div = uimin(ratio, jcc_div->jcd_maxdiv);
KASSERT(div <= __SHIFTOUT_MASK(JH71X0_CLK_DIV_MASK));
jh71x0_clkc_update(sc, jcc,
__SHIFTIN(div, JH71X0_CLK_DIV_MASK), JH71X0_CLK_DIV_MASK);
return 0;
}
const char *
jh71x0_clkc_div_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_DIV);
struct jh71x0_clkc_div *jcc_div = &jcc->jcc_div;
return jcc_div->jcd_parent;
}
/*
* FRACTIONAL DIVIDER operations
*/
u_int
jh71x0_clkc_fracdiv_get_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_FRACDIV);
struct clk * const clk = &jcc->jcc_clk;
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return 0;
u_int rate = clk_get_rate(clk_parent);
if (rate == 0)
return 0;
uint32_t val = RD4(sc, jcc->jcc_reg);
unsigned long div100 =
100UL * __SHIFTOUT(val, JH71X0_CLK_INT_MASK) +
__SHIFTOUT(val, JH71X0_CLK_FRAC_MASK);
return (div100 >= JH71X0_CLK_FRAC_MIN) ? 100UL * rate / div100 : 0;
}
int
jh71x0_clkc_fracdiv_set_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, u_int new_rate)
{
KASSERT(jcc->jcc_type == JH71X0CLK_FRACDIV);
struct clk * const clk = &jcc->jcc_clk;
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return ENXIO;
#if 0
if (jcc_div->jcd_maxdiv == 0)
return ENXIO;
u_int parent_rate = clk_get_rate(clk_parent);
if (parent_rate == 0) {
return (new_rate == 0) ? 0 : ERANGE;
}
u_int ratio = howmany(parent_rate, new_rate);
u_int div = uimin(ratio, jcc_div->jcd_maxdiv);
KASSERT(div <= __SHIFTOUT_MASK(JH71X0_CLK_DIV_MASK));
jh71x0_clkc_update(sc, jcc,
__SHIFTIN(div, JH71X0_CLK_DIV_MASK), JH71X0_CLK_DIV_MASK);
#endif
return 0;
}
const char *
jh71x0_clkc_fracdiv_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_FRACDIV);
struct jh71x0_clkc_fracdiv *jcc_fracdiv = &jcc->jcc_fracdiv;
return jcc_fracdiv->jcd_parent;
}
/*
* MUXDIV operations
*/
int
jh71x0_clkc_muxdiv_set_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, const char *name)
{
KASSERT(jcc->jcc_type == JH71X0CLK_MUXDIV);
struct jh71x0_clkc_muxdiv * const jcmd = &jcc->jcc_muxdiv;
size_t i;
for (i = 0; i < jcmd->jcmd_nparents; i++) {
if (jcmd->jcmd_parents[i] != NULL &&
strcmp(jcmd->jcmd_parents[i], name) == 0)
break;
}
if (i >= jcmd->jcmd_nparents)
return EINVAL;
KASSERT(i <= __SHIFTOUT_MASK(JH71X0_CLK_MUX_MASK));
uint32_t val = RD4(sc, jcc->jcc_reg);
val &= ~JH71X0_CLK_MUX_MASK;
val |= __SHIFTIN(i, JH71X0_CLK_MUX_MASK);
WR4(sc, jcc->jcc_reg, val);
return 0;
}
const char *
jh71x0_clkc_muxdiv_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_MUXDIV);
uint32_t val = RD4(sc, jcc->jcc_reg);
size_t pindex = __SHIFTOUT(val, JH71X0_CLK_MUX_MASK);
if (pindex >= jcc->jcc_muxdiv.jcmd_nparents)
return NULL;
return jcc->jcc_muxdiv.jcmd_parents[pindex];
}
u_int
jh71x0_clkc_muxdiv_get_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_MUXDIV);
struct clk * const clk = &jcc->jcc_clk;
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return 0;
u_int rate = clk_get_rate(clk_parent);
if (rate == 0)
return 0;
uint32_t val = RD4(sc, jcc->jcc_reg);
uint32_t div = __SHIFTOUT(val, JH71X0_CLK_DIV_MASK);
return rate / div;
}
int
jh71x0_clkc_muxdiv_set_rate(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc, u_int new_rate)
{
KASSERT(jcc->jcc_type == JH71X0CLK_MUXDIV);
struct jh71x0_clkc_muxdiv * const jcc_muxdiv = &jcc->jcc_muxdiv;
struct clk * const clk = &jcc->jcc_clk;
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL)
return ENXIO;
if (jcc_muxdiv->jcmd_maxdiv == 0)
return ENXIO;
u_int parent_rate = clk_get_rate(clk_parent);
if (parent_rate == 0) {
return (new_rate == 0) ? 0 : ERANGE;
}
u_int ratio = howmany(parent_rate, new_rate);
u_int div = uimin(ratio, jcc_muxdiv->jcmd_maxdiv);
KASSERT(div <= __SHIFTOUT_MASK(JH71X0_CLK_DIV_MASK));
jh71x0_clkc_update(sc, jcc,
__SHIFTIN(div, JH71X0_CLK_DIV_MASK), JH71X0_CLK_DIV_MASK);
return 0;
}
/*
* INV operations
*/
const char *
jh71x0_clkc_inv_get_parent(struct jh71x0_clkc_softc *sc,
struct jh71x0_clkc_clk *jcc)
{
KASSERT(jcc->jcc_type == JH71X0CLK_INV);
struct jh71x0_clkc_inv * const jci = &jcc->jcc_inv;
return jci->jci_parent;
}
struct jh71x0_clkc_clkops jh71x0_clkc_gate_ops = {
.jcco_enable = jh71x0_clkc_gate_enable,
.jcco_getparent = jh71x0_clkc_gate_get_parent,
};
struct jh71x0_clkc_clkops jh71x0_clkc_div_ops = {
.jcco_setrate = jh71x0_clkc_div_set_rate,
.jcco_getrate = jh71x0_clkc_div_get_rate,
.jcco_getparent = jh71x0_clkc_div_get_parent,
};
struct jh71x0_clkc_clkops jh71x0_clkc_fracdiv_ops = {
.jcco_setrate = jh71x0_clkc_fracdiv_set_rate,
.jcco_getrate = jh71x0_clkc_fracdiv_get_rate,
.jcco_getparent = jh71x0_clkc_fracdiv_get_parent,
};
struct jh71x0_clkc_clkops jh71x0_clkc_ffactor_ops = {
.jcco_setrate = jh71x0_clkc_fixed_factor_set_rate,
.jcco_getrate = jh71x0_clkc_fixed_factor_get_rate,
.jcco_getparent = jh71x0_clkc_fixed_factor_get_parent,
};
struct jh71x0_clkc_clkops jh71x0_clkc_mux_ops = {
.jcco_setparent = jh71x0_clkc_mux_set_parent,
.jcco_getparent = jh71x0_clkc_mux_get_parent,
};
struct jh71x0_clkc_clkops jh71x0_clkc_muxdiv_ops = {
.jcco_setrate = jh71x0_clkc_muxdiv_set_rate,
.jcco_getrate = jh71x0_clkc_muxdiv_get_rate,
.jcco_setparent = jh71x0_clkc_muxdiv_set_parent,
.jcco_getparent = jh71x0_clkc_muxdiv_get_parent,
};
struct jh71x0_clkc_clkops jh71x0_clkc_inv_ops = {
.jcco_getparent = jh71x0_clkc_inv_get_parent,
};
static struct clk *
jh71x0_clkc_get(void *priv, const char *name)
{
struct jh71x0_clkc_softc * const sc = priv;
for (u_int id = 0; id < sc->sc_nclks; id++) {
struct jh71x0_clkc_clk * const jcc = &sc->sc_clk[id];
if (strcmp(name, jcc->jcc_clk.name) == 0) {
return &jcc->jcc_clk;
}
}
return NULL;
}
static void
jh71x0_clkc_put(void *priv, struct clk *clk)
{
}
static int
jh71x0_clkc_set_rate(void *priv, struct clk *clk, u_int rate)
{
struct jh71x0_clkc_softc * const sc = priv;
struct jh71x0_clkc_clk * const jcc =
container_of(clk, struct jh71x0_clkc_clk, jcc_clk);
if (clk->flags & CLK_SET_RATE_PARENT) {
struct clk *clk_parent = clk_get_parent(clk);
if (clk_parent == NULL) {
aprint_debug("%s: no parent for %s\n", __func__,
jcc->jcc_clk.name);
return ENXIO;
}
return clk_set_rate(clk_parent, rate);
}
if (jcc->jcc_ops->jcco_setrate)
return jcc->jcc_ops->jcco_setrate(sc, jcc, rate);
return ENXIO;
}
static u_int
jh71x0_clkc_get_rate(void *priv, struct clk *clk)
{
struct jh71x0_clkc_softc * const sc = priv;
struct jh71x0_clkc_clk * const jcc =
container_of(clk, struct jh71x0_clkc_clk, jcc_clk);
if (jcc->jcc_ops->jcco_getrate)
return jcc->jcc_ops->jcco_getrate(sc, jcc);
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent == NULL) {
aprint_debug("%s: no parent for %s\n", __func__,
jcc->jcc_clk.name);
return 0;
}
return clk_get_rate(clk_parent);
}
static int
jh71x0_clkc_enable(void *priv, struct clk *clk)
{
struct jh71x0_clkc_softc * const sc = priv;
struct jh71x0_clkc_clk * const jcc =
container_of(clk, struct jh71x0_clkc_clk, jcc_clk);
struct clk * const clk_parent = clk_get_parent(clk);
if (clk_parent != NULL) {
int error = clk_enable(clk_parent);
if (error != 0)
return error;
}
switch (jcc->jcc_type) {
case JH71X0CLK_GATE:
jh71x0_clkc_update(sc, jcc, JH71X0_CLK_ENABLE, 0);
break;
case JH71X0CLK_DIV: {
struct jh71x0_clkc_div * const jcc_div = &jcc->jcc_div;
if (jcc_div->jcd_flags & JH71X0CLKC_DIV_GATE) {
jh71x0_clkc_update(sc, jcc, JH71X0_CLK_ENABLE, 0);
}
break;
}
case JH71X0CLK_MUX: {
struct jh71x0_clkc_mux * const jcc_mux = &jcc->jcc_mux;
if (jcc_mux->jcm_flags & JH71X0CLKC_MUX_GATE) {
jh71x0_clkc_update(sc, jcc, JH71X0_CLK_ENABLE, 0);
}
break;
}
case JH71X0CLK_FIXED_FACTOR:
case JH71X0CLK_INV:
case JH71X0CLK_MUXDIV:
break;
default:
printf("%s: type %d\n", __func__, jcc->jcc_type);
return ENXIO;
}
return 0;
}
static int
jh71x0_clkc_disable(void *priv, struct clk *clk)
{
struct jh71x0_clkc_softc * const sc = priv;
struct jh71x0_clkc_clk * const jcc =
container_of(clk, struct jh71x0_clkc_clk, jcc_clk);
switch (jcc->jcc_type) {
case JH71X0CLK_GATE:
jh71x0_clkc_update(sc, jcc, 0, JH71X0_CLK_ENABLE);
break;
case JH71X0CLK_DIV: {
struct jh71x0_clkc_div * const jcc_div = &jcc->jcc_div;
if (jcc_div->jcd_flags & JH71X0CLKC_DIV_GATE) {
jh71x0_clkc_update(sc, jcc, 0, JH71X0_CLK_ENABLE);
}
break;
}
case JH71X0CLK_MUX: {
struct jh71x0_clkc_mux * const jcc_mux = &jcc->jcc_mux;
if (jcc_mux->jcm_flags & JH71X0CLKC_MUX_GATE) {
jh71x0_clkc_update(sc, jcc, 0, JH71X0_CLK_ENABLE);
}
break;
}
case JH71X0CLK_FIXED_FACTOR:
case JH71X0CLK_INV:
case JH71X0CLK_MUXDIV:
break;
default:
return ENXIO;
}
return 0;
}
static struct jh71x0_clkc_clk *
jh71x0_clkc_clock_find(struct jh71x0_clkc_softc *sc, const char *name)
{
for (size_t id = 0; id < sc->sc_nclks; id++) {
struct jh71x0_clkc_clk * const jcc = &sc->sc_clk[id];
if (jcc->jcc_clk.name == NULL)
continue;
if (strcmp(jcc->jcc_clk.name, name) == 0)
return jcc;
}
return NULL;
}
static int
jh71x0_clkc_set_parent(void *priv, struct clk *clk,
struct clk *clk_parent)
{
struct jh71x0_clkc_softc * const sc = priv;
struct jh71x0_clkc_clk * const jcc =
container_of(clk, struct jh71x0_clkc_clk, jcc_clk);
if (jcc->jcc_ops->jcco_setparent == NULL)
return EINVAL;
return jcc->jcc_ops->jcco_setparent(sc, jcc, clk_parent->name);
}
static struct clk *
jh71x0_clkc_get_parent(void *priv, struct clk *clk)
{
struct jh71x0_clkc_softc * const sc = priv;
struct jh71x0_clkc_clk * const jcc =
container_of(clk, struct jh71x0_clkc_clk, jcc_clk);
if (jcc->jcc_ops->jcco_getparent == NULL)
return NULL;
const char *parent = jcc->jcc_ops->jcco_getparent(sc, jcc);
if (parent == NULL)
return NULL;
struct jh71x0_clkc_clk *jcc_parent = jh71x0_clkc_clock_find(sc, parent);
if (jcc_parent != NULL)
return &jcc_parent->jcc_clk;
/* No parent in this domain, try FDT */
return fdtbus_clock_get(sc->sc_phandle, parent);
}
const struct clk_funcs jh71x0_clkc_funcs = {
.get = jh71x0_clkc_get,
.put = jh71x0_clkc_put,
.set_rate = jh71x0_clkc_set_rate,
.get_rate = jh71x0_clkc_get_rate,
.enable = jh71x0_clkc_enable,
.disable = jh71x0_clkc_disable,
.set_parent = jh71x0_clkc_set_parent,
.get_parent = jh71x0_clkc_get_parent,
};