Module Name:    src
Committed By:   jmcneill
Date:           Mon Dec 30 12:31:10 UTC 2024

Modified Files:
        src/sys/arch/evbarm/conf: GENERIC64
        src/sys/dev/acpi: files.acpi
Added Files:
        src/sys/dev/acpi: qcomipcc.c qcomipcc.h qcompas.c qcompep.c qcompep.h
            qcomscm.c qcomscm.h qcomsmem.c qcomsmem.h qcomsmptp.c qcomsmptp.h
            qcomspmi.c

Log Message:
arm64: Add support for Snapdragon X1E battery and charger sensors.

This is a port of a set of drivers from OpenBSD, adapted to use ACPI
instead of FDT bindings. These drivers are required to boot firmware on
the application DSP which is required to read sensors. This is an impressive
collection of work on the OpenBSD side -- big thank you to Patrick for writing
this code and his help with this porting effort.

Firmware is signed and as a result may be board specific. The qcompas(4)
driver looks for firmware files in qcompas/<subsystem> where <subsystem> is
the string returned by the _SUB method on the ADSP device node. For example,
on Lenovo ThinkPad T14s Gen 6 the subsystem ID is "233817AA".

The files "qcadsp8380.mbn" and "adsp_dtbs.elf" must be placed in
this directory to read sensor data. These files can be obtained by
downloading the appropriate OEM drivers for Windows.

When everything is online, the sensors are reported by sysmon_envsys and
the charger creates a sysmon_pswitch of type PSWITCH_TYPE_ACADAPTER. The
goal is to match the acpiacad(4) and acpibat(4) behaviour as much as possible.

XXX Firmware loading sometimes fails, still looking into this. When this
    happens, sensors are not registered.

[battery]
    design voltage:     0.000                                         V
           voltage:    12.980                                         V
        design cap:    58.000                                        Wh
     last full cap:    59.810                                        Wh
            charge:    59.740                      2.000%   8.998%   Wh (99.88%)
       charge rate:       N/A
    discharge rate:       N/A
          charging:     FALSE
      charge state:    NORMAL
  discharge cycles:         4        0        0        0        0 none
       temperature:    30.230                                      degC
[charger]
         connected:      TRUE


To generate a diff of this commit:
cvs rdiff -u -r1.221 -r1.222 src/sys/arch/evbarm/conf/GENERIC64
cvs rdiff -u -r1.135 -r1.136 src/sys/dev/acpi/files.acpi
cvs rdiff -u -r0 -r1.1 src/sys/dev/acpi/qcomipcc.c \
    src/sys/dev/acpi/qcomipcc.h src/sys/dev/acpi/qcompas.c \
    src/sys/dev/acpi/qcompep.c src/sys/dev/acpi/qcompep.h \
    src/sys/dev/acpi/qcomscm.c src/sys/dev/acpi/qcomscm.h \
    src/sys/dev/acpi/qcomsmem.c src/sys/dev/acpi/qcomsmem.h \
    src/sys/dev/acpi/qcomsmptp.c src/sys/dev/acpi/qcomsmptp.h \
    src/sys/dev/acpi/qcomspmi.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/evbarm/conf/GENERIC64
diff -u src/sys/arch/evbarm/conf/GENERIC64:1.221 src/sys/arch/evbarm/conf/GENERIC64:1.222
--- src/sys/arch/evbarm/conf/GENERIC64:1.221	Mon Dec  9 22:17:31 2024
+++ src/sys/arch/evbarm/conf/GENERIC64	Mon Dec 30 12:31:09 2024
@@ -1,5 +1,5 @@
 #
-#	$NetBSD: GENERIC64,v 1.221 2024/12/09 22:17:31 jmcneill Exp $
+#	$NetBSD: GENERIC64,v 1.222 2024/12/30 12:31:09 jmcneill Exp $
 #
 #	GENERIC ARM (aarch64) kernel
 #
@@ -158,6 +158,13 @@ gpioleds*	at fdt?
 # System Controller
 syscon* 	at fdt? pass 1		# Generic System Controller
 sunxisramc*	at fdt? pass 1		# Allwinner System Control
+qcomipcc*	at acpi?		# Qualcomm IPCC
+qcompas*	at acpi?		# Qualcomm ADSP PAS
+qcompep*	at acpi?		# Qualcomm PEP / AOSS
+qcomscm*	at acpi?		# Qualcomm Secure Channel Manager
+qcomsmem*	at acpi?		# Qualcomm Shared Memory
+qcomsmptp*	at acpi?		# Qualcomm Shared Memory P2P
+qcomspmi*	at acpi?		# Qualcomm SPMI
 
 # Timer
 gtmr*		at fdt? pass 2		# ARM Generic Timer

Index: src/sys/dev/acpi/files.acpi
diff -u src/sys/dev/acpi/files.acpi:1.135 src/sys/dev/acpi/files.acpi:1.136
--- src/sys/dev/acpi/files.acpi:1.135	Fri Dec 13 13:30:10 2024
+++ src/sys/dev/acpi/files.acpi	Mon Dec 30 12:31:10 2024
@@ -1,4 +1,4 @@
-#	$NetBSD: files.acpi,v 1.135 2024/12/13 13:30:10 martin Exp $
+#	$NetBSD: files.acpi,v 1.136 2024/12/30 12:31:10 jmcneill Exp $
 
 defflag	opt_acpi.h	ACPIVERBOSE ACPI_DEBUG ACPI_ACTIVATE_DEV
 			ACPI_DSDT_OVERRIDE ACPI_SCANPCI ACPI_BREAKPOINT
@@ -347,4 +347,39 @@ device	qcomiic: i2cbus
 attach	qcomiic at acpinodebus
 file	dev/acpi/qcomiic.c		qcomiic
 
+# Qualcomm SPMI controller
+device	qcomspmi
+attach	qcomspmi at acpinodebus
+file	dev/acpi/qcomspmi.c		qcomspmi
+
+# Qualcomm Secure Channel Manager
+device	qcomscm
+attach	qcomscm at acpinodebus
+file	dev/acpi/qcomscm.c		qcomscm
+
+# Qualcomm IPCC driver
+device	qcomipcc
+attach	qcomipcc at acpinodebus
+file	dev/acpi/qcomipcc.c		qcomipcc
+
+# Qualcomm PEP / AOSS driver
+device	qcompep
+attach	qcompep at acpinodebus
+file	dev/acpi/qcompep.c		qcompep
+
+# Qualcomm Shared Memory driver
+device	qcomsmem
+attach	qcomsmem at acpinodebus
+file	dev/acpi/qcomsmem.c		qcomsmem
+
+# Qualcomm SMP2P driver
+device	qcomsmptp
+attach	qcomsmptp at acpinodebus
+file	dev/acpi/qcomsmptp.c		qcomsmptp
+
+# Qualcomm PAS driver
+device	qcompas
+attach	qcompas at acpinodebus
+file	dev/acpi/qcompas.c		qcompas
+
 include	"dev/acpi/wmi/files.wmi"

Added files:

Index: src/sys/dev/acpi/qcomipcc.c
diff -u /dev/null src/sys/dev/acpi/qcomipcc.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomipcc.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,262 @@
+/* $NetBS$ */
+/*	$OpenBSD: qcipcc.c,v 1.2 2023/05/19 20:54:55 patrick Exp $	*/
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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 <sys/kmem.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpi_intr.h>
+#include <dev/acpi/qcomipcc.h>
+
+#define IPCC_SEND_ID			0x0c
+#define IPCC_RECV_ID			0x10
+#define IPCC_RECV_SIGNAL_ENABLE		0x14
+#define IPCC_RECV_SIGNAL_DISABLE	0x18
+#define IPCC_RECV_SIGNAL_CLEAR		0x1c
+
+#define IPCC_SIGNAL_ID_MASK		__BITS(15,0)
+#define IPCC_CLIENT_ID_MASK		__BITS(31,16)
+
+#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 qcipcc_intrhand {
+	TAILQ_ENTRY(qcipcc_intrhand) ih_q;
+	int (*ih_func)(void *);
+	void *ih_arg;
+	void *ih_sc;
+	uint32_t ih_client_id;
+	uint32_t ih_signal_id;
+};
+
+struct qcipcc_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_iot;
+	bus_space_handle_t	sc_ioh;
+
+	void			*sc_ih;
+
+	TAILQ_HEAD(,qcipcc_intrhand) sc_intrq;
+};
+
+static struct qcipcc_softc *qcipcc = NULL;
+
+struct qcipcc_channel {
+	struct qcipcc_softc	*ch_sc;
+	uint32_t		ch_client_id;
+	uint32_t		ch_signal_id;
+};
+
+#define QCIPCC_8380_BASE	0x00408000
+#define QCIPCC_SIZE		0x1000
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "QCOM06C2",		.value = QCIPCC_8380_BASE },
+	DEVICE_COMPAT_EOL
+};
+
+static int	qcipcc_match(device_t, cfdata_t, void *);
+static void	qcipcc_attach(device_t, device_t, void *);
+static int	qcipcc_intr(void *);
+
+CFATTACH_DECL_NEW(qcomipcc, sizeof(struct qcipcc_softc),
+    qcipcc_match, qcipcc_attach, NULL, NULL);
+
+static int
+qcipcc_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+static void
+qcipcc_attach(device_t parent, device_t self, void *aux)
+{
+	struct qcipcc_softc *sc = device_private(self);
+	struct acpi_attach_args *aa = aux;
+	ACPI_HANDLE hdl;
+	struct acpi_resources res;
+	struct acpi_irq *irq;
+	bus_addr_t base_addr;
+	ACPI_STATUS rv;
+
+	if (qcipcc != NULL) {
+		aprint_error(": already attached!\n");
+		return;
+	}
+
+	hdl = aa->aa_node->ad_handle;
+	rv = acpi_resource_parse(self, hdl, "_CRS", &res,
+	    &acpi_resource_parse_ops_default);
+	if (ACPI_FAILURE(rv)) {
+		return;
+	}
+
+	irq = acpi_res_irq(&res, 0);
+	if (irq == NULL) {
+		aprint_error_dev(self, "couldn't find irq resource\n");
+		goto done;
+	}
+
+	base_addr = acpi_compatible_lookup(aa, compat_data)->value;
+
+	sc->sc_dev = self;
+	sc->sc_iot = aa->aa_memt;
+	if (bus_space_map(sc->sc_iot, base_addr, QCIPCC_SIZE,
+	    BUS_SPACE_MAP_NONPOSTED, &sc->sc_ioh) != 0) {
+		aprint_error_dev(self, "couldn't map registers\n");
+		goto done;
+	}
+
+	TAILQ_INIT(&sc->sc_intrq);
+
+	sc->sc_ih = acpi_intr_establish(self,
+	    (uint64_t)(uintptr_t)hdl,
+	    IPL_VM, false, qcipcc_intr, sc, device_xname(self));
+	if (sc->sc_ih == NULL) {
+		aprint_error_dev(self,
+		    "couldn't establish interrupt\n");
+		goto done;
+	}
+
+	qcipcc = sc;
+
+done:
+	acpi_resource_cleanup(&res);
+}
+
+static int
+qcipcc_intr(void *arg)
+{
+	struct qcipcc_softc *sc = arg;
+	struct qcipcc_intrhand *ih;
+	uint16_t client_id, signal_id;
+	uint32_t reg;
+	int handled = 0;
+
+	while ((reg = HREAD4(sc, IPCC_RECV_ID)) != ~0) {
+		HWRITE4(sc, IPCC_RECV_SIGNAL_CLEAR, reg);
+
+		client_id = __SHIFTOUT(reg, IPCC_CLIENT_ID_MASK);
+		signal_id = __SHIFTOUT(reg, IPCC_SIGNAL_ID_MASK);
+
+		TAILQ_FOREACH(ih, &sc->sc_intrq, ih_q) {
+			if (ih->ih_client_id != client_id ||
+			    ih->ih_signal_id != signal_id)
+				continue;
+			ih->ih_func(ih->ih_arg);
+			handled = 1;
+		}
+	}
+
+	return handled;
+}
+
+void *
+qcipcc_intr_establish(uint16_t client_id, uint16_t signal_id, int ipl,
+    int (*func)(void *), void *arg)
+{
+	struct qcipcc_softc *sc = qcipcc;
+	struct qcipcc_intrhand *ih;
+
+	if (sc == NULL) {
+		return NULL;
+	}
+
+	ih = kmem_zalloc(sizeof(*ih), KM_SLEEP);
+	ih->ih_func = func;
+	ih->ih_arg = arg;
+	ih->ih_sc = sc;
+	ih->ih_client_id = client_id;
+	ih->ih_signal_id = signal_id;
+	TAILQ_INSERT_TAIL(&sc->sc_intrq, ih, ih_q);
+
+	qcipcc_intr_enable(ih);
+
+	return ih;
+}
+
+void
+qcipcc_intr_disestablish(void *cookie)
+{
+	struct qcipcc_intrhand *ih = cookie;
+	struct qcipcc_softc *sc = ih->ih_sc;
+
+	qcipcc_intr_disable(ih);
+
+	TAILQ_REMOVE(&sc->sc_intrq, ih, ih_q);
+	kmem_free(ih, sizeof(*ih));
+}
+
+void
+qcipcc_intr_enable(void *cookie)
+{
+	struct qcipcc_intrhand *ih = cookie;
+	struct qcipcc_softc *sc = ih->ih_sc;
+
+	HWRITE4(sc, IPCC_RECV_SIGNAL_ENABLE,
+	    __SHIFTIN(ih->ih_client_id, IPCC_CLIENT_ID_MASK) |
+	    __SHIFTIN(ih->ih_signal_id, IPCC_SIGNAL_ID_MASK));
+}
+
+void
+qcipcc_intr_disable(void *cookie)
+{
+	struct qcipcc_intrhand *ih = cookie;
+	struct qcipcc_softc *sc = ih->ih_sc;
+
+	HWRITE4(sc, IPCC_RECV_SIGNAL_DISABLE,
+	    __SHIFTIN(ih->ih_client_id, IPCC_CLIENT_ID_MASK) |
+	    __SHIFTIN(ih->ih_signal_id, IPCC_SIGNAL_ID_MASK));
+}
+
+void *
+qcipcc_channel(uint16_t client_id, uint16_t signal_id)
+{
+	struct qcipcc_softc *sc = qcipcc;
+	struct qcipcc_channel *ch;
+
+	if (qcipcc == NULL) {
+		return NULL;
+	}
+
+	ch = kmem_zalloc(sizeof(*ch), KM_SLEEP);
+	ch->ch_sc = sc;
+	ch->ch_client_id = client_id;
+	ch->ch_signal_id = signal_id;
+
+	return ch;
+}
+
+int
+qcipcc_send(void *cookie)
+{
+	struct qcipcc_channel *ch = cookie;
+	struct qcipcc_softc *sc = ch->ch_sc;
+
+	HWRITE4(sc, IPCC_SEND_ID,
+	    __SHIFTIN(ch->ch_client_id, IPCC_CLIENT_ID_MASK) |
+	    __SHIFTIN(ch->ch_signal_id, IPCC_SIGNAL_ID_MASK));
+
+	return 0;
+}
Index: src/sys/dev/acpi/qcomipcc.h
diff -u /dev/null src/sys/dev/acpi/qcomipcc.h:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomipcc.h	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,28 @@
+/* $NetBS$ */
+/*	$OpenBSD: qcipcc.c,v 1.2 2023/05/19 20:54:55 patrick Exp $	*/
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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.
+ */
+
+#pragma once
+
+void	*qcipcc_intr_establish(uint16_t, uint16_t, int,
+	    int (*)(void *), void *);
+void	qcipcc_intr_disestablish(void *);
+void	qcipcc_intr_enable(void *);
+void	qcipcc_intr_disable(void *);
+
+void	*qcipcc_channel(uint16_t, uint16_t);
+int	qcipcc_send(void *);
Index: src/sys/dev/acpi/qcompas.c
diff -u /dev/null src/sys/dev/acpi/qcompas.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcompas.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,1610 @@
+/* $NetBSD: qcompas.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*	$OpenBSD: qcpas.c,v 1.8 2024/11/08 21:13:34 landry Exp $	*/
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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 <sys/kmem.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/callout.h>
+#include <sys/exec_elf.h>
+
+#include <dev/firmload.h>
+#include <dev/sysmon/sysmonvar.h>
+#include <dev/sysmon/sysmon_taskq.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpi_intr.h>
+#include <dev/acpi/qcomipcc.h>
+#include <dev/acpi/qcompep.h>
+#include <dev/acpi/qcomscm.h>
+#include <dev/acpi/qcomsmem.h>
+#include <dev/acpi/qcomsmptp.h>
+
+#define DRIVER_NAME		"qcompas"
+
+#define MDT_TYPE_MASK				(7 << 24)
+#define MDT_TYPE_HASH				(2 << 24)
+#define MDT_RELOCATABLE				(1 << 27)
+
+extern struct arm32_bus_dma_tag arm_generic_dma_tag;
+
+enum qcpas_batt_sensor {
+	/* Battery sensors (must be first) */
+	QCPAS_DVOLTAGE,
+	QCPAS_VOLTAGE,
+	QCPAS_DCAPACITY,
+	QCPAS_LFCCAPACITY,
+	QCPAS_CAPACITY,
+	QCPAS_CHARGERATE,
+	QCPAS_DISCHARGERATE,
+	QCPAS_CHARGING,
+	QCPAS_CHARGE_STATE,
+	QCPAS_DCYCLES,
+	QCPAS_TEMPERATURE,
+	/* AC adapter sensors */
+	QCPAS_ACADAPTER,
+	/* Total number of sensors */
+	QCPAS_NUM_SENSORS
+};
+
+struct qcpas_dmamem {
+	bus_dmamap_t		tdm_map;
+	bus_dma_segment_t	tdm_seg;
+	size_t			tdm_size;
+	void			*tdm_kva;
+};
+#define QCPAS_DMA_MAP(_tdm)	((_tdm)->tdm_map)
+#define QCPAS_DMA_LEN(_tdm)	((_tdm)->tdm_size)
+#define QCPAS_DMA_DVA(_tdm)	((_tdm)->tdm_map->dm_segs[0].ds_addr)
+#define QCPAS_DMA_KVA(_tdm)	((_tdm)->tdm_kva)
+
+struct qcpas_softc {
+	device_t		sc_dev;
+	bus_dma_tag_t		sc_dmat;
+
+	char			*sc_sub;
+
+	void			*sc_ih[5];
+
+	kmutex_t		sc_ready_lock;
+	kcondvar_t		sc_ready_cv;
+	bool			sc_ready;
+
+	paddr_t			sc_mem_phys[2];
+	size_t			sc_mem_size[2];
+	uint8_t			*sc_mem_region[2];
+	vaddr_t			sc_mem_reloc[2];
+
+	const char		*sc_fwname;
+	const char		*sc_dtb_fwname;
+	uint32_t		sc_pas_id;
+	uint32_t		sc_dtb_pas_id;
+	uint32_t		sc_lite_pas_id;
+	const char		*sc_load_state;
+	uint32_t		sc_glink_remote_pid;
+	uint32_t		sc_crash_reason;
+
+	struct qcpas_dmamem	*sc_metadata[2];
+
+	/* GLINK */
+	volatile uint32_t	*sc_tx_tail;
+	volatile uint32_t	*sc_tx_head;
+	volatile uint32_t	*sc_rx_tail;
+	volatile uint32_t	*sc_rx_head;
+
+	uint32_t		sc_tx_off;
+	uint32_t		sc_rx_off;
+
+	uint8_t			*sc_tx_fifo;
+	int			sc_tx_fifolen;
+	uint8_t			*sc_rx_fifo;
+	int			sc_rx_fifolen;
+	void			*sc_glink_ih;
+
+	void			*sc_ipcc;
+
+	uint32_t		sc_glink_max_channel;
+	TAILQ_HEAD(,qcpas_glink_channel) sc_glink_channels;
+
+	uint32_t		sc_warning_capacity;
+	uint32_t		sc_low_capacity;
+	uint32_t		sc_power_state;
+	struct sysmon_envsys	*sc_sme;
+	envsys_data_t		sc_sens[QCPAS_NUM_SENSORS];
+	struct sysmon_envsys	*sc_sme_acadapter;
+	struct sysmon_pswitch	sc_smpsw_acadapter;
+	callout_t		sc_rtr_refresh;
+};
+
+static int	qcpas_match(device_t, cfdata_t, void *);
+static void	qcpas_attach(device_t, device_t, void *);
+
+CFATTACH_DECL_NEW(qcompas, sizeof(struct qcpas_softc),
+    qcpas_match, qcpas_attach, NULL, NULL);
+
+static void	qcpas_mountroot(device_t);
+static void	qcpas_firmload(void *);
+static int	qcpas_map_memory(struct qcpas_softc *);
+static int	qcpas_mdt_init(struct qcpas_softc *, int, u_char *, size_t);
+static void	qcpas_glink_attach(struct qcpas_softc *);
+static void	qcpas_glink_recv(void *);
+static void	qcpas_get_limits(struct sysmon_envsys *, envsys_data_t *,
+				 sysmon_envsys_lim_t *, uint32_t *);
+
+static struct qcpas_dmamem *
+		qcpas_dmamem_alloc(struct qcpas_softc *, bus_size_t, bus_size_t);
+static void	qcpas_dmamem_free(struct qcpas_softc *, struct qcpas_dmamem *);
+
+static int	qcpas_intr_wdog(void *);
+static int	qcpas_intr_fatal(void *);
+static int	qcpas_intr_ready(void *);
+static int	qcpas_intr_handover(void *);
+static int	qcpas_intr_stop_ack(void *);
+
+struct qcpas_mem_region {
+	bus_addr_t		start;
+	bus_size_t		size;
+};
+
+struct qcpas_data {
+	bus_addr_t		reg_addr;
+	bus_size_t		reg_size;
+	uint32_t		pas_id;
+	uint32_t		dtb_pas_id;
+	uint32_t		lite_pas_id;
+	const char		*load_state;
+	uint32_t		glink_remote_pid;
+	struct qcpas_mem_region	mem_region[2];
+	const char		*fwname;
+	const char		*dtb_fwname;
+	uint32_t		crash_reason;
+};
+
+static struct qcpas_data qcpas_x1e_data = {
+	.reg_addr = 0x30000000,
+	.reg_size = 0x100,
+	.pas_id = 1,
+	.dtb_pas_id = 36,
+	.lite_pas_id = 31,
+	.load_state = "adsp",
+	.glink_remote_pid = 2,
+	.mem_region = {
+		[0] = { .start = 0x87e00000, .size = 0x3a00000 },
+		[1] = { .start = 0x8b800000, .size = 0x80000 },
+	},
+	.fwname = "qcadsp8380.mbn",
+	.dtb_fwname = "adsp_dtbs.elf",
+	.crash_reason = 423,
+};
+
+#define IPCC_CLIENT_LPASS       	3
+#define IPCC_MPROC_SIGNAL_GLINK_QMP	0
+
+static const struct device_compatible_entry compat_data[] = {
+        { .compat = "QCOM0C1B",         .data = &qcpas_x1e_data },
+        DEVICE_COMPAT_EOL
+};
+
+static int
+qcpas_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+static void
+qcpas_attach(device_t parent, device_t self, void *aux)
+{
+	struct qcpas_softc *sc = device_private(self);
+	struct acpi_attach_args *aa = aux;
+	const struct qcpas_data *data;
+	struct acpi_resources res;
+	ACPI_STATUS rv;
+	int i;
+
+	rv = acpi_resource_parse(self, aa->aa_node->ad_handle, "_CRS", &res,
+	    &acpi_resource_parse_ops_default);
+	if (ACPI_FAILURE(rv)) {
+		return;
+	}
+	acpi_resource_cleanup(&res);
+
+	data = acpi_compatible_lookup(aa, compat_data)->data;
+
+	sc->sc_dev = self;
+	sc->sc_dmat = &arm_generic_dma_tag;
+	mutex_init(&sc->sc_ready_lock, MUTEX_DEFAULT, IPL_VM);
+	cv_init(&sc->sc_ready_cv, "qcpasrdy");
+
+	sc->sc_fwname = data->fwname;
+	sc->sc_dtb_fwname = data->dtb_fwname;
+	sc->sc_pas_id = data->pas_id;
+	sc->sc_dtb_pas_id = data->dtb_pas_id;
+	sc->sc_lite_pas_id = data->lite_pas_id;
+	sc->sc_load_state = data->load_state;
+	sc->sc_glink_remote_pid = data->glink_remote_pid;
+	sc->sc_crash_reason = data->crash_reason;
+	for (i = 0; i < __arraycount(sc->sc_mem_phys); i++) {
+		sc->sc_mem_phys[i] = data->mem_region[i].start;
+		KASSERT((sc->sc_mem_phys[i] & PAGE_MASK) == 0);
+		sc->sc_mem_size[i] = data->mem_region[i].size;
+		KASSERT((sc->sc_mem_size[i] & PAGE_MASK) == 0);
+	}
+
+	rv = acpi_eval_string(aa->aa_node->ad_handle, "_SUB", &sc->sc_sub);
+	if (ACPI_FAILURE(rv)) {
+		aprint_error_dev(self, "failed to evaluate _SUB: %s\n",
+		    AcpiFormatException(rv));
+		return;
+	}
+	aprint_verbose_dev(self, "subsystem ID %s\n", sc->sc_sub);
+
+	sc->sc_ih[0] = acpi_intr_establish(self,
+	    (uint64_t)(uintptr_t)aa->aa_node->ad_handle,
+	    IPL_VM, false, qcpas_intr_wdog, sc, device_xname(self));
+	sc->sc_ih[1] =
+	    qcsmptp_intr_establish(0, qcpas_intr_fatal, sc);
+	sc->sc_ih[2] =
+	    qcsmptp_intr_establish(1, qcpas_intr_ready, sc);
+	sc->sc_ih[3] =
+	    qcsmptp_intr_establish(2, qcpas_intr_handover, sc);
+	sc->sc_ih[4] =
+	    qcsmptp_intr_establish(3, qcpas_intr_stop_ack, sc);
+
+	if (qcpas_map_memory(sc) != 0)
+		return;
+
+	config_mountroot(self, qcpas_mountroot);
+}
+
+static void
+qcpas_firmload(void *arg)
+{
+	struct qcpas_softc *sc = arg;
+	firmware_handle_t fwh = NULL, dtb_fwh = NULL;
+	char fwname[128];
+	size_t fwlen = 0, dtb_fwlen = 0;
+	u_char *fw = NULL, *dtb_fw = NULL;
+	int ret, error;
+
+	snprintf(fwname, sizeof(fwname), "%s/%s", sc->sc_sub, sc->sc_fwname);
+	error = firmware_open(DRIVER_NAME, fwname, &fwh);
+	if (error == 0) {
+		fwlen = firmware_get_size(fwh);
+		fw = fwlen ? firmware_malloc(fwlen) : NULL;
+		error = fw == NULL ? ENOMEM :
+			firmware_read(fwh, 0, fw, fwlen);
+	}
+	if (error) {
+		device_printf(sc->sc_dev, "failed to load %s/%s: %d\n",
+		    DRIVER_NAME, fwname, error);
+		goto cleanup;
+	}
+	aprint_normal_dev(sc->sc_dev, "loading %s/%s\n", DRIVER_NAME, fwname);
+
+	if (sc->sc_lite_pas_id) {
+		if (qcscm_pas_shutdown(sc->sc_lite_pas_id)) {
+			device_printf(sc->sc_dev,
+			    "failed to shutdown lite firmware\n");
+		}
+	}
+
+	if (sc->sc_dtb_pas_id) {
+		snprintf(fwname, sizeof(fwname), "%s/%s", sc->sc_sub,
+		    sc->sc_dtb_fwname);
+		error = firmware_open(DRIVER_NAME, fwname, &dtb_fwh);
+		if (error == 0) {
+			dtb_fwlen = firmware_get_size(dtb_fwh);
+			dtb_fw = dtb_fwlen ? firmware_malloc(dtb_fwlen) : NULL;
+			error = dtb_fw == NULL ? ENOMEM :
+				firmware_read(dtb_fwh, 0, dtb_fw, dtb_fwlen);
+		}
+		if (error) {
+			device_printf(sc->sc_dev, "failed to load %s/%s: %d\n",
+			    DRIVER_NAME, fwname, error);
+			goto cleanup;
+		}
+		aprint_normal_dev(sc->sc_dev, "loading %s/%s\n", DRIVER_NAME, fwname);
+	}
+
+	if (sc->sc_load_state) {
+		char buf[64];
+		snprintf(buf, sizeof(buf),
+		    "{class: image, res: load_state, name: %s, val: on}",
+		    sc->sc_load_state);
+		ret = qcaoss_send(buf, sizeof(buf));
+		if (ret != 0) {
+			device_printf(sc->sc_dev, "failed to toggle load state\n");
+			goto cleanup;
+		}
+	}
+
+	if (sc->sc_dtb_pas_id) {
+		qcpas_mdt_init(sc, sc->sc_dtb_pas_id, dtb_fw, dtb_fwlen);
+	}
+
+	ret = qcpas_mdt_init(sc, sc->sc_pas_id, fw, fwlen);
+	if (ret != 0) {
+		device_printf(sc->sc_dev, "failed to boot coprocessor\n");
+		goto cleanup;
+	}
+
+	qcpas_glink_attach(sc);
+
+	/* Battery sensors */
+	sc->sc_sme = sysmon_envsys_create();
+	sc->sc_sme->sme_name = "battery";
+	sc->sc_sme->sme_cookie = sc;
+	sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;
+	sc->sc_sme->sme_class = SME_CLASS_BATTERY;
+	sc->sc_sme->sme_get_limits = qcpas_get_limits;
+
+	/* AC adapter sensors */
+	sc->sc_sme_acadapter = sysmon_envsys_create();
+	sc->sc_sme_acadapter->sme_name = "charger";
+	sc->sc_sme_acadapter->sme_cookie = sc;
+	sc->sc_sme_acadapter->sme_flags = SME_DISABLE_REFRESH;
+	sc->sc_sme_acadapter->sme_class = SME_CLASS_ACADAPTER;
+
+#define INIT_SENSOR(sme, idx, unit, str)				\
+	do {								\
+		strlcpy(sc->sc_sens[idx].desc, str,			\
+		    sizeof(sc->sc_sens[0].desc));			\
+		sc->sc_sens[idx].units = unit;				\
+		sc->sc_sens[idx].state = ENVSYS_SINVALID;		\
+		sysmon_envsys_sensor_attach(sme,			\
+		    &sc->sc_sens[idx]);					\
+	} while (0)
+
+	INIT_SENSOR(sc->sc_sme, QCPAS_DVOLTAGE, ENVSYS_SVOLTS_DC, "design voltage");
+	INIT_SENSOR(sc->sc_sme, QCPAS_VOLTAGE, ENVSYS_SVOLTS_DC, "voltage");
+	INIT_SENSOR(sc->sc_sme, QCPAS_DCAPACITY, ENVSYS_SWATTHOUR, "design cap");
+	INIT_SENSOR(sc->sc_sme, QCPAS_LFCCAPACITY, ENVSYS_SWATTHOUR, "last full cap");
+	INIT_SENSOR(sc->sc_sme, QCPAS_CAPACITY, ENVSYS_SWATTHOUR, "charge");
+	INIT_SENSOR(sc->sc_sme, QCPAS_CHARGERATE, ENVSYS_SWATTS, "charge rate");
+	INIT_SENSOR(sc->sc_sme, QCPAS_DISCHARGERATE, ENVSYS_SWATTS, "discharge rate");
+	INIT_SENSOR(sc->sc_sme, QCPAS_CHARGING, ENVSYS_BATTERY_CHARGE, "charging");
+	INIT_SENSOR(sc->sc_sme, QCPAS_CHARGE_STATE, ENVSYS_BATTERY_CAPACITY, "charge state");
+	INIT_SENSOR(sc->sc_sme, QCPAS_DCYCLES, ENVSYS_INTEGER, "discharge cycles");
+	INIT_SENSOR(sc->sc_sme, QCPAS_TEMPERATURE, ENVSYS_STEMP, "temperature");
+	INIT_SENSOR(sc->sc_sme_acadapter, QCPAS_ACADAPTER, ENVSYS_INDICATOR, "connected");
+
+#undef INIT_SENSOR
+
+	sc->sc_sens[QCPAS_CHARGE_STATE].value_cur =
+	    ENVSYS_BATTERY_CAPACITY_NORMAL;
+	sc->sc_sens[QCPAS_CAPACITY].flags |=
+	    ENVSYS_FPERCENT | ENVSYS_FVALID_MAX | ENVSYS_FMONLIMITS;
+	sc->sc_sens[QCPAS_CHARGE_STATE].flags |=
+	    ENVSYS_FMONSTCHANGED;
+
+	sc->sc_sens[QCPAS_VOLTAGE].flags = ENVSYS_FMONNOTSUPP;
+	sc->sc_sens[QCPAS_CHARGERATE].flags = ENVSYS_FMONNOTSUPP;
+	sc->sc_sens[QCPAS_DISCHARGERATE].flags = ENVSYS_FMONNOTSUPP;
+	sc->sc_sens[QCPAS_DCAPACITY].flags = ENVSYS_FMONNOTSUPP;
+	sc->sc_sens[QCPAS_LFCCAPACITY].flags = ENVSYS_FMONNOTSUPP;
+	sc->sc_sens[QCPAS_DVOLTAGE].flags = ENVSYS_FMONNOTSUPP;
+
+	sc->sc_sens[QCPAS_CHARGERATE].flags |= ENVSYS_FHAS_ENTROPY;
+	sc->sc_sens[QCPAS_DISCHARGERATE].flags |= ENVSYS_FHAS_ENTROPY;
+
+	sysmon_envsys_register(sc->sc_sme);
+	sysmon_envsys_register(sc->sc_sme_acadapter);
+
+	sc->sc_smpsw_acadapter.smpsw_name = "acpiacad0";
+	sc->sc_smpsw_acadapter.smpsw_type = PSWITCH_TYPE_ACADAPTER;
+	sysmon_pswitch_register(&sc->sc_smpsw_acadapter);
+
+cleanup:
+	if (dtb_fw != NULL) {
+		firmware_free(dtb_fw, dtb_fwlen);
+	}
+	if (fw != NULL) {
+		firmware_free(fw, fwlen);
+	}
+	if (dtb_fwh != NULL) {
+		firmware_close(dtb_fwh);
+	}
+	if (fwh != NULL) {
+		firmware_close(fwh);
+	}
+}
+
+static void
+qcpas_mountroot(device_t self)
+{
+	struct qcpas_softc *sc = device_private(self);
+
+	sysmon_task_queue_sched(0, qcpas_firmload, sc);
+}
+
+static int
+qcpas_map_memory(struct qcpas_softc *sc)
+{
+	int i;
+
+	for (i = 0; i < __arraycount(sc->sc_mem_phys); i++) {
+		paddr_t pa, epa;
+		vaddr_t va;
+
+		if (sc->sc_mem_size[i] == 0)
+			break;
+
+		va = uvm_km_alloc(kernel_map, sc->sc_mem_size[i], 0, UVM_KMF_VAONLY);
+		KASSERT(va != 0);
+		sc->sc_mem_region[i] = (void *)va;
+
+		for (pa = sc->sc_mem_phys[i], epa = sc->sc_mem_phys[i] + sc->sc_mem_size[i];
+		     pa < epa;
+		     pa += PAGE_SIZE, va += PAGE_SIZE) {
+			pmap_kenter_pa(va, pa, VM_PROT_READ|VM_PROT_WRITE, PMAP_WRITE_COMBINE);
+		}
+		pmap_update(pmap_kernel());
+	}
+
+	return 0;
+}
+
+static int
+qcpas_mdt_init(struct qcpas_softc *sc, int pas_id, u_char *fw, size_t fwlen)
+{
+	Elf32_Ehdr *ehdr;
+	Elf32_Phdr *phdr;
+	paddr_t minpa = -1, maxpa = 0;
+	int i, hashseg = 0, relocate = 0;
+	uint8_t *metadata;
+	int error;
+	ssize_t off;
+	int idx;
+
+	if (pas_id == sc->sc_dtb_pas_id)
+		idx = 1;
+	else
+		idx = 0;
+
+	ehdr = (Elf32_Ehdr *)fw;
+	phdr = (Elf32_Phdr *)&ehdr[1];
+
+	if (ehdr->e_phnum < 2 || phdr[0].p_type == PT_LOAD)
+		return EINVAL;
+
+	for (i = 0; i < ehdr->e_phnum; i++) {
+		if ((phdr[i].p_flags & MDT_TYPE_MASK) == MDT_TYPE_HASH) {
+			if (i > 0 && !hashseg)
+				hashseg = i;
+			continue;
+		}
+		if (phdr[i].p_type != PT_LOAD || phdr[i].p_memsz == 0)
+			continue;
+		if (phdr[i].p_flags & MDT_RELOCATABLE)
+			relocate = 1;
+		if (phdr[i].p_paddr < minpa)
+			minpa = phdr[i].p_paddr;
+		if (phdr[i].p_paddr + phdr[i].p_memsz > maxpa)
+			maxpa =
+			    roundup(phdr[i].p_paddr + phdr[i].p_memsz,
+			    PAGE_SIZE);
+	}
+
+	if (!hashseg)
+		return EINVAL;
+
+	if (sc->sc_metadata[idx] == NULL) {
+		sc->sc_metadata[idx] = qcpas_dmamem_alloc(sc, phdr[0].p_filesz +
+		    phdr[hashseg].p_filesz, PAGE_SIZE);
+		if (sc->sc_metadata[idx] == NULL) {
+			return EINVAL;
+		}
+	}
+
+	metadata = QCPAS_DMA_KVA(sc->sc_metadata[idx]);
+
+	memcpy(metadata, fw, phdr[0].p_filesz);
+	if (phdr[0].p_filesz + phdr[hashseg].p_filesz == fwlen) {
+		memcpy(metadata + phdr[0].p_filesz,
+		    fw + phdr[0].p_filesz, phdr[hashseg].p_filesz);
+	} else if (phdr[hashseg].p_offset + phdr[hashseg].p_filesz <= fwlen) {
+		memcpy(metadata + phdr[0].p_filesz,
+		    fw + phdr[hashseg].p_offset, phdr[hashseg].p_filesz);
+	} else {
+		device_printf(sc->sc_dev, "metadata split segment not supported\n");
+		return EINVAL;
+	}
+
+	cpu_drain_writebuf();
+
+	error = qcscm_pas_init_image(pas_id,
+	    QCPAS_DMA_DVA(sc->sc_metadata[idx]));
+	if (error != 0) {
+		device_printf(sc->sc_dev, "init image failed: %d\n", error);
+		qcpas_dmamem_free(sc, sc->sc_metadata[idx]);
+		return error;
+	}
+
+	if (relocate) {
+		if (qcscm_pas_mem_setup(pas_id,
+		    sc->sc_mem_phys[idx], maxpa - minpa) != 0) {
+			device_printf(sc->sc_dev, "mem setup failed\n");
+			qcpas_dmamem_free(sc, sc->sc_metadata[idx]);
+			return EINVAL;
+		}
+	}
+
+	sc->sc_mem_reloc[idx] = relocate ? minpa : sc->sc_mem_phys[idx];
+
+	for (i = 0; i < ehdr->e_phnum; i++) {
+		if ((phdr[i].p_flags & MDT_TYPE_MASK) == MDT_TYPE_HASH ||
+		    phdr[i].p_type != PT_LOAD || phdr[i].p_memsz == 0)
+			continue;
+		off = phdr[i].p_paddr - sc->sc_mem_reloc[idx];
+		if (off < 0 || off + phdr[i].p_memsz > sc->sc_mem_size[0])
+			return EINVAL;
+		if (phdr[i].p_filesz > phdr[i].p_memsz)
+			return EINVAL;
+
+		if (phdr[i].p_filesz && phdr[i].p_offset < fwlen &&
+		    phdr[i].p_offset + phdr[i].p_filesz <= fwlen) {
+			memcpy(sc->sc_mem_region[idx] + off,
+			    fw + phdr[i].p_offset, phdr[i].p_filesz);
+		} else if (phdr[i].p_filesz) {
+			device_printf(sc->sc_dev, "firmware split segment not supported\n");
+			return EINVAL;
+		}
+
+		if (phdr[i].p_memsz > phdr[i].p_filesz)
+			memset(sc->sc_mem_region[idx] + off + phdr[i].p_filesz,
+			    0, phdr[i].p_memsz - phdr[i].p_filesz);
+	}
+
+	cpu_drain_writebuf();
+
+	if (qcscm_pas_auth_and_reset(pas_id) != 0) {
+		device_printf(sc->sc_dev, "auth and reset failed\n");
+		qcpas_dmamem_free(sc, sc->sc_metadata[idx]);
+		return EINVAL;
+	}
+
+	if (pas_id == sc->sc_dtb_pas_id)
+		return 0;
+
+	mutex_enter(&sc->sc_ready_lock);
+	while (!sc->sc_ready) {
+		error = cv_timedwait(&sc->sc_ready_cv, &sc->sc_ready_lock,
+		    hz * 5);
+		if (error == EWOULDBLOCK) {
+			break;
+		}
+	}
+	mutex_exit(&sc->sc_ready_lock);
+	if (!sc->sc_ready) {
+		device_printf(sc->sc_dev, "timeout waiting for ready signal\n");
+		return ETIMEDOUT;
+	}
+
+	/* XXX: free metadata ? */
+
+	return 0;
+}
+
+static struct qcpas_dmamem *
+qcpas_dmamem_alloc(struct qcpas_softc *sc, bus_size_t size, bus_size_t align)
+{
+	struct qcpas_dmamem *tdm;
+	int nsegs;
+
+	tdm = kmem_zalloc(sizeof(*tdm), KM_SLEEP);
+	tdm->tdm_size = size;
+
+	if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
+	    BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &tdm->tdm_map) != 0)
+		goto tdmfree;
+
+	if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0,
+	    &tdm->tdm_seg, 1, &nsegs, BUS_DMA_WAITOK) != 0)
+		goto destroy;
+
+	if (bus_dmamem_map(sc->sc_dmat, &tdm->tdm_seg, nsegs, size,
+	    &tdm->tdm_kva, BUS_DMA_WAITOK | BUS_DMA_PREFETCHABLE) != 0)
+		goto free;
+
+	if (bus_dmamap_load(sc->sc_dmat, tdm->tdm_map, tdm->tdm_kva, size,
+	    NULL, BUS_DMA_WAITOK) != 0)
+		goto unmap;
+
+	memset(tdm->tdm_kva, 0, size);
+
+	return (tdm);
+
+unmap:
+	bus_dmamem_unmap(sc->sc_dmat, tdm->tdm_kva, size);
+free:
+	bus_dmamem_free(sc->sc_dmat, &tdm->tdm_seg, 1);
+destroy:
+	bus_dmamap_destroy(sc->sc_dmat, tdm->tdm_map);
+tdmfree:
+	kmem_free(tdm, sizeof(*tdm));
+
+	return (NULL);
+}
+
+static void
+qcpas_dmamem_free(struct qcpas_softc *sc, struct qcpas_dmamem *tdm)
+{
+	bus_dmamem_unmap(sc->sc_dmat, tdm->tdm_kva, tdm->tdm_size);
+	bus_dmamem_free(sc->sc_dmat, &tdm->tdm_seg, 1);
+	bus_dmamap_destroy(sc->sc_dmat, tdm->tdm_map);
+	kmem_free(tdm, sizeof(*tdm));
+}
+
+static void
+qcpas_report_crash(struct qcpas_softc *sc, const char *source)
+{
+	char *msg;
+	int size;
+
+	msg = qcsmem_get(-1, sc->sc_crash_reason, &size);
+	if (msg == NULL || size <= 0) {
+		device_printf(sc->sc_dev, "%s\n", source);
+	} else {
+		device_printf(sc->sc_dev, "%s: \"%s\"\n", source, msg);
+	}
+}
+
+static int
+qcpas_intr_wdog(void *cookie)
+{
+	struct qcpas_softc *sc = cookie;
+
+	qcpas_report_crash(sc, "watchdog");
+
+	return 0;
+}
+
+static int
+qcpas_intr_fatal(void *cookie)
+{
+	struct qcpas_softc *sc = cookie;
+
+	qcpas_report_crash(sc, "fatal error");
+
+	return 0;
+}
+
+static int
+qcpas_intr_ready(void *cookie)
+{
+	struct qcpas_softc *sc = cookie;
+
+	aprint_debug_dev(sc->sc_dev, "%s\n", __func__);
+
+	mutex_enter(&sc->sc_ready_lock);
+	sc->sc_ready = true;
+	cv_broadcast(&sc->sc_ready_cv);
+	mutex_exit(&sc->sc_ready_lock);
+
+	return 0;
+}
+
+static int
+qcpas_intr_handover(void *cookie)
+{
+	struct qcpas_softc *sc = cookie;
+
+	aprint_debug_dev(sc->sc_dev, "%s\n", __func__);
+
+	return 0;
+}
+
+static int
+qcpas_intr_stop_ack(void *cookie)
+{
+	struct qcpas_softc *sc = cookie;
+
+	aprint_debug_dev(sc->sc_dev, "%s\n", __func__);
+
+	return 0;
+}
+
+/* GLINK */
+
+#define SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR	478
+#define SMEM_GLINK_NATIVE_XPRT_FIFO_0		479
+#define SMEM_GLINK_NATIVE_XPRT_FIFO_1		480
+
+struct glink_msg {
+	uint16_t cmd;
+	uint16_t param1;
+	uint32_t param2;
+	uint8_t data[];
+} __packed;
+
+struct qcpas_glink_intent_pair {
+	uint32_t size;
+	uint32_t iid;
+} __packed;
+
+struct qcpas_glink_intent {
+	TAILQ_ENTRY(qcpas_glink_intent) it_q;
+	uint32_t it_id;
+	uint32_t it_size;
+	int it_inuse;
+};
+
+struct qcpas_glink_channel {
+	TAILQ_ENTRY(qcpas_glink_channel) ch_q;
+	struct qcpas_softc *ch_sc;
+	struct qcpas_glink_protocol *ch_proto;
+	uint32_t ch_rcid;
+	uint32_t ch_lcid;
+	uint32_t ch_max_intent;
+	TAILQ_HEAD(,qcpas_glink_intent) ch_l_intents;
+	TAILQ_HEAD(,qcpas_glink_intent) ch_r_intents;
+};
+
+#define GLINK_CMD_VERSION		0
+#define GLINK_CMD_VERSION_ACK		1
+#define  GLINK_VERSION				1
+#define  GLINK_FEATURE_INTENT_REUSE		(1 << 0)
+#define GLINK_CMD_OPEN			2
+#define GLINK_CMD_CLOSE			3
+#define GLINK_CMD_OPEN_ACK		4
+#define GLINK_CMD_INTENT		5
+#define GLINK_CMD_RX_DONE		6
+#define GLINK_CMD_RX_INTENT_REQ		7
+#define GLINK_CMD_RX_INTENT_REQ_ACK	8
+#define GLINK_CMD_TX_DATA		9
+#define GLINK_CMD_CLOSE_ACK		11
+#define GLINK_CMD_TX_DATA_CONT		12
+#define GLINK_CMD_READ_NOTIF		13
+#define GLINK_CMD_RX_DONE_W_REUSE	14
+
+static int	qcpas_glink_intr(void *);
+
+static void	qcpas_glink_tx(struct qcpas_softc *, uint8_t *, int);
+static void	qcpas_glink_tx_commit(struct qcpas_softc *);
+static void	qcpas_glink_rx(struct qcpas_softc *, uint8_t *, int);
+static void	qcpas_glink_rx_commit(struct qcpas_softc *);
+
+static void	qcpas_glink_send(void *, void *, int);
+
+static int	qcpas_pmic_rtr_init(void *);
+static int	qcpas_pmic_rtr_recv(void *, uint8_t *, int);
+
+struct qcpas_glink_protocol {
+	const char *name;
+	int (*init)(void *cookie);
+	int (*recv)(void *cookie, uint8_t *buf, int len);
+} qcpas_glink_protocols[] = {
+	{ "PMIC_RTR_ADSP_APPS", qcpas_pmic_rtr_init , qcpas_pmic_rtr_recv },
+};
+
+static void
+qcpas_glink_attach(struct qcpas_softc *sc)
+{
+	uint32_t remote = sc->sc_glink_remote_pid;
+	uint32_t *descs;
+	int size;
+
+	if (qcsmem_alloc(remote, SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, 32) != 0 ||
+	    qcsmem_alloc(remote, SMEM_GLINK_NATIVE_XPRT_FIFO_0, 16384) != 0)
+		return;
+
+	descs = qcsmem_get(remote, SMEM_GLINK_NATIVE_XPRT_DESCRIPTOR, &size);
+	if (descs == NULL || size != 32)
+		return;
+
+	sc->sc_tx_tail = &descs[0];
+	sc->sc_tx_head = &descs[1];
+	sc->sc_rx_tail = &descs[2];
+	sc->sc_rx_head = &descs[3];
+
+	sc->sc_tx_fifo = qcsmem_get(remote, SMEM_GLINK_NATIVE_XPRT_FIFO_0,
+	    &sc->sc_tx_fifolen);
+	if (sc->sc_tx_fifo == NULL)
+		return;
+	sc->sc_rx_fifo = qcsmem_get(remote, SMEM_GLINK_NATIVE_XPRT_FIFO_1,
+	    &sc->sc_rx_fifolen);
+	if (sc->sc_rx_fifo == NULL)
+		return;
+
+	sc->sc_ipcc = qcipcc_channel(IPCC_CLIENT_LPASS,
+	    IPCC_MPROC_SIGNAL_GLINK_QMP);
+	if (sc->sc_ipcc == NULL)
+		return;
+
+	TAILQ_INIT(&sc->sc_glink_channels);
+
+	sc->sc_glink_ih = qcipcc_intr_establish(IPCC_CLIENT_LPASS,
+	    IPCC_MPROC_SIGNAL_GLINK_QMP, IPL_VM, qcpas_glink_intr, sc);
+	if (sc->sc_glink_ih == NULL)
+		return;
+
+	/* Expect peer to send initial message */
+}
+
+static void
+qcpas_glink_rx(struct qcpas_softc *sc, uint8_t *buf, int len)
+{
+	uint32_t head, tail;
+	int avail;
+
+	head = *sc->sc_rx_head;
+	tail = *sc->sc_rx_tail + sc->sc_rx_off;
+	if (tail >= sc->sc_rx_fifolen)
+		tail -= sc->sc_rx_fifolen;
+
+	/* Checked by caller */
+	KASSERT(head != tail);
+
+	if (head >= tail)
+		avail = head - tail;
+	else
+		avail = (sc->sc_rx_fifolen - tail) + head;
+
+	/* Dumb, but should do. */
+	KASSERT(avail >= len);
+
+	while (len > 0) {
+		*buf = sc->sc_rx_fifo[tail];
+		tail++;
+		if (tail >= sc->sc_rx_fifolen)
+			tail -= sc->sc_rx_fifolen;
+		buf++;
+		sc->sc_rx_off++;
+		len--;
+	}
+}
+
+static void
+qcpas_glink_rx_commit(struct qcpas_softc *sc)
+{
+	uint32_t tail;
+
+	tail = *sc->sc_rx_tail + roundup(sc->sc_rx_off, 8);
+	if (tail >= sc->sc_rx_fifolen)
+		tail -= sc->sc_rx_fifolen;
+
+	membar_producer();
+	*sc->sc_rx_tail = tail;
+	sc->sc_rx_off = 0;
+}
+
+static void
+qcpas_glink_tx(struct qcpas_softc *sc, uint8_t *buf, int len)
+{
+	uint32_t head, tail;
+	int avail;
+
+	head = *sc->sc_tx_head + sc->sc_tx_off;
+	if (head >= sc->sc_tx_fifolen)
+		head -= sc->sc_tx_fifolen;
+	tail = *sc->sc_tx_tail;
+
+	if (head < tail)
+		avail = tail - head;
+	else
+		avail = (sc->sc_rx_fifolen - head) + tail;
+
+	/* Dumb, but should do. */
+	KASSERT(avail >= len);
+
+	while (len > 0) {
+		sc->sc_tx_fifo[head] = *buf;
+		head++;
+		if (head >= sc->sc_tx_fifolen)
+			head -= sc->sc_tx_fifolen;
+		buf++;
+		sc->sc_tx_off++;
+		len--;
+	}
+}
+
+static void
+qcpas_glink_tx_commit(struct qcpas_softc *sc)
+{
+	uint32_t head;
+
+	head = *sc->sc_tx_head + roundup(sc->sc_tx_off, 8);
+	if (head >= sc->sc_tx_fifolen)
+		head -= sc->sc_tx_fifolen;
+
+	membar_producer();
+	*sc->sc_tx_head = head;
+	sc->sc_tx_off = 0;
+	qcipcc_send(sc->sc_ipcc);
+}
+
+static void
+qcpas_glink_send(void *cookie, void *buf, int len)
+{
+	struct qcpas_glink_channel *ch = cookie;
+	struct qcpas_softc *sc = ch->ch_sc;
+	struct qcpas_glink_intent *it;
+	struct glink_msg msg;
+	uint32_t chunk_size, left_size;
+
+	TAILQ_FOREACH(it, &ch->ch_r_intents, it_q) {
+		if (!it->it_inuse)
+			break;
+		if (it->it_size < len)
+			continue;
+	}
+	if (it == NULL) {
+		device_printf(sc->sc_dev, "all intents in use\n");
+		return;
+	}
+	it->it_inuse = 1;
+
+	msg.cmd = GLINK_CMD_TX_DATA;
+	msg.param1 = ch->ch_lcid;
+	msg.param2 = it->it_id;
+
+	chunk_size = len;
+	left_size = 0;
+
+	qcpas_glink_tx(sc, (char *)&msg, sizeof(msg));
+	qcpas_glink_tx(sc, (char *)&chunk_size, sizeof(chunk_size));
+	qcpas_glink_tx(sc, (char *)&left_size, sizeof(left_size));
+	qcpas_glink_tx(sc, buf, len);
+	qcpas_glink_tx_commit(sc);
+}
+
+static void
+qcpas_glink_recv_version(struct qcpas_softc *sc, uint32_t ver,
+    uint32_t features)
+{
+	struct glink_msg msg;
+
+	if (ver != GLINK_VERSION) {
+		device_printf(sc->sc_dev,
+		    "unsupported glink version %u\n", ver);
+		return;
+	}
+
+	msg.cmd = GLINK_CMD_VERSION_ACK;
+	msg.param1 = GLINK_VERSION;
+	msg.param2 = features & GLINK_FEATURE_INTENT_REUSE;
+
+	qcpas_glink_tx(sc, (char *)&msg, sizeof(msg));
+	qcpas_glink_tx_commit(sc);
+}
+
+static void
+qcpas_glink_recv_open(struct qcpas_softc *sc, uint32_t rcid, uint32_t namelen)
+{
+	struct qcpas_glink_protocol *proto = NULL;
+	struct qcpas_glink_channel *ch;
+	struct glink_msg msg;
+	char *name;
+	int i, err;
+
+	name = kmem_zalloc(namelen, KM_SLEEP);
+	qcpas_glink_rx(sc, name, namelen);
+	qcpas_glink_rx_commit(sc);
+
+	TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) {
+		if (ch->ch_rcid == rcid) {
+			device_printf(sc->sc_dev, "duplicate open for %s\n",
+			    name);
+			kmem_free(name, namelen);
+			return;
+		}
+	}
+
+	for (i = 0; i < __arraycount(qcpas_glink_protocols); i++) {
+		if (strcmp(qcpas_glink_protocols[i].name, name) != 0)
+			continue;
+		proto = &qcpas_glink_protocols[i];
+		break;
+	}
+	if (proto == NULL) {
+		kmem_free(name, namelen);
+		return;
+	}
+
+	ch = kmem_zalloc(sizeof(*ch), KM_SLEEP);
+	ch->ch_sc = sc;
+	ch->ch_proto = proto;
+	ch->ch_rcid = rcid;
+	ch->ch_lcid = ++sc->sc_glink_max_channel;
+	TAILQ_INIT(&ch->ch_l_intents);
+	TAILQ_INIT(&ch->ch_r_intents);
+	TAILQ_INSERT_TAIL(&sc->sc_glink_channels, ch, ch_q);
+
+	/* Assume we can leave HW dangling if proto init fails */
+	err = proto->init(ch);
+	if (err) {
+		TAILQ_REMOVE(&sc->sc_glink_channels, ch, ch_q);
+		kmem_free(ch, sizeof(*ch));
+		kmem_free(name, namelen);
+		return;
+	}
+
+	msg.cmd = GLINK_CMD_OPEN_ACK;
+	msg.param1 = ch->ch_rcid;
+	msg.param2 = 0;
+
+	qcpas_glink_tx(sc, (char *)&msg, sizeof(msg));
+	qcpas_glink_tx_commit(sc);
+
+	msg.cmd = GLINK_CMD_OPEN;
+	msg.param1 = ch->ch_lcid;
+	msg.param2 = strlen(name) + 1;
+
+	qcpas_glink_tx(sc, (char *)&msg, sizeof(msg));
+	qcpas_glink_tx(sc, name, strlen(name) + 1);
+	qcpas_glink_tx_commit(sc);
+
+	kmem_free(name, namelen);
+}
+
+static void
+qcpas_glink_recv_open_ack(struct qcpas_softc *sc, uint32_t lcid)
+{
+	struct qcpas_glink_channel *ch;
+	struct glink_msg msg;
+	struct qcpas_glink_intent_pair intent;
+	int i;
+
+	TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) {
+		if (ch->ch_lcid == lcid)
+			break;
+	}
+	if (ch == NULL) {
+		device_printf(sc->sc_dev, "unknown channel %u for OPEN_ACK\n",
+		    lcid);
+		return;
+	}
+
+	/* Respond with default intent now that channel is open */
+	for (i = 0; i < 5; i++) {
+		struct qcpas_glink_intent *it;
+
+		it = kmem_zalloc(sizeof(*it), KM_SLEEP);
+		it->it_id = ++ch->ch_max_intent;
+		it->it_size = 1024;
+		TAILQ_INSERT_TAIL(&ch->ch_l_intents, it, it_q);
+
+		msg.cmd = GLINK_CMD_INTENT;
+		msg.param1 = ch->ch_lcid;
+		msg.param2 = 1;
+		intent.size = it->it_size;
+		intent.iid = it->it_id;
+	}
+
+	qcpas_glink_tx(sc, (char *)&msg, sizeof(msg));
+	qcpas_glink_tx(sc, (char *)&intent, sizeof(intent));
+	qcpas_glink_tx_commit(sc);
+}
+
+static void
+qcpas_glink_recv_intent(struct qcpas_softc *sc, uint32_t rcid, uint32_t count)
+{
+	struct qcpas_glink_intent_pair *intents;
+	struct qcpas_glink_channel *ch;
+	struct qcpas_glink_intent *it;
+	int i;
+
+	intents = kmem_zalloc(sizeof(*intents) * count, KM_SLEEP);
+	qcpas_glink_rx(sc, (char *)intents, sizeof(*intents) * count);
+	qcpas_glink_rx_commit(sc);
+
+	TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) {
+		if (ch->ch_rcid == rcid)
+			break;
+	}
+	if (ch == NULL) {
+		device_printf(sc->sc_dev, "unknown channel %u for INTENT\n",
+		    rcid);
+		kmem_free(intents, sizeof(*intents) * count);
+		return;
+	}
+
+	for (i = 0; i < count; i++) {
+		it = kmem_zalloc(sizeof(*it), KM_SLEEP);
+		it->it_id = intents[i].iid;
+		it->it_size = intents[i].size;
+		TAILQ_INSERT_TAIL(&ch->ch_r_intents, it, it_q);
+	}
+
+	kmem_free(intents, sizeof(*intents) * count);
+}
+
+static void
+qcpas_glink_recv_tx_data(struct qcpas_softc *sc, uint32_t rcid, uint32_t liid)
+{
+	struct qcpas_glink_channel *ch;
+	struct qcpas_glink_intent *it;
+	struct glink_msg msg;
+	uint32_t chunk_size, left_size;
+	char *buf;
+
+	qcpas_glink_rx(sc, (char *)&chunk_size, sizeof(chunk_size));
+	qcpas_glink_rx(sc, (char *)&left_size, sizeof(left_size));
+	qcpas_glink_rx_commit(sc);
+
+	buf = kmem_zalloc(chunk_size, KM_SLEEP);
+	qcpas_glink_rx(sc, buf, chunk_size);
+	qcpas_glink_rx_commit(sc);
+
+	TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) {
+		if (ch->ch_rcid == rcid)
+			break;
+	}
+	if (ch == NULL) {
+		device_printf(sc->sc_dev, "unknown channel %u for TX_DATA\n",
+		    rcid);
+		kmem_free(buf, chunk_size);
+		return;
+	}
+
+	TAILQ_FOREACH(it, &ch->ch_l_intents, it_q) {
+		if (it->it_id == liid)
+			break;
+	}
+	if (it == NULL) {
+		device_printf(sc->sc_dev, "unknown intent %u for TX_DATA\n",
+		    liid);
+		kmem_free(buf, chunk_size);
+		return;
+	}
+
+	/* FIXME: handle message chunking */
+	KASSERT(left_size == 0);
+
+	ch->ch_proto->recv(ch, buf, chunk_size);
+	kmem_free(buf, chunk_size);
+
+	if (!left_size) {
+		msg.cmd = GLINK_CMD_RX_DONE_W_REUSE;
+		msg.param1 = ch->ch_lcid;
+		msg.param2 = it->it_id;
+
+		qcpas_glink_tx(sc, (char *)&msg, sizeof(msg));
+		qcpas_glink_tx_commit(sc);
+	}
+}
+
+static void
+qcpas_glink_recv_rx_done(struct qcpas_softc *sc, uint32_t rcid, uint32_t riid,
+    int reuse)
+{
+	struct qcpas_glink_channel *ch;
+	struct qcpas_glink_intent *it;
+
+	TAILQ_FOREACH(ch, &sc->sc_glink_channels, ch_q) {
+		if (ch->ch_rcid == rcid)
+			break;
+	}
+	if (ch == NULL) {
+		device_printf(sc->sc_dev, "unknown channel %u for RX_DONE\n",
+		    rcid);
+		return;
+	}
+
+	TAILQ_FOREACH(it, &ch->ch_r_intents, it_q) {
+		if (it->it_id == riid)
+			break;
+	}
+	if (it == NULL) {
+		device_printf(sc->sc_dev, "unknown intent %u for RX_DONE\n",
+		    riid);
+		return;
+	}
+
+	/* FIXME: handle non-reuse */
+	KASSERT(reuse);
+
+	KASSERT(it->it_inuse);
+	it->it_inuse = 0;
+}
+
+static void
+qcpas_glink_recv(void *arg)
+{
+	struct qcpas_softc *sc = arg;
+	struct glink_msg msg;
+
+	while (*sc->sc_rx_tail != *sc->sc_rx_head) {
+		membar_consumer();
+		qcpas_glink_rx(sc, (uint8_t *)&msg, sizeof(msg));
+		qcpas_glink_rx_commit(sc);
+
+		switch (msg.cmd) {
+		case GLINK_CMD_VERSION:
+			qcpas_glink_recv_version(sc, msg.param1, msg.param2);
+			break;
+		case GLINK_CMD_OPEN:
+			qcpas_glink_recv_open(sc, msg.param1, msg.param2);
+			break;
+		case GLINK_CMD_OPEN_ACK:
+			qcpas_glink_recv_open_ack(sc, msg.param1);
+			break;
+		case GLINK_CMD_INTENT:
+			qcpas_glink_recv_intent(sc, msg.param1, msg.param2);
+			break;
+		case GLINK_CMD_RX_INTENT_REQ:
+			/* Nothing to do so far */
+			break;
+		case GLINK_CMD_TX_DATA:
+			qcpas_glink_recv_tx_data(sc, msg.param1, msg.param2);
+			break;
+		case GLINK_CMD_RX_DONE:
+			qcpas_glink_recv_rx_done(sc, msg.param1, msg.param2, 0);
+			break;
+		case GLINK_CMD_RX_DONE_W_REUSE:
+			qcpas_glink_recv_rx_done(sc, msg.param1, msg.param2, 1);
+			break;
+		default:
+			device_printf(sc->sc_dev, "unknown cmd %u\n", msg.cmd);
+			return;
+		}
+	}
+}
+
+static int
+qcpas_glink_intr(void *cookie)
+{
+	struct qcpas_softc *sc = cookie;
+
+	sysmon_task_queue_sched(0, qcpas_glink_recv, sc);
+
+	return 1;
+}
+
+/* GLINK PMIC Router */
+
+struct pmic_glink_hdr {
+	uint32_t owner;
+#define PMIC_GLINK_OWNER_BATTMGR	32778
+#define PMIC_GLINK_OWNER_USBC		32779
+#define PMIC_GLINK_OWNER_USBC_PAN	32780
+	uint32_t type;
+#define PMIC_GLINK_TYPE_REQ_RESP	1
+#define PMIC_GLINK_TYPE_NOTIFY		2
+	uint32_t opcode;
+};
+
+#define BATTMGR_OPCODE_BAT_STATUS		0x1
+#define BATTMGR_OPCODR_REQUEST_NOTIFICATION	0x4
+#define BATTMGR_OPCODE_NOTIF			0x7
+#define BATTMGR_OPCODE_BAT_INFO			0x9
+#define BATTMGR_OPCODE_BAT_DISCHARGE_TIME	0xc
+#define BATTMGR_OPCODE_BAT_CHARGE_TIME		0xd
+
+#define BATTMGR_NOTIF_BAT_PROPERTY		0x30
+#define BATTMGR_NOTIF_USB_PROPERTY		0x32
+#define BATTMGR_NOTIF_WLS_PROPERTY		0x34
+#define BATTMGR_NOTIF_BAT_STATUS		0x80
+#define BATTMGR_NOTIF_BAT_INFO			0x81
+
+#define BATTMGR_CHEMISTRY_LEN			4
+#define BATTMGR_STRING_LEN			128
+
+struct battmgr_bat_info {
+	uint32_t power_unit;
+	uint32_t design_capacity;
+	uint32_t last_full_capacity;
+	uint32_t battery_tech;
+	uint32_t design_voltage;
+	uint32_t capacity_low;
+	uint32_t capacity_warning;
+	uint32_t cycle_count;
+	uint32_t accuracy;
+	uint32_t max_sample_time_ms;
+	uint32_t min_sample_time_ms;
+	uint32_t max_average_interval_ms;
+	uint32_t min_average_interval_ms;
+	uint32_t capacity_granularity1;
+	uint32_t capacity_granularity2;
+	uint32_t swappable;
+	uint32_t capabilities;
+	char model_number[BATTMGR_STRING_LEN];
+	char serial_number[BATTMGR_STRING_LEN];
+	char battery_type[BATTMGR_STRING_LEN];
+	char oem_info[BATTMGR_STRING_LEN];
+	char battery_chemistry[BATTMGR_CHEMISTRY_LEN];
+	char uid[BATTMGR_STRING_LEN];
+	uint32_t critical_bias;
+	uint8_t day;
+	uint8_t month;
+	uint16_t year;
+	uint32_t battery_id;
+};
+
+struct battmgr_bat_status {
+	uint32_t battery_state;
+#define BATTMGR_BAT_STATE_DISCHARGE	(1 << 0)
+#define BATTMGR_BAT_STATE_CHARGING	(1 << 1)
+#define BATTMGR_BAT_STATE_CRITICAL_LOW	(1 << 2)
+	uint32_t capacity;
+	int32_t rate;
+	uint32_t battery_voltage;
+	uint32_t power_state;
+#define BATTMGR_PWR_STATE_AC_ON			(1 << 0)
+	uint32_t charging_source;
+#define BATTMGR_CHARGING_SOURCE_AC		1
+#define BATTMGR_CHARGING_SOURCE_USB		2
+#define BATTMGR_CHARGING_SOURCE_WIRELESS	3
+	uint32_t temperature;
+};
+
+static void	qcpas_pmic_rtr_refresh(void *);
+static void	qcpas_pmic_rtr_bat_info(struct qcpas_softc *,
+		    struct battmgr_bat_info *);
+static void	qcpas_pmic_rtr_bat_status(struct qcpas_softc *,
+		    struct battmgr_bat_status *);
+
+static void
+qcpas_pmic_rtr_battmgr_req_info(void *cookie)
+{
+	struct {
+		struct pmic_glink_hdr hdr;
+		uint32_t battery_id;
+	} msg;
+
+	msg.hdr.owner = PMIC_GLINK_OWNER_BATTMGR;
+	msg.hdr.type = PMIC_GLINK_TYPE_REQ_RESP;
+	msg.hdr.opcode = BATTMGR_OPCODE_BAT_INFO;
+	msg.battery_id = 0;
+	qcpas_glink_send(cookie, &msg, sizeof(msg));
+}
+
+static void
+qcpas_pmic_rtr_battmgr_req_status(void *cookie)
+{
+	struct {
+		struct pmic_glink_hdr hdr;
+		uint32_t battery_id;
+	} msg;
+
+	msg.hdr.owner = PMIC_GLINK_OWNER_BATTMGR;
+	msg.hdr.type = PMIC_GLINK_TYPE_REQ_RESP;
+	msg.hdr.opcode = BATTMGR_OPCODE_BAT_STATUS;
+	msg.battery_id = 0;
+	qcpas_glink_send(cookie, &msg, sizeof(msg));
+}
+
+static int
+qcpas_pmic_rtr_init(void *cookie)
+{
+	struct qcpas_glink_channel *ch = cookie;
+	struct qcpas_softc *sc = ch->ch_sc;
+
+	callout_init(&sc->sc_rtr_refresh, 0);
+	callout_setfunc(&sc->sc_rtr_refresh, qcpas_pmic_rtr_refresh, ch);
+
+	callout_schedule(&sc->sc_rtr_refresh, hz * 5);
+
+	return 0;
+}
+
+static int
+qcpas_pmic_rtr_recv(void *cookie, uint8_t *buf, int len)
+{
+	struct qcpas_glink_channel *ch = cookie;
+	struct qcpas_softc *sc = ch->ch_sc;
+	struct pmic_glink_hdr hdr;
+	uint32_t notification;
+
+	if (len < sizeof(hdr)) {
+		device_printf(sc->sc_dev, "pmic glink message too small\n");
+		return 0;
+	}
+
+	memcpy(&hdr, buf, sizeof(hdr));
+
+	switch (hdr.owner) {
+	case PMIC_GLINK_OWNER_BATTMGR:
+		switch (hdr.opcode) {
+		case BATTMGR_OPCODE_NOTIF:
+			if (len - sizeof(hdr) != sizeof(uint32_t)) {
+				device_printf(sc->sc_dev,
+				    "invalid battgmr notification\n");
+				return 0;
+			}
+			memcpy(&notification, buf + sizeof(hdr),
+			    sizeof(uint32_t));
+			switch (notification) {
+			case BATTMGR_NOTIF_BAT_INFO:
+				qcpas_pmic_rtr_battmgr_req_info(cookie);
+				/* FALLTHROUGH */
+			case BATTMGR_NOTIF_BAT_STATUS:
+			case BATTMGR_NOTIF_BAT_PROPERTY:
+				qcpas_pmic_rtr_battmgr_req_status(cookie);
+				break;
+			default:
+				aprint_debug_dev(sc->sc_dev,
+				    "unknown battmgr notification 0x%02x\n",
+				    notification);
+				break;
+			}
+			break;
+		case BATTMGR_OPCODE_BAT_INFO: {
+			struct battmgr_bat_info *bat;
+			if (len - sizeof(hdr) < sizeof(*bat)) {
+				device_printf(sc->sc_dev,
+				    "invalid battgmr bat info\n");
+				return 0;
+			}
+			bat = kmem_alloc(sizeof(*bat), KM_SLEEP);
+			memcpy(bat, buf + sizeof(hdr), sizeof(*bat));
+			qcpas_pmic_rtr_bat_info(sc, bat);
+			kmem_free(bat, sizeof(*bat));
+			break;
+		}
+		case BATTMGR_OPCODE_BAT_STATUS: {
+			struct battmgr_bat_status *bat;
+			if (len - sizeof(hdr) != sizeof(*bat)) {
+				device_printf(sc->sc_dev,
+				    "invalid battgmr bat status\n");
+				return 0;
+			}
+			bat = kmem_alloc(sizeof(*bat), KM_SLEEP);
+			memcpy(bat, buf + sizeof(hdr), sizeof(*bat));
+			qcpas_pmic_rtr_bat_status(sc, bat);
+			kmem_free(bat, sizeof(*bat));
+			break;
+		}
+		default:
+			device_printf(sc->sc_dev,
+			    "unknown battmgr opcode 0x%02x\n",
+			    hdr.opcode);
+			break;
+		}
+		break;
+	default:
+		device_printf(sc->sc_dev,
+		    "unknown pmic glink owner 0x%04x\n",
+		    hdr.owner);
+		break;
+	}
+
+	return 0;
+}
+
+static void
+qcpas_pmic_rtr_refresh(void *arg)
+{
+	struct qcpas_glink_channel *ch = arg;
+	struct qcpas_softc *sc = ch->ch_sc;
+
+	qcpas_pmic_rtr_battmgr_req_status(ch);
+
+	callout_schedule(&sc->sc_rtr_refresh, hz * 5);
+}
+
+static void
+qcpas_pmic_rtr_bat_info(struct qcpas_softc *sc, struct battmgr_bat_info *bat)
+{
+	sc->sc_warning_capacity = bat->capacity_warning;
+	sc->sc_low_capacity = bat->capacity_low;
+
+	sc->sc_sens[QCPAS_DCAPACITY].value_cur =
+	    bat->design_capacity * 1000;
+	sc->sc_sens[QCPAS_DCAPACITY].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_LFCCAPACITY].value_cur =
+	    bat->last_full_capacity * 1000;
+	sc->sc_sens[QCPAS_LFCCAPACITY].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_DVOLTAGE].value_cur =
+	    bat->design_voltage * 1000;
+	sc->sc_sens[QCPAS_DVOLTAGE].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_DCYCLES].value_cur =
+	    bat->cycle_count;
+	sc->sc_sens[QCPAS_DCYCLES].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_CAPACITY].value_max =
+	    bat->last_full_capacity * 1000;
+	sysmon_envsys_update_limits(sc->sc_sme,
+	    &sc->sc_sens[QCPAS_CAPACITY]);
+}
+
+void
+qcpas_pmic_rtr_bat_status(struct qcpas_softc *sc,
+    struct battmgr_bat_status *bat)
+{
+	sc->sc_sens[QCPAS_CHARGING].value_cur = 
+	    (bat->battery_state & BATTMGR_BAT_STATE_CHARGING) != 0;
+	sc->sc_sens[QCPAS_CHARGING].state = ENVSYS_SVALID;
+	if ((bat->battery_state & BATTMGR_BAT_STATE_CHARGING) != 0) {
+		sc->sc_sens[QCPAS_CHARGERATE].value_cur =
+		    abs(bat->rate) * 1000;
+		sc->sc_sens[QCPAS_CHARGERATE].state = ENVSYS_SVALID;
+		sc->sc_sens[QCPAS_DISCHARGERATE].state = ENVSYS_SINVALID;
+	} else if ((bat->battery_state & BATTMGR_BAT_STATE_DISCHARGE) != 0) {
+		sc->sc_sens[QCPAS_CHARGERATE].state = ENVSYS_SINVALID;
+		sc->sc_sens[QCPAS_DISCHARGERATE].value_cur =
+		    abs(bat->rate) * 1000;
+		sc->sc_sens[QCPAS_DISCHARGERATE].state = ENVSYS_SVALID;
+	} else {
+		sc->sc_sens[QCPAS_DISCHARGERATE].state = ENVSYS_SINVALID;
+		sc->sc_sens[QCPAS_CHARGERATE].state = ENVSYS_SINVALID;
+	}
+
+	sc->sc_sens[QCPAS_VOLTAGE].value_cur =
+	    bat->battery_voltage * 1000;
+	sc->sc_sens[QCPAS_VOLTAGE].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_TEMPERATURE].value_cur =
+	    (bat->temperature * 10000) + 273150000;
+	sc->sc_sens[QCPAS_TEMPERATURE].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_CAPACITY].value_cur =
+	    bat->capacity * 1000;
+	sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SVALID;
+
+	sc->sc_sens[QCPAS_CHARGE_STATE].value_cur =
+	    ENVSYS_BATTERY_CAPACITY_NORMAL;
+	sc->sc_sens[QCPAS_CHARGE_STATE].state = ENVSYS_SVALID;
+
+	if (bat->capacity < sc->sc_warning_capacity) {
+		sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SWARNUNDER;
+		sc->sc_sens[QCPAS_CHARGE_STATE].value_cur =
+		    ENVSYS_BATTERY_CAPACITY_WARNING;
+	}
+
+	if (bat->capacity < sc->sc_low_capacity) {
+		sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SCRITUNDER;
+		sc->sc_sens[QCPAS_CHARGE_STATE].value_cur =
+		    ENVSYS_BATTERY_CAPACITY_LOW;
+	}
+
+	if ((bat->battery_state & BATTMGR_BAT_STATE_CRITICAL_LOW) != 0) {
+		sc->sc_sens[QCPAS_CAPACITY].state = ENVSYS_SCRITICAL;
+		sc->sc_sens[QCPAS_CHARGE_STATE].value_cur =
+		    ENVSYS_BATTERY_CAPACITY_CRITICAL;
+	}
+
+	if ((bat->power_state & BATTMGR_PWR_STATE_AC_ON) !=
+	    (sc->sc_power_state & BATTMGR_PWR_STATE_AC_ON)) {
+		sysmon_pswitch_event(&sc->sc_smpsw_acadapter,
+		    (bat->power_state & BATTMGR_PWR_STATE_AC_ON) != 0 ?
+		    PSWITCH_EVENT_PRESSED : PSWITCH_EVENT_RELEASED);
+
+		aprint_debug_dev(sc->sc_dev, "AC adapter %sconnected\n",
+		    (bat->power_state & BATTMGR_PWR_STATE_AC_ON) == 0 ?
+		    "not " : "");
+	}
+
+	sc->sc_power_state = bat->power_state;
+	sc->sc_sens[QCPAS_ACADAPTER].value_cur =
+	    (bat->power_state & BATTMGR_PWR_STATE_AC_ON) != 0;
+	sc->sc_sens[QCPAS_ACADAPTER].state = ENVSYS_SVALID;
+}
+
+static void
+qcpas_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
+    sysmon_envsys_lim_t *limits, uint32_t *props)
+{
+	struct qcpas_softc *sc = sme->sme_cookie;
+
+	if (edata->sensor != QCPAS_CAPACITY) {
+		return;
+	}
+
+	limits->sel_critmin = sc->sc_low_capacity * 1000;
+	limits->sel_warnmin = sc->sc_warning_capacity * 1000;
+
+	*props |= PROP_BATTCAP | PROP_BATTWARN | PROP_DRIVER_LIMITS;
+}
Index: src/sys/dev/acpi/qcompep.c
diff -u /dev/null src/sys/dev/acpi/qcompep.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcompep.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,235 @@
+/* $NetBSD: qcompep.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*	$OpenBSD: qcaoss.c,v 1.1 2023/05/23 14:10:27 patrick Exp $	*/
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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 <sys/kmem.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/qcompep.h>
+#include <dev/acpi/qcomipcc.h>
+
+#define AOSS_DESC_MAGIC			0x0
+#define AOSS_DESC_VERSION		0x4
+#define AOSS_DESC_FEATURES		0x8
+#define AOSS_DESC_UCORE_LINK_STATE	0xc
+#define AOSS_DESC_UCORE_LINK_STATE_ACK	0x10
+#define AOSS_DESC_UCORE_CH_STATE	0x14
+#define AOSS_DESC_UCORE_CH_STATE_ACK	0x18
+#define AOSS_DESC_UCORE_MBOX_SIZE	0x1c
+#define AOSS_DESC_UCORE_MBOX_OFFSET	0x20
+#define AOSS_DESC_MCORE_LINK_STATE	0x24
+#define AOSS_DESC_MCORE_LINK_STATE_ACK	0x28
+#define AOSS_DESC_MCORE_CH_STATE	0x2c
+#define AOSS_DESC_MCORE_CH_STATE_ACK	0x30
+#define AOSS_DESC_MCORE_MBOX_SIZE	0x34
+#define AOSS_DESC_MCORE_MBOX_OFFSET	0x38
+
+#define AOSS_MAGIC			0x4d41494c
+#define AOSS_VERSION			1
+
+#define AOSS_STATE_UP			(0xffffU << 0)
+#define AOSS_STATE_DOWN			(0xffffU << 16)
+
+#define AOSSREAD4(sc, reg)						\
+	bus_space_read_4((sc)->sc_iot, (sc)->sc_aoss_ioh, (reg))
+#define AOSSWRITE4(sc, reg, val)					\
+	bus_space_write_4((sc)->sc_iot, (sc)->sc_aoss_ioh, (reg), (val))
+
+struct qcpep_data {
+	bus_addr_t		aoss_base;
+	bus_size_t		aoss_size;
+	uint32_t		aoss_client_id;
+	uint32_t		aoss_signal_id;
+};
+
+struct qcpep_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_iot;
+
+	const struct qcpep_data	*sc_data;
+
+	bus_space_handle_t	sc_aoss_ioh;
+	size_t			sc_aoss_offset;
+	size_t			sc_aoss_size;
+	void *			sc_aoss_ipcc;
+};
+
+struct qcpep_softc *qcpep_sc;
+
+static const struct qcpep_data qcpep_x1e_data = {
+	.aoss_base = 0x0c300000,
+	.aoss_size = 0x400,
+	.aoss_client_id = 0,	/* IPCC_CLIENT_AOP */
+	.aoss_signal_id = 0,	/* IPCC_MPROC_SIGNAL_GLINK_QMP */
+};
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "QCOM0C17",		.data = &qcpep_x1e_data },
+	DEVICE_COMPAT_EOL
+};
+
+static int	qcpep_match(device_t, cfdata_t, void *);
+static void	qcpep_attach(device_t, device_t, void *);
+
+CFATTACH_DECL_NEW(qcompep, sizeof(struct qcpep_softc),
+    qcpep_match, qcpep_attach, NULL, NULL);
+
+static int
+qcpep_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+static void
+qcpep_attach(device_t parent, device_t self, void *aux)
+{
+	struct qcpep_softc *sc = device_private(self);
+	struct acpi_attach_args *aa = aux;
+	struct acpi_resources res;
+	ACPI_STATUS rv;
+	int i;
+
+        rv = acpi_resource_parse(self, aa->aa_node->ad_handle,
+	    "_CRS", &res, &acpi_resource_parse_ops_default);
+        if (ACPI_FAILURE(rv)) {
+                return;
+        }
+	acpi_resource_cleanup(&res);
+
+	sc->sc_dev = self;
+	sc->sc_iot = aa->aa_memt;
+	sc->sc_data = acpi_compatible_lookup(aa, compat_data)->data;
+
+	if (bus_space_map(sc->sc_iot, sc->sc_data->aoss_base,
+	    sc->sc_data->aoss_size, BUS_SPACE_MAP_NONPOSTED, &sc->sc_aoss_ioh)) {
+		aprint_error_dev(self, "couldn't map registers\n");
+		return;
+	}
+
+	sc->sc_aoss_ipcc = qcipcc_channel(sc->sc_data->aoss_client_id,
+					  sc->sc_data->aoss_signal_id);
+	if (sc->sc_aoss_ipcc == NULL) {
+		aprint_error_dev(self, "couldn't find ipcc mailbox\n");
+		return;
+	}
+
+	if (AOSSREAD4(sc, AOSS_DESC_MAGIC) != AOSS_MAGIC ||
+	    AOSSREAD4(sc, AOSS_DESC_VERSION) != AOSS_VERSION) {
+		aprint_error_dev(self, "invalid QMP info\n");
+		return;
+	}
+
+	sc->sc_aoss_offset = AOSSREAD4(sc, AOSS_DESC_MCORE_MBOX_OFFSET);
+	sc->sc_aoss_size = AOSSREAD4(sc, AOSS_DESC_MCORE_MBOX_SIZE);
+	if (sc->sc_aoss_size == 0) {
+		aprint_error_dev(self, "invalid AOSS mailbox size\n");
+		return;
+	}
+
+	AOSSWRITE4(sc, AOSS_DESC_UCORE_LINK_STATE_ACK,
+	    AOSSREAD4(sc, AOSS_DESC_UCORE_LINK_STATE));
+
+	AOSSWRITE4(sc, AOSS_DESC_MCORE_LINK_STATE, AOSS_STATE_UP);
+	qcipcc_send(sc->sc_aoss_ipcc);
+
+	for (i = 1000; i > 0; i--) {
+		if (AOSSREAD4(sc, AOSS_DESC_MCORE_LINK_STATE_ACK) == AOSS_STATE_UP)
+			break;
+		delay(1000);
+	}
+	if (i == 0) {
+		aprint_error_dev(self, "didn't get link state ack\n");
+		return;
+	}
+
+	AOSSWRITE4(sc, AOSS_DESC_MCORE_CH_STATE, AOSS_STATE_UP);
+	qcipcc_send(sc->sc_aoss_ipcc);
+
+	for (i = 1000; i > 0; i--) {
+		if (AOSSREAD4(sc, AOSS_DESC_UCORE_CH_STATE) == AOSS_STATE_UP)
+			break;
+		delay(1000);
+	}
+	if (i == 0) {
+		aprint_error_dev(self, "didn't get open channel\n");
+		return;
+	}
+
+	AOSSWRITE4(sc, AOSS_DESC_UCORE_CH_STATE_ACK, AOSS_STATE_UP);
+	qcipcc_send(sc->sc_aoss_ipcc);
+
+	for (i = 1000; i > 0; i--) {
+		if (AOSSREAD4(sc, AOSS_DESC_MCORE_CH_STATE_ACK) == AOSS_STATE_UP)
+			break;
+		delay(1000);
+	}
+	if (i == 0) {
+		aprint_error_dev(self, "didn't get channel ack\n");
+		return;
+	}
+
+	qcpep_sc = sc;
+}
+
+int
+qcaoss_send(char *data, size_t len)
+{
+	struct qcpep_softc *sc = qcpep_sc;
+	uint32_t reg;
+	int i;
+
+	if (sc == NULL)
+		return ENXIO;
+
+	if (data == NULL || sizeof(uint32_t) + len > sc->sc_aoss_size ||
+	    (len % sizeof(uint32_t)) != 0)
+		return EINVAL;
+
+	/* Write data first, needs to be 32-bit access. */
+	for (i = 0; i < len; i += 4) {
+		memcpy(&reg, data + i, sizeof(reg));
+		AOSSWRITE4(sc, sc->sc_aoss_offset + sizeof(uint32_t) + i, reg);
+	}
+
+	/* Commit transaction by writing length. */
+	AOSSWRITE4(sc, sc->sc_aoss_offset, len);
+
+	/* Assert it's stored and inform peer. */
+	if (AOSSREAD4(sc, sc->sc_aoss_offset) != len) {
+		device_printf(sc->sc_dev,
+		    "aoss message readback failed\n");
+	}
+	qcipcc_send(sc->sc_aoss_ipcc);
+
+	for (i = 1000; i > 0; i--) {
+		if (AOSSREAD4(sc, sc->sc_aoss_offset) == 0)
+			break;
+		delay(1000);
+	}
+	if (i == 0) {
+		device_printf(sc->sc_dev, "timeout sending message\n");
+		AOSSWRITE4(sc, sc->sc_aoss_offset, 0);
+		return ETIMEDOUT;
+	}
+
+	return 0;
+}
Index: src/sys/dev/acpi/qcompep.h
diff -u /dev/null src/sys/dev/acpi/qcompep.h:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcompep.h	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,20 @@
+/* $NetBS$ */
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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.
+ */
+
+#pragma once
+
+int	qcaoss_send(char *, size_t);
Index: src/sys/dev/acpi/qcomscm.c
diff -u /dev/null src/sys/dev/acpi/qcomscm.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomscm.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,1038 @@
+/* $NetBSD: qcomscm.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/* $OpenBSD: qcscm.c,v 1.9 2024/08/04 15:30:08 kettenis Exp $ */
+/*
+ * Copyright (c) 2022 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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 <sys/kmem.h>
+#include <sys/bus.h>
+#include <sys/uuid.h>
+
+#include <dev/efi/efi.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/qcomscm.h>
+
+#define ARM_SMCCC_STD_CALL			(0U << 31)
+#define ARM_SMCCC_FAST_CALL			(1U << 31)
+#define ARM_SMCCC_LP64				(1U << 30)
+#define ARM_SMCCC_OWNER_SIP			2
+
+#define QCTEE_TZ_OWNER_TZ_APPS			48
+#define QCTEE_TZ_OWNER_QSEE_OS			50
+
+#define QCTEE_TZ_SVC_APP_ID_PLACEHOLDER		0
+#define QCTEE_TZ_SVC_APP_MGR			1
+
+#define QCTEE_OS_RESULT_SUCCESS			0
+#define QCTEE_OS_RESULT_INCOMPLETE		1
+#define QCTEE_OS_RESULT_BLOCKED_ON_LISTENER	2
+#define QCTEE_OS_RESULT_FAILURE			0xffffffff
+
+#define QCTEE_OS_SCM_RES_APP_ID			0xee01
+#define QCTEE_OS_SCM_RES_QSEOS_LISTENER_ID	0xee02
+
+#define QCTEE_UEFI_GET_VARIABLE			0x8000
+#define QCTEE_UEFI_SET_VARIABLE			0x8001
+#define QCTEE_UEFI_GET_NEXT_VARIABLE		0x8002
+#define QCTEE_UEFI_QUERY_VARIABLE_INFO		0x8003
+
+#define QCTEE_UEFI_SUCCESS			0
+#define QCTEE_UEFI_BUFFER_TOO_SMALL		0x80000005
+#define QCTEE_UEFI_DEVICE_ERROR			0x80000007
+#define QCTEE_UEFI_NOT_FOUND			0x8000000e
+
+#define QCSCM_SVC_PIL			0x02
+#define QCSCM_PIL_PAS_INIT_IMAGE	0x01
+#define QCSCM_PIL_PAS_MEM_SETUP		0x02
+#define QCSCM_PIL_PAS_AUTH_AND_RESET	0x05
+#define QCSCM_PIL_PAS_SHUTDOWN		0x06
+#define QCSCM_PIL_PAS_IS_SUPPORTED	0x07
+#define QCSCM_PIL_PAS_MSS_RESET		0x0a
+
+#define QCSCM_INTERRUPTED		1
+#define QCSCM_EBUSY			-22
+
+#define QCSCM_ARGINFO_NUM(x)		(((x) & 0xf) << 0)
+#define QCSCM_ARGINFO_TYPE(x, y)	(((y) & 0x3) << (4 + 2 * (x)))
+#define QCSCM_ARGINFO_TYPE_VAL		0
+#define QCSCM_ARGINFO_TYPE_RO		1
+#define QCSCM_ARGINFO_TYPE_RW		2
+#define QCSCM_ARGINFO_TYPE_BUFVAL	3
+
+#define EFI_VARIABLE_NON_VOLATILE	0x00000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS	0x00000002
+#define EFI_VARIABLE_RUNTIME_ACCESS	0x00000004
+
+#define UNIX_GPS_EPOCH_OFFSET		315964800
+
+#define EFI_VAR_RTCINFO			__UNCONST(u"RTCInfo")
+
+extern struct arm32_bus_dma_tag arm_generic_dma_tag;
+
+struct qcscm_dmamem {
+	bus_dmamap_t		qdm_map;
+	bus_dma_segment_t	qdm_seg;
+	size_t			qdm_size;
+	void			*qdm_kva;
+};
+
+#define QCSCM_DMA_MAP(_qdm)		((_qdm)->qdm_map)
+#define QCSCM_DMA_LEN(_qdm)		((_qdm)->qdm_size)
+#define QCSCM_DMA_DVA(_qdm)		((uint64_t)(_qdm)->qdm_map->dm_segs[0].ds_addr)
+#define QCSCM_DMA_KVA(_qdm, off)	((void *)((uintptr_t)(_qdm)->qdm_kva + (off)))
+
+static struct uuid qcscm_uefi_rtcinfo_guid =
+  { 0x882f8c2b, 0x9646, 0x435f,
+    0x8d, 0xe5, { 0xf2, 0x08, 0xff, 0x80, 0xc1, 0xbd } };
+
+struct qcscm_softc {
+	device_t		sc_dev;
+	bus_dma_tag_t		sc_dmat;
+
+	struct qcscm_dmamem	*sc_extarg;
+	uint32_t		sc_uefi_id;
+};
+
+static int	qcscm_match(device_t, cfdata_t, void *);
+static void	qcscm_attach(device_t, device_t, void *);
+
+CFATTACH_DECL_NEW(qcomscm, sizeof(struct qcscm_softc),
+    qcscm_match, qcscm_attach, NULL, NULL);
+
+static inline void	qcscm_smc_exec(uint64_t *, uint64_t *);
+int	qcscm_smc_call(struct qcscm_softc *, uint8_t, uint8_t, uint8_t,
+	    uint32_t, uint64_t *, int, uint64_t *);
+int	qcscm_tee_app_get_id(struct qcscm_softc *, const char *, uint32_t *);
+int	qcscm_tee_app_send(struct qcscm_softc *, uint32_t, uint64_t, uint64_t,
+	    uint64_t, uint64_t);
+
+efi_status qcscm_uefi_get_variable(struct qcscm_softc *, efi_char *,
+	    int, struct uuid *, uint32_t *, uint8_t *, int *);
+efi_status qcscm_uefi_set_variable(struct qcscm_softc *, efi_char *,
+	    int, struct uuid *, uint32_t, uint8_t *, int);
+efi_status qcscm_uefi_get_next_variable(struct qcscm_softc *,
+	    efi_char *, int *, struct uuid *);
+
+efi_status qcscm_efi_get_variable(efi_char *, struct uuid *, uint32_t *,
+	    u_long *, void *);
+efi_status qcscm_efi_set_variable(efi_char *, struct uuid *, uint32_t,
+	    u_long, void *);
+efi_status qcscm_efi_get_next_variable_name(u_long *, efi_char *, struct uuid *);
+
+#ifdef QCSCM_DEBUG
+void	qcscm_uefi_dump_variables(struct qcscm_softc *);
+void	qcscm_uefi_dump_variable(struct qcscm_softc *, efi_char *, int,
+	    struct uuid *);
+#endif
+
+int	qcscm_uefi_rtc_get(uint32_t *);
+int	qcscm_uefi_rtc_set(uint32_t);
+
+struct qcscm_dmamem *
+	 qcscm_dmamem_alloc(struct qcscm_softc *, bus_size_t, bus_size_t);
+void	 qcscm_dmamem_free(struct qcscm_softc *, struct qcscm_dmamem *);
+
+struct qcscm_softc *qcscm_sc;
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "QCOM04DD" },
+	DEVICE_COMPAT_EOL
+};
+
+static int
+qcscm_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+static void
+qcscm_attach(device_t parent, device_t self, void *aux)
+{
+	struct qcscm_softc *sc = device_private(self);
+	int error;
+
+	sc->sc_dev = self;
+	error = bus_dmatag_subregion(&arm_generic_dma_tag,
+	    0, __MASK(32), &sc->sc_dmat, 0);
+	KASSERT(error == 0);
+
+	sc->sc_extarg = qcscm_dmamem_alloc(sc, PAGE_SIZE, 8);
+	if (sc->sc_extarg == NULL) {
+		aprint_error(": can't allocate memory for extended args\n");
+		return;
+	}
+
+	aprint_naive("\n");
+	aprint_normal("\n");
+
+#if notyet
+	error = qcscm_tee_app_get_id(sc, "qcom.tz.uefisecapp", &sc->sc_uefi_id);
+	if (error != 0) {
+		aprint_error_dev(self, "can't retrieve UEFI App: %d\n", error);
+		sc->sc_uefi_id = UINT32_MAX;
+	}
+#else
+	sc->sc_uefi_id = UINT32_MAX;
+#endif
+
+	qcscm_sc = sc;
+
+#ifdef QCSCM_DEBUG
+	qcscm_uefi_dump_variables(sc);
+	qcscm_uefi_dump_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO),
+	    &qcscm_uefi_rtcinfo_guid);
+#endif
+}
+
+/* Expects an uint64_t[18] */
+static inline void
+qcscm_smc_exec(uint64_t *in, uint64_t *out)
+{
+	asm volatile(
+	    "ldp x0, x1, [%[in], #0]\n"
+	    "ldp x2, x3, [%[in], #16]\n"
+	    "ldp x4, x5, [%[in], #32]\n"
+	    "ldp x6, x7, [%[in], #48]\n"
+	    "ldp x8, x9, [%[in], #64]\n"
+	    "ldp x10, x11, [%[in], #80]\n"
+	    "ldp x12, x13, [%[in], #96]\n"
+	    "ldp x14, x15, [%[in], #112]\n"
+	    "ldp x16, x17, [%[in], #128]\n"
+	    "smc #0\n"
+	    "stp x0, x1, [%[out], #0]\n"
+	    "stp x2, x3, [%[out], #16]\n"
+	    "stp x4, x5, [%[out], #32]\n"
+	    "stp x6, x7, [%[out], #48]\n"
+	    "stp x8, x9, [%[out], #64]\n"
+	    "stp x10, x11, [%[out], #80]\n"
+	    "stp x12, x13, [%[out], #96]\n"
+	    "stp x14, x15, [%[out], #112]\n"
+	    "stp x16, x17, [%[out], #128]\n"
+	    :
+	    : [in] "r" (in), [out] "r" (out)
+	    : "x0", "x1", "x2", "x3", "x4", "x5",
+	      "x6", "x7", "x8", "x9", "x10", "x11",
+	      "x12", "x13", "x14", "x15", "x16", "x17",
+	      "memory");
+}
+
+int
+qcscm_smc_call(struct qcscm_softc *sc, uint8_t owner, uint8_t svc, uint8_t cmd,
+    uint32_t arginfo, uint64_t *args, int arglen, uint64_t *res)
+{
+	uint64_t smcreq[18] = { 0 }, smcres[18] = { 0 };
+	uint64_t *smcextreq;
+	int i, busy_retry;
+
+	/* 4 of our 10 possible args fit into x2-x5 */
+	smcreq[0] = ARM_SMCCC_STD_CALL | ARM_SMCCC_LP64 |
+	    owner << 24 | svc << 8 | cmd;
+#ifdef QCSCM_DEBUG_SMC
+	device_printf(sc->sc_dev, "owner %#x svc %#x cmd %#x req %#lx\n",
+	    owner, svc, cmd, smcreq[0]);
+#endif
+	smcreq[1] = arginfo;
+	for (i = 0; i < uimin(arglen, 4); i++)
+		smcreq[2 + i] = args[i];
+
+	/* In case we have more than 4, use x5 as ptr to extra args */
+	if (arglen > 4) {
+		smcreq[5] = QCSCM_DMA_DVA(sc->sc_extarg);
+		smcextreq = QCSCM_DMA_KVA(sc->sc_extarg, 0);
+		for (i = 0; i < uimin(arglen - 3, 7); i++) {
+			smcextreq[i] = args[3 + i];
+		}
+		cpu_drain_writebuf();
+	}
+
+#ifdef QCSCM_DEBUG_SMC
+	device_printf(sc->sc_dev, "smcreq[before]:");
+	for (i = 0; i < __arraycount(smcreq); i++) {
+		printf(" %#lx", smcreq[i]);
+	}
+	printf("\n");
+#endif
+	for (busy_retry = 20; busy_retry > 0; busy_retry--) {
+		int intr_retry = 1000000;
+		for (;;) {
+			qcscm_smc_exec(smcreq, smcres);
+			/* If the call gets interrupted, try again and re-pass x0/x6 */
+			if (smcres[0] == QCSCM_INTERRUPTED) {
+				if (--intr_retry == 0) {
+					break;
+				}
+				smcreq[0] = smcres[0];
+				smcreq[6] = smcres[6];
+				continue;
+			}
+			break;
+		}
+
+		if (smcres[0] != QCSCM_EBUSY) {
+			break;
+		}
+		delay(30000);
+	}
+
+#ifdef QCSCM_DEBUG_SMC
+	device_printf(sc->sc_dev, "smcreq[after]:");
+	for (i = 0; i < __arraycount(smcreq); i++) {
+		printf(" %#lx", smcreq[i]);
+	}
+	printf("\n");
+	device_printf(sc->sc_dev, "smcres[after]:");
+	for (i = 0; i < __arraycount(smcres); i++) {
+		printf(" %#lx", smcres[i]);
+	}
+	printf("\n");
+#endif
+
+	if (res) {
+		res[0] = smcres[1];
+		res[1] = smcres[2];
+		res[2] = smcres[3];
+	}
+
+	return smcres[0];
+}
+
+/* Retrieve id of app running in TEE by name */
+int
+qcscm_tee_app_get_id(struct qcscm_softc *sc, const char *name, uint32_t *id)
+{
+	struct qcscm_dmamem *qdm;
+	uint64_t res[3];
+	uint64_t args[2];
+	uint32_t arginfo;
+	int ret;
+
+	/* Max name length is 64 */
+	if (strlen(name) > 64)
+		return EINVAL;
+
+	/* Alloc some phys mem to hold the name */
+	qdm = qcscm_dmamem_alloc(sc, PAGE_SIZE, 8);
+	if (qdm == NULL)
+		return ENOMEM;
+
+	/* Copy name of app we want to get an id for to page */
+	memcpy(QCSCM_DMA_KVA(qdm, 0), name, strlen(name));
+
+	/* Pass address of name and length */
+	arginfo = QCSCM_ARGINFO_NUM(__arraycount(args));
+	arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_RW);
+	arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_VAL);
+	args[0] = QCSCM_DMA_DVA(qdm);
+	args[1] = strlen(name);
+
+	cpu_drain_writebuf();
+
+	/* Make call into TEE */
+	ret = qcscm_smc_call(sc, QCTEE_TZ_OWNER_QSEE_OS, QCTEE_TZ_SVC_APP_MGR,
+	    0x03, arginfo, args, __arraycount(args), res);
+
+	/* If the call succeeded, check the response status */
+	if (ret == 0)
+		ret = res[0];
+
+	/* If the response status is successful as well, retrieve data */
+	if (ret == 0)
+		*id = res[2];
+
+	qcscm_dmamem_free(sc, qdm);
+	return ret;
+}
+
+/* Message interface to app running in TEE */
+int
+qcscm_tee_app_send(struct qcscm_softc *sc, uint32_t id, uint64_t req_phys,
+    uint64_t req_len, uint64_t rsp_phys, uint64_t rsp_len)
+{
+	uint64_t res[3];
+	uint64_t args[5];
+	uint32_t arginfo;
+	int ret;
+
+	/* Pass id of app we target, plus request and response buffers */
+	arginfo = QCSCM_ARGINFO_NUM(__arraycount(args));
+	arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL);
+	arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_RW);
+	arginfo |= QCSCM_ARGINFO_TYPE(2, QCSCM_ARGINFO_TYPE_VAL);
+	arginfo |= QCSCM_ARGINFO_TYPE(3, QCSCM_ARGINFO_TYPE_RW);
+	arginfo |= QCSCM_ARGINFO_TYPE(4, QCSCM_ARGINFO_TYPE_VAL);
+	args[0] = id;
+	args[1] = req_phys;
+	args[2] = req_len;
+	args[3] = rsp_phys;
+	args[4] = rsp_len;
+
+	/* Make call into TEE */
+	ret = qcscm_smc_call(sc, QCTEE_TZ_OWNER_TZ_APPS,
+	    QCTEE_TZ_SVC_APP_ID_PLACEHOLDER, 0x01,
+	    arginfo, args, __arraycount(args), res);
+
+	/* If the call succeeded, check the response status */
+	if (ret == 0)
+		ret = res[0];
+
+	return ret;
+}
+
+struct qcscm_req_uefi_get_variable {
+	uint32_t command_id;
+	uint32_t length;
+	uint32_t name_offset;
+	uint32_t name_size;
+	uint32_t guid_offset;
+	uint32_t guid_size;
+	uint32_t data_size;
+};
+
+struct qcscm_rsp_uefi_get_variable {
+	uint32_t command_id;
+	uint32_t length;
+	uint32_t status;
+	uint32_t attributes;
+	uint32_t data_offset;
+	uint32_t data_size;
+};
+
+efi_status
+qcscm_uefi_get_variable(struct qcscm_softc *sc,
+    efi_char *name, int name_size, struct uuid *guid,
+    uint32_t *attributes, uint8_t *data, int *data_size)
+{
+	struct qcscm_req_uefi_get_variable *req;
+	struct qcscm_rsp_uefi_get_variable *resp;
+	struct qcscm_dmamem *qdm;
+	size_t reqsize, respsize;
+	off_t reqoff, respoff;
+	int ret;
+
+	if (sc->sc_uefi_id == UINT32_MAX)
+		return QCTEE_UEFI_DEVICE_ERROR;
+
+	reqsize = ALIGN(sizeof(*req)) + ALIGN(name_size) + ALIGN(sizeof(*guid));
+	respsize = ALIGN(sizeof(*resp)) + ALIGN(*data_size);
+
+	reqoff = 0;
+	respoff = reqsize;
+
+	qdm = qcscm_dmamem_alloc(sc, round_page(reqsize + respsize), 8);
+	if (qdm == NULL)
+		return QCTEE_UEFI_DEVICE_ERROR;
+
+	req = QCSCM_DMA_KVA(qdm, reqoff);
+	req->command_id = QCTEE_UEFI_GET_VARIABLE;
+	req->data_size = *data_size;
+	req->name_offset = ALIGN(sizeof(*req));
+	req->name_size = name_size;
+	req->guid_offset = ALIGN(req->name_offset + req->name_size);
+	req->guid_size = sizeof(*guid);
+	req->length = req->guid_offset + req->guid_size;
+
+	memcpy((char *)req + req->guid_offset, guid, sizeof(*guid));
+	memcpy((char *)req + req->name_offset, name, name_size);
+
+	cpu_drain_writebuf();
+
+	ret = qcscm_tee_app_send(sc, sc->sc_uefi_id,
+	    QCSCM_DMA_DVA(qdm) + reqoff, reqsize,
+	    QCSCM_DMA_DVA(qdm) + respoff, respsize);
+	if (ret) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	cpu_drain_writebuf();
+
+	resp = QCSCM_DMA_KVA(qdm, respoff);
+	if (resp->command_id != QCTEE_UEFI_GET_VARIABLE ||
+	    resp->length < sizeof(*resp)) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	if (resp->status) {
+		if (resp->status == QCTEE_UEFI_BUFFER_TOO_SMALL)
+			*data_size = resp->data_size;
+		if (attributes)
+			*attributes = resp->attributes;
+		ret = resp->status;
+		qcscm_dmamem_free(sc, qdm);
+		return ret;
+	}
+
+	if (resp->length > respsize ||
+	    resp->data_offset + resp->data_size > resp->length) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	if (attributes)
+		*attributes = resp->attributes;
+
+	if (*data_size == 0) {
+		*data_size = resp->data_size;
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_SUCCESS;
+	}
+
+	if (resp->data_size > *data_size) {
+		*data_size = resp->data_size;
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_BUFFER_TOO_SMALL;
+	}
+
+	memcpy(data, (char *)resp + resp->data_offset, resp->data_size);
+	*data_size = resp->data_size;
+
+	qcscm_dmamem_free(sc, qdm);
+	return EFI_SUCCESS;
+}
+
+struct qcscm_req_uefi_set_variable {
+	uint32_t command_id;
+	uint32_t length;
+	uint32_t name_offset;
+	uint32_t name_size;
+	uint32_t guid_offset;
+	uint32_t guid_size;
+	uint32_t attributes;
+	uint32_t data_offset;
+	uint32_t data_size;
+};
+
+struct qcscm_rsp_uefi_set_variable {
+	uint32_t command_id;
+	uint32_t length;
+	uint32_t status;
+	uint32_t unknown[2];
+};
+
+efi_status
+qcscm_uefi_set_variable(struct qcscm_softc *sc,
+    efi_char *name, int name_size, struct uuid *guid,
+    uint32_t attributes, uint8_t *data, int data_size)
+{
+	struct qcscm_req_uefi_set_variable *req;
+	struct qcscm_rsp_uefi_set_variable *resp;
+	struct qcscm_dmamem *qdm;
+	size_t reqsize, respsize;
+	off_t reqoff, respoff;
+	int ret;
+
+	if (sc->sc_uefi_id == UINT32_MAX)
+		return QCTEE_UEFI_DEVICE_ERROR;
+
+	reqsize = ALIGN(sizeof(*req)) + ALIGN(name_size) + ALIGN(sizeof(*guid)) +
+	    ALIGN(data_size);
+	respsize = ALIGN(sizeof(*resp));
+
+	reqoff = 0;
+	respoff = reqsize;
+
+	qdm = qcscm_dmamem_alloc(sc, round_page(reqsize + respsize), 8);
+	if (qdm == NULL)
+		return QCTEE_UEFI_DEVICE_ERROR;
+
+	req = QCSCM_DMA_KVA(qdm, reqoff);
+	req->command_id = QCTEE_UEFI_SET_VARIABLE;
+	req->attributes = attributes;
+	req->name_offset = ALIGN(sizeof(*req));
+	req->name_size = name_size;
+	req->guid_offset = ALIGN(req->name_offset + req->name_size);
+	req->guid_size = sizeof(*guid);
+	req->data_offset = ALIGN(req->guid_offset + req->guid_size);
+	req->data_size = data_size;
+	req->length = req->data_offset + req->data_size;
+
+	memcpy((char *)req + req->name_offset, name, name_size);
+	memcpy((char *)req + req->guid_offset, guid, sizeof(*guid));
+	memcpy((char *)req + req->data_offset, data, data_size);
+
+	ret = qcscm_tee_app_send(sc, sc->sc_uefi_id,
+	    QCSCM_DMA_DVA(qdm) + reqoff, reqsize,
+	    QCSCM_DMA_DVA(qdm) + respoff, respsize);
+	if (ret) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	resp = QCSCM_DMA_KVA(qdm, respoff);
+	if (resp->command_id != QCTEE_UEFI_SET_VARIABLE ||
+	    resp->length < sizeof(*resp) || resp->length > respsize) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	if (resp->status) {
+		ret = resp->status;
+		qcscm_dmamem_free(sc, qdm);
+		return ret;
+	}
+
+	qcscm_dmamem_free(sc, qdm);
+	return QCTEE_UEFI_SUCCESS;
+}
+
+struct qcscm_req_uefi_get_next_variable {
+	uint32_t command_id;
+	uint32_t length;
+	uint32_t guid_offset;
+	uint32_t guid_size;
+	uint32_t name_offset;
+	uint32_t name_size;
+};
+
+struct qcscm_rsp_uefi_get_next_variable {
+	uint32_t command_id;
+	uint32_t length;
+	uint32_t status;
+	uint32_t guid_offset;
+	uint32_t guid_size;
+	uint32_t name_offset;
+	uint32_t name_size;
+};
+
+efi_status
+qcscm_uefi_get_next_variable(struct qcscm_softc *sc,
+    efi_char *name, int *name_size, struct uuid *guid)
+{
+	struct qcscm_req_uefi_get_next_variable *req;
+	struct qcscm_rsp_uefi_get_next_variable *resp;
+	struct qcscm_dmamem *qdm;
+	size_t reqsize, respsize;
+	off_t reqoff, respoff;
+	int ret;
+
+	if (sc->sc_uefi_id == UINT32_MAX)
+		return QCTEE_UEFI_DEVICE_ERROR;
+
+	reqsize = ALIGN(sizeof(*req)) + ALIGN(sizeof(*guid)) + ALIGN(*name_size);
+	respsize = ALIGN(sizeof(*resp)) + ALIGN(sizeof(*guid)) + ALIGN(*name_size);
+
+	reqoff = 0;
+	respoff = reqsize;
+
+	qdm = qcscm_dmamem_alloc(sc, round_page(reqsize + respsize), 8);
+	if (qdm == NULL)
+		return QCTEE_UEFI_DEVICE_ERROR;
+
+	req = QCSCM_DMA_KVA(qdm, reqoff);
+	req->command_id = QCTEE_UEFI_GET_NEXT_VARIABLE;
+	req->guid_offset = ALIGN(sizeof(*req));
+	req->guid_size = sizeof(*guid);
+	req->name_offset = ALIGN(req->guid_offset + req->guid_size);
+	req->name_size = *name_size;
+	req->length = req->name_offset + req->name_size;
+
+	memcpy((char *)req + req->guid_offset, guid, sizeof(*guid));
+	memcpy((char *)req + req->name_offset, name, *name_size);
+
+	ret = qcscm_tee_app_send(sc, sc->sc_uefi_id,
+	    QCSCM_DMA_DVA(qdm) + reqoff, reqsize,
+	    QCSCM_DMA_DVA(qdm) + respoff, respsize);
+	if (ret) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	resp = QCSCM_DMA_KVA(qdm, respoff);
+	if (resp->command_id != QCTEE_UEFI_GET_NEXT_VARIABLE ||
+	    resp->length < sizeof(*resp) || resp->length > respsize) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	if (resp->status) {
+		if (resp->status == QCTEE_UEFI_BUFFER_TOO_SMALL)
+			*name_size = resp->name_size;
+		ret = resp->status;
+		qcscm_dmamem_free(sc, qdm);
+		return ret;
+	}
+
+	if (resp->guid_offset + resp->guid_size > resp->length ||
+	    resp->name_offset + resp->name_size > resp->length) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	if (resp->guid_size != sizeof(*guid)) {
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_DEVICE_ERROR;
+	}
+
+	if (resp->name_size > *name_size) {
+		*name_size = resp->name_size;
+		qcscm_dmamem_free(sc, qdm);
+		return QCTEE_UEFI_BUFFER_TOO_SMALL;
+	}
+
+	memcpy(guid, (char *)resp + resp->guid_offset, sizeof(*guid));
+	memcpy(name, (char *)resp + resp->name_offset, resp->name_size);
+	*name_size = resp->name_size;
+
+	qcscm_dmamem_free(sc, qdm);
+	return QCTEE_UEFI_SUCCESS;
+}
+
+efi_status
+qcscm_efi_get_variable(efi_char *name, struct uuid *guid, uint32_t *attributes,
+   u_long *data_size, void *data)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	efi_status status;
+	int name_size;
+	int size;
+
+	name_size = 0;
+	while (name[name_size])
+		name_size++;
+	name_size++;
+
+	size = *data_size;
+	status = qcscm_uefi_get_variable(sc, name, name_size * 2, guid,
+	    attributes, data, &size);
+	*data_size = size;
+
+	/* Convert 32-bit status code to 64-bit. */
+	return ((status & 0xf0000000) << 32 | (status & 0x0fffffff));
+}
+
+efi_status
+qcscm_efi_set_variable(efi_char *name, struct uuid *guid, uint32_t attributes,
+    u_long data_size, void *data)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	efi_status status;
+	int name_size;
+
+	name_size = 0;
+	while (name[name_size])
+		name_size++;
+	name_size++;
+
+	status = qcscm_uefi_set_variable(sc, name, name_size * 2, guid,
+	    attributes, data, data_size);
+
+	/* Convert 32-bit status code to 64-bit. */
+	return ((status & 0xf0000000) << 32 | (status & 0x0fffffff));
+}
+
+efi_status
+qcscm_efi_get_next_variable_name(u_long *name_size, efi_char *name,
+    struct uuid *guid)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	efi_status status;
+	int size;
+
+	size = *name_size;
+	status = qcscm_uefi_get_next_variable(sc, name, &size, guid);
+	*name_size = size;
+
+	/* Convert 32-bit status code to 64-bit. */
+	return ((status & 0xf0000000) << 32 | (status & 0x0fffffff));
+}
+
+#ifdef QCSCM_DEBUG
+
+void
+qcscm_uefi_dump_variables(struct qcscm_softc *sc)
+{
+	efi_char name[128];
+	struct uuid guid;
+	int namesize = sizeof(name);
+	int i, ret;
+
+	memset(name, 0, sizeof(name));
+	memset(&guid, 0, sizeof(guid));
+
+	for (;;) {
+		ret = qcscm_uefi_get_next_variable(sc, name, &namesize, &guid);
+		if (ret == 0) {
+			printf("%s: ", device_xname(sc->sc_dev));
+			for (i = 0; i < namesize / 2; i++)
+				printf("%c", name[i]);
+			printf(" { 0x%08x, 0x%04x, 0x%04x, { ",
+			   guid.time_low, guid.time_mid, guid.time_hi_and_version);
+			printf(" 0x%02x, 0x%02x,",
+			   guid.clock_seq_hi_and_reserved,
+			   guid.clock_seq_low);
+			for (i = 0; i < 6; i++) {
+				printf(" 0x%02x,", guid.node[i]);
+			}
+			printf(" }");
+			printf("\n");
+			namesize = sizeof(name);
+			continue;
+		}
+		break;
+	}
+}
+
+void
+qcscm_uefi_dump_variable(struct qcscm_softc *sc, efi_char *name, int namesize,
+    struct uuid *guid)
+{
+	uint8_t data[512];
+	int datasize = sizeof(data);
+	int i, ret;
+
+	ret = qcscm_uefi_get_variable(sc, name, namesize, guid,
+	    NULL, data, &datasize);
+	if (ret != QCTEE_UEFI_SUCCESS) {
+		printf("%s: error reading ", device_xname(sc->sc_dev));
+		for (i = 0; i < namesize / 2; i++)
+			printf("%c", name[i]);
+		printf("\n");
+		return;
+	}
+
+	printf("%s: ", device_xname(sc->sc_dev));
+	for (i = 0; i < namesize / 2; i++)
+		printf("%c", name[i]);
+	printf(" = ");
+	for (i = 0; i < datasize; i++)
+		printf("%02x", data[i]);
+	printf("\n");
+}
+
+#endif
+
+int
+qcscm_uefi_rtc_get(uint32_t *off)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	uint32_t rtcinfo[3];
+	int rtcinfosize = sizeof(rtcinfo);
+
+	if (sc == NULL)
+		return ENXIO;
+
+	if (qcscm_uefi_get_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO),
+	    &qcscm_uefi_rtcinfo_guid, NULL, (uint8_t *)rtcinfo,
+	    &rtcinfosize) != 0)
+		return EIO;
+
+	/* UEFI stores the offset based on GPS epoch */
+	*off = rtcinfo[0] + UNIX_GPS_EPOCH_OFFSET;
+	return 0;
+}
+
+int
+qcscm_uefi_rtc_set(uint32_t off)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	uint32_t rtcinfo[3];
+	int rtcinfosize = sizeof(rtcinfo);
+
+	if (sc == NULL)
+		return ENXIO;
+
+	if (qcscm_uefi_get_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO),
+	    &qcscm_uefi_rtcinfo_guid, NULL, (uint8_t *)rtcinfo,
+	    &rtcinfosize) != 0)
+		return EIO;
+
+	/* UEFI stores the offset based on GPS epoch */
+	off -= UNIX_GPS_EPOCH_OFFSET;
+
+	/* No need to set if we're not changing anything */
+	if (rtcinfo[0] == off)
+		return 0;
+
+	rtcinfo[0] = off;
+
+	if (qcscm_uefi_set_variable(sc, EFI_VAR_RTCINFO, sizeof(EFI_VAR_RTCINFO),
+	    &qcscm_uefi_rtcinfo_guid, EFI_VARIABLE_NON_VOLATILE |
+	    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
+	    (uint8_t *)rtcinfo, sizeof(rtcinfo)) != 0)
+		return EIO;
+
+	return 0;
+}
+
+int
+qcscm_pas_init_image(uint32_t peripheral, paddr_t metadata)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	uint64_t res[3];
+	uint64_t args[2];
+	uint32_t arginfo;
+	int ret;
+
+	if (sc == NULL)
+		return ENXIO;
+
+	arginfo = QCSCM_ARGINFO_NUM(__arraycount(args));
+	arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL);
+	arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_RW);
+	args[0] = peripheral;
+	args[1] = metadata;
+
+	/* Make call into TEE */
+	ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL,
+	    QCSCM_PIL_PAS_INIT_IMAGE, arginfo, args, __arraycount(args), res);
+
+	/* If the call succeeded, check the response status */
+	if (ret == 0)
+		ret = res[0];
+
+	return ret;
+}
+
+int
+qcscm_pas_mem_setup(uint32_t peripheral, paddr_t addr, size_t size)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	uint64_t res[3];
+	uint64_t args[3];
+	uint32_t arginfo;
+	int ret;
+
+	if (sc == NULL)
+		return ENXIO;
+
+	arginfo = QCSCM_ARGINFO_NUM(__arraycount(args));
+	arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL);
+	arginfo |= QCSCM_ARGINFO_TYPE(1, QCSCM_ARGINFO_TYPE_VAL);
+	arginfo |= QCSCM_ARGINFO_TYPE(2, QCSCM_ARGINFO_TYPE_VAL);
+	args[0] = peripheral;
+	args[1] = addr;
+	args[2] = size;
+
+	/* Make call into TEE */
+	ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL,
+	    QCSCM_PIL_PAS_MEM_SETUP, arginfo, args, __arraycount(args), res);
+
+	/* If the call succeeded, check the response status */
+	if (ret == 0)
+		ret = res[0];
+
+	return ret;
+}
+
+int
+qcscm_pas_auth_and_reset(uint32_t peripheral)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	uint64_t res[3];
+	uint64_t args[1];
+	uint32_t arginfo;
+	int ret;
+
+	if (sc == NULL)
+		return ENXIO;
+
+	arginfo = QCSCM_ARGINFO_NUM(__arraycount(args));
+	arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL);
+	args[0] = peripheral;
+
+	/* Make call into TEE */
+	ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL,
+	    QCSCM_PIL_PAS_AUTH_AND_RESET, arginfo, args, __arraycount(args), res);
+
+	/* If the call succeeded, check the response status */
+	if (ret == 0)
+		ret = res[0];
+
+	return ret;
+}
+
+int
+qcscm_pas_shutdown(uint32_t peripheral)
+{
+	struct qcscm_softc *sc = qcscm_sc;
+	uint64_t res[3];
+	uint64_t args[1];
+	uint32_t arginfo;
+	int ret;
+
+	if (sc == NULL)
+		return ENXIO;
+
+	arginfo = QCSCM_ARGINFO_NUM(__arraycount(args));
+	arginfo |= QCSCM_ARGINFO_TYPE(0, QCSCM_ARGINFO_TYPE_VAL);
+	args[0] = peripheral;
+
+	/* Make call into TEE */
+	ret = qcscm_smc_call(sc, ARM_SMCCC_OWNER_SIP, QCSCM_SVC_PIL,
+	    QCSCM_PIL_PAS_SHUTDOWN, arginfo, args, __arraycount(args), res);
+
+	/* If the call succeeded, check the response status */
+	if (ret == 0)
+		ret = res[0];
+
+	return ret;
+}
+
+/* DMA code */
+struct qcscm_dmamem *
+qcscm_dmamem_alloc(struct qcscm_softc *sc, bus_size_t size, bus_size_t align)
+{
+	struct qcscm_dmamem *qdm;
+	int nsegs;
+
+	qdm = kmem_zalloc(sizeof(*qdm), KM_SLEEP);
+	qdm->qdm_size = size;
+
+	if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
+	    BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &qdm->qdm_map) != 0)
+		goto qdmfree;
+
+	if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0,
+	    &qdm->qdm_seg, 1, &nsegs, BUS_DMA_WAITOK) != 0)
+		goto destroy;
+
+	if (bus_dmamem_map(sc->sc_dmat, &qdm->qdm_seg, nsegs, size,
+	    &qdm->qdm_kva, BUS_DMA_WAITOK | BUS_DMA_COHERENT) != 0)
+		goto free;
+
+	if (bus_dmamap_load(sc->sc_dmat, qdm->qdm_map, qdm->qdm_kva, size,
+	    NULL, BUS_DMA_WAITOK) != 0)
+		goto unmap;
+
+	memset(qdm->qdm_kva, 0, size);
+
+	return (qdm);
+
+unmap:
+	bus_dmamem_unmap(sc->sc_dmat, qdm->qdm_kva, size);
+free:
+	bus_dmamem_free(sc->sc_dmat, &qdm->qdm_seg, 1);
+destroy:
+	bus_dmamap_destroy(sc->sc_dmat, qdm->qdm_map);
+qdmfree:
+	kmem_free(qdm, sizeof(*qdm));
+
+	return (NULL);
+}
+
+void
+qcscm_dmamem_free(struct qcscm_softc *sc, struct qcscm_dmamem *qdm)
+{
+	bus_dmamem_unmap(sc->sc_dmat, qdm->qdm_kva, qdm->qdm_size);
+	bus_dmamem_free(sc->sc_dmat, &qdm->qdm_seg, 1);
+	bus_dmamap_destroy(sc->sc_dmat, qdm->qdm_map);
+	kmem_free(qdm, sizeof(*qdm));
+}
Index: src/sys/dev/acpi/qcomscm.h
diff -u /dev/null src/sys/dev/acpi/qcomscm.h:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomscm.h	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,23 @@
+/* $NetBSD: qcomscm.h,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*
+ * Copyright (c) 2022 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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.
+ */
+
+#pragma once
+
+int qcscm_pas_init_image(uint32_t, paddr_t);
+int qcscm_pas_mem_setup(uint32_t, paddr_t, size_t);
+int qcscm_pas_auth_and_reset(uint32_t);
+int qcscm_pas_shutdown(uint32_t);
Index: src/sys/dev/acpi/qcomsmem.c
diff -u /dev/null src/sys/dev/acpi/qcomsmem.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomsmem.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,573 @@
+/* $NetBSD: qcomsmem.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*	$OpenBSD: qcsmem.c,v 1.1 2023/05/19 21:13:49 patrick Exp $	*/
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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 <sys/kmem.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/qcomsmem.h>
+
+#define QCSMEM_ITEM_FIXED	8
+#define QCSMEM_ITEM_COUNT	512
+#define QCSMEM_HOST_COUNT	15
+
+struct qcsmem_proc_comm {
+	uint32_t command;
+	uint32_t status;
+	uint32_t params[2];
+};
+
+struct qcsmem_global_entry {
+	uint32_t allocated;
+	uint32_t offset;
+	uint32_t size;
+	uint32_t aux_base;
+#define QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK	0xfffffffc
+};
+
+struct qcsmem_header {
+	struct qcsmem_proc_comm proc_comm[4];
+	uint32_t version[32];
+#define QCSMEM_HEADER_VERSION_MASTER_SBL_IDX	7
+#define QCSMEM_HEADER_VERSION_GLOBAL_HEAP	11
+#define QCSMEM_HEADER_VERSION_GLOBAL_PART	12
+	uint32_t initialized;
+	uint32_t free_offset;
+	uint32_t available;
+	uint32_t reserved;
+	struct qcsmem_global_entry toc[QCSMEM_ITEM_COUNT];
+};
+
+struct qcsmem_ptable_entry {
+	uint32_t offset;
+	uint32_t size;
+	uint32_t flags;
+	uint16_t host[2];
+#define QCSMEM_LOCAL_HOST			0
+#define QCSMEM_GLOBAL_HOST			0xfffe
+	uint32_t cacheline;
+	uint32_t reserved[7];
+};
+
+struct qcsmem_ptable {
+	uint32_t magic;
+#define QCSMEM_PTABLE_MAGIC	0x434f5424
+	uint32_t version;
+#define QCSMEM_PTABLE_VERSION	1
+	uint32_t num_entries;
+	uint32_t reserved[5];
+	struct qcsmem_ptable_entry entry[];
+};
+
+struct qcsmem_partition_header {
+	uint32_t magic;
+#define QCSMEM_PART_HDR_MAGIC	0x54525024
+	uint16_t host[2];
+	uint32_t size;
+	uint32_t offset_free_uncached;
+	uint32_t offset_free_cached;
+	uint32_t reserved[3];
+};
+
+struct qcsmem_partition {
+	struct qcsmem_partition_header *phdr;
+	size_t cacheline;
+	size_t size;
+};
+
+struct qcsmem_private_entry {
+	uint16_t canary;
+#define QCSMEM_PRIV_ENTRY_CANARY	0xa5a5
+	uint16_t item;
+	uint32_t size;
+	uint16_t padding_data;
+	uint16_t padding_hdr;
+	uint32_t reserved;
+};
+
+struct qcsmem_info {
+	uint32_t magic;
+#define QCSMEM_INFO_MAGIC	0x49494953
+	uint32_t size;
+	uint32_t base_addr;
+	uint32_t reserved;
+	uint32_t num_items;
+};
+
+struct qcsmem_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_iot;
+	void			*sc_smem;
+	bus_space_handle_t	sc_mtx_ioh;
+
+	bus_addr_t		sc_aux_base;
+	bus_size_t		sc_aux_size;
+
+	int			sc_item_count;
+	struct qcsmem_partition	sc_global_partition;
+	struct qcsmem_partition	sc_partitions[QCSMEM_HOST_COUNT];
+};
+
+#define QCMTX_OFF(idx)		((idx) * 0x1000)
+#define QCMTX_NUM_LOCKS		32
+#define QCMTX_APPS_PROC_ID	1
+
+#define MTXREAD4(sc, reg)						\
+	bus_space_read_4((sc)->sc_iot, (sc)->sc_mtx_ioh, (reg))
+#define MTXWRITE4(sc, reg, val)						\
+	bus_space_write_4((sc)->sc_iot, (sc)->sc_mtx_ioh, (reg), (val))
+
+struct qcsmem_softc *qcsmem_sc;
+
+#define QCSMEM_X1E_BASE		0xffe00000
+#define QCSMEM_X1E_SIZE		0x200000
+
+#define QCMTX_X1E_BASE		0x01f40000
+#define QCMTX_X1E_SIZE		0x20000
+
+#define QCSMEM_X1E_LOCK_IDX	3
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "QCOM0C84" },
+	DEVICE_COMPAT_EOL
+};
+
+static int	qcsmem_match(device_t, cfdata_t, void *);
+static void	qcsmem_attach(device_t, device_t, void *);
+static int	qcmtx_lock(struct qcsmem_softc *, u_int, u_int);
+static void	qcmtx_unlock(struct qcsmem_softc *, u_int);
+
+CFATTACH_DECL_NEW(qcomsmem, sizeof(struct qcsmem_softc),
+    qcsmem_match, qcsmem_attach, NULL, NULL);
+
+static int
+qcsmem_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+static void
+qcsmem_attach(device_t parent, device_t self, void *aux)
+{
+	struct qcsmem_softc *sc = device_private(self);
+	struct acpi_attach_args *aa = aux;
+	struct qcsmem_header *header;
+	struct qcsmem_ptable *ptable;
+	struct qcsmem_ptable_entry *pte;
+	struct qcsmem_info *info;
+	struct qcsmem_partition *part;
+	struct qcsmem_partition_header *phdr;
+	uintptr_t smem_va;
+	uint32_t hdr_version;
+	int i;
+
+	sc->sc_dev = self;
+	sc->sc_iot = aa->aa_memt;
+	sc->sc_smem = AcpiOsMapMemory(QCSMEM_X1E_BASE, QCSMEM_X1E_SIZE);
+	KASSERT(sc->sc_smem != NULL);
+
+	sc->sc_aux_base = QCSMEM_X1E_BASE;
+	sc->sc_aux_size = QCSMEM_X1E_SIZE;
+
+	if (bus_space_map(sc->sc_iot, QCMTX_X1E_BASE,
+	    QCMTX_X1E_SIZE, 0, &sc->sc_mtx_ioh)) {
+		aprint_error(": can't map mutex registers\n");
+		return;
+	}
+
+	smem_va = (uintptr_t)sc->sc_smem;
+
+	ptable = (void *)(smem_va + sc->sc_aux_size - PAGE_SIZE);
+	if (ptable->magic != QCSMEM_PTABLE_MAGIC ||
+	    ptable->version != QCSMEM_PTABLE_VERSION) {
+		aprint_error(": unsupported ptable 0x%x/0x%x\n",
+		    ptable->magic, ptable->version);
+		return;
+	}
+
+	header = (void *)smem_va;
+	hdr_version = header->version[QCSMEM_HEADER_VERSION_MASTER_SBL_IDX] >> 16;
+	if (hdr_version != QCSMEM_HEADER_VERSION_GLOBAL_PART) {
+		aprint_error(": unsupported header 0x%x\n", hdr_version);
+		return;
+	}
+
+	for (i = 0; i < ptable->num_entries; i++) {
+		pte = &ptable->entry[i];
+		if (!pte->offset || !pte->size)
+			continue;
+		if (pte->host[0] == QCSMEM_GLOBAL_HOST &&
+		    pte->host[1] == QCSMEM_GLOBAL_HOST)
+			part = &sc->sc_global_partition;
+		else if (pte->host[0] == QCSMEM_LOCAL_HOST &&
+		    pte->host[1] < QCSMEM_HOST_COUNT)
+			part = &sc->sc_partitions[pte->host[1]];
+		else if (pte->host[1] == QCSMEM_LOCAL_HOST &&
+		    pte->host[0] < QCSMEM_HOST_COUNT)
+			part = &sc->sc_partitions[pte->host[0]];
+		else
+			continue;
+		if (part->phdr != NULL)
+			continue;
+		phdr = (void *)(smem_va + pte->offset);
+		if (phdr->magic != QCSMEM_PART_HDR_MAGIC) {
+			aprint_error(": unsupported partition 0x%x\n",
+			    phdr->magic);
+			return;
+		}
+		if (pte->host[0] != phdr->host[0] ||
+		    pte->host[1] != phdr->host[1]) {
+			aprint_error(": bad hosts 0x%x/0x%x+0x%x/0x%x\n",
+			    pte->host[0], phdr->host[0],
+			    pte->host[1], phdr->host[1]);
+			return;
+		}
+		if (pte->size != phdr->size) {
+			aprint_error(": bad size 0x%x/0x%x\n",
+			    pte->size, phdr->size);
+			return;
+		}
+		if (phdr->offset_free_uncached > phdr->size) {
+			aprint_error(": bad size 0x%x > 0x%x\n",
+			    phdr->offset_free_uncached, phdr->size);
+			return;
+		}
+		part->phdr = phdr;
+		part->size = pte->size;
+		part->cacheline = pte->cacheline;
+	}
+	if (sc->sc_global_partition.phdr == NULL) {
+		aprint_error(": could not find global partition\n");
+		return;
+	}
+
+	sc->sc_item_count = QCSMEM_ITEM_COUNT;
+	info = (struct qcsmem_info *)&ptable->entry[ptable->num_entries];
+	if (info->magic == QCSMEM_INFO_MAGIC)
+		sc->sc_item_count = info->num_items;
+
+	aprint_naive("\n");
+	aprint_normal("\n");
+
+	qcsmem_sc = sc;
+}
+
+static int
+qcsmem_alloc_private(struct qcsmem_softc *sc, struct qcsmem_partition *part,
+    int item, int size)
+{
+	struct qcsmem_private_entry *entry, *last;
+	struct qcsmem_partition_header *phdr = part->phdr;
+	uintptr_t phdr_va = (uintptr_t)phdr;
+
+	entry = (void *)&phdr[1];
+	last = (void *)(phdr_va + phdr->offset_free_uncached);
+
+	if ((void *)last > (void *)(phdr_va + part->size))
+		return EINVAL;
+
+	while (entry < last) {
+		if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
+			device_printf(sc->sc_dev, "invalid canary\n");
+			return EINVAL;
+		}
+
+		if (entry->item == item)
+			return 0;
+
+		entry = (void *)((uintptr_t)&entry[1] + entry->padding_hdr +
+		    entry->size);
+	}
+
+	if ((void *)entry > (void *)(phdr_va + part->size))
+		return EINVAL;
+
+	if ((uintptr_t)&entry[1] + roundup(size, 8) >
+	    phdr_va + phdr->offset_free_cached)
+		return EINVAL;
+
+	entry->canary = QCSMEM_PRIV_ENTRY_CANARY;
+	entry->item = item;
+	entry->size = roundup(size, 8);
+	entry->padding_data = entry->size - size;
+	entry->padding_hdr = 0;
+	membar_producer();
+
+	phdr->offset_free_uncached += sizeof(*entry) + entry->size;
+
+	return 0;
+}
+
+static int
+qcsmem_alloc_global(struct qcsmem_softc *sc, int item, int size)
+{
+	struct qcsmem_header *header;
+	struct qcsmem_global_entry *entry;
+
+	header = (void *)sc->sc_smem;
+	entry = &header->toc[item];
+	if (entry->allocated)
+		return 0;
+
+	size = roundup(size, 8);
+	if (size > header->available)
+		return EINVAL;
+
+	entry->offset = header->free_offset;
+	entry->size = size;
+	membar_producer();
+	entry->allocated = 1;
+
+	header->free_offset += size;
+	header->available -= size;
+
+	return 0;
+}
+
+int
+qcsmem_alloc(int host, int item, int size)
+{
+	struct qcsmem_softc *sc = qcsmem_sc;
+	struct qcsmem_partition *part;
+	int ret;
+
+	if (sc == NULL)
+		return ENXIO;
+
+	if (item < QCSMEM_ITEM_FIXED)
+		return EPERM;
+
+	if (item >= sc->sc_item_count)
+		return ENXIO;
+
+	ret = qcmtx_lock(sc, QCSMEM_X1E_LOCK_IDX, 1000);
+	if (ret)
+		return ret;
+
+	if (host < QCSMEM_HOST_COUNT &&
+	    sc->sc_partitions[host].phdr != NULL) {
+		part = &sc->sc_partitions[host];
+		ret = qcsmem_alloc_private(sc, part, item, size);
+	} else if (sc->sc_global_partition.phdr != NULL) {
+		part = &sc->sc_global_partition;
+		ret = qcsmem_alloc_private(sc, part, item, size);
+	} else {
+		ret = qcsmem_alloc_global(sc, item, size);
+	}
+
+	qcmtx_unlock(sc, QCSMEM_X1E_LOCK_IDX);
+
+	return ret;
+}
+
+static void *
+qcsmem_get_private(struct qcsmem_softc *sc, struct qcsmem_partition *part,
+    int item, int *size)
+{
+	struct qcsmem_private_entry *entry, *last;
+	struct qcsmem_partition_header *phdr = part->phdr;
+	uintptr_t phdr_va = (uintptr_t)phdr;
+
+	entry = (void *)&phdr[1];
+	last = (void *)(phdr_va + phdr->offset_free_uncached);
+
+	while (entry < last) {
+		if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
+			device_printf(sc->sc_dev, "invalid canary\n");
+			return NULL;
+		}
+
+		if (entry->item == item) {
+			if (size != NULL) {
+				if (entry->size > part->size ||
+				    entry->padding_data > entry->size)
+					return NULL;
+				*size = entry->size - entry->padding_data;
+			}
+
+			return (void *)((uintptr_t)&entry[1] + entry->padding_hdr);
+		}
+
+		entry = (void *)((uintptr_t)&entry[1] + entry->padding_hdr +
+		    entry->size);
+	}
+
+	if ((uintptr_t)entry > phdr_va + part->size)
+		return NULL;
+
+	entry = (void *)(phdr_va + phdr->size -
+	    roundup(sizeof(*entry), part->cacheline));
+	last = (void *)(phdr_va + phdr->offset_free_cached);
+
+	if ((uintptr_t)entry < phdr_va ||
+	    (uintptr_t)last > phdr_va + part->size)
+		return NULL;
+
+	while (entry > last) {
+		if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
+			device_printf(sc->sc_dev, "invalid canary\n");
+			return NULL;
+		}
+
+		if (entry->item == item) {
+			if (size != NULL) {
+				if (entry->size > part->size ||
+				    entry->padding_data > entry->size)
+					return NULL;
+				*size = entry->size - entry->padding_data;
+			}
+
+			return (void *)((uintptr_t)entry - entry->size);
+		}
+
+		entry = (void *)((uintptr_t)entry - entry->size -
+		    roundup(sizeof(*entry), part->cacheline));
+	}
+
+	if ((uintptr_t)entry < phdr_va)
+		return NULL;
+
+	return NULL;
+}
+
+static void *
+qcsmem_get_global(struct qcsmem_softc *sc, int item, int *size)
+{
+	struct qcsmem_header *header;
+	struct qcsmem_global_entry *entry;
+	uint32_t aux_base;
+
+	header = (void *)sc->sc_smem;
+	entry = &header->toc[item];
+	if (!entry->allocated)
+		return NULL;
+
+	aux_base = entry->aux_base & QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK;
+	if (aux_base != 0 && aux_base != sc->sc_aux_base)
+		return NULL;
+
+	if (entry->size + entry->offset > sc->sc_aux_size)
+		return NULL;
+
+	if (size != NULL)
+		*size = entry->size;
+
+	return (void *)((uintptr_t)sc->sc_smem +
+	    entry->offset);
+}
+
+void *
+qcsmem_get(int host, int item, int *size)
+{
+	struct qcsmem_softc *sc = qcsmem_sc;
+	struct qcsmem_partition *part;
+	void *p = NULL;
+	int ret;
+
+	if (sc == NULL)
+		return NULL;
+
+	if (item >= sc->sc_item_count)
+		return NULL;
+
+	ret = qcmtx_lock(sc, QCSMEM_X1E_LOCK_IDX, 1000);
+	if (ret)
+		return NULL;
+
+	if (host >= 0 &&
+	    host < QCSMEM_HOST_COUNT &&
+	    sc->sc_partitions[host].phdr != NULL) {
+		part = &sc->sc_partitions[host];
+		p = qcsmem_get_private(sc, part, item, size);
+	} else if (sc->sc_global_partition.phdr != NULL) {
+		part = &sc->sc_global_partition;
+		p = qcsmem_get_private(sc, part, item, size);
+	} else {
+		p = qcsmem_get_global(sc, item, size);
+	}
+
+	qcmtx_unlock(sc, QCSMEM_X1E_LOCK_IDX);
+	return p;
+}
+
+void
+qcsmem_memset(void *ptr, uint8_t val, size_t len)
+{
+	if (len % 8 == 0 && val == 0) {
+		volatile uint64_t *p = ptr;
+		size_t n;
+
+		for (n = 0; n < len; n += 8) {
+			p[n] = val;
+		}
+	} else {
+		volatile uint8_t *p = ptr;
+		size_t n;
+
+		for (n = 0; n < len; n++) {
+			p[n] = val;
+		}
+	}
+}
+
+static int
+qcmtx_dolockunlock(struct qcsmem_softc *sc, u_int idx, int lock)
+{
+	if (idx >= QCMTX_NUM_LOCKS)
+		return ENXIO;
+
+	if (lock) {
+		MTXWRITE4(sc, QCMTX_OFF(idx), QCMTX_APPS_PROC_ID);
+		if (MTXREAD4(sc, QCMTX_OFF(idx)) !=
+		    QCMTX_APPS_PROC_ID)
+			return EAGAIN;
+		KASSERT(MTXREAD4(sc, QCMTX_OFF(idx)) == QCMTX_APPS_PROC_ID);
+	} else {
+		KASSERT(MTXREAD4(sc, QCMTX_OFF(idx)) == QCMTX_APPS_PROC_ID);
+		MTXWRITE4(sc, QCMTX_OFF(idx), 0);
+	}
+
+	return 0;
+}
+
+static int
+qcmtx_lock(struct qcsmem_softc *sc, u_int idx, u_int timeout_ms)
+{
+	int rv = EINVAL;
+	u_int n;
+
+	for (n = 0; n < timeout_ms; n++) {
+		rv = qcmtx_dolockunlock(sc, idx, 1);
+		if (rv != EAGAIN) {
+			break;
+		}
+		delay(1000);
+	}
+
+	return rv;
+}
+
+static void
+qcmtx_unlock(struct qcsmem_softc *sc, u_int idx)
+{
+	qcmtx_dolockunlock(sc, idx, 0);
+}
Index: src/sys/dev/acpi/qcomsmem.h
diff -u /dev/null src/sys/dev/acpi/qcomsmem.h:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomsmem.h	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,22 @@
+/* $NetBSD: qcomsmem.h,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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.
+ */
+
+#pragma once
+
+int	qcsmem_alloc(int, int, int);
+void *	qcsmem_get(int, int, int *);
+void	qcsmem_memset(void *, uint8_t, size_t);
Index: src/sys/dev/acpi/qcomsmptp.c
diff -u /dev/null src/sys/dev/acpi/qcomsmptp.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomsmptp.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,332 @@
+/* $NetBSD: qcomsmptp.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*	$OpenBSD: qcsmptp.c,v 1.2 2023/07/04 14:32:21 patrick Exp $	*/
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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 <sys/kmem.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/qcomipcc.h>
+#include <dev/acpi/qcomsmem.h>
+#include <dev/acpi/qcomsmptp.h>
+
+#define SMP2P_MAX_ENTRY		16
+#define SMP2P_MAX_ENTRY_NAME	16
+
+struct qcsmptp_smem_item {
+	uint32_t magic;
+#define SMP2P_MAGIC			0x504d5324
+	uint8_t version;
+#define SMP2P_VERSION			1
+	unsigned features:24;
+#define SMP2P_FEATURE_SSR_ACK		(1 << 0)
+	uint16_t local_pid;
+	uint16_t remote_pid;
+	uint16_t total_entries;
+	uint16_t valid_entries;
+	uint32_t flags;
+#define SMP2P_FLAGS_RESTART_DONE	(1 << 0)
+#define SMP2P_FLAGS_RESTART_ACK		(1 << 1)
+
+	struct {
+		uint8_t name[SMP2P_MAX_ENTRY_NAME];
+		uint32_t value;
+	} entries[SMP2P_MAX_ENTRY];
+} __packed;
+
+struct qcsmptp_intrhand {
+	TAILQ_ENTRY(qcsmptp_intrhand) ih_q;
+	int (*ih_func)(void *);
+	void *ih_arg;
+	void *ih_ic;
+	int ih_pin;
+	int ih_enabled;
+};
+
+struct qcsmptp_interrupt_controller {
+	TAILQ_HEAD(,qcsmptp_intrhand) ic_intrq;
+	struct qcsmptp_softc *ic_sc;
+};
+
+struct qcsmptp_entry {
+	TAILQ_ENTRY(qcsmptp_entry) e_q;
+	const char *e_name;
+	uint32_t *e_value;
+	uint32_t e_last_value;
+	struct qcsmptp_interrupt_controller *e_ic;
+};
+
+struct qcsmptp_softc {
+	device_t		sc_dev;
+	void			*sc_ih;
+
+	uint16_t		sc_local_pid;
+	uint16_t		sc_remote_pid;
+	uint32_t		sc_smem_id[2];
+
+	struct qcsmptp_smem_item *sc_in;
+	struct qcsmptp_smem_item *sc_out;
+
+	TAILQ_HEAD(,qcsmptp_entry) sc_inboundq;
+	TAILQ_HEAD(,qcsmptp_entry) sc_outboundq;
+
+	int			sc_negotiated;
+	int			sc_ssr_ack_enabled;
+	int			sc_ssr_ack;
+
+	uint16_t		sc_valid_entries;
+
+	void			*sc_ipcc;
+};
+
+static struct qcsmptp_interrupt_controller *qcsmptp_ic = NULL;
+
+static int	qcsmptp_match(device_t, cfdata_t, void *);
+static void	qcsmptp_attach(device_t, device_t, void *);
+
+static int	qcsmptp_intr(void *);
+
+CFATTACH_DECL_NEW(qcomsmptp, sizeof(struct qcsmptp_softc),
+    qcsmptp_match, qcsmptp_attach, NULL, NULL);
+
+#define IPCC_CLIENT_LPASS	3
+#define IPCC_MPROC_SIGNAL_SMP2P	2
+
+#define QCSMPTP_X1E_LOCAL_PID	0
+#define QCSMPTP_X1E_REMOTE_PID	2
+#define QCSMPTP_X1E_SMEM_ID0	443
+#define QCSMPTP_X1E_SMEM_ID1	429
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "QCOM0C5C" },
+	DEVICE_COMPAT_EOL
+};
+
+static int
+qcsmptp_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+static void
+qcsmptp_attach(device_t parent, device_t self, void *aux)
+{
+	struct qcsmptp_softc *sc = device_private(self);
+	struct qcsmptp_interrupt_controller *ic;
+	struct qcsmptp_entry *e;
+
+	sc->sc_dev = self;
+
+	TAILQ_INIT(&sc->sc_inboundq);
+	TAILQ_INIT(&sc->sc_outboundq);
+
+	sc->sc_ih = qcipcc_intr_establish(IPCC_CLIENT_LPASS,
+	    IPCC_MPROC_SIGNAL_SMP2P, IPL_VM, qcsmptp_intr, sc);
+	if (sc->sc_ih == NULL) {
+		aprint_error(": can't establish interrupt\n");
+		return;
+	}
+
+	sc->sc_local_pid = QCSMPTP_X1E_LOCAL_PID;
+	sc->sc_remote_pid = QCSMPTP_X1E_REMOTE_PID;
+	sc->sc_smem_id[0] = QCSMPTP_X1E_SMEM_ID0;
+	sc->sc_smem_id[1] = QCSMPTP_X1E_SMEM_ID1;
+
+	aprint_naive("\n");
+	aprint_normal("\n");
+
+	sc->sc_ipcc = qcipcc_channel(IPCC_CLIENT_LPASS,
+	    IPCC_MPROC_SIGNAL_SMP2P);
+	if (sc->sc_ipcc == NULL) {
+		aprint_error_dev(self, "can't get mailbox\n");
+		return;
+	}
+
+	if (qcsmem_alloc(sc->sc_remote_pid, sc->sc_smem_id[0],
+	    sizeof(*sc->sc_in)) != 0) {
+		aprint_error_dev(self, "can't alloc smp2p item\n");
+		return;
+	}
+
+	sc->sc_in = qcsmem_get(sc->sc_remote_pid, sc->sc_smem_id[0], NULL);
+	if (sc->sc_in == NULL) {
+		aprint_error_dev(self, "can't get smp2p item\n");
+		return;
+	}
+
+	if (qcsmem_alloc(sc->sc_remote_pid, sc->sc_smem_id[1],
+	    sizeof(*sc->sc_out)) != 0) {
+		aprint_error_dev(self, "can't alloc smp2p item\n");
+		return;
+	}
+
+	sc->sc_out = qcsmem_get(sc->sc_remote_pid, sc->sc_smem_id[1], NULL);
+	if (sc->sc_out == NULL) {
+		aprint_error_dev(self, "can't get smp2p item\n");
+		return;
+	}
+
+	qcsmem_memset(sc->sc_out, 0, sizeof(*sc->sc_out));
+	sc->sc_out->magic = SMP2P_MAGIC;
+	sc->sc_out->local_pid = sc->sc_local_pid;
+	sc->sc_out->remote_pid = sc->sc_remote_pid;
+	sc->sc_out->total_entries = SMP2P_MAX_ENTRY;
+	sc->sc_out->features = SMP2P_FEATURE_SSR_ACK;
+	membar_sync();
+	sc->sc_out->version = SMP2P_VERSION;
+	qcipcc_send(sc->sc_ipcc);
+
+	e = kmem_zalloc(sizeof(*e), KM_SLEEP);
+	e->e_name = "master-kernel";
+	e->e_value = &sc->sc_out->entries[sc->sc_out->valid_entries].value;
+	sc->sc_out->valid_entries++;
+	TAILQ_INSERT_TAIL(&sc->sc_outboundq, e, e_q);
+	/* TODO: provide as smem state */
+
+	e = kmem_zalloc(sizeof(*e), KM_SLEEP);
+	e->e_name = "slave-kernel";
+	ic = kmem_zalloc(sizeof(*ic), KM_SLEEP);
+	TAILQ_INIT(&ic->ic_intrq);
+	ic->ic_sc = sc;
+	e->e_ic = ic;
+	TAILQ_INSERT_TAIL(&sc->sc_inboundq, e, e_q);
+
+	qcsmptp_ic = ic;
+}
+
+static int
+qcsmptp_intr(void *arg)
+{
+	struct qcsmptp_softc *sc = arg;
+	struct qcsmptp_entry *e;
+	struct qcsmptp_intrhand *ih;
+	uint32_t changed, val;
+	int do_ack = 0, i;
+
+	/* Do initial feature negotiation if inbound is new. */
+	if (!sc->sc_negotiated) {
+		if (sc->sc_in->version != sc->sc_out->version)
+			return 1;
+		sc->sc_out->features &= sc->sc_in->features;
+		if (sc->sc_out->features & SMP2P_FEATURE_SSR_ACK)
+			sc->sc_ssr_ack_enabled = 1;
+		sc->sc_negotiated = 1;
+	}
+	if (!sc->sc_negotiated) {
+		return 1;
+	}
+
+	/* Use ACK mechanism if negotiated. */
+	if (sc->sc_ssr_ack_enabled &&
+	    !!(sc->sc_in->flags & SMP2P_FLAGS_RESTART_DONE) != sc->sc_ssr_ack)
+		do_ack = 1;
+
+	/* Catch up on new inbound entries that got added in the meantime. */
+	for (i = sc->sc_valid_entries; i < sc->sc_in->valid_entries; i++) {
+		TAILQ_FOREACH(e, &sc->sc_inboundq, e_q) {
+			if (strncmp(sc->sc_in->entries[i].name, e->e_name,
+			    SMP2P_MAX_ENTRY_NAME) != 0)
+				continue;
+			e->e_value = &sc->sc_in->entries[i].value;
+		}
+	}
+	sc->sc_valid_entries = i;
+
+	/* For each inbound "interrupt controller". */
+	TAILQ_FOREACH(e, &sc->sc_inboundq, e_q) {
+		if (e->e_value == NULL)
+			continue;
+		val = *e->e_value;
+		if (val == e->e_last_value)
+			continue;
+		changed = val ^ e->e_last_value;
+		e->e_last_value = val;
+		TAILQ_FOREACH(ih, &e->e_ic->ic_intrq, ih_q) {
+			if (!ih->ih_enabled)
+				continue;
+			if ((changed & (1 << ih->ih_pin)) == 0)
+				continue;
+			ih->ih_func(ih->ih_arg);
+		}
+	}
+
+	if (do_ack) {
+		sc->sc_ssr_ack = !sc->sc_ssr_ack;
+		if (sc->sc_ssr_ack)
+			sc->sc_out->flags |= SMP2P_FLAGS_RESTART_ACK;
+		else
+			sc->sc_out->flags &= ~SMP2P_FLAGS_RESTART_ACK;
+		membar_sync();
+		qcipcc_send(sc->sc_ipcc);
+	}
+
+	return 1;
+}
+
+void *
+qcsmptp_intr_establish(u_int pin, int (*func)(void *), void *arg)
+{
+	struct qcsmptp_interrupt_controller *ic = qcsmptp_ic;
+	struct qcsmptp_intrhand *ih;
+
+	if (ic == NULL) {
+		return NULL;
+	}
+
+	ih = kmem_zalloc(sizeof(*ih), KM_SLEEP);
+	ih->ih_func = func;
+	ih->ih_arg = arg;
+	ih->ih_ic = ic;
+	ih->ih_pin = pin;
+	TAILQ_INSERT_TAIL(&ic->ic_intrq, ih, ih_q);
+
+	qcsmptp_intr_enable(ih);
+
+	return ih;
+}
+
+void
+qcsmptp_intr_disestablish(void *cookie)
+{
+	struct qcsmptp_intrhand *ih = cookie;
+	struct qcsmptp_interrupt_controller *ic = ih->ih_ic;
+
+	qcsmptp_intr_disable(ih);
+
+	TAILQ_REMOVE(&ic->ic_intrq, ih, ih_q);
+	kmem_free(ih, sizeof(*ih));
+}
+
+void
+qcsmptp_intr_enable(void *cookie)
+{
+	struct qcsmptp_intrhand *ih = cookie;
+
+	ih->ih_enabled = 1;
+}
+
+void
+qcsmptp_intr_disable(void *cookie)
+{
+	struct qcsmptp_intrhand *ih = cookie;
+
+	ih->ih_enabled = 0;
+}
Index: src/sys/dev/acpi/qcomsmptp.h
diff -u /dev/null src/sys/dev/acpi/qcomsmptp.h:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomsmptp.h	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,23 @@
+/* $NetBSD: qcomsmptp.h,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*
+ * Copyright (c) 2023 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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.
+ */
+
+#pragma once
+
+void	*qcsmptp_intr_establish(u_int, int (*)(void *), void *);
+void	qcsmptp_intr_disestablish(void *);
+void	qcsmptp_intr_enable(void *);
+void	qcsmptp_intr_disable(void *);
Index: src/sys/dev/acpi/qcomspmi.c
diff -u /dev/null src/sys/dev/acpi/qcomspmi.c:1.1
--- /dev/null	Mon Dec 30 12:31:10 2024
+++ src/sys/dev/acpi/qcomspmi.c	Mon Dec 30 12:31:10 2024
@@ -0,0 +1,399 @@
+/* $NetBSD: qcomspmi.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
+/*	$OpenBSD: qcspmi.c,v 1.6 2024/08/14 10:54:58 mglocker Exp $	*/
+/*
+ * Copyright (c) 2022 Patrick Wildt <patr...@blueri.se>
+ *
+ * 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/kmem.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+
+#include <dev/acpi/acpivar.h>
+
+/* SPMI commands */
+#define SPMI_CMD_EXT_WRITEL	0x30
+#define SPMI_CMD_EXT_READL	0x38
+
+/* Core registers. */
+#define SPMI_VERSION		0x00
+#define  SPMI_VERSION_V2_MIN		0x20010000
+#define  SPMI_VERSION_V3_MIN		0x30000000
+#define  SPMI_VERSION_V5_MIN		0x50000000
+#define  SPMI_VERSION_V7_MIN		0x70000000
+#define SPMI_ARB_APID_MAP(sc, x)	((sc)->sc_arb_apid_map + (x) * 0x4)
+#define  SPMI_ARB_APID_MAP_PPID_MASK	0xfff
+#define  SPMI_ARB_APID_MAP_PPID_SHIFT	8
+#define  SPMI_ARB_APID_MAP_IRQ_OWNER	(1 << 14)
+
+/* Channel registers. */
+#define SPMI_CHAN_OFF(sc, x)	((sc)->sc_chan_stride * (x))
+#define SPMI_OBSV_OFF(sc, x, y)	\
+	((sc)->sc_obsv_ee_stride * (x) + (sc)->sc_obsv_apid_stride * (y))
+#define SPMI_COMMAND		0x00
+#define  SPMI_COMMAND_OP_EXT_WRITEL	(0 << 27)
+#define  SPMI_COMMAND_OP_EXT_READL	(1 << 27)
+#define  SPMI_COMMAND_OP_EXT_WRITE	(2 << 27)
+#define  SPMI_COMMAND_OP_RESET		(3 << 27)
+#define  SPMI_COMMAND_OP_SLEEP		(4 << 27)
+#define  SPMI_COMMAND_OP_SHUTDOWN	(5 << 27)
+#define  SPMI_COMMAND_OP_WAKEUP		(6 << 27)
+#define  SPMI_COMMAND_OP_AUTHENTICATE	(7 << 27)
+#define  SPMI_COMMAND_OP_MSTR_READ	(8 << 27)
+#define  SPMI_COMMAND_OP_MSTR_WRITE	(9 << 27)
+#define  SPMI_COMMAND_OP_EXT_READ	(13 << 27)
+#define  SPMI_COMMAND_OP_WRITE		(14 << 27)
+#define  SPMI_COMMAND_OP_READ		(15 << 27)
+#define  SPMI_COMMAND_OP_ZERO_WRITE	(16 << 27)
+#define  SPMI_COMMAND_ADDR(x)		(((x) & 0xff) << 4)
+#define  SPMI_COMMAND_LEN(x)		(((x) & 0x7) << 0)
+#define SPMI_CONFIG		0x04
+#define SPMI_STATUS		0x08
+#define  SPMI_STATUS_DONE		(1 << 0)
+#define  SPMI_STATUS_FAILURE		(1 << 1)
+#define  SPMI_STATUS_DENIED		(1 << 2)
+#define  SPMI_STATUS_DROPPED		(1 << 3)
+#define SPMI_WDATA0		0x10
+#define SPMI_WDATA1		0x14
+#define SPMI_RDATA0		0x18
+#define SPMI_RDATA1		0x1c
+#define SPMI_ACC_ENABLE		0x100
+#define  SPMI_ACC_ENABLE_BIT		(1 << 0)
+#define SPMI_IRQ_STATUS		0x104
+#define SPMI_IRQ_CLEAR		0x108
+
+/* Intr registers */
+#define SPMI_OWNER_ACC_STATUS(sc, x, y)	\
+	((sc)->sc_chan_stride * (x) + 0x4 * (y))
+
+/* Config registers */
+#define SPMI_OWNERSHIP_TABLE(sc, x)	((sc)->sc_ownership_table + (x) * 0x4)
+#define  SPMI_OWNERSHIP_TABLE_OWNER(x)	((x) & 0x7)
+
+/* Misc */
+#define SPMI_MAX_PERIPH		1024
+#define SPMI_MAX_PPID		4096
+#define SPMI_PPID_TO_APID_VALID	(1U << 15)
+#define SPMI_PPID_TO_APID_MASK	(0x7fff)
+
+/* Intr commands */
+#define INTR_RT_STS		0x10
+#define INTR_SET_TYPE		0x11
+#define INTR_POLARITY_HIGH	0x12
+#define INTR_POLARITY_LOW	0x13
+#define INTR_LATCHED_CLR	0x14
+#define INTR_EN_SET		0x15
+#define INTR_EN_CLR		0x16
+#define INTR_LATCHED_STS	0x18
+
+#define HREAD4(sc, obj, reg)						\
+	bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, 			\
+			 (sc)->sc_data->regs[obj] + (reg))
+#define HWRITE4(sc, obj, reg, val)					\
+	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, 			\
+			  (sc)->sc_data->regs[obj] + (reg), (val))
+
+#define QCSPMI_REG_CORE		0
+#define QCSPMI_REG_CHNLS	1
+#define QCSPMI_REG_OBSRVR	2
+#define QCSPMI_REG_INTR		3
+#define QCSPMI_REG_CNFG		4
+#define QCSPMI_REG_MAX		5
+
+struct qcspmi_apid {
+	uint16_t		ppid;
+	uint8_t			write_ee;
+	uint8_t			irq_ee;
+};
+
+struct qcspmi_data {
+	bus_size_t		regs[QCSPMI_REG_MAX];
+	int			ee;
+};
+
+struct qcspmi_softc {
+	device_t		sc_dev;
+
+	bus_space_tag_t		sc_iot;
+	bus_space_handle_t	sc_ioh;
+
+	const struct qcspmi_data *sc_data;
+
+	int			sc_ee;
+
+	struct qcspmi_apid	sc_apid[SPMI_MAX_PERIPH];
+	uint16_t		sc_ppid_to_apid[SPMI_MAX_PPID];
+	uint16_t		sc_max_periph;
+	bus_size_t		sc_chan_stride;
+	bus_size_t		sc_obsv_ee_stride;
+	bus_size_t		sc_obsv_apid_stride;
+	bus_size_t		sc_arb_apid_map;
+	bus_size_t		sc_ownership_table;
+};
+
+static int	qcspmi_match(device_t, cfdata_t, void *);
+static void	qcspmi_attach(device_t, device_t, void *);
+
+int	qcspmi_cmd_read(struct qcspmi_softc *, uint8_t, uint8_t,
+	    uint16_t, void *, size_t);
+int	qcspmi_cmd_write(struct qcspmi_softc *, uint8_t, uint8_t, uint16_t,
+	    const void *, size_t);
+
+CFATTACH_DECL_NEW(qcomspmi, sizeof(struct qcspmi_softc),
+    qcspmi_match, qcspmi_attach, NULL, NULL);
+
+static const struct qcspmi_data qcspmi_x1e_data = {
+	.ee = 0,
+	.regs = {
+		[QCSPMI_REG_CORE]   = 0x0,
+		[QCSPMI_REG_CHNLS]  = 0x100000,
+		[QCSPMI_REG_OBSRVR] = 0x40000,
+		[QCSPMI_REG_INTR]   = 0xc0000,
+		[QCSPMI_REG_CNFG]   = 0x2d000,
+	},
+};
+
+static const struct device_compatible_entry compat_data[] = {
+        { .compat = "QCOM0C0B", .data = &qcspmi_x1e_data },
+        DEVICE_COMPAT_EOL
+};
+
+static int
+qcspmi_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	return acpi_compatible_match(aa, compat_data);
+}
+
+void
+qcspmi_attach(device_t parent, device_t self, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+	struct qcspmi_softc *sc = device_private(self);
+	struct qcspmi_apid *apid, *last_apid;
+	struct acpi_resources res;
+        struct acpi_mem *mem;
+	uint32_t val, ppid, irq_own;
+	ACPI_STATUS rv;
+	int error, i;
+
+	rv = acpi_resource_parse(sc->sc_dev, aa->aa_node->ad_handle, "_CRS",
+	    &res, &acpi_resource_parse_ops_default);
+	if (ACPI_FAILURE(rv)) {
+		return;
+	}
+
+	mem = acpi_res_mem(&res, 0);
+	if (mem == NULL) {
+		aprint_error_dev(self, "couldn't find mem resource\n");
+		goto done;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_data = acpi_compatible_lookup(aa, compat_data)->data;
+	sc->sc_iot = aa->aa_memt;
+	error = bus_space_map(sc->sc_iot, mem->ar_base, mem->ar_length, 0,
+	    &sc->sc_ioh);
+	if (error != 0) {
+		aprint_error_dev(self, "couldn't map registers\n");
+		goto done;
+	}
+
+	/* Support only version 5 and 7 for now */
+	val = HREAD4(sc, QCSPMI_REG_CORE, SPMI_VERSION);
+	if (val < SPMI_VERSION_V5_MIN) {
+		printf(": unsupported version 0x%08x\n", val);
+		return;
+	}
+
+	if (val < SPMI_VERSION_V7_MIN) {
+		sc->sc_max_periph = 512;
+		sc->sc_chan_stride = 0x10000;
+		sc->sc_obsv_ee_stride = 0x10000;
+		sc->sc_obsv_apid_stride = 0x00080;
+		sc->sc_arb_apid_map = 0x00900;
+		sc->sc_ownership_table = 0x00700;
+	} else {
+		sc->sc_max_periph = 1024;
+		sc->sc_chan_stride = 0x01000;
+		sc->sc_obsv_ee_stride = 0x08000;
+		sc->sc_obsv_apid_stride = 0x00020;
+		sc->sc_arb_apid_map = 0x02000;
+		sc->sc_ownership_table = 0x00000;
+	}
+
+	KASSERT(sc->sc_max_periph <= SPMI_MAX_PERIPH);
+
+	sc->sc_ee = sc->sc_data->ee;
+
+	for (i = 0; i < sc->sc_max_periph; i++) {
+		val = HREAD4(sc, QCSPMI_REG_CORE, SPMI_ARB_APID_MAP(sc, i));
+		if (!val)
+			continue;
+		ppid = (val >> SPMI_ARB_APID_MAP_PPID_SHIFT) &
+		    SPMI_ARB_APID_MAP_PPID_MASK;
+		irq_own = val & SPMI_ARB_APID_MAP_IRQ_OWNER;
+		val = HREAD4(sc, QCSPMI_REG_CNFG, SPMI_OWNERSHIP_TABLE(sc, i));
+		apid = &sc->sc_apid[i];
+		apid->write_ee = SPMI_OWNERSHIP_TABLE_OWNER(val);
+		apid->irq_ee = 0xff;
+		if (irq_own)
+			apid->irq_ee = apid->write_ee;
+		last_apid = &sc->sc_apid[sc->sc_ppid_to_apid[ppid] &
+		    SPMI_PPID_TO_APID_MASK];
+		if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID) ||
+		    apid->write_ee == sc->sc_ee) {
+			sc->sc_ppid_to_apid[ppid] = SPMI_PPID_TO_APID_VALID | i;
+		} else if ((sc->sc_ppid_to_apid[ppid] &
+		    SPMI_PPID_TO_APID_VALID) && irq_own &&
+		    last_apid->write_ee == sc->sc_ee) {
+			last_apid->irq_ee = apid->irq_ee;
+		}
+	}
+
+done:
+	acpi_resource_cleanup(&res);
+}
+
+int
+qcspmi_cmd_read(struct qcspmi_softc *sc, uint8_t sid, uint8_t cmd,
+    uint16_t addr, void *buf, size_t len)
+{
+	uint8_t *cbuf = buf;
+	uint32_t reg;
+	uint16_t apid, ppid;
+	int bc = len - 1;
+	int i;
+
+	if (len == 0 || len > 8)
+		return EINVAL;
+
+	/* TODO: support more types */
+	if (cmd != SPMI_CMD_EXT_READL)
+		return EINVAL;
+
+	ppid = (sid << 8) | (addr >> 8);
+	if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID))
+		return ENXIO;
+	apid = sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_MASK;
+
+	HWRITE4(sc, QCSPMI_REG_OBSRVR,
+	    SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_COMMAND,
+	    SPMI_COMMAND_OP_EXT_READL | SPMI_COMMAND_ADDR(addr) |
+	    SPMI_COMMAND_LEN(bc));
+
+	for (i = 1000; i > 0; i--) {
+		reg = HREAD4(sc, QCSPMI_REG_OBSRVR,
+		    SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_STATUS);
+		if (reg & SPMI_STATUS_DONE)
+			break;
+		if (reg & SPMI_STATUS_FAILURE) {
+			printf(": transaction failed\n");
+			return EIO;
+		}
+		if (reg & SPMI_STATUS_DENIED) {
+			printf(": transaction denied\n");
+			return EIO;
+		}
+		if (reg & SPMI_STATUS_DROPPED) {
+			printf(": transaction dropped\n");
+			return EIO;
+		}
+	}
+	if (i == 0) {
+		printf("\n");
+		return ETIMEDOUT;
+	}
+
+	if (len > 0) {
+		reg = HREAD4(sc, QCSPMI_REG_OBSRVR,
+		    SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_RDATA0);
+		memcpy(cbuf, &reg, MIN(len, 4));
+		cbuf += MIN(len, 4);
+		len -= MIN(len, 4);
+	}
+	if (len > 0) {
+		reg = HREAD4(sc, QCSPMI_REG_OBSRVR,
+		    SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_RDATA1);
+		memcpy(cbuf, &reg, MIN(len, 4));
+		cbuf += MIN(len, 4);
+		len -= MIN(len, 4);
+	}
+
+	return 0;
+}
+
+int
+qcspmi_cmd_write(struct qcspmi_softc *sc, uint8_t sid, uint8_t cmd,
+    uint16_t addr, const void *buf, size_t len)
+{
+	const uint8_t *cbuf = buf;
+	uint32_t reg;
+	uint16_t apid, ppid;
+	int bc = len - 1;
+	int i;
+
+	if (len == 0 || len > 8)
+		return EINVAL;
+
+	/* TODO: support more types */
+	if (cmd != SPMI_CMD_EXT_WRITEL)
+		return EINVAL;
+
+	ppid = (sid << 8) | (addr >> 8);
+	if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID))
+		return ENXIO;
+	apid = sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_MASK;
+
+	if (sc->sc_apid[apid].write_ee != sc->sc_ee)
+		return EPERM;
+
+	if (len > 0) {
+		memcpy(&reg, cbuf, MIN(len, 4));
+		HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) +
+		    SPMI_WDATA0, reg);
+		cbuf += MIN(len, 4);
+		len -= MIN(len, 4);
+	}
+	if (len > 0) {
+		memcpy(&reg, cbuf, MIN(len, 4));
+		HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) +
+		    SPMI_WDATA1, reg);
+		cbuf += MIN(len, 4);
+		len -= MIN(len, 4);
+	}
+
+	HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) + SPMI_COMMAND,
+	    SPMI_COMMAND_OP_EXT_WRITEL | SPMI_COMMAND_ADDR(addr) |
+	    SPMI_COMMAND_LEN(bc));
+
+	for (i = 1000; i > 0; i--) {
+		reg = HREAD4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) +
+		    SPMI_STATUS);
+		if (reg & SPMI_STATUS_DONE)
+			break;
+	}
+	if (i == 0)
+		return ETIMEDOUT;
+
+	if (reg & SPMI_STATUS_FAILURE ||
+	    reg & SPMI_STATUS_DENIED ||
+	    reg & SPMI_STATUS_DROPPED)
+		return EIO;
+
+	return 0;
+}

Reply via email to