diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/input/Kconfig | 8 | ||||
-rw-r--r-- | drivers/input/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/apple_spi_kbd.c | 271 | ||||
-rw-r--r-- | drivers/mailbox/Kconfig | 11 | ||||
-rw-r--r-- | drivers/mailbox/Makefile | 1 | ||||
-rw-r--r-- | drivers/mailbox/apple-mbox.c | 92 | ||||
-rw-r--r-- | drivers/nvme/Kconfig | 21 | ||||
-rw-r--r-- | drivers/nvme/Makefile | 2 | ||||
-rw-r--r-- | drivers/nvme/nvme.c | 86 | ||||
-rw-r--r-- | drivers/nvme/nvme.h | 70 | ||||
-rw-r--r-- | drivers/nvme/nvme_apple.c | 240 | ||||
-rw-r--r-- | drivers/nvme/nvme_pci.c | 49 | ||||
-rw-r--r-- | drivers/power/domain/apple-pmgr.c | 73 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 7 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/apple_spi.c | 285 |
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, +}; |