root/sys/dev/bhnd/cores/pmu/bhnd_pmu.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
 * Copyright (c) 2017 The FreeBSD Foundation
 * All rights reserved.
 *
 * Portions of this software were developed by Landon Fuller
 * under sponsorship from the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

#include <machine/bus.h>
#include <machine/resource.h>

#include <dev/bhnd/bhndreg.h>
#include <dev/bhnd/bhndvar.h>
#include <dev/bhnd/cores/chipc/chipc.h>

#include "bhnd_nvram_map.h"

#include "bhnd_pmureg.h"
#include "bhnd_pmuvar.h"

#include "bhnd_pmu_private.h"

/*
 * Broadcom PMU driver.
 * 
 * On modern BHND chipsets, the PMU, GCI, and SRENG (Save/Restore Engine?)
 * register blocks are found within a dedicated PMU core (attached via
 * the AHB 'always on bus').
 * 
 * On earlier chipsets, these register blocks are found at the same
 * offsets within the ChipCommon core.
 */

static int      bhnd_pmu_sysctl_bus_freq(SYSCTL_HANDLER_ARGS);
static int      bhnd_pmu_sysctl_cpu_freq(SYSCTL_HANDLER_ARGS);
static int      bhnd_pmu_sysctl_mem_freq(SYSCTL_HANDLER_ARGS);

static uint32_t bhnd_pmu_read_4(bus_size_t reg, void *ctx);
static void     bhnd_pmu_write_4(bus_size_t reg, uint32_t val, void *ctx);
static uint32_t bhnd_pmu_read_chipst(void *ctx);

static const struct bhnd_pmu_io bhnd_pmu_res_io = {
        .rd4            = bhnd_pmu_read_4,
        .wr4            = bhnd_pmu_write_4,
        .rd_chipst      = bhnd_pmu_read_chipst
};

/**
 * Default bhnd_pmu driver implementation of DEVICE_PROBE().
 */
int
bhnd_pmu_probe(device_t dev)
{
        return (BUS_PROBE_DEFAULT);
}

/**
 * Default bhnd_pmu driver implementation of DEVICE_ATTACH().
 * 
 * @param dev PMU device.
 * @param res The PMU device registers. The driver will maintain a borrowed
 * reference to this resource for the lifetime of the device.
 */
int
bhnd_pmu_attach(device_t dev, struct bhnd_resource *res)
{
        struct bhnd_pmu_softc   *sc;
        struct sysctl_ctx_list  *ctx;
        struct sysctl_oid       *tree;
        devclass_t               bhnd_class;
        device_t                 core, bus;
        int                      error;

        sc = device_get_softc(dev);
        sc->dev = dev;
        sc->res = res;

        /* Fetch capability flags */
        sc->caps = bhnd_bus_read_4(sc->res, BHND_PMU_CAP);

        /* Find the bus and bus-attached core */
        bhnd_class = devclass_find("bhnd");
        core = sc->dev;
        while ((bus = device_get_parent(core)) != NULL) {
                if (device_get_devclass(bus) == bhnd_class)
                        break;

                core = bus;
        }

        if (core == NULL) {
                device_printf(sc->dev, "bhnd bus not found\n");
                return (ENXIO);
        }

        /* Fetch chip and board info */
        sc->cid = *bhnd_get_chipid(core);
        if ((error = bhnd_read_board_info(core, &sc->board))) {
                device_printf(sc->dev, "error fetching board info: %d\n",
                    error);
                return (ENXIO);
        }

        /* Initialize query state */
        error = bhnd_pmu_query_init(&sc->query, dev, sc->cid, &bhnd_pmu_res_io,
            sc);
        if (error)
                return (error);
        sc->io = sc->query.io; 
        sc->io_ctx = sc->query.io_ctx;

        BPMU_LOCK_INIT(sc);

        /* Allocate our own core clkctl state directly; we use this to wait on
         * PMU state transitions, avoiding a cyclic dependency between bhnd(4)'s
         * clkctl handling and registration of this device as a PMU */
        sc->clkctl = bhnd_alloc_core_clkctl(core, dev, sc->res, BHND_CLK_CTL_ST,
            BHND_PMU_MAX_TRANSITION_DLY);
        if (sc->clkctl == NULL) {
                device_printf(sc->dev, "failed to allocate clkctl for %s\n",
                    device_get_nameunit(core));
                error = ENOMEM;
                goto failed;
        }

        /* Locate ChipCommon device */
        sc->chipc_dev = bhnd_retain_provider(dev, BHND_SERVICE_CHIPC);
        if (sc->chipc_dev == NULL) {
                device_printf(sc->dev, "chipcommon device not found\n");
                error = ENXIO;
                goto failed;
        }

        /* Initialize PMU */
        if ((error = bhnd_pmu_init(sc))) {
                device_printf(sc->dev, "PMU init failed: %d\n", error);
                goto failed;
        }

        /* Register ourselves with the bus */
        if ((error = bhnd_register_provider(dev, BHND_SERVICE_PMU))) {
                device_printf(sc->dev, "failed to register PMU with bus : %d\n",
                    error);
                goto failed;
        }

        /* Set up sysctl nodes */
        ctx = device_get_sysctl_ctx(dev);
        tree = device_get_sysctl_tree(dev);

        SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
            "bus_freq", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0,
            bhnd_pmu_sysctl_bus_freq, "IU", "Bus clock frequency");

        SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
            "cpu_freq", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0,
            bhnd_pmu_sysctl_cpu_freq, "IU", "CPU clock frequency");

        SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
            "mem_freq", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0,
            bhnd_pmu_sysctl_mem_freq, "IU", "Memory clock frequency");

        return (0);

failed:
        BPMU_LOCK_DESTROY(sc);
        bhnd_pmu_query_fini(&sc->query);

        if (sc->clkctl != NULL)
                bhnd_free_core_clkctl(sc->clkctl);

        if (sc->chipc_dev != NULL) {
                bhnd_release_provider(sc->dev, sc->chipc_dev,
                    BHND_SERVICE_CHIPC);
        }

        return (error);
}

/**
 * Default bhnd_pmu driver implementation of DEVICE_DETACH().
 */
int
bhnd_pmu_detach(device_t dev)
{
        struct bhnd_pmu_softc   *sc;
        int                      error;

        sc = device_get_softc(dev);

        if ((error = bhnd_deregister_provider(dev, BHND_SERVICE_ANY)))
                return (error);

        BPMU_LOCK_DESTROY(sc);
        bhnd_pmu_query_fini(&sc->query);
        bhnd_free_core_clkctl(sc->clkctl);
        bhnd_release_provider(sc->dev, sc->chipc_dev, BHND_SERVICE_CHIPC);

        return (0);
}

/**
 * Default bhnd_pmu driver implementation of DEVICE_SUSPEND().
 */
int
bhnd_pmu_suspend(device_t dev)
{
        return (0);
}

/**
 * Default bhnd_pmu driver implementation of DEVICE_RESUME().
 */
int
bhnd_pmu_resume(device_t dev)
{
        struct bhnd_pmu_softc   *sc;
        int                      error;

        sc = device_get_softc(dev);

        /* Re-initialize PMU */
        if ((error = bhnd_pmu_init(sc))) {
                device_printf(sc->dev, "PMU init failed: %d\n", error);
                return (error);
        }

        return (0);
}

static int
bhnd_pmu_sysctl_bus_freq(SYSCTL_HANDLER_ARGS)
{
        struct bhnd_pmu_softc   *sc;
        uint32_t                 freq;

        sc = arg1;

        BPMU_LOCK(sc);
        freq = bhnd_pmu_si_clock(&sc->query);
        BPMU_UNLOCK(sc);

        return (sysctl_handle_32(oidp, NULL, freq, req));
}

static int
bhnd_pmu_sysctl_cpu_freq(SYSCTL_HANDLER_ARGS)
{
        struct bhnd_pmu_softc   *sc;
        uint32_t                 freq;

        sc = arg1;

        BPMU_LOCK(sc);
        freq = bhnd_pmu_cpu_clock(&sc->query);
        BPMU_UNLOCK(sc);

        return (sysctl_handle_32(oidp, NULL, freq, req));
}

static int
bhnd_pmu_sysctl_mem_freq(SYSCTL_HANDLER_ARGS)
{
        struct bhnd_pmu_softc   *sc;
        uint32_t                 freq;

        sc = arg1;

        BPMU_LOCK(sc);
        freq = bhnd_pmu_mem_clock(&sc->query);
        BPMU_UNLOCK(sc);

        return (sysctl_handle_32(oidp, NULL, freq, req));
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_READ_CHIPCTRL().
 */
static uint32_t
bhnd_pmu_read_chipctrl_method(device_t dev, uint32_t reg)
{
        struct bhnd_pmu_softc *sc;
        uint32_t rval;

        sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        rval = BHND_PMU_CCTRL_READ(sc, reg);
        BPMU_UNLOCK(sc);

        return (rval);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_WRITE_CHIPCTRL().
 */
static void
bhnd_pmu_write_chipctrl_method(device_t dev, uint32_t reg, uint32_t value,
    uint32_t mask)
{
        struct bhnd_pmu_softc *sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        BHND_PMU_CCTRL_WRITE(sc, reg, value, mask);
        BPMU_UNLOCK(sc);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_READ_REGCTRL().
 */
static uint32_t
bhnd_pmu_read_regctrl_method(device_t dev, uint32_t reg)
{
        struct bhnd_pmu_softc *sc;
        uint32_t rval;

        sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        rval = BHND_PMU_REGCTRL_READ(sc, reg);
        BPMU_UNLOCK(sc);

        return (rval);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_WRITE_REGCTRL().
 */
static void
bhnd_pmu_write_regctrl_method(device_t dev, uint32_t reg, uint32_t value,
    uint32_t mask)
{
        struct bhnd_pmu_softc *sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        BHND_PMU_REGCTRL_WRITE(sc, reg, value, mask);
        BPMU_UNLOCK(sc);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_READ_PLLCTRL().
 */
static uint32_t
bhnd_pmu_read_pllctrl_method(device_t dev, uint32_t reg)
{
        struct bhnd_pmu_softc *sc;
        uint32_t rval;

        sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        rval = BHND_PMU_PLL_READ(sc, reg);
        BPMU_UNLOCK(sc);

        return (rval);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_WRITE_PLLCTRL().
 */
static void
bhnd_pmu_write_pllctrl_method(device_t dev, uint32_t reg, uint32_t value,
    uint32_t mask)
{
        struct bhnd_pmu_softc *sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        BHND_PMU_PLL_WRITE(sc, reg, value, mask);
        BPMU_UNLOCK(sc);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_SET_VOLTAGE_RAW().
 */
static int
bhnd_pmu_set_voltage_raw_method(device_t dev, bhnd_pmu_regulator regulator,
    uint32_t value)
{
        struct bhnd_pmu_softc   *sc;
        int                      error;

        sc = device_get_softc(dev);

        switch (regulator) {
        case BHND_REGULATOR_PAREF_LDO:
                if (value > UINT8_MAX)
                        return (EINVAL);

                BPMU_LOCK(sc);
                error = bhnd_pmu_set_ldo_voltage(sc, SET_LDO_VOLTAGE_PAREF,
                    value);
                BPMU_UNLOCK(sc);

                return (error);

        default:
                return (ENODEV);
        }
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_ENABLE_REGULATOR().
 */
static int
bhnd_pmu_enable_regulator_method(device_t dev, bhnd_pmu_regulator regulator)
{
        struct bhnd_pmu_softc   *sc;
        int                      error;

        sc = device_get_softc(dev);

        switch (regulator) {
        case BHND_REGULATOR_PAREF_LDO:
                BPMU_LOCK(sc);
                error = bhnd_pmu_paref_ldo_enable(sc, true);
                BPMU_UNLOCK(sc);

                return (error);

        default:
                return (ENODEV);
        }
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_DISABLE_REGULATOR().
 */
static int
bhnd_pmu_disable_regulator_method(device_t dev, bhnd_pmu_regulator regulator)
{
        struct bhnd_pmu_softc   *sc;
        int                      error;

        sc = device_get_softc(dev);

        switch (regulator) {
        case BHND_REGULATOR_PAREF_LDO:
                BPMU_LOCK(sc);
                error = bhnd_pmu_paref_ldo_enable(sc, false);
                BPMU_UNLOCK(sc);

                return (error);

        default:
                return (ENODEV);
        }
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_GET_CLOCK_LATENCY().
 */
static int
bhnd_pmu_get_clock_latency_method(device_t dev, bhnd_clock clock,
    u_int *latency)
{
        struct bhnd_pmu_softc   *sc;
        u_int                    pwrup_delay;
        int                      error;

        sc = device_get_softc(dev);

        switch (clock) {
        case BHND_CLOCK_HT:
                BPMU_LOCK(sc);
                error = bhnd_pmu_fast_pwrup_delay(sc, &pwrup_delay);
                BPMU_UNLOCK(sc);

                if (error)
                        return (error);

                *latency = pwrup_delay;
                return (0);

        default:
                return (ENODEV);
        }
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_GET_CLOCK_FREQ().
 */
static int
bhnd_pmu_get_clock_freq_method(device_t dev, bhnd_clock clock, uint32_t *freq)
{
        struct bhnd_pmu_softc   *sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        switch (clock) {
        case BHND_CLOCK_HT:
                *freq = bhnd_pmu_si_clock(&sc->query);
                break;

        case BHND_CLOCK_ALP:
                *freq = bhnd_pmu_alp_clock(&sc->query);
                break;

        case BHND_CLOCK_ILP:
                *freq = bhnd_pmu_ilp_clock(&sc->query);
                break;

        case BHND_CLOCK_DYN:
        default:
                BPMU_UNLOCK(sc);
                return (ENODEV);
        }

        BPMU_UNLOCK(sc);
        return (0);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_REQUEST_SPURAVOID().
 */
static int
bhnd_pmu_request_spuravoid_method(device_t dev, bhnd_pmu_spuravoid spuravoid)
{
        struct bhnd_pmu_softc   *sc;
        int                      error;

        sc = device_get_softc(dev);

        BPMU_LOCK(sc);
        error = bhnd_pmu_set_spuravoid(sc, spuravoid);
        BPMU_UNLOCK(sc);

        return (error);
}

/**
 * Default bhnd_pmu driver implementation of BHND_PMU_GET_TRANSITION_LATENCY().
 */
static u_int
bhnd_pmu_get_max_transition_latency_method(device_t dev)
{
        return (BHND_PMU_MAX_TRANSITION_DLY);
}

/* bhnd_pmu_query read_4 callback */
static uint32_t
bhnd_pmu_read_4(bus_size_t reg, void *ctx)
{
        struct bhnd_pmu_softc *sc = ctx;
        return (bhnd_bus_read_4(sc->res, reg));
}

/* bhnd_pmu_query write_4 callback */
static void
bhnd_pmu_write_4(bus_size_t reg, uint32_t val, void *ctx)
{
        struct bhnd_pmu_softc *sc = ctx;
        return (bhnd_bus_write_4(sc->res, reg, val));
}

/* bhnd_pmu_query read_chipst callback */
static uint32_t
bhnd_pmu_read_chipst(void *ctx)
{
        struct bhnd_pmu_softc *sc = ctx;
        return (BHND_CHIPC_READ_CHIPST(sc->chipc_dev));
}

static device_method_t bhnd_pmu_methods[] = {
        /* Device interface */
        DEVMETHOD(device_probe,                         bhnd_pmu_probe),
        DEVMETHOD(device_detach,                        bhnd_pmu_detach),
        DEVMETHOD(device_suspend,                       bhnd_pmu_suspend),
        DEVMETHOD(device_resume,                        bhnd_pmu_resume),

        /* BHND PMU interface */
        DEVMETHOD(bhnd_pmu_read_chipctrl,               bhnd_pmu_read_chipctrl_method),
        DEVMETHOD(bhnd_pmu_write_chipctrl,              bhnd_pmu_write_chipctrl_method),
        DEVMETHOD(bhnd_pmu_read_regctrl,                bhnd_pmu_read_regctrl_method),
        DEVMETHOD(bhnd_pmu_write_regctrl,               bhnd_pmu_write_regctrl_method),
        DEVMETHOD(bhnd_pmu_read_pllctrl,                bhnd_pmu_read_pllctrl_method),
        DEVMETHOD(bhnd_pmu_write_pllctrl,               bhnd_pmu_write_pllctrl_method),
        DEVMETHOD(bhnd_pmu_set_voltage_raw,             bhnd_pmu_set_voltage_raw_method),
        DEVMETHOD(bhnd_pmu_enable_regulator,            bhnd_pmu_enable_regulator_method),
        DEVMETHOD(bhnd_pmu_disable_regulator,           bhnd_pmu_disable_regulator_method),

        DEVMETHOD(bhnd_pmu_get_clock_latency,           bhnd_pmu_get_clock_latency_method),
        DEVMETHOD(bhnd_pmu_get_clock_freq,              bhnd_pmu_get_clock_freq_method),

        DEVMETHOD(bhnd_pmu_get_max_transition_latency,  bhnd_pmu_get_max_transition_latency_method),
        DEVMETHOD(bhnd_pmu_request_spuravoid,           bhnd_pmu_request_spuravoid_method),

        DEVMETHOD_END
};

DEFINE_CLASS_0(bhnd_pmu, bhnd_pmu_driver, bhnd_pmu_methods, sizeof(struct bhnd_pmu_softc));
MODULE_VERSION(bhnd_pmu, 1);