The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/dev/bhnd/cores/chipc/pwrctl/bhnd_pwrctl.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

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

Cache object: 14b2bae4735898f3d64bcde2610e8335


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.