[PATCH 1/2] spi: apple: Add driver for Apple SPI controller

Mark Kettenis kettenis at openbsd.org
Sun Jan 16 18:06:12 CET 2022


Add a driver for the SPI controller integrated on Apple SoCs.
This is necessary to support the keyboard on Apple Silicon laopts
since their keyboard uses an Apple-specific HID over SPI protocol.

Signed-off-by: Mark Kettenis <kettenis at openbsd.org>
---
 arch/arm/Kconfig        |   2 +
 drivers/spi/Kconfig     |   7 +
 drivers/spi/Makefile    |   1 +
 drivers/spi/apple_spi.c | 283 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 293 insertions(+)
 create mode 100644 drivers/spi/apple_spi.c

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 04b4a20211..fed993a53d 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -937,6 +937,7 @@ config ARCH_APPLE
 	select DM_MAILBOX
 	select DM_RESET
 	select DM_SERIAL
+	select DM_SPI
 	select DM_USB
 	select DM_VIDEO
 	select IOMMU
@@ -946,6 +947,7 @@ config ARCH_APPLE
 	select POSITION_INDEPENDENT
 	select POWER_DOMAIN
 	select REGMAP
+	select SPI
 	select SYSCON
 	select SYSRESET
 	select SYSRESET_WATCHDOG
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index d07e9a28af..0a6a85f9c4 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -50,6 +50,13 @@ config ALTERA_SPI
 	  IP core. Please find details on the "Embedded Peripherals IP
 	  User Guide" of Altera.
 
+config APPLE_SPI
+	bool "Apple SPI driver"
+	default y if ARCH_APPLE
+	help
+	  Enable the Apple SPI driver. This driver can be used to
+	  access the SPI flash and keyboard on machines based on Apple SoCs.
+
 config ATCSPI200_SPI
 	bool "Andestech ATCSPI200 SPI driver"
 	help
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d2f24bccef..bea746f3e3 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_SPI_MEM) += spi-mem-nodm.o
 endif
 
 obj-$(CONFIG_ALTERA_SPI) += altera_spi.o
+obj-$(CONFIG_APPLE_SPI) += apple_spi.o
 obj-$(CONFIG_ATH79_SPI) += ath79_spi.o
 obj-$(CONFIG_ATMEL_QSPI) += atmel-quadspi.o
 obj-$(CONFIG_ATMEL_SPI) += atmel_spi.o
diff --git a/drivers/spi/apple_spi.c b/drivers/spi/apple_spi.c
new file mode 100644
index 0000000000..3ae89dc638
--- /dev/null
+++ b/drivers/spi/apple_spi.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Mark Kettenis <kettenis at openbsd.org>
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <clk.h>
+#include <spi.h>
+#include <asm/io.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+
+#define APPLE_SPI_CTRL			0x000
+#define APPLE_SPI_CTRL_RUN		BIT(0)
+#define APPLE_SPI_CTRL_TX_RESET		BIT(2)
+#define APPLE_SPI_CTRL_RX_RESET		BIT(3)
+
+#define APPLE_SPI_CFG			0x004
+#define APPLE_SPI_CFG_CPHA		BIT(1)
+#define APPLE_SPI_CFG_CPOL		BIT(2)
+#define APPLE_SPI_CFG_MODE		GENMASK(6, 5)
+#define APPLE_SPI_CFG_MODE_POLLED	0
+#define APPLE_SPI_CFG_MODE_IRQ		1
+#define APPLE_SPI_CFG_MODE_DMA		2
+#define APPLE_SPI_CFG_IE_RXCOMPLETE	BIT(7)
+#define APPLE_SPI_CFG_IE_TXRXTHRESH	BIT(8)
+#define APPLE_SPI_CFG_LSB_FIRST		BIT(13)
+#define APPLE_SPI_CFG_WORD_SIZE		GENMASK(16, 15)
+#define APPLE_SPI_CFG_WORD_SIZE_8B	0
+#define APPLE_SPI_CFG_WORD_SIZE_16B	1
+#define APPLE_SPI_CFG_WORD_SIZE_32B	2
+#define APPLE_SPI_CFG_FIFO_THRESH	GENMASK(18, 17)
+#define APPLE_SPI_CFG_FIFO_THRESH_8B	0
+#define APPLE_SPI_CFG_FIFO_THRESH_4B	1
+#define APPLE_SPI_CFG_FIFO_THRESH_1B	2
+#define APPLE_SPI_CFG_IE_TXCOMPLETE	BIT(21)
+
+#define APPLE_SPI_STATUS		0x008
+#define APPLE_SPI_STATUS_RXCOMPLETE	BIT(0)
+#define APPLE_SPI_STATUS_TXRXTHRESH	BIT(1)
+#define APPLE_SPI_STATUS_TXCOMPLETE	BIT(2)
+
+#define APPLE_SPI_PIN			0x00c
+#define APPLE_SPI_PIN_KEEP_MOSI		BIT(0)
+#define APPLE_SPI_PIN_CS		BIT(1)
+
+#define APPLE_SPI_TXDATA		0x010
+#define APPLE_SPI_RXDATA		0x020
+#define APPLE_SPI_CLKDIV		0x030
+#define APPLE_SPI_CLKDIV_MIN		0x002
+#define APPLE_SPI_CLKDIV_MAX		0x7ff
+#define APPLE_SPI_RXCNT			0x034
+#define APPLE_SPI_WORD_DELAY		0x038
+#define APPLE_SPI_TXCNT			0x04c
+
+#define APPLE_SPI_FIFOSTAT		0x10c
+#define APPLE_SPI_FIFOSTAT_TXFULL	BIT(4)
+#define APPLE_SPI_FIFOSTAT_LEVEL_TX	GENMASK(15, 8)
+#define APPLE_SPI_FIFOSTAT_RXEMPTY	BIT(20)
+#define APPLE_SPI_FIFOSTAT_LEVEL_RX	GENMASK(31, 24)
+
+#define APPLE_SPI_IE_XFER		0x130
+#define APPLE_SPI_IF_XFER		0x134
+#define APPLE_SPI_XFER_RXCOMPLETE	BIT(0)
+#define APPLE_SPI_XFER_TXCOMPLETE	BIT(1)
+
+#define APPLE_SPI_IE_FIFO		0x138
+#define APPLE_SPI_IF_FIFO		0x13c
+#define APPLE_SPI_FIFO_RXTHRESH		BIT(4)
+#define APPLE_SPI_FIFO_TXTHRESH		BIT(5)
+#define APPLE_SPI_FIFO_RXFULL		BIT(8)
+#define APPLE_SPI_FIFO_TXEMPTY		BIT(9)
+#define APPLE_SPI_FIFO_RXUNDERRUN	BIT(16)
+#define APPLE_SPI_FIFO_TXOVERFLOW	BIT(17)
+
+#define APPLE_SPI_SHIFTCFG		0x150
+#define APPLE_SPI_SHIFTCFG_CLK_ENABLE	BIT(0)
+#define APPLE_SPI_SHIFTCFG_CS_ENABLE	BIT(1)
+#define APPLE_SPI_SHIFTCFG_AND_CLK_DATA	BIT(8)
+#define APPLE_SPI_SHIFTCFG_CS_AS_DATA	BIT(9)
+#define APPLE_SPI_SHIFTCFG_TX_ENABLE	BIT(10)
+#define APPLE_SPI_SHIFTCFG_RX_ENABLE	BIT(11)
+#define APPLE_SPI_SHIFTCFG_BITS		GENMASK(21, 16)
+#define APPLE_SPI_SHIFTCFG_OVERRIDE_CS	BIT(24)
+
+#define APPLE_SPI_PINCFG		0x154
+#define APPLE_SPI_PINCFG_KEEP_CLK	BIT(0)
+#define APPLE_SPI_PINCFG_KEEP_CS	BIT(1)
+#define APPLE_SPI_PINCFG_KEEP_MOSI	BIT(2)
+#define APPLE_SPI_PINCFG_CLK_IDLE_VAL	BIT(8)
+#define APPLE_SPI_PINCFG_CS_IDLE_VAL	BIT(9)
+#define APPLE_SPI_PINCFG_MOSI_IDLE_VAL	BIT(10)
+
+#define APPLE_SPI_DELAY_PRE		0x160
+#define APPLE_SPI_DELAY_POST		0x168
+#define APPLE_SPI_DELAY_ENABLE		BIT(0)
+#define APPLE_SPI_DELAY_NO_INTERBYTE	BIT(1)
+#define APPLE_SPI_DELAY_SET_SCK		BIT(4)
+#define APPLE_SPI_DELAY_SET_MOSI	BIT(6)
+#define APPLE_SPI_DELAY_SCK_VAL		BIT(8)
+#define APPLE_SPI_DELAY_MOSI_VAL	BIT(12)
+
+#define APPLE_SPI_FIFO_DEPTH		16
+
+#define APPLE_SPI_TIMEOUT_MS		200
+
+struct apple_spi_priv {
+	void *base;
+	u32 clkfreq;
+};
+
+static void apple_spi_set_cs(struct apple_spi_priv *priv, int on)
+{
+	writel(on ? 0 : APPLE_SPI_PIN_CS, priv->base + APPLE_SPI_PIN);
+}
+
+static void apple_spi_tx(struct apple_spi_priv *priv, uint *len,
+			 const void **dout)
+{
+	const u8 *out = *dout;
+	u32 data, fifostat;
+	uint count;
+
+	fifostat = readl(priv->base + APPLE_SPI_FIFOSTAT);
+	count = APPLE_SPI_FIFO_DEPTH -
+		FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_TX, fifostat);
+	while (*len > 0 && count > 0) {
+		data = out ? *out++ : 0;
+		writel(data, priv->base + APPLE_SPI_TXDATA);
+		(*len)--;
+		count--;
+	}
+
+	*dout = out;
+}
+
+static void apple_spi_rx(struct apple_spi_priv *priv, uint *len,
+			 void **din)
+{
+	u8 *in = *din;
+	u32 data, fifostat;
+	uint count;
+
+	fifostat = readl(priv->base + APPLE_SPI_FIFOSTAT);
+	count = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_RX, fifostat);
+	while (*len > 0 && count > 0) {
+		data = readl(priv->base + APPLE_SPI_RXDATA);
+		if (in)
+			*in++ = data;
+		(*len)--;
+		count--;
+	}
+
+	*din = in;
+}
+
+static int apple_spi_xfer(struct udevice *dev, unsigned int bitlen,
+			  const void *dout, void *din, unsigned long flags)
+{
+	struct apple_spi_priv *priv = dev_get_priv(dev->parent);
+	unsigned long start = get_timer(0);
+	uint txlen, rxlen;
+	int ret = 0;
+
+	if ((bitlen % 8) != 0)
+		return -EINVAL;
+	txlen = rxlen = bitlen / 8;
+
+	if (flags & SPI_XFER_BEGIN)
+		apple_spi_set_cs(priv, 1);
+
+	if (txlen > 0) {
+		/* Reset FIFOs */
+		writel(APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET,
+		       priv->base + APPLE_SPI_CTRL);
+
+		/* Set the transfer length */
+		writel(txlen, priv->base + APPLE_SPI_TXCNT);
+		writel(rxlen, priv->base + APPLE_SPI_RXCNT);
+
+		/* Prime transmit FIFO */
+		apple_spi_tx(priv, &txlen, &dout);
+
+		/* Start transfer */
+		writel(APPLE_SPI_CTRL_RUN, priv->base + APPLE_SPI_CTRL);
+
+		while ((txlen > 0 || rxlen > 0)) {
+			apple_spi_rx(priv, &rxlen, &din);
+			apple_spi_tx(priv, &txlen, &dout);
+
+			if (get_timer(start) > APPLE_SPI_TIMEOUT_MS) {
+				ret = -ETIMEDOUT;
+				break;
+			}
+		}
+
+		/* Stop transfer. */
+		writel(0, priv->base + APPLE_SPI_CTRL);
+	}
+
+	if (flags & SPI_XFER_END)
+		apple_spi_set_cs(priv, 0);
+
+	return ret;
+}
+
+static int apple_spi_set_speed(struct udevice *dev, uint speed)
+{
+	struct apple_spi_priv *priv = dev_get_priv(dev);
+	u32 div;
+
+	div = DIV_ROUND_UP(priv->clkfreq, speed);
+	if (div < APPLE_SPI_CLKDIV_MIN)
+		div = APPLE_SPI_CLKDIV_MIN;
+	if (div > APPLE_SPI_CLKDIV_MAX)
+		div = APPLE_SPI_CLKDIV_MAX;
+
+	writel(div, priv->base + APPLE_SPI_CLKDIV);
+
+	return 0;
+}
+
+static int apple_spi_set_mode(struct udevice *bus, uint mode)
+{
+	return 0;
+}
+
+struct dm_spi_ops apple_spi_ops = {
+	.xfer = apple_spi_xfer,
+	.set_speed = apple_spi_set_speed,
+	.set_mode = apple_spi_set_mode,
+};
+
+static int apple_spi_probe(struct udevice *dev)
+{
+	struct apple_spi_priv *priv = dev_get_priv(dev);
+	struct clk clkdev;
+	int ret;
+
+	priv->base = dev_read_addr_ptr(dev);
+	if (!priv->base)
+		return -EINVAL;
+
+	ret = clk_get_by_index(dev, 0, &clkdev);
+	if (ret)
+		return ret;
+	priv->clkfreq = clk_get_rate(&clkdev);
+
+	/* Set CS high (inactive) and disable override and auto-CS */
+	writel(APPLE_SPI_PIN_CS, priv->base + APPLE_SPI_PIN);
+	writel(readl(priv->base + APPLE_SPI_SHIFTCFG) & ~APPLE_SPI_SHIFTCFG_OVERRIDE_CS,
+	       priv->base + APPLE_SPI_SHIFTCFG);
+	writel((readl(priv->base + APPLE_SPI_PINCFG) & ~APPLE_SPI_PINCFG_CS_IDLE_VAL) |
+	       APPLE_SPI_PINCFG_KEEP_CS, priv->base + APPLE_SPI_PINCFG);
+
+	/* Reset FIFOs */
+	writel(APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET,
+	       priv->base + APPLE_SPI_CTRL);
+
+	/* Configure defaults */
+	writel(FIELD_PREP(APPLE_SPI_CFG_MODE, APPLE_SPI_CFG_MODE_IRQ) |
+	       FIELD_PREP(APPLE_SPI_CFG_WORD_SIZE, APPLE_SPI_CFG_WORD_SIZE_8B) |
+	       FIELD_PREP(APPLE_SPI_CFG_FIFO_THRESH, APPLE_SPI_CFG_FIFO_THRESH_8B),
+	       priv->base + APPLE_SPI_CFG);
+
+	return 0;
+}
+
+static const struct udevice_id apple_spi_of_match[] = {
+	{ .compatible = "apple,spi" },
+	{ /* sentinel */ }
+};
+
+U_BOOT_DRIVER(apple_spi) = {
+	.name = "apple_spi",
+	.id = UCLASS_SPI,
+	.of_match = apple_spi_of_match,
+	.probe = apple_spi_probe,
+	.priv_auto = sizeof(struct apple_spi_priv),
+	.ops = &apple_spi_ops,
+};
-- 
2.34.1



More information about the U-Boot mailing list