1 /* $NetBSD: clk.c,v 1.7 2019/07/23 17:44:03 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2015 Jared D. 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: clk.c,v 1.7 2019/07/23 17:44:03 jmcneill Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/sysctl.h> 34 #include <sys/kmem.h> 35 36 #include <dev/clk/clk.h> 37 #include <dev/clk/clk_backend.h> 38 39 static struct sysctllog *clk_log; 40 static const struct sysctlnode *clk_node; 41 42 static int 43 create_clk_node(void) 44 { 45 const struct sysctlnode *hw_node; 46 int error; 47 48 if (clk_node) 49 return 0; 50 51 error = sysctl_createv(&clk_log, 0, NULL, &hw_node, 52 CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL, 53 NULL, 0, NULL, 0, CTL_HW, CTL_EOL); 54 if (error) 55 return error; 56 57 error = sysctl_createv(&clk_log, 0, &hw_node, &clk_node, 58 CTLFLAG_PERMANENT, CTLTYPE_NODE, "clk", NULL, 59 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); 60 if (error) 61 return error; 62 63 return 0; 64 } 65 66 static int 67 create_domain_node(struct clk_domain *domain) 68 { 69 int error; 70 71 if (domain->node) 72 return 0; 73 74 error = create_clk_node(); 75 if (error) 76 return error; 77 78 error = sysctl_createv(&clk_log, 0, &clk_node, &domain->node, 79 0, CTLTYPE_NODE, domain->name, NULL, 80 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); 81 if (error) 82 return error; 83 84 return 0; 85 } 86 87 static int 88 clk_sysctl_rate_helper(SYSCTLFN_ARGS) 89 { 90 struct sysctlnode node; 91 struct clk *clk; 92 uint64_t rate; 93 94 node = *rnode; 95 clk = node.sysctl_data; 96 node.sysctl_data = &rate; 97 98 rate = clk_get_rate(clk); 99 100 return sysctl_lookup(SYSCTLFN_CALL(&node)); 101 } 102 103 static int 104 clk_sysctl_parent_helper(SYSCTLFN_ARGS) 105 { 106 struct sysctlnode node; 107 struct clk *clk, *clk_parent; 108 109 node = *rnode; 110 clk = node.sysctl_data; 111 112 clk_parent = clk_get_parent(clk); 113 if (clk_parent && clk_parent->name) 114 node.sysctl_data = __UNCONST(clk_parent->name); 115 else 116 node.sysctl_data = __UNCONST("?"); 117 118 return sysctl_lookup(SYSCTLFN_CALL(&node)); 119 } 120 121 static int 122 clk_sysctl_parent_domain_helper(SYSCTLFN_ARGS) 123 { 124 struct sysctlnode node; 125 struct clk *clk, *clk_parent; 126 127 node = *rnode; 128 clk = node.sysctl_data; 129 130 clk_parent = clk_get_parent(clk); 131 if (clk_parent && clk_parent->domain && clk_parent->domain->name) 132 node.sysctl_data = __UNCONST(clk_parent->domain->name); 133 else 134 node.sysctl_data = __UNCONST("?"); 135 136 return sysctl_lookup(SYSCTLFN_CALL(&node)); 137 } 138 139 static void 140 clk_normalize_name(char *name) 141 { 142 unsigned char *p; 143 144 for (p = (unsigned char *)name; *p; p++) 145 if (!isalpha(*p) && !isdigit(*p) && *p != '-' && *p != '_') 146 *p = '_'; 147 } 148 149 int 150 clk_attach(struct clk *clk) 151 { 152 const struct sysctlnode *node; 153 struct clk_domain *domain = clk->domain; 154 char *name; 155 size_t namelen; 156 int error; 157 158 KASSERT(domain != NULL); 159 160 if (!domain->name || !clk->name) { 161 /* Names are required to create sysctl nodes */ 162 return 0; 163 } 164 165 namelen = strlen(clk->name) + 1; 166 name = kmem_zalloc(namelen, KM_SLEEP); 167 memcpy(name, clk->name, namelen); 168 clk_normalize_name(name); 169 170 error = create_domain_node(domain); 171 if (error != 0) 172 goto sysctl_failed; 173 174 error = sysctl_createv(&clk_log, 0, &domain->node, &node, 175 CTLFLAG_PRIVATE, CTLTYPE_NODE, name, NULL, 176 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); 177 if (error) 178 goto sysctl_failed; 179 180 error = sysctl_createv(&clk_log, 0, &node, NULL, 181 CTLFLAG_PRIVATE, CTLTYPE_QUAD, "rate", NULL, 182 clk_sysctl_rate_helper, 0, (void *)clk, 0, 183 CTL_CREATE, CTL_EOL); 184 if (error) 185 goto sysctl_failed; 186 187 error = sysctl_createv(&clk_log, 0, &node, NULL, 188 CTLFLAG_PRIVATE, CTLTYPE_STRING, "parent", NULL, 189 clk_sysctl_parent_helper, 0, (void *)clk, 0, 190 CTL_CREATE, CTL_EOL); 191 if (error) 192 goto sysctl_failed; 193 194 error = sysctl_createv(&clk_log, 0, &node, NULL, 195 CTLFLAG_PRIVATE, CTLTYPE_STRING, "parent_domain", NULL, 196 clk_sysctl_parent_domain_helper, 0, (void *)clk, 0, 197 CTL_CREATE, CTL_EOL); 198 if (error) 199 goto sysctl_failed; 200 201 sysctl_failed: 202 if (error) 203 aprint_error("%s: failed to create sysctl node for %s (%s): %d\n", 204 domain->name, clk->name, name, error); 205 206 kmem_free(name, namelen); 207 return error; 208 } 209 210 struct clk * 211 clk_get(struct clk_domain *domain, const char *name) 212 { 213 return domain->funcs->get(domain->priv, name); 214 } 215 216 void 217 clk_put(struct clk *clk) 218 { 219 if (clk->domain->funcs->put) 220 clk->domain->funcs->put(clk->domain->priv, clk); 221 } 222 223 u_int 224 clk_get_rate(struct clk *clk) 225 { 226 return clk->domain->funcs->get_rate(clk->domain->priv, clk); 227 } 228 229 int 230 clk_set_rate(struct clk *clk, u_int rate) 231 { 232 if (clk->flags & CLK_SET_RATE_PARENT) 233 return clk_set_rate(clk_get_parent(clk), rate); 234 235 if (clk->domain->funcs->set_rate) 236 return clk->domain->funcs->set_rate(clk->domain->priv, 237 clk, rate); 238 239 if (clk_get_rate(clk) == rate) 240 return 0; 241 242 return EINVAL; 243 } 244 245 u_int 246 clk_round_rate(struct clk *clk, u_int rate) 247 { 248 if (clk->domain->funcs->round_rate) { 249 return clk->domain->funcs->round_rate(clk->domain->priv, 250 clk, rate); 251 } 252 return 0; 253 } 254 255 int 256 clk_enable(struct clk *clk) 257 { 258 if (clk->domain->funcs->enable) 259 return clk->domain->funcs->enable(clk->domain->priv, clk); 260 else 261 return 0; 262 } 263 264 int 265 clk_disable(struct clk *clk) 266 { 267 if (clk->domain->funcs->disable) 268 return clk->domain->funcs->disable(clk->domain->priv, clk); 269 else 270 return EINVAL; 271 } 272 273 int 274 clk_set_parent(struct clk *clk, struct clk *parent_clk) 275 { 276 if (clk->domain->funcs->set_parent) 277 return clk->domain->funcs->set_parent(clk->domain->priv, 278 clk, parent_clk); 279 else 280 return EINVAL; 281 } 282 283 struct clk * 284 clk_get_parent(struct clk *clk) 285 { 286 if (clk->domain->funcs->get_parent) 287 return clk->domain->funcs->get_parent(clk->domain->priv, clk); 288 else 289 return NULL; 290 } 291