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