1 1.6 jmcneill /* $NetBSD: sunxi_ccu_fractional.c,v 1.6 2019/11/23 21:30:41 jmcneill Exp $ */ 2 1.1 bouyer 3 1.1 bouyer /*- 4 1.1 bouyer * Copyright (c) 2017 Jared McNeill <jmcneill (at) invisible.ca> 5 1.1 bouyer * All rights reserved. 6 1.1 bouyer * 7 1.1 bouyer * Redistribution and use in source and binary forms, with or without 8 1.1 bouyer * modification, are permitted provided that the following conditions 9 1.1 bouyer * are met: 10 1.1 bouyer * 1. Redistributions of source code must retain the above copyright 11 1.1 bouyer * notice, this list of conditions and the following disclaimer. 12 1.1 bouyer * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 bouyer * notice, this list of conditions and the following disclaimer in the 14 1.1 bouyer * documentation and/or other materials provided with the distribution. 15 1.1 bouyer * 16 1.1 bouyer * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 1.1 bouyer * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 1.1 bouyer * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 1.1 bouyer * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 1.1 bouyer * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 1.1 bouyer * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 1.1 bouyer * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 1.1 bouyer * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 1.1 bouyer * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 1.1 bouyer * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 1.1 bouyer * SUCH DAMAGE. 27 1.1 bouyer */ 28 1.1 bouyer 29 1.1 bouyer #include <sys/cdefs.h> 30 1.6 jmcneill __KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.6 2019/11/23 21:30:41 jmcneill Exp $"); 31 1.1 bouyer 32 1.1 bouyer #include <sys/param.h> 33 1.1 bouyer #include <sys/bus.h> 34 1.1 bouyer 35 1.1 bouyer #include <dev/clk/clk_backend.h> 36 1.1 bouyer 37 1.1 bouyer #include <arm/sunxi/sunxi_ccu.h> 38 1.1 bouyer 39 1.1 bouyer int 40 1.1 bouyer sunxi_ccu_fractional_enable(struct sunxi_ccu_softc *sc, 41 1.1 bouyer struct sunxi_ccu_clk *clk, int enable) 42 1.1 bouyer { 43 1.1 bouyer struct sunxi_ccu_fractional *fractional = &clk->u.fractional; 44 1.1 bouyer uint32_t val; 45 1.1 bouyer 46 1.1 bouyer KASSERT(clk->type == SUNXI_CCU_FRACTIONAL); 47 1.1 bouyer 48 1.1 bouyer if (!fractional->enable) 49 1.1 bouyer return enable ? 0 : EINVAL; 50 1.1 bouyer 51 1.1 bouyer val = CCU_READ(sc, fractional->reg); 52 1.1 bouyer if (enable) 53 1.1 bouyer val |= fractional->enable; 54 1.1 bouyer else 55 1.1 bouyer val &= ~fractional->enable; 56 1.1 bouyer CCU_WRITE(sc, fractional->reg, val); 57 1.1 bouyer 58 1.1 bouyer return 0; 59 1.1 bouyer } 60 1.1 bouyer 61 1.1 bouyer u_int 62 1.1 bouyer sunxi_ccu_fractional_get_rate(struct sunxi_ccu_softc *sc, 63 1.1 bouyer struct sunxi_ccu_clk *clk) 64 1.1 bouyer { 65 1.1 bouyer struct sunxi_ccu_fractional *fractional = &clk->u.fractional; 66 1.1 bouyer struct clk *clkp, *clkp_parent; 67 1.1 bouyer u_int rate, m; 68 1.1 bouyer uint32_t val; 69 1.1 bouyer 70 1.1 bouyer KASSERT(clk->type == SUNXI_CCU_FRACTIONAL); 71 1.1 bouyer 72 1.1 bouyer clkp = &clk->base; 73 1.1 bouyer clkp_parent = clk_get_parent(clkp); 74 1.1 bouyer if (clkp_parent == NULL) 75 1.1 bouyer return 0; 76 1.1 bouyer 77 1.1 bouyer rate = clk_get_rate(clkp_parent); 78 1.1 bouyer if (rate == 0) 79 1.1 bouyer return 0; 80 1.1 bouyer 81 1.3 jmcneill val = CCU_READ(sc, fractional->reg); 82 1.1 bouyer 83 1.3 jmcneill if (fractional->prediv != 0) { 84 1.3 jmcneill rate = rate / (__SHIFTOUT(val, fractional->prediv) + 1); 85 1.3 jmcneill } else if (fractional->prediv_val > 0) { 86 1.3 jmcneill rate = rate / fractional->prediv_val; 87 1.3 jmcneill } 88 1.1 bouyer 89 1.1 bouyer if (fractional->enable && !(val & fractional->enable)) 90 1.1 bouyer return 0; 91 1.1 bouyer 92 1.2 bouyer if ((val & fractional->div_en) == 0) { 93 1.1 bouyer int sel = __SHIFTOUT(val, fractional->frac_sel); 94 1.1 bouyer return fractional->frac[sel]; 95 1.1 bouyer } 96 1.1 bouyer m = __SHIFTOUT(val, fractional->m); 97 1.1 bouyer 98 1.3 jmcneill if (fractional->flags & SUNXI_CCU_FRACTIONAL_PLUSONE) 99 1.3 jmcneill m++; 100 1.3 jmcneill 101 1.1 bouyer return rate * m; 102 1.1 bouyer } 103 1.1 bouyer 104 1.1 bouyer int 105 1.1 bouyer sunxi_ccu_fractional_set_rate(struct sunxi_ccu_softc *sc, 106 1.1 bouyer struct sunxi_ccu_clk *clk, u_int new_rate) 107 1.1 bouyer { 108 1.1 bouyer struct sunxi_ccu_fractional *fractional = &clk->u.fractional; 109 1.1 bouyer struct clk *clkp, *clkp_parent; 110 1.1 bouyer u_int parent_rate, best_rate, best_m; 111 1.1 bouyer u_int m, rate; 112 1.1 bouyer int best_diff; 113 1.1 bouyer uint32_t val; 114 1.1 bouyer int i; 115 1.1 bouyer 116 1.1 bouyer clkp = &clk->base; 117 1.1 bouyer clkp_parent = clk_get_parent(clkp); 118 1.1 bouyer if (clkp_parent == NULL) 119 1.1 bouyer return ENXIO; 120 1.1 bouyer 121 1.1 bouyer parent_rate = clk_get_rate(clkp_parent); 122 1.1 bouyer if (parent_rate == 0) 123 1.1 bouyer return (new_rate == 0) ? 0 : ERANGE; 124 1.1 bouyer 125 1.3 jmcneill val = CCU_READ(sc, fractional->reg); 126 1.3 jmcneill 127 1.3 jmcneill if (fractional->prediv != 0) { 128 1.3 jmcneill if (fractional->prediv_val > 0) { 129 1.3 jmcneill val &= ~fractional->prediv; 130 1.3 jmcneill val |= __SHIFTIN(fractional->prediv_val - 1, 131 1.3 jmcneill fractional->prediv); 132 1.3 jmcneill } 133 1.3 jmcneill parent_rate = parent_rate / (__SHIFTOUT(val, fractional->prediv) + 1); 134 1.3 jmcneill } else if (fractional->prediv_val > 0) { 135 1.3 jmcneill parent_rate = parent_rate / fractional->prediv_val; 136 1.3 jmcneill } 137 1.1 bouyer 138 1.1 bouyer for (i = 0; i < __arraycount(fractional->frac); i++) { 139 1.1 bouyer if (fractional->frac[i] == new_rate) { 140 1.6 jmcneill val &= ~fractional->prediv; 141 1.2 bouyer val &= ~fractional->div_en; 142 1.1 bouyer val &= ~fractional->frac_sel; 143 1.1 bouyer val |= __SHIFTIN(i, fractional->frac_sel); 144 1.5 jmcneill if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE) 145 1.5 jmcneill val |= fractional->enable; 146 1.1 bouyer CCU_WRITE(sc, fractional->reg, val); 147 1.1 bouyer return 0; 148 1.1 bouyer } 149 1.1 bouyer } 150 1.2 bouyer val |= fractional->div_en; 151 1.1 bouyer 152 1.1 bouyer best_rate = 0; 153 1.1 bouyer best_diff = INT_MAX; 154 1.1 bouyer 155 1.1 bouyer for (m = fractional->m_min; m <= fractional->m_max; m++) { 156 1.1 bouyer rate = parent_rate * m; 157 1.1 bouyer const int diff = abs(new_rate - rate); 158 1.1 bouyer if (diff < best_diff) { 159 1.1 bouyer best_diff = diff; 160 1.1 bouyer best_rate = rate; 161 1.1 bouyer best_m = m; 162 1.2 bouyer if (diff == 0) 163 1.2 bouyer break; 164 1.1 bouyer } 165 1.1 bouyer } 166 1.1 bouyer 167 1.1 bouyer if (best_rate == 0) 168 1.1 bouyer return ERANGE; 169 1.1 bouyer 170 1.3 jmcneill if (fractional->flags & SUNXI_CCU_FRACTIONAL_PLUSONE) 171 1.3 jmcneill best_m--; 172 1.3 jmcneill 173 1.1 bouyer val &= ~fractional->m; 174 1.1 bouyer val |= __SHIFTIN(best_m, fractional->m); 175 1.4 jmcneill if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE) 176 1.4 jmcneill val |= fractional->enable; 177 1.1 bouyer CCU_WRITE(sc, fractional->reg, val); 178 1.1 bouyer 179 1.4 jmcneill 180 1.1 bouyer return 0; 181 1.1 bouyer } 182 1.1 bouyer 183 1.2 bouyer u_int 184 1.2 bouyer sunxi_ccu_fractional_round_rate(struct sunxi_ccu_softc *sc, 185 1.2 bouyer struct sunxi_ccu_clk *clk, u_int try_rate) 186 1.2 bouyer { 187 1.2 bouyer struct sunxi_ccu_fractional *fractional = &clk->u.fractional; 188 1.2 bouyer struct clk *clkp, *clkp_parent; 189 1.2 bouyer u_int parent_rate, best_rate; 190 1.2 bouyer u_int m, rate; 191 1.2 bouyer int best_diff; 192 1.3 jmcneill uint32_t val; 193 1.2 bouyer int i; 194 1.2 bouyer 195 1.2 bouyer clkp = &clk->base; 196 1.2 bouyer clkp_parent = clk_get_parent(clkp); 197 1.2 bouyer if (clkp_parent == NULL) 198 1.2 bouyer return 0; 199 1.2 bouyer 200 1.2 bouyer parent_rate = clk_get_rate(clkp_parent); 201 1.2 bouyer if (parent_rate == 0) 202 1.2 bouyer return 0; 203 1.2 bouyer 204 1.3 jmcneill val = CCU_READ(sc, fractional->reg); 205 1.3 jmcneill 206 1.3 jmcneill if (fractional->prediv_val > 0) { 207 1.3 jmcneill parent_rate = parent_rate / fractional->prediv_val; 208 1.3 jmcneill } else if (fractional->prediv != 0) { 209 1.3 jmcneill val = CCU_READ(sc, fractional->reg); 210 1.3 jmcneill parent_rate = parent_rate / (__SHIFTOUT(val, fractional->prediv) + 1); 211 1.3 jmcneill } 212 1.2 bouyer 213 1.2 bouyer for (i = 0; i < __arraycount(fractional->frac); i++) { 214 1.2 bouyer if (fractional->frac[i] == try_rate) { 215 1.2 bouyer return try_rate; 216 1.2 bouyer } 217 1.2 bouyer } 218 1.2 bouyer 219 1.2 bouyer best_rate = 0; 220 1.2 bouyer best_diff = INT_MAX; 221 1.2 bouyer 222 1.2 bouyer for (m = fractional->m_min; m <= fractional->m_max; m++) { 223 1.2 bouyer rate = parent_rate * m; 224 1.2 bouyer const int diff = abs(try_rate - rate); 225 1.2 bouyer if (diff < best_diff) { 226 1.2 bouyer best_diff = diff; 227 1.2 bouyer best_rate = rate; 228 1.2 bouyer if (diff == 0) 229 1.2 bouyer break; 230 1.2 bouyer } 231 1.2 bouyer } 232 1.2 bouyer 233 1.2 bouyer return best_rate; 234 1.2 bouyer } 235 1.2 bouyer 236 1.1 bouyer const char * 237 1.1 bouyer sunxi_ccu_fractional_get_parent(struct sunxi_ccu_softc *sc, 238 1.1 bouyer struct sunxi_ccu_clk *clk) 239 1.1 bouyer { 240 1.1 bouyer struct sunxi_ccu_fractional *fractional = &clk->u.fractional; 241 1.1 bouyer 242 1.1 bouyer KASSERT(clk->type == SUNXI_CCU_FRACTIONAL); 243 1.1 bouyer 244 1.1 bouyer return fractional->parent; 245 1.1 bouyer } 246