Index | Thread | Search

From:
joshua stein <jcs@jcs.org>
Subject:
intel lpss spi driver
To:
tech@openbsd.org
Date:
Wed, 12 Nov 2025 08:54:39 -0600

Download raw body.

Thread
  • joshua stein:

    intel lpss spi driver

Needed for an upcoming driver...


diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC
index d30c1d85139..d119b9d45b0 100644
--- sys/arch/amd64/conf/GENERIC
+++ sys/arch/amd64/conf/GENERIC
@@ -206,6 +206,9 @@ wskbd*	at icc? mux 1
 skgpio0 at isa? port 0x680	# Soekris net6501 GPIO and LEDs
 gpio* at skgpio?
 
+ispi*	at acpi?		# Intel LPSS SPI controller
+ispi*	at pci?
+
 #option PCMCIAVERBOSE
 
 # PCI PCMCIA controllers
diff --git sys/conf/files sys/conf/files
index 1390f7d29c3..88454c9271c 100644
--- sys/conf/files
+++ sys/conf/files
@@ -20,6 +20,7 @@ define	radiobus {}
 define	i2cbus {}
 define	gpiobus {}
 define	onewirebus {}
+define	spi {}
 define	video {}
 define	intrmap {}
 define	fdt {[early = 0]}
@@ -566,6 +567,10 @@ device	qcscm
 device	ufshci: scsi
 file	dev/ic/ufshci.c			ufshci needs-flag
 
+# Intel LPSS SPI controller
+device	ispi: spi
+file	dev/ic/ispi.c			ispi
+
 # legitimate pseudo-devices
 pseudo-device vnd: disk
 pseudo-device rd: disk
diff --git sys/dev/acpi/acpi.c sys/dev/acpi/acpi.c
index 7a57236436a..4d41dff3b40 100644
--- sys/dev/acpi/acpi.c
+++ sys/dev/acpi/acpi.c
@@ -62,7 +62,6 @@ struct pool acpiwqpool;
 
 #define ACPIEN_RETRIES 15
 
-struct aml_node *acpi_pci_match(struct device *, struct pci_attach_args *);
 pcireg_t acpi_pci_min_powerstate(pci_chipset_tag_t, pcitag_t);
 void	 acpi_pci_set_powerstate(pci_chipset_tag_t, pcitag_t, int, int);
 int	acpi_pci_notify(struct aml_node *, int, void *);
diff --git sys/dev/acpi/acpivar.h sys/dev/acpi/acpivar.h
index 819a4a5f4e8..f683efc5690 100644
--- sys/dev/acpi/acpivar.h
+++ sys/dev/acpi/acpivar.h
@@ -314,6 +314,8 @@ int	 acpi_bus_space_map(bus_space_tag_t, bus_addr_t, bus_size_t, int,
 	     bus_space_handle_t *);
 void	 acpi_bus_space_unmap(bus_space_tag_t, bus_space_handle_t, bus_size_t);
 
+struct aml_node *acpi_pci_match(struct device *, struct pci_attach_args *);
+
 struct	 bios_attach_args;
 int	 acpi_probe(struct device *, struct cfdata *, struct bios_attach_args *);
 u_int	 acpi_checksum(const void *, size_t);
diff --git sys/dev/acpi/files.acpi sys/dev/acpi/files.acpi
index 4c465a0d661..6fc08faca3f 100644
--- sys/dev/acpi/files.acpi
+++ sys/dev/acpi/files.acpi
@@ -212,6 +212,10 @@ file	dev/acpi/dwgpio.c		dwgpio
 attach	dwiic at acpi with dwiic_acpi
 file	dev/acpi/dwiic_acpi.c		dwiic_acpi
 
+# Intel LPSS SPI controller
+attach	ispi at acpi with ispi_acpi
+file	dev/acpi/ispi_acpi.c		ispi_acpi
+
 # Chromebook keyboard backlight
 device	acpicbkbd
 attach	acpicbkbd at acpi
diff --git sys/dev/acpi/ispi_acpi.c sys/dev/acpi/ispi_acpi.c
new file mode 100644
index 00000000000..345bffa0d7e
--- /dev/null
+++ sys/dev/acpi/ispi_acpi.c
@@ -0,0 +1,116 @@
+/* $OpenBSD$ */
+/*
+ * Intel LPSS SPI controller
+ * ACPI attachment
+ *
+ * Copyright (c) 2015-2019 joshua stein <jcs@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or 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/kernel.h>
+#include <sys/kthread.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpidev.h>
+#include <dev/acpi/amltypes.h>
+#include <dev/acpi/dsdt.h>
+
+#include <dev/ic/ispivar.h>
+
+int	ispi_acpi_match(struct device *, void *, void *);
+void	ispi_acpi_attach(struct device *, struct device *, void *);
+
+int	ispi_activate(struct device *, int);
+void	ispi_acpi_bus_scan(struct ispi_softc *);
+
+int	ispi_configure(void *, int, int, int);
+void	ispi_start(struct ispi_softc *);
+void	ispi_send(struct ispi_softc *);
+void	ispi_recv(struct ispi_softc *);
+
+struct cfattach ispi_acpi_ca = {
+	sizeof(struct ispi_softc),
+	ispi_acpi_match,
+	ispi_acpi_attach,
+	NULL,
+	ispi_activate,
+};
+
+const char *ispi_acpi_hids[] = {
+	"INT33C0",
+	"INT33C1",
+	"INT3430",
+	"INT3431",
+	NULL
+};
+
+int
+ispi_acpi_match(struct device *parent, void *match, void *aux)
+{
+	struct acpi_attach_args *aaa = aux;
+	struct cfdata *cf = match;
+
+	return acpi_matchhids(aaa, ispi_acpi_hids, cf->cf_driver->cd_name);
+}
+
+void
+ispi_acpi_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct ispi_softc *sc = (struct ispi_softc *)self;
+	struct acpi_attach_args *aa = aux;
+
+	sc->sc_acpi = (struct acpi_softc *)parent;
+	sc->sc_devnode = aa->aaa_node;
+	rw_init(&sc->sc_buslock, sc->sc_dev.dv_xname);
+
+	printf(": %s", sc->sc_devnode->name);
+
+	ispi_init(sc);
+}
+
+void
+ispi_acpi_bus_scan(struct ispi_softc *sc)
+{
+	aml_find_node(sc->sc_devnode, "_HID", ispi_acpi_found_hid, sc);
+}
+
+int
+ispi_acpi_found_hid(struct aml_node *node, void *arg)
+{
+	struct ispi_softc *sc = (struct ispi_softc *)arg;
+	int64_t sta;
+	char cdev[32], dev[32];
+
+	if (node->parent == sc->sc_devnode)
+		return 0;
+
+	if (acpi_parsehid(node, arg, cdev, dev, sizeof(cdev)) != 0)
+		return 0;
+
+	sta = acpi_getsta(acpi_softc, node->parent);
+	if ((sta & STA_PRESENT) == 0)
+		return 0;
+
+	DPRINTF(("%s: found HID %s at %s\n", sc->sc_dev.dv_xname, dev,
+	    aml_nodename(node)));
+
+	acpi_attach_deps(acpi_softc, node->parent);
+
+	/* TODO */
+
+	return 0;
+}
diff --git sys/dev/ic/ispi.c sys/dev/ic/ispi.c
new file mode 100644
index 00000000000..a8aa1e2af65
--- /dev/null
+++ sys/dev/ic/ispi.c
@@ -0,0 +1,368 @@
+/* $OpenBSD$ */
+/*
+ * Intel LPSS SPI controller
+ *
+ * Copyright (c) 2015-2019 joshua stein <jcs@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or 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/kernel.h>
+#include <sys/kthread.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpidev.h>
+#include <dev/acpi/amltypes.h>
+#include <dev/acpi/dsdt.h>
+
+#include <dev/spi/spivar.h>
+
+#include <dev/pci/lpssreg.h>
+#include <dev/ic/ispivar.h>
+
+#define SSCR0			0x0	/* SSP Control Register 0 */
+#define   SSCR0_EDSS_0		(0 << 20)
+#define   SSCR0_EDSS_1		(1 << 20)
+#define   SSCR0_SCR_SHIFT	(8)
+#define   SSCR0_SCR_MASK	(0xFFF)
+#define   SSCR0_SSE		(1 << 7)
+#define   SSCR0_ECS_ON_CHIP	(0 << 6)
+#define   SSCR0_FRF_MOTOROLA	(0 << 4)
+#define   SSCR0_DSS_SHIFT	(0)
+#define   SSCR0_DSS_MASK	(0xF)
+#define SSCR1			0x4	/* SSP Control Register 1 */
+#define   SSCR1_RIE		(1 << 0) /* Receive FIFO Interrupt Enable */
+#define   SSCR1_TIE		(1 << 1) /* Transmit FIFO Interrupt Enable */
+#define   SSCR1_LBM		(1 << 2) /* Loop-Back Mode */
+#define   SSCR1_SPO_LOW		(0 << 3) /* Motorola SPI SSPSCLK polarity setting */
+#define   SSCR1_SPO_HIGH	(1 << 3)
+#define   SSCR1_SPH_FIRST	(0 << 4) /* Motorola SPI SSPSCLK phase setting */
+#define   SSCR1_SPH_SECOND	(1 << 4)
+#define   SSCR1_MWDS		(1 << 5) /* Microwire Transmit Data Size */
+#define   SSCR1_IFS_LOW		(0 << 16)
+#define   SSCR1_IFS_HIGH	(1 << 16)
+#define   SSCR1_PINTE		(1 << 18) /* Peripheral Trailing Byte Interrupt Enable */
+#define   SSCR1_TINTE		(1 << 19) /* Receiver Time-out Interrupt enable */
+#define   SSCR1_RX_THRESH_DEF	8
+#define   SSCR1_RX_THRESH(x)	(((x) - 1) << 10)
+#define   SSCR1_RFT		(0x00003c00)	/* Receive FIFO Threshold (mask) */
+#define   SSCR1_TX_THRESH_DEF	8
+#define   SSCR1_TX_THRESH(x)	(((x) - 1) << 6)
+#define   SSCR1_TFT		(0x000003c0)	/* Transmit FIFO Threshold (mask) */
+#define SSSR			0x8	/* SSP Status Register */
+#define   SSSR_TUR		(1 << 21)  /* Tx FIFO underrun */
+#define   SSSR_TINT		(1 << 19)  /* Rx Time-out interrupt */
+#define   SSSR_PINT		(1 << 18)  /* Peripheral trailing byte interrupt */
+#define   SSSR_ROR		(1 << 7)   /* Rx FIFO Overrun */
+#define   SSSR_BSY		(1 << 4)   /* SSP Busy */
+#define   SSSR_RNE		(1 << 3)   /* Receive FIFO not empty */
+#define   SSSR_TNF		(1 << 2)   /* Transmit FIFO not full */
+#define SSDR			0x10	/* SSP Data Register */
+#define SSTO			0x28	/* SSP Time out */
+#define SSPSP			0x2C	/* SSP Programmable Serial Protocol */
+#define SSITF			0x44	/* SPI Transmit FIFO */
+#define   SSITF_LEVEL_SHIFT	(16)
+#define   SSITF_LEVEL_MASK	(0x3f)
+#define   SSITF_TX_LO_THRESH(x)	(((x) - 1) << 8)
+#define   SSITF_TX_HI_THRESH(x)	((x) - 1)
+#define SSIRF			0x48	/* SPI Receive FIFO */
+#define   SSIRF_LEVEL_SHIFT	(8)
+#define   SSIRF_LEVEL_MASK	(0x3f)
+#define   SSIRF_RX_THRESH(x)	((x) - 1)
+
+void		ispi_cs_change(struct ispi_softc *, int);
+uint32_t	ispi_lpss_read(struct ispi_softc *, int);
+void		ispi_lpss_write(struct ispi_softc *, int, uint32_t);
+int		ispi_rx_fifo_empty(struct ispi_softc *);
+int		ispi_tx_fifo_full(struct ispi_softc *);
+
+struct cfdriver ispi_cd = {
+	NULL, "ispi", DV_DULL
+};
+
+int
+ispi_activate(struct device *self, int act)
+{
+	config_activate_children(self, act);
+	return 0;
+}
+
+int
+ispi_spi_print(void *aux, const char *pnp)
+{
+	struct spi_attach_args *sa = aux;
+
+	if (pnp != NULL)
+		printf("\"%s\" at %s", sa->sa_name, pnp);
+
+	return UNCONF;
+}
+
+int
+ispi_acquire_bus(void *cookie, int flags)
+{
+	struct ispi_softc *sc = cookie;
+
+	DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
+
+	rw_enter(&sc->sc_buslock, RW_WRITE);
+	ispi_cs_change(sc, 1);
+
+	return 0;
+}
+
+void
+ispi_release_bus(void *cookie, int flags)
+{
+	struct ispi_softc *sc = cookie;
+
+	DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
+
+	ispi_cs_change(sc, 0);
+	rw_exit(&sc->sc_buslock);
+}
+
+void
+ispi_init(struct ispi_softc *sc)
+{
+	uint32_t csctrl;
+
+	if (!sc->sc_rx_threshold)
+		sc->sc_rx_threshold = SSCR1_RX_THRESH_DEF;
+	if (!sc->sc_tx_threshold)
+		sc->sc_tx_threshold = SSCR1_TX_THRESH_DEF;
+
+	ispi_write(sc, SSCR0, 0);
+	ispi_write(sc, SSCR1, 0);
+	ispi_write(sc, SSTO, 0);
+	ispi_write(sc, SSPSP, 0);
+
+	/* lpss: enable software chip select control */
+	csctrl = ispi_lpss_read(sc, sc->sc_reg_cs_ctrl);
+	csctrl &= ~(LPSS_CS_CONTROL_SW_MODE | LPSS_CS_CONTROL_CS_HIGH);
+	csctrl |= LPSS_CS_CONTROL_SW_MODE | LPSS_CS_CONTROL_CS_HIGH;
+	ispi_lpss_write(sc, sc->sc_reg_cs_ctrl, csctrl);
+
+	ispi_cs_change(sc, 0);
+}
+
+uint32_t
+ispi_read(struct ispi_softc *sc, int reg)
+{
+	uint32_t val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg);
+	DPRINTF(("%s: %s(0x%x) = 0x%x\n", sc->sc_dev.dv_xname, __func__, reg,
+	    val));
+	return val;
+}
+
+void
+ispi_write(struct ispi_softc *sc, int reg, uint32_t val)
+{
+	DPRINTF(("%s: %s(0x%x, 0x%x)\n", sc->sc_dev.dv_xname, __func__, reg,
+	    val));
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg, val);
+}
+
+uint32_t
+ispi_lpss_read(struct ispi_softc *sc, int reg)
+{
+	return ispi_read(sc, sc->sc_lpss_reg_offset + reg);
+}
+
+void
+ispi_lpss_write(struct ispi_softc *sc, int reg, uint32_t val)
+{
+	ispi_write(sc, sc->sc_lpss_reg_offset + reg, val);
+}
+
+void
+ispi_config(void *cookie, struct spi_config *conf)
+{
+	struct ispi_softc *sc = cookie;
+	uint32_t sscr0, sscr1;
+	unsigned int rate;
+	int rx_threshold, tx_threshold;
+
+	DPRINTF(("%s: %s: ssp clk %ld sc_freq %d\n", sc->sc_dev.dv_xname,
+	    __func__, sc->sc_ssp_clk, conf->sc_freq));
+
+	rate = min(sc->sc_ssp_clk, conf->sc_freq);
+	if (rate <= 1)
+		rate = sc->sc_ssp_clk;
+
+	sscr0 = ((sc->sc_ssp_clk / rate) - 1) & 0xfff;
+	sscr0 |= SSCR0_FRF_MOTOROLA;
+	sscr0 |= (conf->sc_bpw - 1);
+	sscr0 |= SSCR0_SSE;
+	sscr0 |= (conf->sc_bpw > 16 ? SSCR0_EDSS_1 : SSCR0_EDSS_0);
+
+	ispi_clear_status(sc);
+
+	rx_threshold = SSIRF_RX_THRESH(sc->sc_rx_threshold);
+	tx_threshold = SSITF_TX_LO_THRESH(sc->sc_tx_threshold) |
+	    SSITF_TX_HI_THRESH(sc->sc_tx_threshold_hi);
+
+	if ((ispi_read(sc, SSIRF) & 0xff) != rx_threshold)
+		ispi_write(sc, SSIRF, rx_threshold);
+
+	if ((ispi_read(sc, SSITF) & 0xffff) != tx_threshold)
+		ispi_write(sc, SSITF, tx_threshold);
+
+	ispi_write(sc, SSCR0, sscr0 & ~SSCR0_SSE);
+
+	ispi_write(sc, SSTO, 1000); /* timeout */
+
+	sscr1 = (SSCR1_RX_THRESH(sc->sc_rx_threshold) & SSCR1_RFT) |
+	    (SSCR1_TX_THRESH(sc->sc_tx_threshold) & SSCR1_TFT);
+
+#if 0
+	/* enable interrupts */
+	sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
+#endif
+	ispi_write(sc, SSCR1, sscr1);
+
+	/* restart SSP */
+	ispi_write(sc, SSCR0, sscr0);
+	ispi_write(sc, SSCR1, sscr1);
+}
+
+int
+ispi_flush(struct ispi_softc *sc)
+{
+	int tries = 500;
+
+	DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
+
+	do {
+		while (!ispi_rx_fifo_empty(sc))
+			ispi_read(sc, SSDR);
+	} while ((ispi_read(sc, SSSR) & SSSR_BSY) && --tries);
+
+	DPRINTF(("%s: flushed with %d left\n", sc->sc_dev.dv_xname, tries));
+
+	ispi_write(sc, SSSR, SSSR_ROR);
+
+	return (tries != 0);
+}
+
+void
+ispi_cs_change(struct ispi_softc *sc, int cs_assert)
+{
+	uint32_t t;
+	int tries = 500;
+
+	DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
+
+	if (!cs_assert) {
+		/* wait until idle */
+		while ((ispi_read(sc, SSSR) & SSSR_BSY) && --tries)
+			;
+	}
+
+	t = ispi_lpss_read(sc, sc->sc_reg_cs_ctrl);
+	if (cs_assert)
+		t &= ~LPSS_CS_CONTROL_CS_HIGH;
+	else
+		t |= LPSS_CS_CONTROL_CS_HIGH;
+	ispi_lpss_write(sc, sc->sc_reg_cs_ctrl, t);
+
+	DELAY(10);
+}
+
+int
+ispi_transfer(void *cookie, char *out, char *in, int len, int flags)
+{
+	struct ispi_softc *sc = cookie;
+	int s = spltty();
+
+	DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
+
+	sc->sc_ridx = sc->sc_widx = 0;
+
+	/* drain input buffer */
+	ispi_flush(sc);
+
+	while (sc->sc_ridx < len || sc->sc_widx < len) {
+		while (!ispi_rx_fifo_empty(sc) && sc->sc_ridx < len) {
+			if (in)
+				in[sc->sc_ridx] = ispi_read(sc, SSDR) & 0xff;
+			else
+				ispi_read(sc, SSDR);
+
+			sc->sc_ridx++;
+		}
+
+		while (!ispi_tx_fifo_full(sc) && sc->sc_widx < len) {
+			if (out)
+				ispi_write(sc, SSDR, out[sc->sc_widx]);
+			else
+				ispi_write(sc, SSDR, 0);
+
+			sc->sc_widx++;
+		}
+	}
+
+	DPRINTF(("%s: %s: done transmitting %s %d\n", sc->sc_dev.dv_xname,
+	    __func__, (out ? "out" : "in"), len));
+
+	splx(s);
+	return 0;
+}
+
+int
+ispi_status(struct ispi_softc *sc)
+{
+	return ispi_read(sc, SSSR);
+}
+
+void
+ispi_clear_status(struct ispi_softc *sc)
+{
+	ispi_write(sc, SSSR, SSSR_TUR | SSSR_TINT | SSSR_PINT | SSSR_ROR);
+}
+
+int
+ispi_rx_fifo_empty(struct ispi_softc *sc)
+{
+	return !(ispi_status(sc) & SSSR_RNE);
+}
+
+int
+ispi_tx_fifo_full(struct ispi_softc *sc)
+{
+	return !(ispi_status(sc) & SSSR_TNF);
+}
+
+int
+ispi_rx_fifo_overrun(struct ispi_softc *sc)
+{
+	if (ispi_status(sc) & SSSR_ROR) {
+		printf("%s: %s\n", sc->sc_dev.dv_xname, __func__);
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+ispi_intr(void *arg)
+{
+#ifdef ISPI_DEBUG
+	struct ispi_softc *sc = arg;
+	DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
+#endif
+	return 1;
+}
diff --git sys/dev/ic/ispivar.h sys/dev/ic/ispivar.h
new file mode 100644
index 00000000000..2d9ae17b2f1
--- /dev/null
+++ sys/dev/ic/ispivar.h
@@ -0,0 +1,101 @@
+/* $OpenBSD$ */
+/*
+ * Intel LPSS SPI controller
+ *
+ * Copyright (c) 2015-2018 joshua stein <jcs@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or 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/kernel.h>
+#include <sys/kthread.h>
+
+#include "acpi.h"
+#if NACPI > 0
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpidev.h>
+#include <dev/acpi/amltypes.h>
+#include <dev/acpi/dsdt.h>
+#endif
+
+#include <dev/pci/pcivar.h>
+
+#include <dev/spi/spivar.h>
+
+/* #define ISPI_DEBUG */
+
+#ifdef ISPI_DEBUG
+#define DPRINTF(x) printf x
+#else
+#define DPRINTF(x)
+#endif
+
+struct ispi_softc {
+	struct device		sc_dev;
+
+	bus_space_tag_t		sc_iot;
+	bus_space_handle_t	sc_ioh;
+
+	void			*sc_ih;
+	struct pci_attach_args	sc_paa;
+	long			sc_ssp_clk;
+	int			sc_lpss_reg_offset;
+	int			sc_reg_cs_ctrl;
+	int			sc_rx_threshold;
+	int			sc_tx_threshold;
+	int			sc_tx_threshold_hi;
+
+	struct spi_controller	sc_spi_tag;
+	struct rwlock		sc_buslock;
+	struct spi_config	sc_spi_conf;
+
+	int			 sc_ridx;
+	int			 sc_widx;
+
+#if NACPI > 0
+	struct acpi_softc	*sc_acpi;
+	struct aml_node		*sc_devnode;
+#endif
+	u_int32_t		sc_caps;
+};
+
+void		ispi_init(struct ispi_softc *sc);
+
+int		ispi_match(struct device *, void *, void *);
+void		ispi_attach(struct device *, struct device *, void *);
+int		ispi_activate(struct device *, int);
+int		ispi_spi_print(void *aux, const char *pnp);
+
+void		ispi_write(struct ispi_softc *sc, int reg, uint32_t val);
+uint32_t	ispi_read(struct ispi_softc *sc, int reg);
+
+#if NACPI > 0
+int		ispi_acpi_found_hid(struct aml_node *node, void *arg);
+#endif
+
+void		ispi_config(void *, struct spi_config *);
+int		ispi_acquire_bus(void *, int);
+void		ispi_release_bus(void *, int);
+int		ispi_transfer(void *, char *, char *, int, int);
+void		ispi_start(struct ispi_softc *);
+void		ispi_send(struct ispi_softc *);
+void		ispi_recv(struct ispi_softc *);
+
+int		ispi_intr(void *);
+int		ispi_status(struct ispi_softc *);
+int		ispi_flush(struct ispi_softc *);
+void		ispi_clear_status(struct ispi_softc *);
+int		ispi_rx_fifo_empty(struct ispi_softc *);
+int		ispi_rx_fifo_overrun(struct ispi_softc *);
diff --git sys/dev/pci/com_pci.c sys/dev/pci/com_pci.c
index e0cd1ed4d14..9461de4b86d 100644
--- sys/dev/pci/com_pci.c
+++ sys/dev/pci/com_pci.c
@@ -22,44 +22,13 @@
 #include <dev/pci/pcidevs.h>
 #include <dev/pci/pcireg.h>
 #include <dev/pci/pcivar.h>
+#include <dev/pci/lpssreg.h>
 
 #include <dev/ic/comreg.h>
 #include <dev/ic/comvar.h>
 
 #define com_usr 31	/* Synopsys DesignWare UART */
 
-/* Intel Low Power Subsystem */
-#define LPSS_CLK		0x200
-#define  LPSS_CLK_GATE			(1 << 0)
-#define  LPSS_CLK_MDIV_SHIFT		1
-#define  LPSS_CLK_MDIV_MASK		0x3fff
-#define  LPSS_CLK_NDIV_SHIFT		16
-#define  LPSS_CLK_NDIV_MASK		0x3fff
-#define  LPSS_CLK_UPDATE		(1U << 31)
-#define LPSS_RESETS		0x204
-#define  LPSS_RESETS_FUNC		(3 << 0)
-#define  LPSS_RESETS_IDMA		(1 << 2)
-#define LPSS_ACTIVELTR		0x210
-#define LPSS_IDLELTR		0x214
-#define  LPSS_LTR_VALUE_MASK		(0x3ff << 0)
-#define  LPSS_LTR_SCALE_MASK		(0x3 << 10)
-#define  LPSS_LTR_SCALE_1US		(2 << 10)
-#define  LPSS_LTR_SCALE_32US		(3 << 10)
-#define  LPSS_LTR_REQ			(1 << 15)
-#define LPSS_SSP		0x220
-#define  LPSS_SSP_DIS_DMA_FIN		(1 << 0)
-#define LPSS_REMAP_ADDR		0x240
-#define LPSS_CAPS		0x2fc
-#define  LPSS_CAPS_TYPE_I2C		(0 << 4)
-#define  LPSS_CAPS_TYPE_UART		(1 << 4)
-#define  LPSS_CAPS_TYPE_SPI		(2 << 4)
-#define  LPSS_CAPS_TYPE_MASK		(0xf << 4)
-#define  LPSS_CAPS_NO_IDMA		(1 << 8)
-
-#define LPSS_REG_OFF		0x200
-#define LPSS_REG_SIZE		0x100
-#define LPSS_REG_NUM		(LPSS_REG_SIZE / sizeof(uint32_t))
-
 #define HREAD4(sc, reg)							\
 	(bus_space_read_4((sc)->sc.sc_iot, (sc)->sc.sc_ioh, (reg)))
 #define HWRITE4(sc, reg, val)						\
@@ -135,7 +104,8 @@ com_pci_attach(struct device *parent, struct device *self, void *aux)
 	 */
 	if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_INTEL) {
 		caps = HREAD4(sc, LPSS_CAPS);
-		if ((caps & LPSS_CAPS_TYPE_MASK) != LPSS_CAPS_TYPE_UART) {
+		if (((caps & LPSS_CAPS_TYPE_MASK) >> LPSS_CAPS_TYPE_SHIFT) !=
+		    LPSS_CAPS_TYPE_UART) {
 			bus_space_unmap(sc->sc.sc_iot, sc->sc.sc_ioh,
 			    sc->sc_ios);
 			printf(": not a UART\n");
diff --git sys/dev/pci/dwiic_pci.c sys/dev/pci/dwiic_pci.c
index b51aa211991..0bd675c6dfa 100644
--- sys/dev/pci/dwiic_pci.c
+++ sys/dev/pci/dwiic_pci.c
@@ -24,20 +24,10 @@
 #include <dev/pci/pcidevs.h>
 #include <dev/pci/pcireg.h>
 #include <dev/pci/pcivar.h>
+#include <dev/pci/lpssreg.h>
 
 #include <dev/ic/dwiicvar.h>
 
-/* 13.3: I2C Additional Registers Summary */
-#define LPSS_RESETS		0x204
-#define  LPSS_RESETS_I2C	(1 << 0) | (1 << 1)
-#define  LPSS_RESETS_IDMA	(1 << 2)
-#define LPSS_ACTIVELTR		0x210
-#define LPSS_IDLELTR		0x214
-#define LPSS_CAPS		0x2fc
-#define  LPSS_CAPS_NO_IDMA	(1 << 8)
-#define  LPSS_CAPS_TYPE_SHIFT	4
-#define  LPSS_CAPS_TYPE_MASK	(0xf << LPSS_CAPS_TYPE_SHIFT)
-
 int		dwiic_pci_match(struct device *, void *, void *);
 void		dwiic_pci_attach(struct device *, struct device *, void *);
 int		dwiic_pci_activate(struct device *, int);
@@ -232,7 +222,7 @@ dwiic_pci_attach(struct device *parent, struct device *self, void *aux)
 
 	/* un-reset - page 958 */
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, LPSS_RESETS,
-	    (LPSS_RESETS_I2C | LPSS_RESETS_IDMA));
+	    (LPSS_RESETS_FUNC | LPSS_RESETS_IDMA));
 
 #if NACPI > 0
 	/* fetch timing parameters from ACPI, if possible */
@@ -308,7 +298,7 @@ dwiic_pci_activate(struct device *self, int act)
 	case DVACT_RESUME:
 		DELAY(10000);	/* 10 msec */
 		bus_space_write_4(sc->sc_iot, sc->sc_ioh, LPSS_RESETS,
-		    (LPSS_RESETS_I2C | LPSS_RESETS_IDMA));
+		    (LPSS_RESETS_FUNC | LPSS_RESETS_IDMA));
 		DELAY(10000);	/* 10 msec */
 		break;
 	}
diff --git sys/dev/pci/files.pci sys/dev/pci/files.pci
index c0c71355de8..16279cd6e33 100644
--- sys/dev/pci/files.pci
+++ sys/dev/pci/files.pci
@@ -897,5 +897,9 @@ device	nhi
 attach	nhi at pci
 file	dev/pci/nhi.c			nhi
 
+# Intel LPSS SPI controller
+attach	ispi at pci with ispi_pci
+file	dev/pci/ispi_pci.c		ispi_pci
+
 include "dev/pci/files.agp"
 include "dev/pci/drm/files.drm"
diff --git sys/dev/pci/ispi_pci.c sys/dev/pci/ispi_pci.c
new file mode 100644
index 00000000000..2c3872964f4
--- /dev/null
+++ sys/dev/pci/ispi_pci.c
@@ -0,0 +1,157 @@
+/* $OpenBSD$ */
+/*
+ * Intel LPSS SPI controller
+ * PCI attachment
+ *
+ * Copyright (c) 2015-2018 joshua stein <jcs@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or 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/kernel.h>
+#include <sys/kthread.h>
+
+#include <dev/pci/pcidevs.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+#include <dev/pci/lpssreg.h>
+
+#include <dev/ic/ispivar.h>
+
+int	ispi_pci_match(struct device *, void *, void *);
+void	ispi_pci_attach(struct device *, struct device *, void *);
+int	ispi_pci_activate(struct device *, int);
+void	ispi_pci_bus_scan(struct ispi_softc *);
+
+struct cfattach ispi_pci_ca = {
+	sizeof(struct ispi_softc),
+	ispi_pci_match,
+	ispi_pci_attach,
+	NULL,
+	ispi_pci_activate,
+};
+
+const struct pci_matchid ispi_pci_ids[] = {
+	{ PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_100SERIES_LP_SPI_3 },
+};
+
+int
+ispi_pci_match(struct device *parent, void *match, void *aux)
+{
+	return (pci_matchbyid(aux, ispi_pci_ids, nitems(ispi_pci_ids)));
+}
+
+void
+ispi_pci_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct ispi_softc *sc = (struct ispi_softc *)self;
+	struct pci_attach_args *pa = aux;
+	bus_size_t iosize;
+	pci_intr_handle_t ih;
+	const char *intrstr = NULL;
+	uint8_t type;
+
+	memcpy(&sc->sc_paa, pa, sizeof(sc->sc_paa));
+
+	pci_set_powerstate(pa->pa_pc, pa->pa_tag, PCI_PMCSR_STATE_D0);
+
+	if (pci_mapreg_map(pa, PCI_MAPREG_START, PCI_MAPREG_MEM_TYPE_64BIT, 0,
+	    &sc->sc_iot, &sc->sc_ioh, NULL, &iosize, 0)) {
+		printf(": can't map mem space\n");
+		return;
+	}
+
+	sc->sc_caps = bus_space_read_4(sc->sc_iot, sc->sc_ioh, LPSS_CAPS);
+	type = (sc->sc_caps & LPSS_CAPS_TYPE_MASK) >> LPSS_CAPS_TYPE_SHIFT;
+	if (type != LPSS_CAPS_TYPE_SPI) {
+		printf(": type %d not supported\n", type);
+		return;
+	}
+
+	switch (PCI_PRODUCT(pa->pa_id)) {
+	case PCI_PRODUCT_INTEL_100SERIES_LP_SPI_3:
+		/* SPT */
+		sc->sc_lpss_reg_offset = 0x200;
+		sc->sc_reg_cs_ctrl = 0x24;
+		sc->sc_rx_threshold = 1;
+		sc->sc_tx_threshold = 32;
+		sc->sc_tx_threshold_hi = 56;
+		sc->sc_ssp_clk = 9600000;
+		break;
+	default:
+		printf(": unknown parameters\n");
+		return;
+	}
+
+	/* un-reset - page 958 */
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, LPSS_RESETS,
+	    (LPSS_RESETS_FUNC | LPSS_RESETS_IDMA));
+
+	/* install interrupt handler */
+	if (pci_intr_map(&sc->sc_paa, &ih) == 0) {
+		intrstr = pci_intr_string(sc->sc_paa.pa_pc, ih);
+		sc->sc_ih = pci_intr_establish(sc->sc_paa.pa_pc, ih, IPL_BIO,
+		    ispi_intr, sc, sc->sc_dev.dv_xname);
+		if (sc->sc_ih != NULL) {
+			printf(": %s", intrstr);
+		}
+	}
+
+	printf("\n");
+
+	rw_init(&sc->sc_buslock, sc->sc_dev.dv_xname);
+
+	ispi_init(sc);
+
+	/* setup spi controller */
+	sc->sc_spi_tag.sc_cookie = sc;
+	sc->sc_spi_tag.sc_config = ispi_config;
+	sc->sc_spi_tag.sc_transfer = ispi_transfer;
+	sc->sc_spi_tag.sc_acquire_bus = ispi_acquire_bus;
+	sc->sc_spi_tag.sc_release_bus = ispi_release_bus;
+
+	ispi_pci_bus_scan(sc);
+}
+
+int
+ispi_pci_activate(struct device *self, int act)
+{
+	struct ispi_softc *sc = (struct ispi_softc *)self;
+
+	switch (act) {
+	case DVACT_WAKEUP:
+		bus_space_write_4(sc->sc_iot, sc->sc_ioh, LPSS_RESETS,
+		    (LPSS_RESETS_FUNC | LPSS_RESETS_IDMA));
+		break;
+	}
+
+	ispi_activate(self, act);
+
+	return 0;
+}
+
+void
+ispi_pci_bus_scan(struct ispi_softc *sc)
+{
+#if NACPI > 0
+	struct aml_node *node = acpi_pci_match((struct device *)sc, &sc->sc_paa);
+
+	if (node == NULL)
+		return;
+
+	sc->sc_devnode = node;
+	aml_find_node(node, "_HID", ispi_acpi_found_hid, sc);
+#endif
+}
diff --git sys/dev/pci/lpssreg.h sys/dev/pci/lpssreg.h
new file mode 100644
index 00000000000..66d2f0b7b18
--- /dev/null
+++ sys/dev/pci/lpssreg.h
@@ -0,0 +1,48 @@
+/* $OpenBSD$ */
+/*
+ * Intel Low Power Subsystem
+ *
+ * Copyright (c) 2015-2017 joshua stein <jcs@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or 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.
+ */
+
+/* 13.3: I2C Additional Registers Summary */
+#define LPSS_CLK		0x200
+#define  LPSS_CLK_GATE			(1 << 0)
+#define  LPSS_CLK_MDIV_SHIFT		1
+#define  LPSS_CLK_MDIV_MASK		0x3fff
+#define  LPSS_CLK_NDIV_SHIFT		16
+#define  LPSS_CLK_NDIV_MASK		0x3fff
+#define  LPSS_CLK_UPDATE		(1U << 31)
+#define LPSS_RESETS		0x204
+#define  LPSS_RESETS_FUNC	(1 << 0) | (1 << 1)
+#define  LPSS_RESETS_IDMA	(1 << 2)
+#define LPSS_ACTIVELTR		0x210
+#define LPSS_IDLELTR		0x214
+#define LPSS_REMAP_ADDR		0x240
+#define LPSS_CAPS		0x2fc
+#define  LPSS_CAPS_NO_IDMA	(1 << 8)
+#define  LPSS_CAPS_TYPE_SHIFT	4
+#define  LPSS_CAPS_TYPE_MASK	(0xf << LPSS_CAPS_TYPE_SHIFT)
+#define  LPSS_CAPS_TYPE_I2C	0
+#define  LPSS_CAPS_TYPE_UART	1
+#define  LPSS_CAPS_TYPE_SPI	2
+#define  LPSS_CAPS_CS_EN_SHIFT	9
+#define  LPSS_CAPS_CS_EN_MASK	(0xf << LPSS_CAPS_CS_EN_SHIFT)
+#define LPSS_CS_CONTROL_SW_MODE	(1 << 0)
+#define LPSS_CS_CONTROL_CS_HIGH	(1 << 1)
+
+#define LPSS_REG_OFF		0x200
+#define LPSS_REG_SIZE		0x100
+#define LPSS_REG_NUM		(LPSS_REG_SIZE / sizeof(uint32_t))
diff --git sys/dev/spi/spivar.h sys/dev/spi/spivar.h
index c56b840a3b6..f00c6c57938 100644
--- sys/dev/spi/spivar.h
+++ sys/dev/spi/spivar.h
@@ -15,6 +15,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#ifndef _DEV_SPI_SPIVAR_H_
+#define _DEV_SPI_SPIVAR_H_
+
 struct spi_config {
 	int		 sc_cs;
 	int		 sc_flags;
@@ -54,3 +57,5 @@ struct spi_attach_args {
 	(*(sc)->sc_acquire_bus)((sc)->sc_cookie, (flags))
 #define	spi_release_bus(sc, flags)					\
 	(*(sc)->sc_release_bus)((sc)->sc_cookie, (flags))
+
+#endif /* _DEV_SPI_SPIVAR_H_ */