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