From: kettenis Date: Wed, 26 May 2021 20:52:21 +0000 (+0000) Subject: Add aplspmi(4), a driver for the Apple SPMI controller, and aplpmu(4) X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=5224032ca17c332d7e5e5abe296595c23aea8a41;p=openbsd Add aplspmi(4), a driver for the Apple SPMI controller, and aplpmu(4) a driver for the Apple "sera" SPMI power management unit that contains the RTC on Apple M1 systems. ok patrick@ --- diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index d40e65471d9..bb564c97b68 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.198 2021/05/24 18:40:19 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.199 2021/05/26 20:52:21 kettenis Exp $ # # GENERIC machine description file # @@ -138,6 +138,8 @@ apldwusb* at fdt? aplintc* at fdt? early 1 aplpcie* at fdt? pci* at aplpcie? +aplspmi* at fdt? +aplpmu* at aplspmi? exuart* at fdt? # iMX diff --git a/sys/arch/arm64/conf/RAMDISK b/sys/arch/arm64/conf/RAMDISK index e68e04b570a..c9bf95cb635 100644 --- a/sys/arch/arm64/conf/RAMDISK +++ b/sys/arch/arm64/conf/RAMDISK @@ -1,4 +1,4 @@ -# $OpenBSD: RAMDISK,v 1.150 2021/05/24 18:40:19 kettenis Exp $ +# $OpenBSD: RAMDISK,v 1.151 2021/05/26 20:52:21 kettenis Exp $ machine arm64 maxusers 4 @@ -102,6 +102,8 @@ apldwusb* at fdt? aplintc* at fdt? early 1 aplpcie* at fdt? pci* at aplpcie? +aplspmi* at fdt? +aplpmu* at aplspmi? exuart* at fdt? # iMX diff --git a/sys/arch/arm64/conf/files.arm64 b/sys/arch/arm64/conf/files.arm64 index 77fa39b9b9a..0f01611f98a 100644 --- a/sys/arch/arm64/conf/files.arm64 +++ b/sys/arch/arm64/conf/files.arm64 @@ -1,4 +1,4 @@ -# $OpenBSD: files.arm64,v 1.41 2021/05/24 18:40:19 kettenis Exp $ +# $OpenBSD: files.arm64,v 1.42 2021/05/26 20:52:21 kettenis Exp $ maxpartitions 16 maxusers 2 8 128 @@ -156,6 +156,15 @@ device aplpcie: pcibus attach aplpcie at fdt file arch/arm64/dev/aplpcie.c aplpcie +define spmi {} +device aplpmu +attach aplpmu at spmi +file arch/arm64/dev/aplpmu.c aplpmu + +device aplspmi: spmi +attach aplspmi at fdt +file arch/arm64/dev/aplspmi.c aplspmi + device bcmintc attach bcmintc at fdt file arch/arm64/dev/bcm2836_intr.c bcmintc diff --git a/sys/arch/arm64/dev/aplpmu.c b/sys/arch/arm64/dev/aplpmu.c new file mode 100644 index 00000000000..5515a8797f8 --- /dev/null +++ b/sys/arch/arm64/dev/aplpmu.c @@ -0,0 +1,147 @@ +/* $OpenBSD: aplpmu.c,v 1.1 2021/05/26 20:52:21 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +extern todr_chip_handle_t todr_handle; + +/* + * This driver is based on preliminary device tree bindings and will + * almost certainly need changes once the official bindings land in + * mainline Linux. Support for these preliminary bindings will be + * dropped as soon as official bindings are available. + */ + +/* + * Apple's "sera" PMU contains an RTC that provides time in 32.16 + * fixed-point format as well as a time offset in 33.15 fixed-point + * format. The sum of the two gives us a standard Unix timestamp with + * sub-second resolution. The time itself is read-only. To set the + * time we need to adjust the time offset. + */ +#define SERA_TIME 0xd002 +#define SERA_TIME_OFFSET 0xd100 +#define SERA_TIME_LEN 6 + +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; +}; + +int aplpmu_match(struct device *, void *, void *); +void aplpmu_attach(struct device *, struct device *, void *); + +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 *); + +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"); +} + +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; + + sc->sc_tag = sa->sa_tag; + sc->sc_sid = sa->sa_sid; + + 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); + + printf("\n"); + + sc->sc_todr.cookie = sc; + sc->sc_todr.todr_gettime = aplpmu_gettime; + sc->sc_todr.todr_settime = aplpmu_settime; + todr_handle = &sc->sc_todr; +} + +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); +} diff --git a/sys/arch/arm64/dev/aplspmi.c b/sys/arch/arm64/dev/aplspmi.c new file mode 100644 index 00000000000..67854244fc9 --- /dev/null +++ b/sys/arch/arm64/dev/aplspmi.c @@ -0,0 +1,215 @@ +/* $OpenBSD: aplspmi.c,v 1.1 2021/05/26 20:52:21 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +/* + * This driver is based on preliminary device tree bindings and will + * almost certainly need changes once the official bindings land in + * mainline Linux. Support for these preliminary bindings will be + * dropped as soon as official bindings are available. + */ + +#define SPMI_STAT 0x00 +#define SPMI_STAT_RXEMPTY (1 << 24) +#define SPMI_STAT_TXEMPTY (1 << 8) +#define SPMI_CMD 0x04 +#define SPMI_CMD_ADDR(x) ((x) << 16) +#define SPMI_CMD_LAST (1 << 15) +#define SPMI_CMD_SID(x) ((x) << 8) +#define SPMI_RESP 0x08 +#define SPMI_INTEN(i) (0x20 + (i) * 4) +#define SPMI_INTSTAT(i) (0x60 + (i) * 4) + +#define HREAD4(sc, reg) \ + (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) +#define HWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) + +struct aplspmi_softc { + struct device sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + struct spmi_controller sc_tag; +}; + +int aplspmi_match(struct device *, void *, void *); +void aplspmi_attach(struct device *, struct device *, void *); + +struct cfattach aplspmi_ca = { + sizeof (struct aplspmi_softc), aplspmi_match, aplspmi_attach +}; + +struct cfdriver aplspmi_cd = { + NULL, "aplspmi", DV_DULL +}; + +int aplspmi_print(void *, const char *); +int aplspmi_cmd_read(void *, uint8_t, uint8_t, uint16_t, void *, size_t); +int aplspmi_cmd_write(void *, uint8_t, uint8_t, uint16_t, + const void *, size_t); + +int +aplspmi_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "apple,spmi"); +} + +void +aplspmi_attach(struct device *parent, struct device *self, void *aux) +{ + struct aplspmi_softc *sc = (struct aplspmi_softc *)self; + struct fdt_attach_args *faa = aux; + struct spmi_attach_args sa; + char name[32]; + uint32_t reg[2]; + int node; + + if (faa->fa_nreg < 1) { + printf(": no registers\n"); + return; + } + + sc->sc_iot = faa->fa_iot; + if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, + faa->fa_reg[0].size, 0, &sc->sc_ioh)) { + printf(": can't map registers\n"); + return; + } + + printf("\n"); + + sc->sc_tag.sc_cookie = sc; + sc->sc_tag.sc_cmd_read = aplspmi_cmd_read; + sc->sc_tag.sc_cmd_write = aplspmi_cmd_write; + + for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) { + if (OF_getpropintarray(node, "reg", reg, + sizeof(reg)) != sizeof(reg)) + continue; + + memset(name, 0, sizeof(name)); + if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) + continue; + if (name[0] == '\0') + continue; + + memset(&sa, 0, sizeof(sa)); + sa.sa_tag = &sc->sc_tag; + sa.sa_sid = reg[0]; + sa.sa_name = name; + sa.sa_node = node; + config_found(self, &sa, aplspmi_print); + } +} + +int +aplspmi_print(void *aux, const char *pnp) +{ + struct spmi_attach_args *sa = aux; + + if (pnp != NULL) + printf("\"%s\" at %s", sa->sa_name, pnp); + printf(" sid 0x%x", sa->sa_sid); + + return UNCONF; +} + +int +aplspmi_read_resp(struct aplspmi_softc *sc, uint32_t *resp) +{ + int retry; + + for (retry = 1000; retry > 0; retry--) { + if ((HREAD4(sc, SPMI_STAT) & SPMI_STAT_RXEMPTY) == 0) + break; + delay(1); + } + if (retry == 0) + return ETIMEDOUT; + + *resp = HREAD4(sc, SPMI_RESP); + return 0; +} + +int +aplspmi_cmd_read(void *cookie, uint8_t sid, uint8_t cmd, uint16_t addr, + void *buf, size_t len) +{ + struct aplspmi_softc *sc = cookie; + uint8_t *cbuf = buf; + uint32_t resp; + int error; + + if (len == 0 || len > 8) + return EINVAL; + + HWRITE4(sc, SPMI_CMD, SPMI_CMD_SID(sid) | cmd | SPMI_CMD_ADDR(addr) | + (len - 1) | SPMI_CMD_LAST); + + error = aplspmi_read_resp(sc, &resp); + if (error) + return error; + + while (len > 0) { + error = aplspmi_read_resp(sc, &resp); + if (error) + return error; + memcpy(cbuf, &resp, MIN(len, 4)); + cbuf += MIN(len, 4); + len -= MIN(len, 4); + } + + return 0; +} + +int +aplspmi_cmd_write(void *cookie, uint8_t sid, uint8_t cmd, uint16_t addr, + const void *buf, size_t len) +{ + struct aplspmi_softc *sc = cookie; + const uint8_t *cbuf = buf; + uint32_t data, resp; + + if (len == 0 || len > 8) + return EINVAL; + + HWRITE4(sc, SPMI_CMD, SPMI_CMD_SID(sid) | cmd | SPMI_CMD_ADDR(addr) | + (len - 1) | SPMI_CMD_LAST); + + while (len > 0) { + memcpy(&data, cbuf, MIN(len, 4)); + HWRITE4(sc, SPMI_CMD, data); + cbuf += MIN(len, 4); + len -= MIN(len, 4); + } + + return aplspmi_read_resp(sc, &resp); +} diff --git a/sys/dev/fdt/spmivar.h b/sys/dev/fdt/spmivar.h new file mode 100644 index 00000000000..5602c78215c --- /dev/null +++ b/sys/dev/fdt/spmivar.h @@ -0,0 +1,39 @@ +/* $OpenBSD: spmivar.h,v 1.1 2021/05/26 20:52:21 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define SPMI_CMD_EXT_WRITEL 0x30 +#define SPMI_CMD_EXT_READL 0x38 + +typedef struct spmi_controller { + void *sc_cookie; + int (*sc_cmd_read)(void *, uint8_t, uint8_t, uint16_t, + void *, size_t); + int (*sc_cmd_write)(void *, uint8_t, uint8_t, uint16_t, + const void *, size_t); +} *spmi_tag_t; + +struct spmi_attach_args { + spmi_tag_t sa_tag; + uint8_t sa_sid; + char *sa_name; + int sa_node; +}; + +#define spmi_cmd_read(sc, sid, cmd, addr, buf, len) \ + (*(sc)->sc_cmd_read)((sc)->sc_cookie, (sid), (cmd), (addr), (buf), (len)) +#define spmi_cmd_write(sc, sid, cmd, addr, buf, len) \ + (*(sc)->sc_cmd_write)((sc)->sc_cookie, (sid), (cmd), (addr), (buf), (len))