summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2022-02-11 01:38:04 +0300
committerTom Rini <trini@konsulko.com>2022-02-11 01:38:04 +0300
commitfe203a05fb663fa9bc42a9ef9ae51a6ed01a4a90 (patch)
tree635b4c3019d51684bdb98a52e20aaeb893c5c40a /drivers
parentc4408291bfff9622f2d3817a13c997debd0e8200 (diff)
parentad41ed120893522e23cc24550bb2d1dfb745a075 (diff)
downloadu-boot-fe203a05fb663fa9bc42a9ef9ae51a6ed01a4a90.tar.xz
Merge branch '2022-02-10-platform-updates'
- Assorted Apple M1 platform updates - Drop CONFIG_SYS_RESET_ADDR, update k3-am64-sk memory values in dts
Diffstat (limited to 'drivers')
-rw-r--r--drivers/input/Kconfig8
-rw-r--r--drivers/input/Makefile1
-rw-r--r--drivers/input/apple_spi_kbd.c271
-rw-r--r--drivers/mailbox/Kconfig11
-rw-r--r--drivers/mailbox/Makefile1
-rw-r--r--drivers/mailbox/apple-mbox.c92
-rw-r--r--drivers/nvme/Kconfig21
-rw-r--r--drivers/nvme/Makefile2
-rw-r--r--drivers/nvme/nvme.c86
-rw-r--r--drivers/nvme/nvme.h70
-rw-r--r--drivers/nvme/nvme_apple.c240
-rw-r--r--drivers/nvme/nvme_pci.c49
-rw-r--r--drivers/power/domain/apple-pmgr.c73
-rw-r--r--drivers/spi/Kconfig7
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/apple_spi.c285
16 files changed, 1156 insertions, 62 deletions
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index 0b753f37bf..2718b3674a 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -47,6 +47,14 @@ config KEYBOARD
and is only used by novena. For new boards, use driver model
instead.
+config APPLE_SPI_KEYB
+ bool "Enable Apple SPI keyboard support"
+ depends on DM_KEYBOARD && DM_SPI
+ help
+ This adds a driver for the keyboards found on various
+ laptops based on Apple SoCs. These keyboards use an
+ Apple-specific HID-over-SPI protocol.
+
config CROS_EC_KEYB
bool "Enable Chrome OS EC keyboard support"
depends on INPUT
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index e440c921e4..b1133f772f 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_$(SPL_TPL_)DM_KEYBOARD) += input.o keyboard-uclass.o
ifndef CONFIG_SPL_BUILD
+obj-$(CONFIG_APPLE_SPI_KEYB) += apple_spi_kbd.o
obj-$(CONFIG_I8042_KEYB) += i8042.o
obj-$(CONFIG_TEGRA_KEYBOARD) += input.o tegra-kbc.o
obj-$(CONFIG_TWL4030_INPUT) += twl4030.o
diff --git a/drivers/input/apple_spi_kbd.c b/drivers/input/apple_spi_kbd.c
new file mode 100644
index 0000000000..7cf12f453a
--- /dev/null
+++ b/drivers/input/apple_spi_kbd.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Mark Kettenis <kettenis@openbsd.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <keyboard.h>
+#include <spi.h>
+#include <stdio_dev.h>
+#include <asm-generic/gpio.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+
+/*
+ * The Apple SPI keyboard controller implements a protocol that
+ * closely resembles HID Keyboard Boot protocol. The key codes are
+ * mapped according to the HID Keyboard/Keypad Usage Table.
+ */
+
+/* Modifier key bits */
+#define HID_MOD_LEFTCTRL BIT(0)
+#define HID_MOD_LEFTSHIFT BIT(1)
+#define HID_MOD_LEFTALT BIT(2)
+#define HID_MOD_LEFTGUI BIT(3)
+#define HID_MOD_RIGHTCTRL BIT(4)
+#define HID_MOD_RIGHTSHIFT BIT(5)
+#define HID_MOD_RIGHTALT BIT(6)
+#define HID_MOD_RIGHTGUI BIT(7)
+
+static const u8 hid_kbd_keymap[] = {
+ KEY_RESERVED, 0xff, 0xff, 0xff,
+ KEY_A, KEY_B, KEY_C, KEY_D,
+ KEY_E, KEY_F, KEY_G, KEY_H,
+ KEY_I, KEY_J, KEY_K, KEY_L,
+ KEY_M, KEY_N, KEY_O, KEY_P,
+ KEY_Q, KEY_R, KEY_S, KEY_T,
+ KEY_U, KEY_V, KEY_W, KEY_X,
+ KEY_Y, KEY_Z, KEY_1, KEY_2,
+ KEY_3, KEY_4, KEY_5, KEY_6,
+ KEY_7, KEY_8, KEY_9, KEY_0,
+ KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB,
+ KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE,
+ KEY_RIGHTBRACE, KEY_BACKSLASH, 0xff, KEY_SEMICOLON,
+ KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT,
+ KEY_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2,
+ KEY_F3, KEY_F4, KEY_F5, KEY_F6,
+ KEY_F7, KEY_F8, KEY_F9, KEY_F10,
+ KEY_F11, KEY_F12, KEY_SYSRQ, KEY_SCROLLLOCK,
+ KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP,
+ KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT,
+ KEY_LEFT, KEY_DOWN, KEY_UP, KEY_NUMLOCK,
+ KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, KEY_KPPLUS,
+ KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3,
+ KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7,
+ KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT,
+ KEY_BACKSLASH, KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL,
+};
+
+/* Report ID used for keyboard input reports. */
+#define KBD_REPORTID 0x01
+
+struct apple_spi_kbd_report {
+ u8 reportid;
+ u8 modifiers;
+ u8 reserved;
+ u8 keycode[6];
+ u8 fn;
+};
+
+struct apple_spi_kbd_priv {
+ struct gpio_desc enable;
+ struct apple_spi_kbd_report old; /* previous keyboard input report */
+ struct apple_spi_kbd_report new; /* current keyboard input report */
+};
+
+/* Keyboard device. */
+#define KBD_DEVICE 0x01
+
+/* The controller sends us fixed-size packets of 256 bytes. */
+struct apple_spi_kbd_packet {
+ u8 flags;
+#define PACKET_READ 0x20
+ u8 device;
+ u16 offset;
+ u16 remaining;
+ u16 len;
+ u8 data[246];
+ u16 crc;
+};
+
+/* Packets contain a single variable-sized message. */
+struct apple_spi_kbd_msg {
+ u8 type;
+#define MSG_REPORT 0x10
+ u8 device;
+ u8 unknown;
+ u8 msgid;
+ u16 rsplen;
+ u16 cmdlen;
+ u8 data[0];
+};
+
+static void apple_spi_kbd_service_modifiers(struct input_config *input)
+{
+ struct apple_spi_kbd_priv *priv = dev_get_priv(input->dev);
+ u8 new = priv->new.modifiers;
+ u8 old = priv->old.modifiers;
+
+ if ((new ^ old) & HID_MOD_LEFTCTRL)
+ input_add_keycode(input, KEY_LEFTCTRL,
+ old & HID_MOD_LEFTCTRL);
+ if ((new ^ old) & HID_MOD_RIGHTCTRL)
+ input_add_keycode(input, KEY_RIGHTCTRL,
+ old & HID_MOD_RIGHTCTRL);
+ if ((new ^ old) & HID_MOD_LEFTSHIFT)
+ input_add_keycode(input, KEY_LEFTSHIFT,
+ old & HID_MOD_LEFTSHIFT);
+ if ((new ^ old) & HID_MOD_RIGHTSHIFT)
+ input_add_keycode(input, KEY_RIGHTSHIFT,
+ old & HID_MOD_RIGHTSHIFT);
+ if ((new ^ old) & HID_MOD_LEFTALT)
+ input_add_keycode(input, KEY_LEFTALT,
+ old & HID_MOD_LEFTALT);
+ if ((new ^ old) & HID_MOD_RIGHTALT)
+ input_add_keycode(input, KEY_RIGHTALT,
+ old & HID_MOD_RIGHTALT);
+ if ((new ^ old) & HID_MOD_LEFTGUI)
+ input_add_keycode(input, KEY_LEFTMETA,
+ old & HID_MOD_LEFTGUI);
+ if ((new ^ old) & HID_MOD_RIGHTGUI)
+ input_add_keycode(input, KEY_RIGHTMETA,
+ old & HID_MOD_RIGHTGUI);
+}
+
+static void apple_spi_kbd_service_key(struct input_config *input, int i,
+ int released)
+{
+ struct apple_spi_kbd_priv *priv = dev_get_priv(input->dev);
+ u8 *new;
+ u8 *old;
+
+ if (released) {
+ new = priv->new.keycode;
+ old = priv->old.keycode;
+ } else {
+ new = priv->old.keycode;
+ old = priv->new.keycode;
+ }
+
+ if (memscan(new, old[i], sizeof(priv->new.keycode)) ==
+ new + sizeof(priv->new.keycode) &&
+ old[i] < ARRAY_SIZE(hid_kbd_keymap))
+ input_add_keycode(input, hid_kbd_keymap[old[i]], released);
+}
+
+static int apple_spi_kbd_check(struct input_config *input)
+{
+ struct udevice *dev = input->dev;
+ struct apple_spi_kbd_priv *priv = dev_get_priv(dev);
+ struct apple_spi_kbd_packet packet;
+ struct apple_spi_kbd_msg *msg;
+ struct apple_spi_kbd_report *report;
+ int i, ret;
+
+ memset(&packet, 0, sizeof(packet));
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The keyboard controller needs delays after asserting CS#
+ * and before deasserting CS#.
+ */
+ ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_BEGIN);
+ if (ret < 0)
+ goto fail;
+ udelay(100);
+ ret = dm_spi_xfer(dev, sizeof(packet) * 8, NULL, &packet, 0);
+ if (ret < 0)
+ goto fail;
+ udelay(100);
+ ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END);
+ if (ret < 0)
+ goto fail;
+
+ dm_spi_release_bus(dev);
+
+ /*
+ * The keyboard controller needs a delay between subsequent
+ * SPI transfers.
+ */
+ udelay(250);
+
+ msg = (struct apple_spi_kbd_msg *)packet.data;
+ report = (struct apple_spi_kbd_report *)msg->data;
+ if (packet.flags == PACKET_READ && packet.device == KBD_DEVICE &&
+ msg->type == MSG_REPORT && msg->device == KBD_DEVICE &&
+ msg->cmdlen == sizeof(struct apple_spi_kbd_report) &&
+ report->reportid == KBD_REPORTID) {
+ memcpy(&priv->new, report,
+ sizeof(struct apple_spi_kbd_report));
+ apple_spi_kbd_service_modifiers(input);
+ for (i = 0; i < sizeof(priv->new.keycode); i++) {
+ apple_spi_kbd_service_key(input, i, 1);
+ apple_spi_kbd_service_key(input, i, 0);
+ }
+ memcpy(&priv->old, &priv->new,
+ sizeof(struct apple_spi_kbd_report));
+ return 1;
+ }
+
+ return 0;
+
+fail:
+ /*
+ * Make sure CS# is deasserted. If this fails there is nothing
+ * we can do, so ignore any errors.
+ */
+ dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END);
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+static int apple_spi_kbd_probe(struct udevice *dev)
+{
+ struct apple_spi_kbd_priv *priv = dev_get_priv(dev);
+ struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct stdio_dev *sdev = &uc_priv->sdev;
+ struct input_config *input = &uc_priv->input;
+ int ret;
+
+ ret = gpio_request_by_name(dev, "spien-gpios", 0, &priv->enable,
+ GPIOD_IS_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* Reset the keyboard controller. */
+ dm_gpio_set_value(&priv->enable, 1);
+ udelay(5000);
+ dm_gpio_set_value(&priv->enable, 0);
+ udelay(5000);
+
+ /* Enable the keyboard controller. */
+ dm_gpio_set_value(&priv->enable, 1);
+
+ input->dev = dev;
+ input->read_keys = apple_spi_kbd_check;
+ input_add_tables(input, false);
+ strcpy(sdev->name, "spikbd");
+
+ return input_stdio_register(sdev);
+}
+
+static const struct keyboard_ops apple_spi_kbd_ops = {
+};
+
+static const struct udevice_id apple_spi_kbd_of_match[] = {
+ { .compatible = "apple,spi-hid-transport" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(apple_spi_kbd) = {
+ .name = "apple_spi_kbd",
+ .id = UCLASS_KEYBOARD,
+ .of_match = apple_spi_kbd_of_match,
+ .probe = apple_spi_kbd_probe,
+ .priv_auto = sizeof(struct apple_spi_kbd_priv),
+ .ops = &apple_spi_kbd_ops,
+};
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index dd4b0ac0c3..73db2af0b8 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -10,6 +10,17 @@ config DM_MAILBOX
the basis of a variety of inter-process/inter-CPU communication
protocols.
+config APPLE_MBOX
+ bool "Enable Apple IOP controller support"
+ depends on DM_MAILBOX && ARCH_APPLE
+ default y
+ help
+ Enable support for the mailboxes that provide a comminucation
+ channel with Apple IOP controllers integrated on Apple SoCs.
+ These IOP controllers are used to implement various functions
+ such as the System Management Controller (SMC) and NVMe and this
+ driver is required to get that functionality up and running.
+
config SANDBOX_MBOX
bool "Enable the sandbox mailbox test driver"
depends on DM_MAILBOX && SANDBOX
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index d2ace8cd21..59e8d0de93 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -4,6 +4,7 @@
#
obj-$(CONFIG_$(SPL_)DM_MAILBOX) += mailbox-uclass.o
+obj-$(CONFIG_APPLE_MBOX) += apple-mbox.o
obj-$(CONFIG_SANDBOX_MBOX) += sandbox-mbox.o
obj-$(CONFIG_SANDBOX_MBOX) += sandbox-mbox-test.o
obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o
diff --git a/drivers/mailbox/apple-mbox.c b/drivers/mailbox/apple-mbox.c
new file mode 100644
index 0000000000..30c8e2f03f
--- /dev/null
+++ b/drivers/mailbox/apple-mbox.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Mark Kettenis <kettenis@openbsd.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <mailbox-uclass.h>
+#include <asm/io.h>
+#include <linux/apple-mailbox.h>
+#include <linux/delay.h>
+
+#define REG_A2I_STAT 0x110
+#define REG_A2I_STAT_EMPTY BIT(17)
+#define REG_A2I_STAT_FULL BIT(16)
+#define REG_I2A_STAT 0x114
+#define REG_I2A_STAT_EMPTY BIT(17)
+#define REG_I2A_STAT_FULL BIT(16)
+#define REG_A2I_MSG0 0x800
+#define REG_A2I_MSG1 0x808
+#define REG_I2A_MSG0 0x830
+#define REG_I2A_MSG1 0x838
+
+struct apple_mbox_priv {
+ void *base;
+};
+
+static int apple_mbox_of_xlate(struct mbox_chan *chan,
+ struct ofnode_phandle_args *args)
+{
+ if (args->args_count != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int apple_mbox_send(struct mbox_chan *chan, const void *data)
+{
+ struct apple_mbox_priv *priv = dev_get_priv(chan->dev);
+ const struct apple_mbox_msg *msg = data;
+
+ writeq(msg->msg0, priv->base + REG_A2I_MSG0);
+ writeq(msg->msg1, priv->base + REG_A2I_MSG1);
+ while (readl(priv->base + REG_A2I_STAT) & REG_A2I_STAT_FULL)
+ udelay(1);
+
+ return 0;
+}
+
+static int apple_mbox_recv(struct mbox_chan *chan, void *data)
+{
+ struct apple_mbox_priv *priv = dev_get_priv(chan->dev);
+ struct apple_mbox_msg *msg = data;
+
+ if (readl(priv->base + REG_I2A_STAT) & REG_I2A_STAT_EMPTY)
+ return -ENODATA;
+
+ msg->msg0 = readq(priv->base + REG_I2A_MSG0);
+ msg->msg1 = readq(priv->base + REG_I2A_MSG1);
+ return 0;
+}
+
+struct mbox_ops apple_mbox_ops = {
+ .of_xlate = apple_mbox_of_xlate,
+ .send = apple_mbox_send,
+ .recv = apple_mbox_recv,
+};
+
+static int apple_mbox_probe(struct udevice *dev)
+{
+ struct apple_mbox_priv *priv = dev_get_priv(dev);
+
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct udevice_id apple_mbox_of_match[] = {
+ { .compatible = "apple,asc-mailbox-v4" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(apple_mbox) = {
+ .name = "apple-mbox",
+ .id = UCLASS_MAILBOX,
+ .of_match = apple_mbox_of_match,
+ .probe = apple_mbox_probe,
+ .priv_auto = sizeof(struct apple_mbox_priv),
+ .ops = &apple_mbox_ops,
+};
diff --git a/drivers/nvme/Kconfig b/drivers/nvme/Kconfig
index 1f6d1f5648..0cb465160b 100644
--- a/drivers/nvme/Kconfig
+++ b/drivers/nvme/Kconfig
@@ -4,8 +4,27 @@
config NVME
bool "NVM Express device support"
- depends on BLK && PCI
+ depends on BLK
select HAVE_BLOCK_DEVICE
help
This option enables support for NVM Express devices.
It supports basic functions of NVMe (read/write).
+
+config NVME_APPLE
+ bool "Apple NVMe controller support"
+ select NVME
+ help
+ This option enables support for the NVMe storage
+ controller integrated on Apple SoCs. This controller
+ isn't PCI-based based and deviates from the NVMe
+ standard implementation in its implementation of
+ the command submission queue and the integration
+ of an NVMMU that needs to be managed.
+
+config NVME_PCI
+ bool "NVM Express PCI device support"
+ depends on PCI
+ select NVME
+ help
+ This option enables support for NVM Express PCI
+ devices.
diff --git a/drivers/nvme/Makefile b/drivers/nvme/Makefile
index 64f102b208..fa7b619446 100644
--- a/drivers/nvme/Makefile
+++ b/drivers/nvme/Makefile
@@ -3,3 +3,5 @@
# Copyright (C) 2017, Bin Meng <bmeng.cn@gmail.com>
obj-y += nvme-uclass.o nvme.o nvme_show.o
+obj-$(CONFIG_NVME_APPLE) += nvme_apple.o
+obj-$(CONFIG_NVME_PCI) += nvme_pci.o
diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c
index 3c529a2fce..1d56517e99 100644
--- a/drivers/nvme/nvme.c
+++ b/drivers/nvme/nvme.c
@@ -12,7 +12,6 @@
#include <log.h>
#include <malloc.h>
#include <memalign.h>
-#include <pci.h>
#include <time.h>
#include <dm/device-internal.h>
#include <linux/compat.h>
@@ -28,33 +27,6 @@
#define IO_TIMEOUT 30
#define MAX_PRP_POOL 512
-enum nvme_queue_id {
- NVME_ADMIN_Q,
- NVME_IO_Q,
- NVME_Q_NUM,
-};
-
-/*
- * An NVM Express queue. Each device has at least two (one for admin
- * commands and one for I/O commands).
- */
-struct nvme_queue {
- struct nvme_dev *dev;
- struct nvme_command *sq_cmds;
- struct nvme_completion *cqes;
- wait_queue_head_t sq_full;
- u32 __iomem *q_db;
- u16 q_depth;
- s16 cq_vector;
- u16 sq_head;
- u16 sq_tail;
- u16 cq_head;
- u16 qid;
- u8 cq_phase;
- u8 cqe_seen;
- unsigned long cmdid_data[];
-};
-
static int nvme_wait_ready(struct nvme_dev *dev, bool enabled)
{
u32 bit = enabled ? NVME_CSTS_RDY : 0;
@@ -168,12 +140,19 @@ static u16 nvme_read_completion_status(struct nvme_queue *nvmeq, u16 index)
*/
static void nvme_submit_cmd(struct nvme_queue *nvmeq, struct nvme_command *cmd)
{
+ struct nvme_ops *ops;
u16 tail = nvmeq->sq_tail;
memcpy(&nvmeq->sq_cmds[tail], cmd, sizeof(*cmd));
flush_dcache_range((ulong)&nvmeq->sq_cmds[tail],
(ulong)&nvmeq->sq_cmds[tail] + sizeof(*cmd));
+ ops = (struct nvme_ops *)nvmeq->dev->udev->driver->ops;
+ if (ops && ops->submit_cmd) {
+ ops->submit_cmd(nvmeq, cmd);
+ return;
+ }
+
if (++tail == nvmeq->q_depth)
tail = 0;
writel(tail, nvmeq->q_db);
@@ -184,6 +163,7 @@ static int nvme_submit_sync_cmd(struct nvme_queue *nvmeq,
struct nvme_command *cmd,
u32 *result, unsigned timeout)
{
+ struct nvme_ops *ops;
u16 head = nvmeq->cq_head;
u16 phase = nvmeq->cq_phase;
u16 status;
@@ -204,6 +184,10 @@ static int nvme_submit_sync_cmd(struct nvme_queue *nvmeq,
return -ETIMEDOUT;
}
+ ops = (struct nvme_ops *)nvmeq->dev->udev->driver->ops;
+ if (ops && ops->complete_cmd)
+ ops->complete_cmd(nvmeq, cmd);
+
status >>= 1;
if (status) {
printf("ERROR: status = %x, phase = %d, head = %d\n",
@@ -244,6 +228,7 @@ static int nvme_submit_admin_cmd(struct nvme_dev *dev, struct nvme_command *cmd,
static struct nvme_queue *nvme_alloc_queue(struct nvme_dev *dev,
int qid, int depth)
{
+ struct nvme_ops *ops;
struct nvme_queue *nvmeq = malloc(sizeof(*nvmeq));
if (!nvmeq)
return NULL;
@@ -269,6 +254,10 @@ static struct nvme_queue *nvme_alloc_queue(struct nvme_dev *dev,
dev->queue_count++;
dev->queues[qid] = nvmeq;
+ ops = (struct nvme_ops *)dev->udev->driver->ops;
+ if (ops && ops->setup_queue)
+ ops->setup_queue(nvmeq);
+
return nvmeq;
free_queue:
@@ -698,7 +687,6 @@ static int nvme_blk_probe(struct udevice *udev)
struct blk_desc *desc = dev_get_uclass_plat(udev);
struct nvme_ns *ns = dev_get_priv(udev);
u8 flbas;
- struct pci_child_plat *pplat;
struct nvme_id_ns *id;
id = memalign(ndev->page_size, sizeof(struct nvme_id_ns));
@@ -723,8 +711,7 @@ static int nvme_blk_probe(struct udevice *udev)
desc->log2blksz = ns->lba_shift;
desc->blksz = 1 << ns->lba_shift;
desc->bdev = udev;
- pplat = dev_get_parent_plat(udev->parent);
- sprintf(desc->vendor, "0x%.4x", pplat->vendor);
+ memcpy(desc->vendor, ndev->vendor, sizeof(ndev->vendor));
memcpy(desc->product, ndev->serial, sizeof(ndev->serial));
memcpy(desc->revision, ndev->firmware_rev, sizeof(ndev->firmware_rev));
@@ -818,27 +805,14 @@ U_BOOT_DRIVER(nvme_blk) = {
.priv_auto = sizeof(struct nvme_ns),
};
-static int nvme_bind(struct udevice *udev)
-{
- static int ndev_num;
- char name[20];
-
- sprintf(name, "nvme#%d", ndev_num++);
-
- return device_set_name(udev, name);
-}
-
-static int nvme_probe(struct udevice *udev)
+int nvme_init(struct udevice *udev)
{
- int ret;
struct nvme_dev *ndev = dev_get_priv(udev);
struct nvme_id_ns *id;
+ int ret;
- ndev->instance = trailing_strtol(udev->name);
-
+ ndev->udev = udev;
INIT_LIST_HEAD(&ndev->namespaces);
- ndev->bar = dm_pci_map_bar(udev, PCI_BASE_ADDRESS_0,
- PCI_REGION_MEM);
if (readl(&ndev->bar->csts) == -1) {
ret = -ENODEV;
printf("Error: %s: Out of memory!\n", udev->name);
@@ -923,17 +897,9 @@ free_nvme:
return ret;
}
-U_BOOT_DRIVER(nvme) = {
- .name = "nvme",
- .id = UCLASS_NVME,
- .bind = nvme_bind,
- .probe = nvme_probe,
- .priv_auto = sizeof(struct nvme_dev),
-};
-
-struct pci_device_id nvme_supported[] = {
- { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, ~0) },
- {}
-};
+int nvme_shutdown(struct udevice *udev)
+{
+ struct nvme_dev *ndev = dev_get_priv(udev);
-U_BOOT_PCI_DEVICE(nvme, nvme_supported);
+ return nvme_disable_ctrl(ndev);
+}
diff --git a/drivers/nvme/nvme.h b/drivers/nvme/nvme.h
index c6aae4da5d..bc1d612dde 100644
--- a/drivers/nvme/nvme.h
+++ b/drivers/nvme/nvme.h
@@ -596,6 +596,7 @@ enum {
/* Represents an NVM Express device. Each nvme_dev is a PCI function. */
struct nvme_dev {
+ struct udevice *udev;
struct list_head node;
struct nvme_queue **queues;
u32 __iomem *dbs;
@@ -608,6 +609,7 @@ struct nvme_dev {
u32 ctrl_config;
struct nvme_bar __iomem *bar;
struct list_head namespaces;
+ char vendor[8];
char serial[20];
char model[40];
char firmware_rev[8];
@@ -621,6 +623,33 @@ struct nvme_dev {
u32 nn;
};
+/* Admin queue and a single I/O queue. */
+enum nvme_queue_id {
+ NVME_ADMIN_Q,
+ NVME_IO_Q,
+ NVME_Q_NUM,
+};
+
+/*
+ * An NVM Express queue. Each device has at least two (one for admin
+ * commands and one for I/O commands).
+ */
+struct nvme_queue {
+ struct nvme_dev *dev;
+ struct nvme_command *sq_cmds;
+ struct nvme_completion *cqes;
+ u32 __iomem *q_db;
+ u16 q_depth;
+ s16 cq_vector;
+ u16 sq_head;
+ u16 sq_tail;
+ u16 cq_head;
+ u16 qid;
+ u8 cq_phase;
+ u8 cqe_seen;
+ unsigned long cmdid_data[];
+};
+
/*
* An NVM Express namespace is equivalent to a SCSI LUN.
* Each namespace is operated as an independent "device".
@@ -635,4 +664,45 @@ struct nvme_ns {
u8 flbas;
};
+struct nvme_ops {
+ /**
+ * setup_queue - Controller-specific NVM Express queue setup.
+ *
+ * @nvmeq: NVM Express queue
+ * Return: 0 if OK, -ve on error
+ */
+ int (*setup_queue)(struct nvme_queue *nvmeq);
+ /**
+ * submit_cmd - Controller-specific NVM Express command submission.
+ *
+ * If this function pointer is set to NULL, normal command
+ * submission is performed according to the NVM Express spec.
+ *
+ * @nvmeq: NVM Express queue
+ * @cmd: NVM Express command
+ */
+ void (*submit_cmd)(struct nvme_queue *nvmeq, struct nvme_command *cmd);
+ /**
+ * complete_cmd - Controller-specific NVM Express command completion
+ *
+ * @nvmeq: NVM Express queue
+ * @cmd: NVM Express command
+ */
+ void (*complete_cmd)(struct nvme_queue *nvmeq, struct nvme_command *cmd);
+};
+
+/**
+ * nvme_init() - Initialize NVM Express device
+ * @udev: The NVM Express device
+ * Return: 0 if OK, -ve on error
+ */
+int nvme_init(struct udevice *udev);
+
+/**
+ * nvme_shutdown() - Shutdown NVM Express device
+ * @udev: The NVM Express device
+ * Return: 0 if OK, -ve on error
+ */
+int nvme_shutdown(struct udevice *udev);
+
#endif /* __DRIVER_NVME_H__ */
diff --git a/drivers/nvme/nvme_apple.c b/drivers/nvme/nvme_apple.c
new file mode 100644
index 0000000000..d9d491c2be
--- /dev/null
+++ b/drivers/nvme/nvme_apple.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2021 Mark Kettenis <kettenis@openbsd.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <mailbox.h>
+#include <mapmem.h>
+#include "nvme.h"
+#include <reset.h>
+
+#include <asm/io.h>
+#include <asm/arch/rtkit.h>
+#include <linux/iopoll.h>
+
+/* ASC registers */
+#define REG_CPU_CTRL 0x0044
+#define REG_CPU_CTRL_RUN BIT(4)
+
+/* Apple NVMe registers */
+#define ANS_MAX_PEND_CMDS_CTRL 0x01210
+#define ANS_MAX_QUEUE_DEPTH 64
+#define ANS_BOOT_STATUS 0x01300
+#define ANS_BOOT_STATUS_OK 0xde71ce55
+#define ANS_MODESEL 0x01304
+#define ANS_UNKNOWN_CTRL 0x24008
+#define ANS_PRP_NULL_CHECK (1 << 11)
+#define ANS_LINEAR_SQ_CTRL 0x24908
+#define ANS_LINEAR_SQ_CTRL_EN (1 << 0)
+#define ANS_ASQ_DB 0x2490c
+#define ANS_IOSQ_DB 0x24910
+#define ANS_NVMMU_NUM 0x28100
+#define ANS_NVMMU_BASE_ASQ 0x28108
+#define ANS_NVMMU_BASE_IOSQ 0x28110
+#define ANS_NVMMU_TCB_INVAL 0x28118
+#define ANS_NVMMU_TCB_STAT 0x28120
+
+#define ANS_NVMMU_TCB_SIZE 0x4000
+#define ANS_NVMMU_TCB_PITCH 0x80
+
+/*
+ * The Apple NVMe controller includes an IOMMU known as NVMMU. The
+ * NVMMU is programmed through an array of TCBs. These TCBs are paired
+ * with the corresponding slot in the submission queues and need to be
+ * configured with the command details before a command is allowed to
+ * execute. This is necessary even for commands that don't do DMA.
+ */
+struct ans_nvmmu_tcb {
+ u8 opcode;
+ u8 flags;
+ u8 slot;
+ u8 pad0;
+ u32 prpl_len;
+ u8 pad1[16];
+ u64 prp1;
+ u64 prp2;
+};
+
+#define ANS_NVMMU_TCB_WRITE BIT(0)
+#define ANS_NVMMU_TCB_READ BIT(1)
+
+struct apple_nvme_priv {
+ struct nvme_dev ndev;
+ void *base; /* NVMe registers */
+ void *asc; /* ASC registers */
+ struct reset_ctl_bulk resets; /* ASC reset */
+ struct mbox_chan chan;
+ struct ans_nvmmu_tcb *tcbs[NVME_Q_NUM]; /* Submission queue TCBs */
+ u32 __iomem *q_db[NVME_Q_NUM]; /* Submission queue doorbell */
+};
+
+static int apple_nvme_setup_queue(struct nvme_queue *nvmeq)
+{
+ struct apple_nvme_priv *priv =
+ container_of(nvmeq->dev, struct apple_nvme_priv, ndev);
+ struct nvme_dev *dev = nvmeq->dev;
+
+ switch (nvmeq->qid) {
+ case NVME_ADMIN_Q:
+ case NVME_IO_Q:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ priv->tcbs[nvmeq->qid] = (void *)memalign(4096, ANS_NVMMU_TCB_SIZE);
+ memset((void *)priv->tcbs[nvmeq->qid], 0, ANS_NVMMU_TCB_SIZE);
+
+ switch (nvmeq->qid) {
+ case NVME_ADMIN_Q:
+ priv->q_db[nvmeq->qid] =
+ ((void __iomem *)dev->bar) + ANS_ASQ_DB;
+ nvme_writeq((ulong)priv->tcbs[nvmeq->qid],
+ ((void __iomem *)dev->bar) + ANS_NVMMU_BASE_ASQ);
+ break;
+ case NVME_IO_Q:
+ priv->q_db[nvmeq->qid] =
+ ((void __iomem *)dev->bar) + ANS_IOSQ_DB;
+ nvme_writeq((ulong)priv->tcbs[nvmeq->qid],
+ ((void __iomem *)dev->bar) + ANS_NVMMU_BASE_IOSQ);
+ break;
+ }
+
+ return 0;
+}
+
+static void apple_nvme_submit_cmd(struct nvme_queue *nvmeq,
+ struct nvme_command *cmd)
+{
+ struct apple_nvme_priv *priv =
+ container_of(nvmeq->dev, struct apple_nvme_priv, ndev);
+ struct ans_nvmmu_tcb *tcb;
+ u16 tail = nvmeq->sq_tail;
+
+ tcb = ((void *)priv->tcbs[nvmeq->qid]) + tail * ANS_NVMMU_TCB_PITCH;
+ memset(tcb, 0, sizeof(*tcb));
+ tcb->opcode = cmd->common.opcode;
+ tcb->flags = ANS_NVMMU_TCB_WRITE | ANS_NVMMU_TCB_READ;
+ tcb->slot = tail;
+ tcb->prpl_len = cmd->rw.length;
+ tcb->prp1 = cmd->common.prp1;
+ tcb->prp2 = cmd->common.prp2;
+
+ writel(tail, priv->q_db[nvmeq->qid]);
+}
+
+static void apple_nvme_complete_cmd(struct nvme_queue *nvmeq,
+ struct nvme_command *cmd)
+{
+ struct apple_nvme_priv *priv =
+ container_of(nvmeq->dev, struct apple_nvme_priv, ndev);
+ struct ans_nvmmu_tcb *tcb;
+ u16 tail = nvmeq->sq_tail;
+
+ tcb = ((void *)priv->tcbs[nvmeq->qid]) + tail * ANS_NVMMU_TCB_PITCH;
+ memset(tcb, 0, sizeof(*tcb));
+ writel(tail, ((void __iomem *)nvmeq->dev->bar) + ANS_NVMMU_TCB_INVAL);
+ readl(((void __iomem *)nvmeq->dev->bar) + ANS_NVMMU_TCB_STAT);
+
+ if (++tail == nvmeq->q_depth)
+ tail = 0;
+ nvmeq->sq_tail = tail;
+}
+
+static int apple_nvme_probe(struct udevice *dev)
+{
+ struct apple_nvme_priv *priv = dev_get_priv(dev);
+ fdt_addr_t addr;
+ u32 ctrl, stat;
+ int ret;
+
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+
+ addr = dev_read_addr_index(dev, 1);
+ if (addr == FDT_ADDR_T_NONE)
+ return -EINVAL;
+ priv->asc = map_sysmem(addr, 0);
+
+ ret = reset_get_bulk(dev, &priv->resets);
+ if (ret < 0)
+ return ret;
+
+ ret = mbox_get_by_index(dev, 0, &priv->chan);
+ if (ret < 0)
+ return ret;
+
+ ctrl = readl(priv->asc + REG_CPU_CTRL);
+ writel(ctrl | REG_CPU_CTRL_RUN, priv->asc + REG_CPU_CTRL);
+
+ ret = apple_rtkit_init(&priv->chan);
+ if (ret < 0)
+ return ret;
+
+ ret = readl_poll_sleep_timeout(priv->base + ANS_BOOT_STATUS, stat,
+ (stat == ANS_BOOT_STATUS_OK), 100,
+ 500000);
+ if (ret < 0) {
+ printf("%s: NVMe firmware didn't boot\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ writel(ANS_LINEAR_SQ_CTRL_EN, priv->base + ANS_LINEAR_SQ_CTRL);
+ writel(((ANS_MAX_QUEUE_DEPTH << 16) | ANS_MAX_QUEUE_DEPTH),
+ priv->base + ANS_MAX_PEND_CMDS_CTRL);
+
+ writel(readl(priv->base + ANS_UNKNOWN_CTRL) & ~ANS_PRP_NULL_CHECK,
+ priv->base + ANS_UNKNOWN_CTRL);
+
+ strcpy(priv->ndev.vendor, "Apple");
+
+ writel((ANS_NVMMU_TCB_SIZE / ANS_NVMMU_TCB_PITCH) - 1,
+ priv->base + ANS_NVMMU_NUM);
+ writel(0, priv->base + ANS_MODESEL);
+
+ priv->ndev.bar = priv->base;
+ return nvme_init(dev);
+}
+
+static int apple_nvme_remove(struct udevice *dev)
+{
+ struct apple_nvme_priv *priv = dev_get_priv(dev);
+ u32 ctrl;
+
+ nvme_shutdown(dev);
+
+ apple_rtkit_shutdown(&priv->chan, APPLE_RTKIT_PWR_STATE_SLEEP);
+
+ ctrl = readl(priv->asc + REG_CPU_CTRL);
+ writel(ctrl & ~REG_CPU_CTRL_RUN, priv->asc + REG_CPU_CTRL);
+
+ reset_assert_bulk(&priv->resets);
+ reset_deassert_bulk(&priv->resets);
+
+ return 0;
+}
+
+static const struct nvme_ops apple_nvme_ops = {
+ .setup_queue = apple_nvme_setup_queue,
+ .submit_cmd = apple_nvme_submit_cmd,
+ .complete_cmd = apple_nvme_complete_cmd,
+};
+
+static const struct udevice_id apple_nvme_ids[] = {
+ { .compatible = "apple,nvme-ans2" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(apple_nvme) = {
+ .name = "apple_nvme",
+ .id = UCLASS_NVME,
+ .of_match = apple_nvme_ids,
+ .priv_auto = sizeof(struct apple_nvme_priv),
+ .probe = apple_nvme_probe,
+ .remove = apple_nvme_remove,
+ .ops = &apple_nvme_ops,
+ .flags = DM_FLAG_OS_PREPARE,
+};
diff --git a/drivers/nvme/nvme_pci.c b/drivers/nvme/nvme_pci.c
new file mode 100644
index 0000000000..5f60fb884f
--- /dev/null
+++ b/drivers/nvme/nvme_pci.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 NXP Semiconductors
+ * Copyright (C) 2017 Bin Meng <bmeng.cn@gmail.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <pci.h>
+#include "nvme.h"
+
+static int nvme_bind(struct udevice *udev)
+{
+ static int ndev_num;
+ char name[20];
+
+ sprintf(name, "nvme#%d", ndev_num++);
+
+ return device_set_name(udev, name);
+}
+
+static int nvme_probe(struct udevice *udev)
+{
+ struct nvme_dev *ndev = dev_get_priv(udev);
+ struct pci_child_plat *pplat;
+
+ pplat = dev_get_parent_plat(udev);
+ sprintf(ndev->vendor, "0x%.4x", pplat->vendor);
+
+ ndev->instance = trailing_strtol(udev->name);
+ ndev->bar = dm_pci_map_bar(udev, PCI_BASE_ADDRESS_0,
+ PCI_REGION_MEM);
+ return nvme_init(udev);
+}
+
+U_BOOT_DRIVER(nvme) = {
+ .name = "nvme",
+ .id = UCLASS_NVME,
+ .bind = nvme_bind,
+ .probe = nvme_probe,
+ .priv_auto = sizeof(struct nvme_dev),
+};
+
+struct pci_device_id nvme_supported[] = {
+ { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, ~0) },
+ {}
+};
+
+U_BOOT_PCI_DEVICE(nvme, nvme_supported);
diff --git a/drivers/power/domain/apple-pmgr.c b/drivers/power/domain/apple-pmgr.c
index d25f136b9d..4d06e76ff5 100644
--- a/drivers/power/domain/apple-pmgr.c
+++ b/drivers/power/domain/apple-pmgr.c
@@ -6,14 +6,22 @@
#include <common.h>
#include <asm/io.h>
#include <dm.h>
+#include <dm/device-internal.h>
#include <linux/err.h>
#include <linux/bitfield.h>
#include <power-domain-uclass.h>
+#include <reset-uclass.h>
#include <regmap.h>
#include <syscon.h>
-#define APPLE_PMGR_PS_TARGET GENMASK(3, 0)
+#define APPLE_PMGR_RESET BIT(31)
+#define APPLE_PMGR_DEV_DISABLE BIT(10)
+#define APPLE_PMGR_WAS_CLKGATED BIT(9)
+#define APPLE_PMGR_WAS_PWRGATED BIT(8)
#define APPLE_PMGR_PS_ACTUAL GENMASK(7, 4)
+#define APPLE_PMGR_PS_TARGET GENMASK(3, 0)
+
+#define APPLE_PMGR_FLAGS (APPLE_PMGR_WAS_CLKGATED | APPLE_PMGR_WAS_PWRGATED)
#define APPLE_PMGR_PS_ACTIVE 0xf
#define APPLE_PMGR_PS_PWRGATE 0x0
@@ -25,6 +33,65 @@ struct apple_pmgr_priv {
u32 offset; /* offset within regmap for this domain */
};
+static int apple_reset_of_xlate(struct reset_ctl *reset_ctl,
+ struct ofnode_phandle_args *args)
+{
+ if (args->args_count != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int apple_reset_request(struct reset_ctl *reset_ctl)
+{
+ return 0;
+}
+
+static int apple_reset_free(struct reset_ctl *reset_ctl)
+{
+ return 0;
+}
+
+static int apple_reset_assert(struct reset_ctl *reset_ctl)
+{
+ struct apple_pmgr_priv *priv = dev_get_priv(reset_ctl->dev->parent);
+
+ regmap_update_bits(priv->regmap, priv->offset,
+ APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE,
+ APPLE_PMGR_DEV_DISABLE);
+ regmap_update_bits(priv->regmap, priv->offset,
+ APPLE_PMGR_FLAGS | APPLE_PMGR_RESET,
+ APPLE_PMGR_RESET);
+
+ return 0;
+}
+
+static int apple_reset_deassert(struct reset_ctl *reset_ctl)
+{
+ struct apple_pmgr_priv *priv = dev_get_priv(reset_ctl->dev->parent);
+
+ regmap_update_bits(priv->regmap, priv->offset,
+ APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, 0);
+ regmap_update_bits(priv->regmap, priv->offset,
+ APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, 0);
+
+ return 0;
+}
+
+struct reset_ops apple_reset_ops = {
+ .of_xlate = apple_reset_of_xlate,
+ .request = apple_reset_request,
+ .rfree = apple_reset_free,
+ .rst_assert = apple_reset_assert,
+ .rst_deassert = apple_reset_deassert,
+};
+
+static struct driver apple_reset_driver = {
+ .name = "apple_reset",
+ .id = UCLASS_RESET,
+ .ops = &apple_reset_ops,
+};
+
static int apple_pmgr_request(struct power_domain *power_domain)
{
return 0;
@@ -78,6 +145,7 @@ static const struct udevice_id apple_pmgr_ids[] = {
static int apple_pmgr_probe(struct udevice *dev)
{
struct apple_pmgr_priv *priv = dev_get_priv(dev);
+ struct udevice *child;
int ret;
ret = dev_power_domain_on(dev);
@@ -92,6 +160,9 @@ static int apple_pmgr_probe(struct udevice *dev)
if (ret < 0)
return ret;
+ device_bind(dev, &apple_reset_driver, "apple_reset", NULL,
+ dev_ofnode(dev), &child);
+
return 0;
}
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..f35f5af1f6
--- /dev/null
+++ b/drivers/spi/apple_spi.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Mark Kettenis <kettenis@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; /* Input clock frequency */
+};
+
+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);
+}
+
+/* Fill Tx FIFO. */
+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;
+}
+
+/* Empty Rx FIFO. */
+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,
+};