Path: blob/main/sys/dev/clk/allwinner/aw_clk_frac.c
39536 views
/*-1* Copyright (c) 2019 Emmanuel Vadot <[email protected]>2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6* 1. Redistributions of source code must retain the above copyright7* notice, this list of conditions and the following disclaimer.8* 2. Redistributions in binary form must reproduce the above copyright9* notice, this list of conditions and the following disclaimer in the10* documentation and/or other materials provided with the distribution.11*12* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR13* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES14* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.15* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,16* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,17* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;18* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED19* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,20* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY21* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF22* SUCH DAMAGE.23*/2425#include <sys/param.h>26#include <sys/systm.h>27#include <sys/bus.h>2829#include <dev/clk/clk.h>3031#include <dev/clk/allwinner/aw_clk.h>32#include <dev/clk/allwinner/aw_clk_frac.h>3334#include "clkdev_if.h"3536/* #define dprintf(format, arg...) printf("%s:(%s)" format, __func__, clknode_get_name(clk), arg) */37#define dprintf(format, arg...)3839/*40* clknode for clocks matching the formula :41*42* clk = (24Mhz * n) / m in integer mode43* clk = frac_out1 or frac_out2 in fractional mode44*45*/4647struct aw_clk_frac_sc {48uint32_t offset;4950struct aw_clk_factor m;51struct aw_clk_factor n;52struct aw_clk_frac frac;5354uint64_t min_freq;55uint64_t max_freq;5657uint32_t mux_shift;58uint32_t mux_mask;59uint32_t gate_shift;60uint32_t lock_shift;61uint32_t lock_retries;6263uint32_t flags;64};6566#define WRITE4(_clk, off, val) \67CLKDEV_WRITE_4(clknode_get_device(_clk), off, val)68#define READ4(_clk, off, val) \69CLKDEV_READ_4(clknode_get_device(_clk), off, val)70#define DEVICE_LOCK(_clk) \71CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))72#define DEVICE_UNLOCK(_clk) \73CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))7475static int76aw_clk_frac_init(struct clknode *clk, device_t dev)77{78struct aw_clk_frac_sc *sc;79uint32_t val, idx;8081sc = clknode_get_softc(clk);8283idx = 0;84if ((sc->flags & AW_CLK_HAS_MUX) != 0) {85DEVICE_LOCK(clk);86READ4(clk, sc->offset, &val);87DEVICE_UNLOCK(clk);8889idx = (val & sc->mux_mask) >> sc->mux_shift;90}9192dprintf("init parent idx %d\n", idx);93clknode_init_parent_idx(clk, idx);94return (0);95}9697static int98aw_clk_frac_set_gate(struct clknode *clk, bool enable)99{100struct aw_clk_frac_sc *sc;101uint32_t val;102103sc = clknode_get_softc(clk);104105if ((sc->flags & AW_CLK_HAS_GATE) == 0)106return (0);107108dprintf("%sabling gate\n", enable ? "En" : "Dis");109DEVICE_LOCK(clk);110READ4(clk, sc->offset, &val);111if (enable)112val |= (1 << sc->gate_shift);113else114val &= ~(1 << sc->gate_shift);115WRITE4(clk, sc->offset, val);116DEVICE_UNLOCK(clk);117118return (0);119}120121static int122aw_clk_frac_set_mux(struct clknode *clk, int index)123{124struct aw_clk_frac_sc *sc;125uint32_t val;126127sc = clknode_get_softc(clk);128129if ((sc->flags & AW_CLK_HAS_MUX) == 0)130return (0);131132dprintf("Set mux to %d\n", index);133DEVICE_LOCK(clk);134READ4(clk, sc->offset, &val);135val &= ~sc->mux_mask;136val |= index << sc->mux_shift;137WRITE4(clk, sc->offset, val);138DEVICE_UNLOCK(clk);139140return (0);141}142143static uint64_t144aw_clk_frac_find_best(struct aw_clk_frac_sc *sc, uint64_t fparent, uint64_t fout,145uint32_t *factor_n, uint32_t *factor_m)146{147uint64_t cur, best;148uint32_t m, n, max_m, max_n, min_m, min_n;149150*factor_n = *factor_m = 0;151best = cur = 0;152153max_m = aw_clk_factor_get_max(&sc->m);154max_n = aw_clk_factor_get_max(&sc->n);155min_m = aw_clk_factor_get_min(&sc->m);156min_n = sc->min_freq / fparent;157158for (n = min_n; n <= max_n; n++) {159for (m = min_m; m <= max_m; m++) {160cur = fparent * n / m;161if (cur < sc->min_freq) {162continue;163}164if (cur > sc->max_freq) {165continue;166}167if (cur == fout) {168*factor_n = n;169*factor_m = m;170return (cur);171}172if (abs((fout - cur)) < abs((fout - best))) {173best = cur;174*factor_n = n;175*factor_m = m;176}177}178}179180return (best);181}182183static int184aw_clk_frac_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout,185int flags, int *stop)186{187struct aw_clk_frac_sc *sc;188uint64_t cur, best, best_frac;189uint32_t val, m, n, best_m, best_n;190int retry, multiple, max_mult, best_mult;191192sc = clknode_get_softc(clk);193194best = best_frac = cur = 0;195best_mult = 0;196max_mult = 1;197198dprintf("Trying to find freq %ju with parent %ju\n", *fout, fparent);199if ((flags & CLK_SET_ROUND_MULTIPLE) != 0)200max_mult = 10;201for (multiple = 1; multiple <= max_mult; multiple++) {202/* First test the fractional frequencies */203dprintf("Testing with multiple %d\n", multiple);204if (*fout * multiple == sc->frac.freq0) {205best = best_frac = sc->frac.freq0;206best_mult = multiple;207dprintf("Found with using frac.freq0 and multiple %d\n", multiple);208break;209}210else if (*fout * multiple == sc->frac.freq1) {211best = best_frac = sc->frac.freq1;212best_mult = multiple;213dprintf("Found with using frac.freq1 and multiple %d\n", multiple);214break;215}216else {217cur = aw_clk_frac_find_best(sc, fparent, *fout * multiple,218&n, &m);219dprintf("Got %ju with n=%d, m=%d\n", cur, n, m);220if (cur == (*fout * multiple)) {221best = cur;222best_mult = multiple;223best_n = n;224best_m = m;225dprintf("This is the one: n=%d m=%d mult=%d\n", best_n, best_m, best_mult);226break;227}228if (abs(((*fout * multiple) - cur)) < abs(((*fout * multiple) - best))) {229best = cur;230best_mult = multiple;231best_n = n;232best_m = m;233dprintf("This is the best for now: n=%d m=%d mult=%d\n", best_n, best_m, best_mult);234}235}236}237238if (best < sc->min_freq ||239best > sc->max_freq) {240printf("%s: Cannot set %ju for %s (min=%ju max=%ju)\n",241__func__, best, clknode_get_name(clk),242sc->min_freq, sc->max_freq);243return (ERANGE);244}245if ((flags & CLK_SET_DRYRUN) != 0) {246*fout = best;247*stop = 1;248return (0);249}250251if ((best < (*fout * best_mult)) &&252((flags & CLK_SET_ROUND_DOWN) == 0)) {253*stop = 1;254return (ERANGE);255}256if ((best > *fout * best_mult) &&257((flags & CLK_SET_ROUND_UP) == 0)) {258*stop = 1;259return (ERANGE);260}261262DEVICE_LOCK(clk);263READ4(clk, sc->offset, &val);264/* Disable clock during freq changes */265val &= ~(1 << sc->gate_shift);266WRITE4(clk, sc->offset, val);267268if (best_frac != 0) {269val &= ~sc->frac.mode_sel;270/* M should be 0 per the manual */271val &= ~sc->m.mask;272if (best_frac == sc->frac.freq0)273val &= ~sc->frac.freq_sel;274else275val |= sc->frac.freq_sel;276} else {277val |= sc->frac.mode_sel; /* Select integer mode */278n = aw_clk_factor_get_value(&sc->n, best_n);279m = aw_clk_factor_get_value(&sc->m, best_m);280val &= ~sc->n.mask;281val &= ~sc->m.mask;282val |= n << sc->n.shift;283val |= m << sc->m.shift;284}285286/* Write the clock changes */287WRITE4(clk, sc->offset, val);288289/* Enable clock now that we've change it */290val |= 1 << sc->gate_shift;291WRITE4(clk, sc->offset, val);292DEVICE_UNLOCK(clk);293294for (retry = 0; retry < sc->lock_retries; retry++) {295READ4(clk, sc->offset, &val);296if ((val & (1 << sc->lock_shift)) != 0)297break;298DELAY(1000);299}300301*fout = best;302*stop = 1;303304return (0);305}306307static int308aw_clk_frac_recalc(struct clknode *clk, uint64_t *freq)309{310struct aw_clk_frac_sc *sc;311uint32_t val, m, n;312313sc = clknode_get_softc(clk);314315DEVICE_LOCK(clk);316READ4(clk, sc->offset, &val);317DEVICE_UNLOCK(clk);318319if ((val & sc->frac.mode_sel) == 0) {320if (val & sc->frac.freq_sel)321*freq = sc->frac.freq1;322else323*freq = sc->frac.freq0;324} else {325m = aw_clk_get_factor(val, &sc->m);326n = aw_clk_get_factor(val, &sc->n);327*freq = *freq * n / m;328}329330return (0);331}332333static clknode_method_t aw_frac_clknode_methods[] = {334/* Device interface */335CLKNODEMETHOD(clknode_init, aw_clk_frac_init),336CLKNODEMETHOD(clknode_set_gate, aw_clk_frac_set_gate),337CLKNODEMETHOD(clknode_set_mux, aw_clk_frac_set_mux),338CLKNODEMETHOD(clknode_recalc_freq, aw_clk_frac_recalc),339CLKNODEMETHOD(clknode_set_freq, aw_clk_frac_set_freq),340CLKNODEMETHOD_END341};342343DEFINE_CLASS_1(aw_frac_clknode, aw_frac_clknode_class, aw_frac_clknode_methods,344sizeof(struct aw_clk_frac_sc), clknode_class);345346int347aw_clk_frac_register(struct clkdom *clkdom, struct aw_clk_frac_def *clkdef)348{349struct clknode *clk;350struct aw_clk_frac_sc *sc;351352clk = clknode_create(clkdom, &aw_frac_clknode_class, &clkdef->clkdef);353if (clk == NULL)354return (1);355356sc = clknode_get_softc(clk);357358sc->offset = clkdef->offset;359360sc->m.shift = clkdef->m.shift;361sc->m.width = clkdef->m.width;362sc->m.mask = ((1 << sc->m.width) - 1) << sc->m.shift;363sc->m.value = clkdef->m.value;364sc->m.flags = clkdef->m.flags;365366sc->n.shift = clkdef->n.shift;367sc->n.width = clkdef->n.width;368sc->n.mask = ((1 << sc->n.width) - 1) << sc->n.shift;369sc->n.value = clkdef->n.value;370sc->n.flags = clkdef->n.flags;371372sc->frac.freq0 = clkdef->frac.freq0;373sc->frac.freq1 = clkdef->frac.freq1;374sc->frac.mode_sel = 1 << clkdef->frac.mode_sel;375sc->frac.freq_sel = 1 << clkdef->frac.freq_sel;376377sc->min_freq = clkdef->min_freq;378sc->max_freq = clkdef->max_freq;379380sc->mux_shift = clkdef->mux_shift;381sc->mux_mask = ((1 << clkdef->mux_width) - 1) << sc->mux_shift;382383sc->gate_shift = clkdef->gate_shift;384385sc->lock_shift = clkdef->lock_shift;386sc->lock_retries = clkdef->lock_retries;387388sc->flags = clkdef->flags;389390clknode_register(clkdom, clk);391392return (0);393}394395396