1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2017 Kyle Evans <[email protected]> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 /* 29 * This is a generic syscon driver, whose purpose is to provide access to 30 * various unrelated bits packed in a single register space. It is usually used 31 * as a fallback to more specific driver, but works well enough for simple 32 * access. 33 */ 34 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 #include "opt_platform.h" 38 39 #include <sys/param.h> 40 #include <sys/systm.h> 41 #include <sys/bus.h> 42 #include <sys/kernel.h> 43 #include <sys/kobj.h> 44 #include <sys/lock.h> 45 #include <sys/malloc.h> 46 #include <sys/module.h> 47 #include <sys/rman.h> 48 #include <sys/sx.h> 49 #include <sys/queue.h> 50 51 #include <machine/bus.h> 52 53 #ifdef FDT 54 #include <dev/ofw/ofw_bus.h> 55 #include <dev/ofw/ofw_bus_subr.h> 56 #endif 57 58 #include "syscon_if.h" 59 #include "syscon.h" 60 61 /* 62 * Syscon interface details 63 */ 64 typedef TAILQ_HEAD(syscon_list, syscon) syscon_list_t; 65 66 /* 67 * Declarations 68 */ 69 static int syscon_method_init(struct syscon *syscon); 70 static int syscon_method_uninit(struct syscon *syscon); 71 static uint32_t syscon_method_read_4(struct syscon *syscon, bus_size_t offset); 72 static int syscon_method_write_4(struct syscon *syscon, bus_size_t offset, 73 uint32_t val); 74 static int syscon_method_modify_4(struct syscon *syscon, bus_size_t offset, 75 uint32_t clear_bits, uint32_t set_bits); 76 77 78 MALLOC_DEFINE(M_SYSCON, "syscon", "Syscon driver"); 79 80 static syscon_list_t syscon_list = TAILQ_HEAD_INITIALIZER(syscon_list); 81 static struct sx syscon_topo_lock; 82 SX_SYSINIT(syscon_topology, &syscon_topo_lock, "Syscon topology lock"); 83 84 /* 85 * Syscon methods. 86 */ 87 static syscon_method_t syscon_methods[] = { 88 SYSCONMETHOD(syscon_init, syscon_method_init), 89 SYSCONMETHOD(syscon_uninit, syscon_method_uninit), 90 SYSCONMETHOD(syscon_read_4, syscon_method_read_4), 91 SYSCONMETHOD(syscon_write_4, syscon_method_write_4), 92 SYSCONMETHOD(syscon_modify_4, syscon_method_modify_4), 93 94 SYSCONMETHOD_END 95 }; 96 DEFINE_CLASS_0(syscon, syscon_class, syscon_methods, 0); 97 98 #define SYSCON_TOPO_SLOCK() sx_slock(&syscon_topo_lock) 99 #define SYSCON_TOPO_XLOCK() sx_xlock(&syscon_topo_lock) 100 #define SYSCON_TOPO_UNLOCK() sx_unlock(&syscon_topo_lock) 101 #define SYSCON_TOPO_ASSERT() sx_assert(&syscon_topo_lock, SA_LOCKED) 102 #define SYSCON_TOPO_XASSERT() sx_assert(&syscon_topo_lock, SA_XLOCKED) 103 104 /* 105 * Default syscon methods for base class. 106 */ 107 static int 108 syscon_method_init(struct syscon *syscon) 109 { 110 111 return (0); 112 }; 113 114 static int 115 syscon_method_uninit(struct syscon *syscon) 116 { 117 118 return (0); 119 }; 120 121 void * 122 syscon_get_softc(struct syscon *syscon) 123 { 124 125 return (syscon->softc); 126 }; 127 128 static uint32_t 129 syscon_method_read_4(struct syscon *syscon, bus_size_t offset) 130 { 131 uint32_t val; 132 133 SYSCON_DEVICE_LOCK(syscon->pdev); 134 val = SYSCON_UNLOCKED_READ_4(syscon, offset); 135 SYSCON_DEVICE_UNLOCK(syscon->pdev); 136 return(val); 137 } 138 139 static int 140 syscon_method_write_4(struct syscon *syscon, bus_size_t offset, uint32_t val) 141 { 142 int rv; 143 144 SYSCON_DEVICE_LOCK(syscon->pdev); 145 rv = SYSCON_UNLOCKED_WRITE_4(syscon, offset, val); 146 SYSCON_DEVICE_UNLOCK(syscon->pdev); 147 return(rv); 148 } 149 150 static int 151 syscon_method_modify_4(struct syscon *syscon, bus_size_t offset, 152 uint32_t clear_bits, uint32_t set_bits) 153 { 154 int rv; 155 156 SYSCON_DEVICE_LOCK(syscon->pdev); 157 rv = SYSCON_UNLOCKED_MODIFY_4(syscon, offset, clear_bits, set_bits); 158 SYSCON_DEVICE_UNLOCK(syscon->pdev); 159 return(rv); 160 } 161 /* 162 * Create and initialize syscon object, but do not register it. 163 */ 164 struct syscon * 165 syscon_create(device_t pdev, syscon_class_t syscon_class) 166 { 167 struct syscon *syscon; 168 169 /* Create object and initialize it. */ 170 syscon = malloc(sizeof(struct syscon), M_SYSCON, 171 M_WAITOK | M_ZERO); 172 kobj_init((kobj_t)syscon, (kobj_class_t)syscon_class); 173 174 /* Allocate softc if required. */ 175 if (syscon_class->size > 0) 176 syscon->softc = malloc(syscon_class->size, M_SYSCON, 177 M_WAITOK | M_ZERO); 178 179 /* Rest of init. */ 180 syscon->pdev = pdev; 181 return (syscon); 182 } 183 184 /* Register syscon object. */ 185 struct syscon * 186 syscon_register(struct syscon *syscon) 187 { 188 int rv; 189 190 #ifdef FDT 191 if (syscon->ofw_node <= 0) 192 syscon->ofw_node = ofw_bus_get_node(syscon->pdev); 193 if (syscon->ofw_node <= 0) 194 return (NULL); 195 #endif 196 197 rv = SYSCON_INIT(syscon); 198 if (rv != 0) { 199 printf("SYSCON_INIT failed: %d\n", rv); 200 return (NULL); 201 } 202 203 #ifdef FDT 204 OF_device_register_xref(OF_xref_from_node(syscon->ofw_node), 205 syscon->pdev); 206 #endif 207 SYSCON_TOPO_XLOCK(); 208 TAILQ_INSERT_TAIL(&syscon_list, syscon, syscon_link); 209 SYSCON_TOPO_UNLOCK(); 210 return (syscon); 211 } 212 213 int 214 syscon_unregister(struct syscon *syscon) 215 { 216 217 SYSCON_TOPO_XLOCK(); 218 TAILQ_REMOVE(&syscon_list, syscon, syscon_link); 219 SYSCON_TOPO_UNLOCK(); 220 #ifdef FDT 221 OF_device_register_xref(OF_xref_from_node(syscon->ofw_node), NULL); 222 #endif 223 return (SYSCON_UNINIT(syscon)); 224 } 225 226 /** 227 * Provider methods 228 */ 229 #ifdef FDT 230 static struct syscon * 231 syscon_find_by_ofw_node(phandle_t node) 232 { 233 struct syscon *entry; 234 235 SYSCON_TOPO_ASSERT(); 236 237 TAILQ_FOREACH(entry, &syscon_list, syscon_link) { 238 if (entry->ofw_node == node) 239 return (entry); 240 } 241 242 return (NULL); 243 } 244 245 struct syscon * 246 syscon_create_ofw_node(device_t pdev, syscon_class_t syscon_class, 247 phandle_t node) 248 { 249 struct syscon *syscon; 250 251 syscon = syscon_create(pdev, syscon_class); 252 if (syscon == NULL) 253 return (NULL); 254 syscon->ofw_node = node; 255 if (syscon_register(syscon) == NULL) 256 return (NULL); 257 return (syscon); 258 } 259 260 phandle_t 261 syscon_get_ofw_node(struct syscon *syscon) 262 { 263 264 return (syscon->ofw_node); 265 } 266 267 int 268 syscon_get_by_ofw_node(device_t cdev, phandle_t node, struct syscon **syscon) 269 { 270 271 SYSCON_TOPO_SLOCK(); 272 *syscon = syscon_find_by_ofw_node(node); 273 if (*syscon == NULL) { 274 SYSCON_TOPO_UNLOCK(); 275 device_printf(cdev, "Failed to find syscon node\n"); 276 return (ENODEV); 277 } 278 SYSCON_TOPO_UNLOCK(); 279 return (0); 280 } 281 282 int 283 syscon_get_by_ofw_property(device_t cdev, phandle_t cnode, char *name, 284 struct syscon **syscon) 285 { 286 pcell_t *cells; 287 int ncells; 288 289 if (cnode <= 0) 290 cnode = ofw_bus_get_node(cdev); 291 if (cnode <= 0) { 292 device_printf(cdev, 293 "%s called on not ofw based device\n", __func__); 294 return (ENXIO); 295 } 296 ncells = OF_getencprop_alloc_multi(cnode, name, sizeof(pcell_t), 297 (void **)&cells); 298 if (ncells < 1) 299 return (ENOENT); 300 301 /* Translate to syscon node. */ 302 SYSCON_TOPO_SLOCK(); 303 *syscon = syscon_find_by_ofw_node(OF_node_from_xref(cells[0])); 304 if (*syscon == NULL) { 305 SYSCON_TOPO_UNLOCK(); 306 device_printf(cdev, "Failed to find syscon node\n"); 307 OF_prop_free(cells); 308 return (ENODEV); 309 } 310 SYSCON_TOPO_UNLOCK(); 311 OF_prop_free(cells); 312 return (0); 313 } 314 #endif 315