From 045474be5ebca9d4491572c134a8288b19f5e14a Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 22 Jan 2022 20:38:11 +0100 Subject: nvme: Split out PCI support Apple SoCs have an integrated NVMe controller that isn't connected over a PCIe bus. In preparation for adding support for this NVMe controller, split out the PCI support into its own file. This file is selected through a new CONFIG_NVME_PCI Kconfig option, so do a wholesale replacement of CONFIG_NVME with CONFIG_NVME_PCI. Signed-off-by: Mark Kettenis Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/nvme/Kconfig | 10 +++++++++- drivers/nvme/Makefile | 1 + drivers/nvme/nvme.c | 38 +++----------------------------------- drivers/nvme/nvme.h | 3 +++ drivers/nvme/nvme_pci.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 drivers/nvme/nvme_pci.c (limited to 'drivers') diff --git a/drivers/nvme/Kconfig b/drivers/nvme/Kconfig index 1f6d1f56484..78da444c8b4 100644 --- a/drivers/nvme/Kconfig +++ b/drivers/nvme/Kconfig @@ -4,8 +4,16 @@ 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_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 64f102b208c..fad9724e17b 100644 --- a/drivers/nvme/Makefile +++ b/drivers/nvme/Makefile @@ -3,3 +3,4 @@ # Copyright (C) 2017, Bin Meng obj-y += nvme-uclass.o nvme.o nvme_show.o +obj-$(CONFIG_NVME_PCI) += nvme_pci.o diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c index 3c529a2fce2..be518ec20b1 100644 --- a/drivers/nvme/nvme.c +++ b/drivers/nvme/nvme.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -698,7 +697,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 +721,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 +815,13 @@ U_BOOT_DRIVER(nvme_blk) = { .priv_auto = sizeof(struct nvme_ns), }; -static int nvme_bind(struct udevice *udev) +int nvme_init(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 ret; struct nvme_dev *ndev = dev_get_priv(udev); struct nvme_id_ns *id; - - ndev->instance = trailing_strtol(udev->name); + int ret; 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); @@ -922,18 +905,3 @@ free_queue: 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) }, - {} -}; - -U_BOOT_PCI_DEVICE(nvme, nvme_supported); diff --git a/drivers/nvme/nvme.h b/drivers/nvme/nvme.h index c6aae4da5df..8e9ae3c7f62 100644 --- a/drivers/nvme/nvme.h +++ b/drivers/nvme/nvme.h @@ -608,6 +608,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]; @@ -635,4 +636,6 @@ struct nvme_ns { u8 flbas; }; +int nvme_init(struct udevice *udev); + #endif /* __DRIVER_NVME_H__ */ diff --git a/drivers/nvme/nvme_pci.c b/drivers/nvme/nvme_pci.c new file mode 100644 index 00000000000..5f60fb884fb --- /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 + */ + +#include +#include +#include +#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); -- cgit v1.2.3 From 456305ec597908536e8e0de32dade43f9db00e8c Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 22 Jan 2022 20:38:12 +0100 Subject: mailbox: apple: Add driver for Apple IOP mailbox This mailbox driver provides a communication channel with the Apple IOP controllers found on Apple SoCs. These IOP controllers are used to implement various functions such as the System Manegement Controller (SMC) and NVMe storage. It allows sending and receiving a 96-bit message over a single channel. The header file with the struct used for mailbox messages is taken straight from Linux. Signed-off-by: Mark Kettenis Signed-off-by: Sven Peter Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/mailbox/Kconfig | 11 ++++++ drivers/mailbox/Makefile | 1 + drivers/mailbox/apple-mbox.c | 92 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 drivers/mailbox/apple-mbox.c (limited to 'drivers') diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index dd4b0ac0c33..73db2af0b81 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 d2ace8cd212..59e8d0de93c 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 00000000000..30c8e2f03fa --- /dev/null +++ b/drivers/mailbox/apple-mbox.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Mark Kettenis + */ + +#include +#include +#include +#include +#include +#include + +#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, +}; -- cgit v1.2.3 From 19d9dad39c28a6e932ee2ec8361f42211613761d Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 22 Jan 2022 20:38:15 +0100 Subject: nvme: Introduce driver ops The NVMe storage controller integrated on Apple SoCs deviates from the NVMe standard in two aspects. It uses a "linear" submission queue and it integrates an NVMMU that needs to be programmed for each NVMe command. Introduce driver ops such that we can set up the linear submission queue and program the NVMMU in the driver for this strange beast. Signed-off-by: Mark Kettenis Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/nvme/nvme.c | 45 ++++++++++++++++++------------------------- drivers/nvme/nvme.h | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c index be518ec20b1..e7cbf39c96a 100644 --- a/drivers/nvme/nvme.c +++ b/drivers/nvme/nvme.c @@ -27,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; @@ -167,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); @@ -183,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; @@ -203,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", @@ -243,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; @@ -268,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: @@ -821,6 +811,7 @@ int nvme_init(struct udevice *udev) struct nvme_id_ns *id; int ret; + ndev->udev = udev; INIT_LIST_HEAD(&ndev->namespaces); if (readl(&ndev->bar->csts) == -1) { ret = -ENODEV; diff --git a/drivers/nvme/nvme.h b/drivers/nvme/nvme.h index 8e9ae3c7f62..bc6b79f8dd1 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; @@ -622,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". @@ -636,6 +664,33 @@ 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); +}; + int nvme_init(struct udevice *udev); #endif /* __DRIVER_NVME_H__ */ -- cgit v1.2.3 From ca99a17e02ab4f99b1455be349858d5a7aa7553c Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 22 Jan 2022 20:38:16 +0100 Subject: nvme: Add shutdown function Add a function to disable the NVMe controller. This will be used to let the driver for the NVMe storage integrated on Apple SoCs shutdown the NVMe controller such we can shutdown the NVMe IOP controller in a clean way afterwards before handing control to the OS. Signed-off-by: Mark Kettenis Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/nvme/nvme.c | 7 +++++++ drivers/nvme/nvme.h | 12 ++++++++++++ 2 files changed, 19 insertions(+) (limited to 'drivers') diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c index e7cbf39c96a..1d56517e996 100644 --- a/drivers/nvme/nvme.c +++ b/drivers/nvme/nvme.c @@ -896,3 +896,10 @@ free_queue: free_nvme: return ret; } + +int nvme_shutdown(struct udevice *udev) +{ + struct nvme_dev *ndev = dev_get_priv(udev); + + return nvme_disable_ctrl(ndev); +} diff --git a/drivers/nvme/nvme.h b/drivers/nvme/nvme.h index bc6b79f8dd1..bc1d612dde4 100644 --- a/drivers/nvme/nvme.h +++ b/drivers/nvme/nvme.h @@ -691,6 +691,18 @@ struct nvme_ops { 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__ */ -- cgit v1.2.3 From 81fafbbeba3211ed60ac8aff41a2e1fcb9a40431 Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 22 Jan 2022 20:38:17 +0100 Subject: power: domain: apple: Add reset support The power management controller found on Apple SoCs als provides a way to reset all devices within a power domain. This is needed to cleanly shutdown the NVMe controller before we hand over control to the OS. Signed-off-by: Mark Kettenis Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass Reviewed-by: Jaehoon Chung --- drivers/power/domain/apple-pmgr.c | 73 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/domain/apple-pmgr.c b/drivers/power/domain/apple-pmgr.c index d25f136b9dc..4d06e76ff5e 100644 --- a/drivers/power/domain/apple-pmgr.c +++ b/drivers/power/domain/apple-pmgr.c @@ -6,14 +6,22 @@ #include #include #include +#include #include #include #include +#include #include #include -#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; } -- cgit v1.2.3 From 50333c94f2de161cdfda2ef1c9845c3a28e7a5d6 Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sat, 22 Jan 2022 20:38:18 +0100 Subject: nvme: apple: Add driver for Apple NVMe storage controller Add a driver for the NVMe storage controller integrated on Apple SoCs. This NVMe controller isn't PCI based and deviates from the NVMe standard in its implementation of the command submission queue and the integration of an NVMMU that needs to be managed. This commit tweaks the core NVMe code to support the linear command submission queue implemented by this controller. But setting up the submission queue and managing the NVMMU controller is handled by implementing the driver ops that were added in an earlier commit. Signed-off-by: Mark Kettenis Tested-on: firefly-rk3399 Tested-by: Mark Kettenis Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/nvme/Kconfig | 11 +++ drivers/nvme/Makefile | 1 + drivers/nvme/nvme_apple.c | 240 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 drivers/nvme/nvme_apple.c (limited to 'drivers') diff --git a/drivers/nvme/Kconfig b/drivers/nvme/Kconfig index 78da444c8b4..0cb465160bb 100644 --- a/drivers/nvme/Kconfig +++ b/drivers/nvme/Kconfig @@ -10,6 +10,17 @@ config NVME 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 diff --git a/drivers/nvme/Makefile b/drivers/nvme/Makefile index fad9724e17b..fa7b6194460 100644 --- a/drivers/nvme/Makefile +++ b/drivers/nvme/Makefile @@ -3,4 +3,5 @@ # Copyright (C) 2017, Bin Meng 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_apple.c b/drivers/nvme/nvme_apple.c new file mode 100644 index 00000000000..d9d491c2be1 --- /dev/null +++ b/drivers/nvme/nvme_apple.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2021 Mark Kettenis + */ + +#include +#include +#include +#include +#include "nvme.h" +#include + +#include +#include +#include + +/* 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, +}; -- cgit v1.2.3 From 7184e2997e7d97ba3e697a0acc1800ddd89d3f6d Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sun, 23 Jan 2022 16:48:12 +0100 Subject: spi: apple: Add driver for Apple SPI controller 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 Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/spi/Kconfig | 7 ++ drivers/spi/Makefile | 1 + drivers/spi/apple_spi.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 drivers/spi/apple_spi.c (limited to 'drivers') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index d07e9a28af8..0a6a85f9c48 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 d2f24bccefd..bea746f3e3e 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 00000000000..f35f5af1f6f --- /dev/null +++ b/drivers/spi/apple_spi.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Mark Kettenis + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include + +#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, +}; -- cgit v1.2.3 From d42f1074253505f4e129267faa0d34676ca54d1f Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Sun, 23 Jan 2022 16:48:13 +0100 Subject: input: apple: Add support for Apple SPI keyboard This driver adds support for the keyboard on Apple Silicon laptops. The controller for this keyboard sits on an SPI bus and uses an Apple-specific HID over SPI protocol. The packets sent by this controller for key presses and key releases are fairly simple and are decoded directly by the code in this driver and converted into standard Linux keycodes. The same controller handles the touchpad found on these laptops. Packets for touchpad events are simply ignored. Signed-off-by: Mark Kettenis Reviewed-by: Simon Glass Tested on: Macbook Air M1 Tested-by: Simon Glass --- drivers/input/Kconfig | 8 ++ drivers/input/Makefile | 1 + drivers/input/apple_spi_kbd.c | 271 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 drivers/input/apple_spi_kbd.c (limited to 'drivers') diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 0b753f37bf4..2718b3674a5 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 e440c921e4e..b1133f772f2 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 00000000000..7cf12f453a3 --- /dev/null +++ b/drivers/input/apple_spi_kbd.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Mark Kettenis + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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, +}; -- cgit v1.2.3