Path: blob/main/sys/dev/clk/rockchip/rk_clk_fract.c
39537 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright 2019 Michal Meloun <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <sys/param.h>28#include <sys/systm.h>29#include <sys/bus.h>3031#include <dev/clk/clk.h>3233#include <dev/clk/rockchip/rk_clk_fract.h>3435#include "clkdev_if.h"3637#define WR4(_clk, off, val) \38CLKDEV_WRITE_4(clknode_get_device(_clk), off, val)39#define RD4(_clk, off, val) \40CLKDEV_READ_4(clknode_get_device(_clk), off, val)41#define MD4(_clk, off, clr, set ) \42CLKDEV_MODIFY_4(clknode_get_device(_clk), off, clr, set)43#define DEVICE_LOCK(_clk) \44CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))45#define DEVICE_UNLOCK(_clk) \46CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))4748#define RK_CLK_FRACT_MASK_SHIFT 164950static int rk_clk_fract_init(struct clknode *clk, device_t dev);51static int rk_clk_fract_recalc(struct clknode *clk, uint64_t *req);52static int rk_clk_fract_set_freq(struct clknode *clknode, uint64_t fin,53uint64_t *fout, int flag, int *stop);54static int rk_clk_fract_set_gate(struct clknode *clk, bool enable);5556struct rk_clk_fract_sc {57uint32_t flags;58uint32_t offset;59uint32_t numerator;60uint32_t denominator;61uint32_t gate_offset;62uint32_t gate_shift;63};6465static clknode_method_t rk_clk_fract_methods[] = {66/* Device interface */67CLKNODEMETHOD(clknode_init, rk_clk_fract_init),68CLKNODEMETHOD(clknode_set_gate, rk_clk_fract_set_gate),69CLKNODEMETHOD(clknode_recalc_freq, rk_clk_fract_recalc),70CLKNODEMETHOD(clknode_set_freq, rk_clk_fract_set_freq),71CLKNODEMETHOD_END72};73DEFINE_CLASS_1(rk_clk_fract, rk_clk_fract_class, rk_clk_fract_methods,74sizeof(struct rk_clk_fract_sc), clknode_class);7576/*77* Compute best rational approximation of input fraction78* for fixed sized fractional divider registers.79* http://en.wikipedia.org/wiki/Continued_fraction80*81* - n_input, d_input Given input fraction82* - n_max, d_max Maximum vaues of divider registers83* - n_out, d_out Computed approximation84*/8586static void87clk_compute_fract_div(88uint64_t n_input, uint64_t d_input,89uint64_t n_max, uint64_t d_max,90uint64_t *n_out, uint64_t *d_out)91{92uint64_t n_prev, d_prev; /* previous convergents */93uint64_t n_cur, d_cur; /* current convergents */94uint64_t n_rem, d_rem; /* fraction remainder */95uint64_t tmp, fact;9697/* Initialize fraction reminder */98n_rem = n_input;99d_rem = d_input;100101/* Init convergents to 0/1 and 1/0 */102n_prev = 0;103d_prev = 1;104n_cur = 1;105d_cur = 0;106107while (d_rem != 0 && n_cur < n_max && d_cur < d_max) {108/* Factor for this step. */109fact = n_rem / d_rem;110111/* Adjust fraction reminder */112tmp = d_rem;113d_rem = n_rem % d_rem;114n_rem = tmp;115116/* Compute new nominator and save last one */117tmp = n_prev + fact * n_cur;118n_prev = n_cur;119n_cur = tmp;120121/* Compute new denominator and save last one */122tmp = d_prev + fact * d_cur;123d_prev = d_cur;124d_cur = tmp;125}126127if (n_cur > n_max || d_cur > d_max) {128*n_out = n_prev;129*d_out = d_prev;130} else {131*n_out = n_cur;132*d_out = d_cur;133}134}135136static int137rk_clk_fract_init(struct clknode *clk, device_t dev)138{139uint32_t reg;140struct rk_clk_fract_sc *sc;141142sc = clknode_get_softc(clk);143DEVICE_LOCK(clk);144RD4(clk, sc->offset, ®);145DEVICE_UNLOCK(clk);146147sc->numerator = (reg >> 16) & 0xFFFF;148sc->denominator = reg & 0xFFFF;149if (sc->denominator == 0)150sc->denominator = 1;151clknode_init_parent_idx(clk, 0);152153return(0);154}155156static int157rk_clk_fract_set_gate(struct clknode *clk, bool enable)158{159struct rk_clk_fract_sc *sc;160uint32_t val = 0;161162sc = clknode_get_softc(clk);163164if ((sc->flags & RK_CLK_FRACT_HAVE_GATE) == 0)165return (0);166167RD4(clk, sc->gate_offset, &val);168169val = 0;170if (!enable)171val |= 1 << sc->gate_shift;172val |= (1 << sc->gate_shift) << RK_CLK_FRACT_MASK_SHIFT;173DEVICE_LOCK(clk);174WR4(clk, sc->gate_offset, val);175DEVICE_UNLOCK(clk);176177return (0);178}179180static int181rk_clk_fract_recalc(struct clknode *clk, uint64_t *freq)182{183struct rk_clk_fract_sc *sc;184185sc = clknode_get_softc(clk);186if (sc->denominator == 0) {187printf("%s: %s denominator is zero!\n", clknode_get_name(clk),188__func__);189*freq = 0;190return(EINVAL);191}192193*freq *= sc->numerator;194*freq /= sc->denominator;195196return (0);197}198199static int200rk_clk_fract_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,201int flags, int *stop)202{203struct rk_clk_fract_sc *sc;204uint64_t div_n, div_d, _fout;205206sc = clknode_get_softc(clk);207208clk_compute_fract_div(*fout, fin, 0xFFFF, 0xFFFF, &div_n, &div_d);209_fout = fin * div_n;210_fout /= div_d;211212/* Rounding. */213if ((flags & CLK_SET_ROUND_UP) && (_fout < *fout)) {214if (div_n > div_d && div_d > 1)215div_n++;216else217div_d--;218} else if ((flags & CLK_SET_ROUND_DOWN) && (_fout > *fout)) {219if (div_n > div_d && div_n > 1)220div_n--;221else222div_d++;223}224225/* Check range after rounding */226if (div_n > 0xFFFF || div_d > 0xFFFF)227return (ERANGE);228229if (div_d == 0) {230printf("%s: %s divider is zero!\n",231clknode_get_name(clk), __func__);232return(EINVAL);233}234/* Recompute final output frequency */235_fout = fin * div_n;236_fout /= div_d;237238*stop = 1;239240if ((flags & CLK_SET_DRYRUN) == 0) {241if (*stop != 0 &&242(flags & (CLK_SET_ROUND_UP | CLK_SET_ROUND_DOWN)) == 0 &&243*fout != _fout)244return (ERANGE);245246sc->numerator = (uint32_t)div_n;247sc->denominator = (uint32_t)div_d;248249DEVICE_LOCK(clk);250WR4(clk, sc->offset, sc->numerator << 16 | sc->denominator);251DEVICE_UNLOCK(clk);252}253254*fout = _fout;255return (0);256}257258int259rk_clk_fract_register(struct clkdom *clkdom, struct rk_clk_fract_def *clkdef)260{261struct clknode *clk;262struct rk_clk_fract_sc *sc;263264clk = clknode_create(clkdom, &rk_clk_fract_class, &clkdef->clkdef);265if (clk == NULL)266return (1);267268sc = clknode_get_softc(clk);269sc->flags = clkdef->flags;270sc->offset = clkdef->offset;271sc->gate_offset = clkdef->gate_offset;272sc->gate_shift = clkdef->gate_shift;273274clknode_register(clkdom, clk);275return (0);276}277278279