This driver supports the PCIe controller on the Apple M1 and M2 SoCs. The code is adapted from the Linux driver.
Signed-off-by: Mark Kettenis <kette...@openbsd.org> --- MAINTAINERS | 1 + arch/arm/Kconfig | 2 + drivers/pci/Kconfig | 9 + drivers/pci/Makefile | 1 + drivers/pci/pcie_apple.c | 354 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 367 insertions(+) create mode 100644 drivers/pci/pcie_apple.c diff --git a/MAINTAINERS b/MAINTAINERS index 3fc4cd0f12..b8a947f9d3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -122,6 +122,7 @@ F: arch/arm/mach-apple/ F: configs/apple_m1_defconfig F: drivers/iommu/apple_dart.c F: drivers/nvme/nvme_apple.c +F: drivers/pci/pcie_apple.c F: drivers/pinctrl/pinctrl-apple.c F: drivers/watchdog/apple_wdt.c F: include/configs/apple.h diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index cac4fa09fd..780815269b 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -963,6 +963,7 @@ config ARCH_APPLE bool "Apple SoCs" select ARM64 select CLK + select CMD_PCI select CMD_USB select DM select DM_GPIO @@ -977,6 +978,7 @@ config ARCH_APPLE select LINUX_KERNEL_IMAGE_HEADER select OF_BOARD_SETUP select OF_CONTROL + select PCI select PINCTRL select POSITION_INDEPENDENT select POWER_DOMAIN diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 22f4995453..d61596cd7c 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -105,6 +105,15 @@ config PCIE_ECAM_SYNQUACER Note that this must be configured when boot because Linux driver expects the PCIe RC has been configured in the bootloader. +config PCIE_APPLE + bool "Enable Apple PCIe driver" + depends on ARCH_APPLE + imply PCI_INIT_R + default y + help + Say Y here if you want to enable PCIe controller support on + Apple SoCs. + config PCI_GT64120 bool "GT64120 PCI support" depends on MIPS diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index dd1ad91ced..d393f1ba03 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_PCI) += pci_auto_common.o pci_common.o obj-$(CONFIG_PCIE_ECAM_GENERIC) += pcie_ecam_generic.o obj-$(CONFIG_PCIE_ECAM_SYNQUACER) += pcie_ecam_synquacer.o +obj-$(CONFIG_PCIE_APPLE) += pcie_apple.o obj-$(CONFIG_PCI_GT64120) += pci_gt64120.o obj-$(CONFIG_PCI_MPC85XX) += pci_mpc85xx.o obj-$(CONFIG_PCI_MSC01) += pci_msc01.o diff --git a/drivers/pci/pcie_apple.c b/drivers/pci/pcie_apple.c new file mode 100644 index 0000000000..9b08e1e5da --- /dev/null +++ b/drivers/pci/pcie_apple.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCIe host bridge driver for Apple system-on-chips. + * + * The HW is ECAM compliant. + * + * Initialization requires enabling power and clocks, along with a + * number of register pokes. + * + * Copyright (C) 2021 Alyssa Rosenzweig <aly...@rosenzweig.io> + * Copyright (C) 2021 Google LLC + * Copyright (C) 2021 Corellium LLC + * Copyright (C) 2021 Mark Kettenis <kette...@openbsd.org> + * + * Author: Alyssa Rosenzweig <aly...@rosenzweig.io> + * Author: Marc Zyngier <m...@kernel.org> + */ + +#include <common.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/devres.h> +#include <mapmem.h> +#include <pci.h> +#include <asm/io.h> +#include <asm-generic/gpio.h> +#include <linux/delay.h> +#include <linux/iopoll.h> + +#define CORE_RC_PHYIF_CTL 0x00024 +#define CORE_RC_PHYIF_CTL_RUN BIT(0) +#define CORE_RC_PHYIF_STAT 0x00028 +#define CORE_RC_PHYIF_STAT_REFCLK BIT(4) +#define CORE_RC_CTL 0x00050 +#define CORE_RC_CTL_RUN BIT(0) +#define CORE_RC_STAT 0x00058 +#define CORE_RC_STAT_READY BIT(0) +#define CORE_FABRIC_STAT 0x04000 +#define CORE_FABRIC_STAT_MASK 0x001F001F +#define CORE_LANE_CFG(port) (0x84000 + 0x4000 * (port)) +#define CORE_LANE_CFG_REFCLK0REQ BIT(0) +#define CORE_LANE_CFG_REFCLK1REQ BIT(1) +#define CORE_LANE_CFG_REFCLK0ACK BIT(2) +#define CORE_LANE_CFG_REFCLK1ACK BIT(3) +#define CORE_LANE_CFG_REFCLKEN (BIT(9) | BIT(10)) +#define CORE_LANE_CTL(port) (0x84004 + 0x4000 * (port)) +#define CORE_LANE_CTL_CFGACC BIT(15) + +#define PORT_LTSSMCTL 0x00080 +#define PORT_LTSSMCTL_START BIT(0) +#define PORT_INTSTAT 0x00100 +#define PORT_INT_TUNNEL_ERR 31 +#define PORT_INT_CPL_TIMEOUT 23 +#define PORT_INT_RID2SID_MAPERR 22 +#define PORT_INT_CPL_ABORT 21 +#define PORT_INT_MSI_BAD_DATA 19 +#define PORT_INT_MSI_ERR 18 +#define PORT_INT_REQADDR_GT32 17 +#define PORT_INT_AF_TIMEOUT 15 +#define PORT_INT_LINK_DOWN 14 +#define PORT_INT_LINK_UP 12 +#define PORT_INT_LINK_BWMGMT 11 +#define PORT_INT_AER_MASK (15 << 4) +#define PORT_INT_PORT_ERR 4 +#define PORT_INT_INTx(i) i +#define PORT_INT_INTx_MASK 15 +#define PORT_INTMSK 0x00104 +#define PORT_INTMSKSET 0x00108 +#define PORT_INTMSKCLR 0x0010c +#define PORT_MSICFG 0x00124 +#define PORT_MSICFG_EN BIT(0) +#define PORT_MSICFG_L2MSINUM_SHIFT 4 +#define PORT_MSIBASE 0x00128 +#define PORT_MSIBASE_1_SHIFT 16 +#define PORT_MSIADDR 0x00168 +#define PORT_LINKSTS 0x00208 +#define PORT_LINKSTS_UP BIT(0) +#define PORT_LINKSTS_BUSY BIT(2) +#define PORT_LINKCMDSTS 0x00210 +#define PORT_OUTS_NPREQS 0x00284 +#define PORT_OUTS_NPREQS_REQ BIT(24) +#define PORT_OUTS_NPREQS_CPL BIT(16) +#define PORT_RXWR_FIFO 0x00288 +#define PORT_RXWR_FIFO_HDR GENMASK(15, 10) +#define PORT_RXWR_FIFO_DATA GENMASK(9, 0) +#define PORT_RXRD_FIFO 0x0028C +#define PORT_RXRD_FIFO_REQ GENMASK(6, 0) +#define PORT_OUTS_CPLS 0x00290 +#define PORT_OUTS_CPLS_SHRD GENMASK(14, 8) +#define PORT_OUTS_CPLS_WAIT GENMASK(6, 0) +#define PORT_APPCLK 0x00800 +#define PORT_APPCLK_EN BIT(0) +#define PORT_APPCLK_CGDIS BIT(8) +#define PORT_STATUS 0x00804 +#define PORT_STATUS_READY BIT(0) +#define PORT_REFCLK 0x00810 +#define PORT_REFCLK_EN BIT(0) +#define PORT_REFCLK_CGDIS BIT(8) +#define PORT_PERST 0x00814 +#define PORT_PERST_OFF BIT(0) +#define PORT_RID2SID(i16) (0x00828 + 4 * (i16)) +#define PORT_RID2SID_VALID BIT(31) +#define PORT_RID2SID_SID_SHIFT 16 +#define PORT_RID2SID_BUS_SHIFT 8 +#define PORT_RID2SID_DEV_SHIFT 3 +#define PORT_RID2SID_FUNC_SHIFT 0 +#define PORT_OUTS_PREQS_HDR 0x00980 +#define PORT_OUTS_PREQS_HDR_MASK GENMASK(9, 0) +#define PORT_OUTS_PREQS_DATA 0x00984 +#define PORT_OUTS_PREQS_DATA_MASK GENMASK(15, 0) +#define PORT_TUNCTRL 0x00988 +#define PORT_TUNCTRL_PERST_ON BIT(0) +#define PORT_TUNCTRL_PERST_ACK_REQ BIT(1) +#define PORT_TUNSTAT 0x0098c +#define PORT_TUNSTAT_PERST_ON BIT(0) +#define PORT_TUNSTAT_PERST_ACK_PEND BIT(1) +#define PORT_PREFMEM_ENABLE 0x00994 + +struct apple_pcie_priv { + struct udevice *dev; + void __iomem *base; + void __iomem *cfg_base; + struct list_head ports; +}; + +struct apple_pcie_port { + struct apple_pcie_priv *pcie; + struct gpio_desc reset; + ofnode np; + void __iomem *base; + struct list_head entry; + int idx; +}; + +static void rmw_set(u32 set, void __iomem *addr) +{ + writel_relaxed(readl_relaxed(addr) | set, addr); +} + +static void rmw_clear(u32 clr, void __iomem *addr) +{ + writel_relaxed(readl_relaxed(addr) & ~clr, addr); +} + +static int apple_pcie_config_address(const struct udevice *bus, + pci_dev_t bdf, uint offset, + void **paddress) +{ + struct apple_pcie_priv *pcie = dev_get_priv(bus); + void *addr; + + addr = pcie->cfg_base; + addr += PCIE_ECAM_OFFSET(PCI_BUS(bdf), PCI_DEV(bdf), + PCI_FUNC(bdf), offset); + *paddress = addr; + + return 0; +} + +static int apple_pcie_read_config(const struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + int ret; + + ret = pci_generic_mmap_read_config(bus, apple_pcie_config_address, + bdf, offset, valuep, size); + return ret; +} + +static int apple_pcie_write_config(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + return pci_generic_mmap_write_config(bus, apple_pcie_config_address, + bdf, offset, value, size); +} + +static const struct dm_pci_ops apple_pcie_ops = { + .read_config = apple_pcie_read_config, + .write_config = apple_pcie_write_config, +}; + +static int apple_pcie_setup_refclk(struct apple_pcie_priv *pcie, + struct apple_pcie_port *port) +{ + u32 stat; + int res; + + res = readl_poll_sleep_timeout(pcie->base + CORE_RC_PHYIF_STAT, stat, + stat & CORE_RC_PHYIF_STAT_REFCLK, + 100, 50000); + if (res < 0) + return res; + + rmw_set(CORE_LANE_CTL_CFGACC, pcie->base + CORE_LANE_CTL(port->idx)); + rmw_set(CORE_LANE_CFG_REFCLK0REQ, pcie->base + CORE_LANE_CFG(port->idx)); + + res = readl_poll_sleep_timeout(pcie->base + CORE_LANE_CFG(port->idx), + stat, stat & CORE_LANE_CFG_REFCLK0ACK, + 100, 50000); + if (res < 0) + return res; + + rmw_set(CORE_LANE_CFG_REFCLK1REQ, pcie->base + CORE_LANE_CFG(port->idx)); + res = readl_poll_sleep_timeout(pcie->base + CORE_LANE_CFG(port->idx), + stat, stat & CORE_LANE_CFG_REFCLK1ACK, + 100, 50000); + + if (res < 0) + return res; + + rmw_clear(CORE_LANE_CTL_CFGACC, pcie->base + CORE_LANE_CTL(port->idx)); + + rmw_set(CORE_LANE_CFG_REFCLKEN, pcie->base + CORE_LANE_CFG(port->idx)); + rmw_set(PORT_REFCLK_EN, port->base + PORT_REFCLK); + + return 0; +} + +static int apple_pcie_setup_port(struct apple_pcie_priv *pcie, ofnode np) +{ + struct apple_pcie_port *port; + struct gpio_desc reset; + fdt_addr_t addr; + u32 stat, idx; + int ret; + + ret = gpio_request_by_name_nodev(np, "reset-gpios", 0, &reset, 0); + if (ret) + return ret; + + port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + ret = ofnode_read_u32_index(np, "reg", 0, &idx); + if (ret) + return ret; + + /* Use the first reg entry to work out the port index */ + port->idx = idx >> 11; + port->pcie = pcie; + port->reset = reset; + port->np = np; + + addr = dev_read_addr_index(pcie->dev, port->idx + 2); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + port->base = map_sysmem(addr, 0); + + rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); + + /* Assert PERST# before setting up the clock */ + dm_gpio_set_value(&reset, 1); + + ret = apple_pcie_setup_refclk(pcie, port); + if (ret < 0) + return ret; + + /* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) */ + udelay(100); + + /* Deassert PERST# */ + rmw_set(PORT_PERST_OFF, port->base + PORT_PERST); + dm_gpio_set_value(&reset, 0); + + /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */ + udelay(100 * 1000); + + ret = readl_poll_sleep_timeout(port->base + PORT_STATUS, stat, + stat & PORT_STATUS_READY, 100, 250000); + if (ret < 0) { + dev_err(pcie->dev, "port %d ready wait timeout\n", port->idx); + return ret; + } + + rmw_clear(PORT_REFCLK_CGDIS, port->base + PORT_REFCLK); + rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK); + + list_add_tail(&port->entry, &pcie->ports); + + writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); + + /* + * Deliberately ignore the link not coming up as connected + * devices (e.g. the WiFi controller) may not be powerd up. + */ + readl_poll_sleep_timeout(port->base + PORT_LINKSTS, stat, + (stat & PORT_LINKSTS_UP), 100, 100000); + + return 0; +} + +static int apple_pcie_probe(struct udevice *dev) +{ + struct apple_pcie_priv *pcie = dev_get_priv(dev); + fdt_addr_t addr; + ofnode of_port; + int i, ret; + + pcie->dev = dev; + addr = dev_read_addr_index(dev, 0); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + pcie->cfg_base = map_sysmem(addr, 0); + + addr = dev_read_addr_index(dev, 1); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + pcie->base = map_sysmem(addr, 0); + + INIT_LIST_HEAD(&pcie->ports); + + for (of_port = ofnode_first_subnode(dev_ofnode(dev)); + ofnode_valid(of_port); + of_port = ofnode_next_subnode(of_port)) { + ret = apple_pcie_setup_port(pcie, of_port); + if (ret) { + dev_err(pcie->dev, "Port %d setup fail: %d\n", i, ret); + return ret; + } + } + + return 0; +} + +static int apple_pcie_remove(struct udevice *dev) +{ + struct apple_pcie_priv *pcie = dev_get_priv(dev); + struct apple_pcie_port *port, *tmp; + + list_for_each_entry_safe(port, tmp, &pcie->ports, entry) { + gpio_free_list_nodev(&port->reset, 1); + free(port); + } + + return 0; +} + +static const struct udevice_id apple_pcie_of_match[] = { + { .compatible = "apple,pcie" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(apple_pcie) = { + .name = "apple_pcie", + .id = UCLASS_PCI, + .of_match = apple_pcie_of_match, + .probe = apple_pcie_probe, + .remove = apple_pcie_remove, + .priv_auto = sizeof(struct apple_pcie_priv), + .ops = &apple_pcie_ops, +}; -- 2.39.0