1 /*- 2 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org> 3 * Copyright (c) 2010, Broadcom Corporation. 4 * All rights reserved. 5 * 6 * This file is derived from the siutils.c source distributed with the 7 * Asus RT-N16 firmware source code release. 8 * 9 * Permission to use, copy, modify, and/or distribute this software for any 10 * purpose with or without fee is hereby granted, provided that the above 11 * copyright notice and this permission notice appear in all copies. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 16 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 18 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 19 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 * 21 * $Id: siutils.c,v 1.821.2.48 2011-02-11 20:59:28 Exp $ 22 */ 23 24 #include <sys/cdefs.h> 25 __FBSDID("$FreeBSD$"); 26 27 #include <sys/param.h> 28 #include <sys/kernel.h> 29 #include <sys/bus.h> 30 #include <sys/limits.h> 31 #include <sys/malloc.h> 32 #include <sys/module.h> 33 #include <sys/systm.h> 34 35 #include <dev/bhnd/bhnd.h> 36 37 #include <dev/bhnd/cores/chipc/chipcreg.h> 38 #include <dev/bhnd/cores/chipc/chipcvar.h> 39 40 #include <dev/bhnd/cores/pmu/bhnd_pmuvar.h> 41 #include <dev/bhnd/cores/pmu/bhnd_pmureg.h> 42 43 #include "bhnd_chipc_if.h" 44 45 #include "bhnd_pwrctl_private.h" 46 47 /* 48 * ChipCommon Power Control. 49 * 50 * Provides a bhnd_pmu_if-compatible interface to device clocking and 51 * power management on non-PMU chipsets. 52 */ 53 54 typedef enum { 55 BHND_PWRCTL_WAR_UP, /**< apply attach/resume workarounds */ 56 BHND_PWRCTL_WAR_RUN, /**< apply running workarounds */ 57 BHND_PWRCTL_WAR_DOWN, /**< apply detach/suspend workarounds */ 58 } bhnd_pwrctl_wars; 59 60 static int bhnd_pwrctl_updateclk(struct bhnd_pwrctl_softc *sc, 61 bhnd_pwrctl_wars wars); 62 63 static struct bhnd_device_quirk pwrctl_quirks[]; 64 65 66 /* Supported parent core device identifiers */ 67 static const struct bhnd_device pwrctl_devices[] = { 68 BHND_DEVICE(BCM, CC, "ChipCommon Power Control", pwrctl_quirks), 69 BHND_DEVICE_END 70 }; 71 72 /* Device quirks table */ 73 static struct bhnd_device_quirk pwrctl_quirks[] = { 74 BHND_CORE_QUIRK (HWREV_LTE(5), PWRCTL_QUIRK_PCICLK_CTL), 75 BHND_CORE_QUIRK (HWREV_RANGE(6, 9), PWRCTL_QUIRK_SLOWCLK_CTL), 76 BHND_CORE_QUIRK (HWREV_RANGE(10, 19), PWRCTL_QUIRK_INSTACLK_CTL), 77 78 BHND_DEVICE_QUIRK_END 79 }; 80 81 static int 82 bhnd_pwrctl_probe(device_t dev) 83 { 84 const struct bhnd_device *id; 85 struct chipc_caps *ccaps; 86 device_t chipc; 87 88 /* Look for compatible chipc parent */ 89 chipc = device_get_parent(dev); 90 if (device_get_devclass(chipc) != devclass_find("bhnd_chipc")) 91 return (ENXIO); 92 93 if (device_get_driver(chipc) != &bhnd_chipc_driver) 94 return (ENXIO); 95 96 /* Verify chipc capability flags */ 97 ccaps = BHND_CHIPC_GET_CAPS(chipc); 98 if (ccaps->pmu || !ccaps->pwr_ctrl) 99 return (ENXIO); 100 101 /* Check for chipc device match */ 102 id = bhnd_device_lookup(chipc, pwrctl_devices, 103 sizeof(pwrctl_devices[0])); 104 if (id == NULL) 105 return (ENXIO); 106 107 device_set_desc(dev, id->desc); 108 return (BUS_PROBE_NOWILDCARD); 109 } 110 111 static int 112 bhnd_pwrctl_attach(device_t dev) 113 { 114 struct bhnd_pwrctl_softc *sc; 115 const struct bhnd_chipid *cid; 116 struct chipc_softc *chipc_sc; 117 bhnd_devclass_t hostb_class; 118 device_t hostb_dev; 119 int error; 120 121 sc = device_get_softc(dev); 122 123 /* TODO: Need further testing on actual PWRCTL hardware */ 124 device_printf(dev, "WARNING: Using untested PWRCTL support\n"); 125 126 sc->dev = dev; 127 sc->chipc_dev = device_get_parent(dev); 128 sc->quirks = bhnd_device_quirks(sc->chipc_dev, pwrctl_devices, 129 sizeof(pwrctl_devices[0])); 130 131 /* On devices that lack a slow clock source, HT must always be 132 * enabled. */ 133 hostb_class = BHND_DEVCLASS_INVALID; 134 hostb_dev = bhnd_find_hostb_device(device_get_parent(sc->chipc_dev)); 135 if (hostb_dev != NULL) 136 hostb_class = bhnd_get_class(hostb_dev); 137 138 cid = bhnd_get_chipid(sc->chipc_dev); 139 switch (cid->chip_id) { 140 case BHND_CHIPID_BCM4311: 141 if (cid->chip_rev <= 1 && hostb_class == BHND_DEVCLASS_PCI) 142 sc->quirks |= PWRCTL_QUIRK_FORCE_HT; 143 break; 144 145 case BHND_CHIPID_BCM4321: 146 if (hostb_class == BHND_DEVCLASS_PCIE || 147 hostb_class == BHND_DEVCLASS_PCI) 148 sc->quirks |= PWRCTL_QUIRK_FORCE_HT; 149 break; 150 151 case BHND_CHIPID_BCM4716: 152 if (hostb_class == BHND_DEVCLASS_PCIE) 153 sc->quirks |= PWRCTL_QUIRK_FORCE_HT; 154 break; 155 } 156 157 /* Fetch core register block from ChipCommon parent */ 158 chipc_sc = device_get_softc(sc->chipc_dev); 159 sc->res = chipc_sc->core; 160 161 PWRCTL_LOCK_INIT(sc); 162 STAILQ_INIT(&sc->clkres_list); 163 164 /* Initialize power control */ 165 PWRCTL_LOCK(sc); 166 167 if ((error = bhnd_pwrctl_init(sc))) { 168 PWRCTL_UNLOCK(sc); 169 goto cleanup; 170 } 171 172 /* Apply default clock transitions */ 173 if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_UP))) { 174 PWRCTL_UNLOCK(sc); 175 goto cleanup; 176 } 177 178 PWRCTL_UNLOCK(sc); 179 180 return (0); 181 182 cleanup: 183 PWRCTL_LOCK_DESTROY(sc); 184 return (error); 185 } 186 187 static int 188 bhnd_pwrctl_detach(device_t dev) 189 { 190 struct bhnd_pwrctl_softc *sc; 191 struct bhnd_pwrctl_clkres *clkres, *crnext; 192 int error; 193 194 sc = device_get_softc(dev); 195 196 if ((error = bhnd_pwrctl_setclk(sc, BHND_CLOCK_DYN))) 197 return (error); 198 199 STAILQ_FOREACH_SAFE(clkres, &sc->clkres_list, cr_link, crnext) 200 free(clkres, M_DEVBUF); 201 202 PWRCTL_LOCK_DESTROY(sc); 203 return (0); 204 } 205 206 static int 207 bhnd_pwrctl_suspend(device_t dev) 208 { 209 struct bhnd_pwrctl_softc *sc; 210 int error; 211 212 sc = device_get_softc(dev); 213 214 /* Update clock state */ 215 PWRCTL_LOCK(sc); 216 error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_DOWN); 217 PWRCTL_UNLOCK(sc); 218 219 return (error); 220 } 221 222 static int 223 bhnd_pwrctl_resume(device_t dev) 224 { 225 struct bhnd_pwrctl_softc *sc; 226 int error; 227 228 sc = device_get_softc(dev); 229 230 PWRCTL_LOCK(sc); 231 232 /* Re-initialize power control registers */ 233 if ((error = bhnd_pwrctl_init(sc))) { 234 device_printf(sc->dev, "PWRCTL init failed: %d\n", error); 235 goto cleanup; 236 } 237 238 /* Restore clock state */ 239 if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_UP))) { 240 device_printf(sc->dev, "clock state restore failed: %d\n", 241 error); 242 goto cleanup; 243 } 244 245 cleanup: 246 PWRCTL_UNLOCK(sc); 247 return (error); 248 } 249 250 /** 251 * Find the clock reservation associated with @p pinfo, if any. 252 * 253 * @param sc Driver instance state. 254 * @param pinfo PMU info for device. 255 */ 256 static struct bhnd_pwrctl_clkres * 257 bhnd_pwrctl_find_res(struct bhnd_pwrctl_softc *sc, 258 struct bhnd_core_pmu_info *pinfo) 259 { 260 struct bhnd_pwrctl_clkres *clkres; 261 262 PWRCTL_LOCK_ASSERT(sc, MA_OWNED); 263 264 STAILQ_FOREACH(clkres, &sc->clkres_list, cr_link) { 265 if (clkres->owner == pinfo->pm_dev) 266 return (clkres); 267 } 268 269 /* not found */ 270 return (NULL); 271 } 272 273 /** 274 * Enumerate all active clock requests, compute the minimum required clock, 275 * and issue any required clock transition. 276 * 277 * @param sc Driver instance state. 278 * @param wars Work-around state. 279 */ 280 static int 281 bhnd_pwrctl_updateclk(struct bhnd_pwrctl_softc *sc, bhnd_pwrctl_wars wars) 282 { 283 struct bhnd_pwrctl_clkres *clkres; 284 bhnd_clock clock; 285 286 PWRCTL_LOCK_ASSERT(sc, MA_OWNED); 287 288 /* Default clock target */ 289 clock = BHND_CLOCK_DYN; 290 291 /* Apply quirk-specific overrides to the clock target */ 292 switch (wars) { 293 case BHND_PWRCTL_WAR_UP: 294 /* Force HT clock */ 295 if (PWRCTL_QUIRK(sc, FORCE_HT)) 296 clock = BHND_CLOCK_HT; 297 break; 298 299 case BHND_PWRCTL_WAR_RUN: 300 /* Cannot transition clock if FORCE_HT */ 301 if (PWRCTL_QUIRK(sc, FORCE_HT)) 302 return (0); 303 break; 304 305 case BHND_PWRCTL_WAR_DOWN: 306 /* Leave default clock unmodified to permit 307 * transition back to BHND_CLOCK_DYN on FORCE_HT devices. */ 308 break; 309 } 310 311 /* Determine required clock */ 312 STAILQ_FOREACH(clkres, &sc->clkres_list, cr_link) 313 clock = bhnd_clock_max(clock, clkres->clock); 314 315 /* Map to supported clock setting */ 316 switch (clock) { 317 case BHND_CLOCK_DYN: 318 case BHND_CLOCK_ILP: 319 clock = BHND_CLOCK_DYN; 320 break; 321 case BHND_CLOCK_ALP: 322 /* In theory FORCE_ALP is supported by the hardware, but 323 * there are currently no known use-cases for it; mapping 324 * to HT is still valid, and allows us to punt on determing 325 * where FORCE_ALP is supported and functional */ 326 clock = BHND_CLOCK_HT; 327 break; 328 case BHND_CLOCK_HT: 329 break; 330 default: 331 device_printf(sc->dev, "unknown clock: %#x\n", clock); 332 return (ENODEV); 333 } 334 335 /* Issue transition */ 336 return (bhnd_pwrctl_setclk(sc, clock)); 337 } 338 339 static int 340 bhnd_pwrctl_core_req_clock(device_t dev, struct bhnd_core_pmu_info *pinfo, 341 bhnd_clock clock) 342 { 343 struct bhnd_pwrctl_softc *sc; 344 struct bhnd_pwrctl_clkres *clkres; 345 int error; 346 347 sc = device_get_softc(dev); 348 error = 0; 349 350 PWRCTL_LOCK(sc); 351 352 clkres = bhnd_pwrctl_find_res(sc, pinfo); 353 354 /* BHND_CLOCK_DYN discards the clock reservation entirely */ 355 if (clock == BHND_CLOCK_DYN) { 356 /* nothing to clean up? */ 357 if (clkres == NULL) { 358 PWRCTL_UNLOCK(sc); 359 return (0); 360 } 361 362 /* drop reservation and apply clock transition */ 363 STAILQ_REMOVE(&sc->clkres_list, clkres, 364 bhnd_pwrctl_clkres, cr_link); 365 366 if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_RUN))) { 367 device_printf(dev, "clock transition failed: %d\n", 368 error); 369 370 /* restore reservation */ 371 STAILQ_INSERT_TAIL(&sc->clkres_list, clkres, cr_link); 372 373 PWRCTL_UNLOCK(sc); 374 return (error); 375 } 376 377 /* deallocate orphaned reservation */ 378 free(clkres, M_DEVBUF); 379 380 PWRCTL_UNLOCK(sc); 381 return (0); 382 } 383 384 /* create (or update) reservation */ 385 if (clkres == NULL) { 386 clkres = malloc(sizeof(struct bhnd_pwrctl_clkres), M_DEVBUF, 387 M_NOWAIT); 388 if (clkres == NULL) 389 return (ENOMEM); 390 391 clkres->owner = pinfo->pm_dev; 392 clkres->clock = clock; 393 394 STAILQ_INSERT_TAIL(&sc->clkres_list, clkres, cr_link); 395 } else { 396 KASSERT(clkres->owner == pinfo->pm_dev, ("invalid owner")); 397 clkres->clock = clock; 398 } 399 400 /* apply clock transition */ 401 error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_RUN); 402 if (error) { 403 STAILQ_REMOVE(&sc->clkres_list, clkres, bhnd_pwrctl_clkres, 404 cr_link); 405 free(clkres, M_DEVBUF); 406 } 407 408 PWRCTL_UNLOCK(sc); 409 return (error); 410 } 411 412 static int 413 bhnd_pwrctl_core_req_ext_rsrc(device_t dev, struct bhnd_core_pmu_info *pinfo, 414 u_int rsrc) 415 { 416 /* HW does not support per-core external resources */ 417 return (ENODEV); 418 } 419 420 static int 421 bhnd_pwrctl_core_release_ext_rsrc(device_t dev, 422 struct bhnd_core_pmu_info *pinfo, u_int rsrc) 423 { 424 /* HW does not support per-core external resources */ 425 return (ENODEV); 426 } 427 428 static int 429 bhnd_pwrctl_core_en_clocks(device_t dev, struct bhnd_core_pmu_info *pinfo, 430 uint32_t clocks) 431 { 432 /* All supported clocks are already enabled by default (?) */ 433 clocks &= ~(BHND_CLOCK_DYN | 434 BHND_CLOCK_ILP | 435 BHND_CLOCK_ALP | 436 BHND_CLOCK_HT); 437 438 if (clocks != 0) { 439 device_printf(dev, "%s requested unknown clocks: %#x\n", 440 device_get_nameunit(pinfo->pm_dev), clocks); 441 return (ENODEV); 442 } 443 444 return (0); 445 } 446 447 static int 448 bhnd_pwrctl_core_release(device_t dev, struct bhnd_core_pmu_info *pinfo) 449 { 450 /* Requesting BHND_CLOCK_DYN releases any outstanding clock 451 * reservations */ 452 return (bhnd_pwrctl_core_req_clock(dev, pinfo, BHND_CLOCK_DYN)); 453 } 454 455 static device_method_t bhnd_pwrctl_methods[] = { 456 /* Device interface */ 457 DEVMETHOD(device_probe, bhnd_pwrctl_probe), 458 DEVMETHOD(device_attach, bhnd_pwrctl_attach), 459 DEVMETHOD(device_detach, bhnd_pwrctl_detach), 460 DEVMETHOD(device_suspend, bhnd_pwrctl_suspend), 461 DEVMETHOD(device_resume, bhnd_pwrctl_resume), 462 463 /* BHND PMU interface */ 464 DEVMETHOD(bhnd_pmu_core_req_clock, bhnd_pwrctl_core_req_clock), 465 DEVMETHOD(bhnd_pmu_core_en_clocks, bhnd_pwrctl_core_en_clocks), 466 DEVMETHOD(bhnd_pmu_core_req_ext_rsrc, bhnd_pwrctl_core_req_ext_rsrc), 467 DEVMETHOD(bhnd_pmu_core_release_ext_rsrc, bhnd_pwrctl_core_release_ext_rsrc), 468 DEVMETHOD(bhnd_pmu_core_release, bhnd_pwrctl_core_release), 469 470 DEVMETHOD_END 471 }; 472 473 DEFINE_CLASS_0(bhnd_pmu, bhnd_pwrctl_driver, bhnd_pwrctl_methods, 474 sizeof(struct bhnd_pwrctl_softc)); 475 EARLY_DRIVER_MODULE(bhnd_pwrctl, bhnd_chipc, bhnd_pwrctl_driver, 476 bhnd_pmu_devclass, NULL, NULL, BUS_PASS_TIMER + BUS_PASS_ORDER_MIDDLE); 477 478 MODULE_DEPEND(bhnd_pwrctl, bhnd, 1, 1, 1); 479 MODULE_VERSION(bhnd_pwrctl, 1); 480