#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"
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
};
int
bhnd_pmu_probe(device_t dev)
{
return (BUS_PROBE_DEFAULT);
}
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;
sc->caps = bhnd_bus_read_4(sc->res, BHND_PMU_CAP);
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);
}
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);
}
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);
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;
}
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;
}
if ((error = bhnd_pmu_init(sc))) {
device_printf(sc->dev, "PMU init failed: %d\n", error);
goto failed;
}
if ((error = bhnd_register_provider(dev, BHND_SERVICE_PMU))) {
device_printf(sc->dev, "failed to register PMU with bus : %d\n",
error);
goto failed;
}
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);
}
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);
}
int
bhnd_pmu_suspend(device_t dev)
{
return (0);
}
int
bhnd_pmu_resume(device_t dev)
{
struct bhnd_pmu_softc *sc;
int error;
sc = device_get_softc(dev);
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));
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
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);
}
static u_int
bhnd_pmu_get_max_transition_latency_method(device_t dev)
{
return (BHND_PMU_MAX_TRANSITION_DLY);
}
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));
}
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));
}
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[] = {
DEVMETHOD(device_probe, bhnd_pmu_probe),
DEVMETHOD(device_detach, bhnd_pmu_detach),
DEVMETHOD(device_suspend, bhnd_pmu_suspend),
DEVMETHOD(device_resume, bhnd_pmu_resume),
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);