1 /*- 2 * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/param.h> 27 #include <sys/systm.h> 28 #include <sys/bus.h> 29 #include <sys/lock.h> 30 #include <sys/mutex.h> 31 #include <sys/rman.h> 32 #include <machine/bus.h> 33 34 #include <dev/clk/clk.h> 35 #include <dev/clk/clk_div.h> 36 #include <dev/clk/clk_fixed.h> 37 #include <dev/clk/clk_mux.h> 38 39 #include "qcom_clk_freqtbl.h" 40 #include "qcom_clk_apssdiv.h" 41 42 #include "clkdev_if.h" 43 44 /* 45 * This is a combination gate, divisor/PLL configuration 46 * for the APSS CPU clock. 47 */ 48 49 #if 0 50 #define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg); 51 #else 52 #define DPRINTF(dev, msg...) 53 #endif 54 55 struct qcom_clk_apssdiv_sc { 56 struct clknode *clknode; 57 uint32_t div_offset; 58 uint32_t div_width; 59 uint32_t div_shift; 60 uint32_t enable_offset; 61 uint32_t enable_shift; 62 const struct qcom_clk_freq_tbl *freq_tbl; 63 }; 64 65 static uint64_t 66 qcom_clk_apssdiv_calc_rate(struct clknode *clk, uint64_t freq, uint32_t cdiv) 67 { 68 uint32_t pre_div; 69 70 /* 71 * The divisor isn't a linear map with a linear pre-divisor. 72 */ 73 if (cdiv > 10) { 74 pre_div = (cdiv + 1) * 2; 75 } else { 76 pre_div = cdiv + 12; 77 } 78 /* 79 * Multiplier is a fixed "2" here. 80 */ 81 return (freq * 2L) / pre_div; 82 } 83 84 static int 85 qcom_clk_apssdiv_recalc(struct clknode *clk, uint64_t *freq) 86 { 87 struct qcom_clk_apssdiv_sc *sc; 88 uint32_t reg, cdiv; 89 90 sc = clknode_get_softc(clk); 91 92 if (freq == NULL || *freq == 0) { 93 printf("%s: called; NULL or 0 frequency\n", __func__); 94 return (ENXIO); 95 } 96 97 CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode)); 98 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®); 99 CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode)); 100 cdiv = (reg >> sc->div_shift) & ((1U << sc->div_width) - 1); 101 102 DPRINTF(clknode_get_device(sc->clknode), 103 "%s: called; cdiv=0x%x, freq=%llu\n", __func__, cdiv, *freq); 104 105 *freq = qcom_clk_apssdiv_calc_rate(clk, *freq, cdiv); 106 107 DPRINTF(clknode_get_device(sc->clknode), 108 "%s: called; freq is %llu\n", __func__, *freq); 109 return (0); 110 } 111 112 #if 0 113 static bool 114 qcom_clk_apssdiv_get_gate_locked(struct qcom_clk_apssdiv_sc *sc) 115 { 116 uint32_t reg; 117 118 if (sc->enable_offset == 0) 119 return (false); 120 121 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset, 122 ®); 123 124 return (!! (reg & (1U << sc->enable_shift))); 125 } 126 #endif 127 128 static int 129 qcom_clk_apssdiv_init(struct clknode *clk, device_t dev) 130 { 131 132 /* 133 * There's only a single parent here for an fixed divisor, 134 * so just set it to 0; the caller doesn't need to supply it. 135 * 136 * Note that the freqtbl entries have an upstream clock, 137 * but the APSS div/gate only has a single upstream and we 138 * don't program anything else specific in here. 139 */ 140 clknode_init_parent_idx(clk, 0); 141 142 return (0); 143 } 144 145 static int 146 qcom_clk_apssdiv_set_gate(struct clknode *clk, bool enable) 147 { 148 struct qcom_clk_apssdiv_sc *sc; 149 uint32_t reg; 150 151 sc = clknode_get_softc(clk); 152 153 if (sc->enable_offset == 0) { 154 return (ENXIO); 155 } 156 157 DPRINTF(clknode_get_device(sc->clknode), 158 "%s: called; enable=%d\n", __func__, enable); 159 160 CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode)); 161 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset, 162 ®); 163 if (enable) { 164 reg |= (1U << sc->enable_shift); 165 } else { 166 reg &= ~(1U << sc->enable_shift); 167 } 168 CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset, 169 reg); 170 CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode)); 171 172 return (0); 173 } 174 175 /* 176 * Set frequency 177 * 178 * fin - the parent frequency, if exists 179 * fout - starts as the requested frequency, ends with the configured 180 * or dry-run frequency 181 * Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN 182 * retval - 0, ERANGE 183 */ 184 static int 185 qcom_clk_apssdiv_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, 186 int flags, int *stop) 187 { 188 const struct qcom_clk_freq_tbl *f; 189 struct qcom_clk_apssdiv_sc *sc; 190 uint64_t f_freq; 191 uint32_t reg; 192 193 sc = clknode_get_softc(clk); 194 195 /* There are no further PLLs to set in this chain */ 196 *stop = 1; 197 198 /* Search the table for a suitable frequency */ 199 f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout); 200 if (f == NULL) { 201 return (ERANGE); 202 } 203 204 /* 205 * Calculate what the resultant frequency would be based on the 206 * parent PLL. 207 */ 208 f_freq = qcom_clk_apssdiv_calc_rate(clk, fin, f->pre_div); 209 210 DPRINTF(clknode_get_device(sc->clknode), 211 "%s: dryrun: %d, fin=%llu fout=%llu f_freq=%llu pre_div=%u" 212 " target_freq=%llu\n", 213 __func__, 214 !! (flags & CLK_SET_DRYRUN), 215 fin, *fout, f_freq, f->pre_div, f->freq); 216 217 if (flags & CLK_SET_DRYRUN) { 218 *fout = f_freq; 219 return (0); 220 } 221 222 /* 223 * Program in the new pre-divisor. 224 */ 225 CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode)); 226 CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®); 227 reg &= ~(((1U << sc->div_width) - 1) << sc->div_shift); 228 reg |= (f->pre_div << sc->div_shift); 229 CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->div_offset, reg); 230 CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode)); 231 232 /* 233 * The linux driver notes there's no status/completion bit to poll. 234 * So sleep for a bit and hope that's enough time for it to 235 * settle. 236 */ 237 DELAY(1); 238 239 *fout = f_freq; 240 241 return (0); 242 } 243 244 static clknode_method_t qcom_clk_apssdiv_methods[] = { 245 /* Device interface */ 246 CLKNODEMETHOD(clknode_init, qcom_clk_apssdiv_init), 247 CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_apssdiv_recalc), 248 CLKNODEMETHOD(clknode_set_gate, qcom_clk_apssdiv_set_gate), 249 CLKNODEMETHOD(clknode_set_freq, qcom_clk_apssdiv_set_freq), 250 CLKNODEMETHOD_END 251 }; 252 253 DEFINE_CLASS_1(qcom_clk_apssdiv, qcom_clk_apssdiv_class, 254 qcom_clk_apssdiv_methods, sizeof(struct qcom_clk_apssdiv_sc), 255 clknode_class); 256 257 int 258 qcom_clk_apssdiv_register(struct clkdom *clkdom, 259 struct qcom_clk_apssdiv_def *clkdef) 260 { 261 struct clknode *clk; 262 struct qcom_clk_apssdiv_sc *sc; 263 264 clk = clknode_create(clkdom, &qcom_clk_apssdiv_class, &clkdef->clkdef); 265 if (clk == NULL) 266 return (1); 267 268 sc = clknode_get_softc(clk); 269 sc->clknode = clk; 270 271 sc->div_offset = clkdef->div_offset; 272 sc->div_width = clkdef->div_width; 273 sc->div_shift = clkdef->div_shift; 274 sc->freq_tbl = clkdef->freq_tbl; 275 sc->enable_offset = clkdef->enable_offset; 276 sc->enable_shift = clkdef->enable_shift; 277 278 clknode_register(clkdom, clk); 279 280 return (0); 281 } 282