1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright 2016 Michal Meloun <mmel@FreeBSD.org> 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 AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, 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/param.h> 30 #include <sys/conf.h> 31 #include <sys/bus.h> 32 #include <sys/kernel.h> 33 #include <sys/systm.h> 34 35 #include <machine/bus.h> 36 37 #include <dev/clk/clk.h> 38 #include <dev/syscon/syscon.h> 39 40 #include <dev/clk/rockchip/rk_cru.h> 41 #include <dev/clk/rockchip/rk_clk_mux.h> 42 43 #include "clkdev_if.h" 44 #include "syscon_if.h" 45 46 #define WR4(_clk, off, val) \ 47 CLKDEV_WRITE_4(clknode_get_device(_clk), off, val) 48 #define RD4(_clk, off, val) \ 49 CLKDEV_READ_4(clknode_get_device(_clk), off, val) 50 #define MD4(_clk, off, clr, set ) \ 51 CLKDEV_MODIFY_4(clknode_get_device(_clk), off, clr, set) 52 #define DEVICE_LOCK(_clk) \ 53 CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) 54 #define DEVICE_UNLOCK(_clk) \ 55 CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) 56 57 #if 0 58 #define dprintf(format, arg...) \ 59 printf("%s:(%s)" format, __func__, clknode_get_name(clk), arg) 60 #else 61 #define dprintf(format, arg...) 62 #endif 63 64 static int rk_clk_mux_init(struct clknode *clk, device_t dev); 65 static int rk_clk_mux_set_mux(struct clknode *clk, int idx); 66 static int rk_clk_mux_set_freq(struct clknode *clk, uint64_t fparent, 67 uint64_t *fout, int flags, int *stop); 68 69 struct rk_clk_mux_sc { 70 uint32_t offset; 71 uint32_t shift; 72 uint32_t mask; 73 int mux_flags; 74 struct syscon *grf; 75 }; 76 77 static clknode_method_t rk_clk_mux_methods[] = { 78 /* Device interface */ 79 CLKNODEMETHOD(clknode_init, rk_clk_mux_init), 80 CLKNODEMETHOD(clknode_set_mux, rk_clk_mux_set_mux), 81 CLKNODEMETHOD(clknode_set_freq, rk_clk_mux_set_freq), 82 CLKNODEMETHOD_END 83 }; 84 DEFINE_CLASS_1(rk_clk_mux, rk_clk_mux_class, rk_clk_mux_methods, 85 sizeof(struct rk_clk_mux_sc), clknode_class); 86 87 static struct syscon * 88 rk_clk_mux_get_grf(struct clknode *clk) 89 { 90 device_t dev; 91 phandle_t node; 92 struct syscon *grf; 93 94 grf = NULL; 95 dev = clknode_get_device(clk); 96 node = ofw_bus_get_node(dev); 97 if (OF_hasprop(node, "rockchip,grf") && 98 syscon_get_by_ofw_property(dev, node, 99 "rockchip,grf", &grf) != 0) { 100 return (NULL); 101 } 102 103 return (grf); 104 } 105 106 static int 107 rk_clk_mux_init(struct clknode *clk, device_t dev) 108 { 109 uint32_t reg; 110 struct rk_clk_mux_sc *sc; 111 int rv; 112 113 sc = clknode_get_softc(clk); 114 115 if ((sc->mux_flags & RK_CLK_MUX_GRF) != 0) { 116 sc->grf = rk_clk_mux_get_grf(clk); 117 if (sc->grf == NULL) 118 panic("clock %s has GRF flag set but no syscon is available", 119 clknode_get_name(clk)); 120 } 121 122 DEVICE_LOCK(clk); 123 if (sc->grf) { 124 reg = SYSCON_READ_4(sc->grf, sc->offset); 125 rv = 0; 126 } else 127 rv = RD4(clk, sc->offset, ®); 128 DEVICE_UNLOCK(clk); 129 if (rv != 0) { 130 return (rv); 131 } 132 reg = (reg >> sc->shift) & sc->mask; 133 clknode_init_parent_idx(clk, reg); 134 return(0); 135 } 136 137 static int 138 rk_clk_mux_set_mux(struct clknode *clk, int idx) 139 { 140 uint32_t reg; 141 struct rk_clk_mux_sc *sc; 142 int rv; 143 144 sc = clknode_get_softc(clk); 145 146 DEVICE_LOCK(clk); 147 if (sc->grf) 148 rv = SYSCON_MODIFY_4(sc->grf, sc->offset, sc->mask << sc->shift, 149 ((idx & sc->mask) << sc->shift) | RK_CLK_MUX_MASK); 150 else 151 rv = MD4(clk, sc->offset, sc->mask << sc->shift, 152 ((idx & sc->mask) << sc->shift) | RK_CLK_MUX_MASK); 153 if (rv != 0) { 154 DEVICE_UNLOCK(clk); 155 return (rv); 156 } 157 if (sc->grf == NULL) 158 RD4(clk, sc->offset, ®); 159 DEVICE_UNLOCK(clk); 160 161 return(0); 162 } 163 164 static int 165 rk_clk_mux_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, 166 int flags, int *stop) 167 { 168 struct rk_clk_mux_sc *sc; 169 struct clknode *p_clk, *p_best_clk; 170 const char **p_names; 171 int p_idx, best_parent; 172 int rv; 173 174 sc = clknode_get_softc(clk); 175 176 if ((sc->mux_flags & RK_CLK_MUX_GRF) != 0) { 177 *stop = 1; 178 return (ENOTSUP); 179 } 180 if ((sc->mux_flags & RK_CLK_MUX_REPARENT) == 0) { 181 *stop = 0; 182 return (0); 183 } 184 185 dprintf("Finding best parent for target freq of %ju\n", *fout); 186 p_names = clknode_get_parent_names(clk); 187 for (p_idx = 0; p_idx != clknode_get_parents_num(clk); p_idx++) { 188 p_clk = clknode_find_by_name(p_names[p_idx]); 189 dprintf("Testing with parent %s (%d)\n", 190 clknode_get_name(p_clk), p_idx); 191 192 rv = clknode_set_freq(p_clk, *fout, flags | CLK_SET_DRYRUN, 0); 193 dprintf("Testing with parent %s (%d) rv=%d\n", 194 clknode_get_name(p_clk), p_idx, rv); 195 if (rv == 0) { 196 best_parent = p_idx; 197 p_best_clk = p_clk; 198 *stop = 1; 199 } 200 } 201 202 if (!*stop) 203 return (0); 204 205 if ((flags & CLK_SET_DRYRUN) != 0) 206 return (0); 207 208 p_idx = clknode_get_parent_idx(clk); 209 if (p_idx != best_parent) { 210 dprintf("Switching parent index from %d to %d\n", p_idx, 211 best_parent); 212 clknode_set_parent_by_idx(clk, best_parent); 213 } 214 215 clknode_set_freq(p_best_clk, *fout, flags, 0); 216 clknode_get_freq(p_best_clk, fout); 217 218 return (0); 219 } 220 221 int 222 rk_clk_mux_register(struct clkdom *clkdom, struct rk_clk_mux_def *clkdef) 223 { 224 struct clknode *clk; 225 struct rk_clk_mux_sc *sc; 226 227 clk = clknode_create(clkdom, &rk_clk_mux_class, &clkdef->clkdef); 228 if (clk == NULL) 229 return (1); 230 231 sc = clknode_get_softc(clk); 232 sc->offset = clkdef->offset; 233 sc->shift = clkdef->shift; 234 sc->mask = (1 << clkdef->width) - 1; 235 sc->mux_flags = clkdef->mux_flags; 236 237 clknode_register(clkdom, clk); 238 return (0); 239 } 240