Add aplspmi(4), a driver for the Apple SPMI controller, and aplpmu(4)
authorkettenis <kettenis@openbsd.org>
Wed, 26 May 2021 20:52:21 +0000 (20:52 +0000)
committerkettenis <kettenis@openbsd.org>
Wed, 26 May 2021 20:52:21 +0000 (20:52 +0000)
a driver for the Apple "sera" SPMI power management unit that contains
the RTC on Apple M1 systems.

ok patrick@

sys/arch/arm64/conf/GENERIC
sys/arch/arm64/conf/RAMDISK
sys/arch/arm64/conf/files.arm64
sys/arch/arm64/dev/aplpmu.c [new file with mode: 0644]
sys/arch/arm64/dev/aplspmi.c [new file with mode: 0644]
sys/dev/fdt/spmivar.h [new file with mode: 0644]

index d40e654..bb564c9 100644 (file)
@@ -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
index e68e04b..c9bf95c 100644 (file)
@@ -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
index 77fa39b..0f01611 100644 (file)
@@ -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 (file)
index 0000000..5515a87
--- /dev/null
@@ -0,0 +1,147 @@
+/*     $OpenBSD: aplpmu.c,v 1.1 2021/05/26 20:52:21 kettenis Exp $     */
+/*
+ * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.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/fdt.h>
+
+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 (file)
index 0000000..6785424
--- /dev/null
@@ -0,0 +1,215 @@
+/*     $OpenBSD: aplspmi.c,v 1.1 2021/05/26 20:52:21 kettenis Exp $    */
+/*
+ * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/fdt/spmivar.h>
+
+/*
+ * 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 (file)
index 0000000..5602c78
--- /dev/null
@@ -0,0 +1,39 @@
+/*     $OpenBSD: spmivar.h,v 1.1 2021/05/26 20:52:21 kettenis Exp $    */
+/*
+ * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * 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))