1 /* $NetBSD: sunxi_ccu_nm.c,v 1.4 2017/10/28 13:13:45 jmcneill 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_nm.c,v 1.4 2017/10/28 13:13:45 jmcneill 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_nm_enable(struct sunxi_ccu_softc *sc, struct sunxi_ccu_clk *clk, 41 int enable) 42 { 43 struct sunxi_ccu_nm *nm = &clk->u.nm; 44 uint32_t val; 45 46 KASSERT(clk->type == SUNXI_CCU_NM); 47 48 if (!nm->enable) 49 return enable ? 0 : EINVAL; 50 51 val = CCU_READ(sc, nm->reg); 52 if (enable) 53 val |= nm->enable; 54 else 55 val &= ~nm->enable; 56 CCU_WRITE(sc, nm->reg, val); 57 58 return 0; 59 } 60 61 u_int 62 sunxi_ccu_nm_get_rate(struct sunxi_ccu_softc *sc, 63 struct sunxi_ccu_clk *clk) 64 { 65 struct sunxi_ccu_nm *nm = &clk->u.nm; 66 struct clk *clkp, *clkp_parent; 67 u_int rate, n, m; 68 uint32_t val; 69 70 KASSERT(clk->type == SUNXI_CCU_NM); 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 val = CCU_READ(sc, nm->reg); 82 n = __SHIFTOUT(val, nm->n); 83 m = __SHIFTOUT(val, nm->m); 84 85 if (nm->enable && !(val & nm->enable)) 86 return 0; 87 88 if (nm->flags & SUNXI_CCU_NM_POWER_OF_TWO) 89 n = 1 << n; 90 else 91 n++; 92 93 m++; 94 95 if (nm->flags & SUNXI_CCU_NM_DIVIDE_BY_TWO) 96 m *= 2; 97 98 return rate / n / m; 99 } 100 101 int 102 sunxi_ccu_nm_set_rate(struct sunxi_ccu_softc *sc, 103 struct sunxi_ccu_clk *clk, u_int new_rate) 104 { 105 struct sunxi_ccu_nm *nm = &clk->u.nm; 106 struct clk *clkp, *clkp_parent; 107 u_int parent_rate, best_rate, best_n, best_m, best_parent; 108 u_int n, m, pindex, rate; 109 int best_diff; 110 uint32_t val; 111 112 const u_int n_max = __SHIFTOUT(nm->n, nm->n); 113 const u_int m_max = __SHIFTOUT(nm->m, nm->m); 114 115 clkp = &clk->base; 116 clkp_parent = clk_get_parent(clkp); 117 if (clkp_parent == NULL) 118 return 0; 119 120 rate = clk_get_rate(clkp_parent); 121 if (rate == 0) 122 return 0; 123 124 best_rate = 0; 125 best_diff = INT_MAX; 126 for (pindex = 0; pindex < nm->nparents; pindex++) { 127 /* XXX 128 * Shouldn't have to set parent to get potential parent clock rate 129 */ 130 val = CCU_READ(sc, nm->reg); 131 val &= ~nm->sel; 132 val |= __SHIFTIN(pindex, nm->sel); 133 CCU_WRITE(sc, nm->reg, val); 134 135 clkp_parent = clk_get_parent(clkp); 136 if (clkp_parent == NULL) 137 continue; 138 parent_rate = clk_get_rate(clkp_parent); 139 if (parent_rate == 0) 140 continue; 141 142 for (n = 0; n <= n_max; n++) { 143 for (m = 0; m <= m_max; m++) { 144 if (nm->flags & SUNXI_CCU_NM_POWER_OF_TWO) 145 rate = parent_rate / (1 << n) / (m + 1); 146 else 147 rate = parent_rate / (n + 1) / (m + 1); 148 if (nm->flags & SUNXI_CCU_NM_DIVIDE_BY_TWO) 149 rate /= 2; 150 151 if (nm->flags & SUNXI_CCU_NM_ROUND_DOWN) { 152 const int diff = new_rate - rate; 153 if (diff >= 0 && rate > best_rate) { 154 best_diff = diff; 155 best_rate = rate; 156 best_n = n; 157 best_m = m; 158 best_parent = pindex; 159 } 160 } else { 161 const int diff = abs(new_rate - rate); 162 if (diff < best_diff) { 163 best_diff = diff; 164 best_rate = rate; 165 best_n = n; 166 best_m = m; 167 best_parent = pindex; 168 } 169 } 170 } 171 } 172 } 173 174 if (best_rate == 0) 175 return ERANGE; 176 177 val = CCU_READ(sc, nm->reg); 178 val &= ~nm->sel; 179 val |= __SHIFTIN(best_parent, nm->sel); 180 val &= ~nm->n; 181 val |= __SHIFTIN(best_n, nm->n); 182 val &= ~nm->m; 183 val |= __SHIFTIN(best_m, nm->m); 184 CCU_WRITE(sc, nm->reg, val); 185 186 return 0; 187 } 188 189 int 190 sunxi_ccu_nm_set_parent(struct sunxi_ccu_softc *sc, 191 struct sunxi_ccu_clk *clk, const char *name) 192 { 193 struct sunxi_ccu_nm *nm = &clk->u.nm; 194 uint32_t val; 195 u_int index; 196 197 KASSERT(clk->type == SUNXI_CCU_NM); 198 199 if (nm->sel == 0) 200 return ENODEV; 201 202 for (index = 0; index < nm->nparents; index++) { 203 if (nm->parents[index] != NULL && 204 strcmp(nm->parents[index], name) == 0) 205 break; 206 } 207 if (index == nm->nparents) 208 return EINVAL; 209 210 val = CCU_READ(sc, nm->reg); 211 val &= ~nm->sel; 212 val |= __SHIFTIN(index, nm->sel); 213 CCU_WRITE(sc, nm->reg, val); 214 215 return 0; 216 } 217 218 const char * 219 sunxi_ccu_nm_get_parent(struct sunxi_ccu_softc *sc, 220 struct sunxi_ccu_clk *clk) 221 { 222 struct sunxi_ccu_nm *nm = &clk->u.nm; 223 u_int index; 224 uint32_t val; 225 226 KASSERT(clk->type == SUNXI_CCU_NM); 227 228 val = CCU_READ(sc, nm->reg); 229 index = __SHIFTOUT(val, nm->sel); 230 231 return nm->parents[index]; 232 } 233