#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/clock_subr.h>
#include <dev/fdt/spmivar.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/fdt.h>
extern void (*cpuresetfn)(void);
extern void (*powerdownfn)(void);
#define SERA_TIME 0xd002
#define SERA_TIME_OFFSET 0xd100
#define SERA_TIME_LEN 6
#define SERA_POWERDOWN 0x9f0f
#define SERA_POWERDOWN_MAGIC 0x08
struct aplpmu_nvmem {
struct aplpmu_softc *an_sc;
struct nvmem_device an_nd;
bus_addr_t an_base;
bus_size_t an_size;
};
struct aplpmu_softc {
struct device sc_dev;
spmi_tag_t sc_tag;
int8_t sc_sid;
struct todr_chip_handle sc_todr;
uint64_t sc_offset;
};
struct aplpmu_softc *aplpmu_sc;
int aplpmu_match(struct device *, void *, void *);
void aplpmu_attach(struct device *, struct device *, void *);
const struct cfattach aplpmu_ca = {
sizeof (struct aplpmu_softc), aplpmu_match, aplpmu_attach
};
struct cfdriver aplpmu_cd = {
NULL, "aplpmu", DV_DULL
};
int aplpmu_gettime(struct todr_chip_handle *, struct timeval *);
int aplpmu_settime(struct todr_chip_handle *, struct timeval *);
void aplpmu_powerdown(void);
int aplpmu_nvmem_read(void *, bus_addr_t, void *, bus_size_t);
int aplpmu_nvmem_write(void *, bus_addr_t, const void *, bus_size_t);
int
aplpmu_match(struct device *parent, void *match, void *aux)
{
struct spmi_attach_args *sa = aux;
return OF_is_compatible(sa->sa_node, "apple,sera-pmu") ||
OF_is_compatible(sa->sa_node, "apple,spmi-pmu") ||
OF_is_compatible(sa->sa_node, "apple,spmi-nvmem");
}
void
aplpmu_attach(struct device *parent, struct device *self, void *aux)
{
struct aplpmu_softc *sc = (struct aplpmu_softc *)self;
struct spmi_attach_args *sa = aux;
uint8_t data[8] = {};
int error, node;
sc->sc_tag = sa->sa_tag;
sc->sc_sid = sa->sa_sid;
if (OF_is_compatible(sa->sa_node, "apple,sera-pmu") ||
OF_is_compatible(sa->sa_node, "apple,sera-pmic")) {
error = spmi_cmd_read(sc->sc_tag, sc->sc_sid,
SPMI_CMD_EXT_READL, SERA_TIME_OFFSET,
&data, SERA_TIME_LEN);
if (error) {
printf(": can't read offset\n");
return;
}
sc->sc_offset = lemtoh64(data);
sc->sc_todr.cookie = sc;
sc->sc_todr.todr_gettime = aplpmu_gettime;
sc->sc_todr.todr_settime = aplpmu_settime;
sc->sc_todr.todr_quality = 0;
todr_attach(&sc->sc_todr);
aplpmu_sc = sc;
powerdownfn = aplpmu_powerdown;
}
printf("\n");
for (node = OF_child(sa->sa_node); node; node = OF_peer(node)) {
struct aplpmu_nvmem *an;
uint32_t reg[2] = { 0x0, 0x10000 };
if (!OF_is_compatible(node, "apple,spmi-pmu-nvmem") &&
!OF_is_compatible(node, "fixed-layout"))
continue;
if (OF_is_compatible(node, "apple,spmi-pmu-nvmem")) {
if (OF_getpropintarray(node, "reg", reg,
sizeof(reg)) != sizeof(reg))
continue;
}
an = malloc(sizeof(*an), M_DEVBUF, M_WAITOK);
an->an_sc = sc;
an->an_base = reg[0];
an->an_size = reg[1];
an->an_nd.nd_node = node;
an->an_nd.nd_cookie = an;
an->an_nd.nd_read = aplpmu_nvmem_read;
an->an_nd.nd_write = aplpmu_nvmem_write;
nvmem_register(&an->an_nd);
}
}
int
aplpmu_gettime(struct todr_chip_handle *handle, struct timeval *tv)
{
struct aplpmu_softc *sc = handle->cookie;
uint8_t data[8] = {};
uint64_t time;
int error;
error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
SERA_TIME, &data, SERA_TIME_LEN);
if (error)
return error;
time = lemtoh64(data) + (sc->sc_offset << 1);
tv->tv_sec = (time >> 16);
tv->tv_usec = (((time & 0xffff) * 1000000) >> 16);
return 0;
}
int
aplpmu_settime(struct todr_chip_handle *handle, struct timeval *tv)
{
struct aplpmu_softc *sc = handle->cookie;
uint8_t data[8] = {};
uint64_t time;
int error;
error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
SERA_TIME, &data, SERA_TIME_LEN);
if (error)
return error;
time = ((uint64_t)tv->tv_sec << 16);
time |= ((uint64_t)tv->tv_usec << 16) / 1000000;
sc->sc_offset = ((time - lemtoh64(data)) >> 1);
htolem64(data, sc->sc_offset);
return spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL,
SERA_TIME_OFFSET, &data, SERA_TIME_LEN);
}
void
aplpmu_powerdown(void)
{
struct aplpmu_softc *sc = aplpmu_sc;
uint8_t data = SERA_POWERDOWN_MAGIC;
spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL,
SERA_POWERDOWN, &data, sizeof(data));
cpuresetfn();
}
int
aplpmu_nvmem_read(void *cookie, bus_addr_t addr, void *data, bus_size_t size)
{
struct aplpmu_nvmem *an = cookie;
struct aplpmu_softc *sc = an->an_sc;
if (addr >= an->an_size || size > an->an_size - addr)
return EINVAL;
return spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
an->an_base + addr, data, size);
}
int
aplpmu_nvmem_write(void *cookie, bus_addr_t addr, const void *data,
bus_size_t size)
{
struct aplpmu_nvmem *an = cookie;
struct aplpmu_softc *sc = an->an_sc;
if (addr >= an->an_size || size > an->an_size - addr)
return EINVAL;
return spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL,
an->an_base + addr, data, size);
}