From patchwork Thu Jan 14 08:15:20 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shawn Lin X-Patchwork-Id: 1426334 X-Patchwork-Delegate: ykai007@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=rock-chips.com Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4DGk9w0wWYz9sVX for ; Thu, 14 Jan 2021 23:28:19 +1100 (AEDT) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 8427A82779; Thu, 14 Jan 2021 13:26:26 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=none (p=none dis=none) header.from=rock-chips.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Received: by phobos.denx.de (Postfix, from userid 109) id 6B4018270F; Thu, 14 Jan 2021 09:15:49 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.4 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, RCVD_IN_SORBS_WEB, SPF_HELO_NONE autolearn=no autolearn_force=no version=3.4.2 Received: from lucky1.263xmail.com (lucky1.263xmail.com [211.157.147.130]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id B0F668270F for ; Thu, 14 Jan 2021 09:15:38 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=none (p=none dis=none) header.from=rock-chips.com Authentication-Results: phobos.denx.de; spf=fail smtp.mailfrom=shawn.lin@rock-chips.com Received: from localhost (unknown [192.168.167.235]) by lucky1.263xmail.com (Postfix) with ESMTP id 25785CF2A1; Thu, 14 Jan 2021 16:15:33 +0800 (CST) X-MAIL-GRAY: 0 X-MAIL-DELIVERY: 1 X-ADDR-CHECKED4: 1 X-ANTISPAM-LEVEL: 2 X-ABS-CHECKED: 0 Received: from localhost.localdomain (unknown [58.22.7.114]) by smtp.263.net (postfix) whith ESMTP id P25768T139698844464896S1610612129167684_; Thu, 14 Jan 2021 16:15:33 +0800 (CST) X-IP-DOMAINF: 1 X-UNIQUE-TAG: <706bcc7c7e70b9f1f9b41cc0bf761924> X-RL-SENDER: shawn.lin@rock-chips.com X-SENDER: lintao@rock-chips.com X-LOGIN-NAME: shawn.lin@rock-chips.com X-FST-TO: sjg@chromium.org X-SENDER-IP: 58.22.7.114 X-ATTACHMENT-NUM: 0 X-System-Flag: 0 From: Shawn Lin To: Simon Glass , Philipp Tomsich , Kever Yang Cc: u-boot@lists.denx.de, Shawn Lin Subject: [PATCH 1/2] phy: rockchip: Add Rockchip Synopsys PCIe 3.0 PHY Date: Thu, 14 Jan 2021 16:15:20 +0800 Message-Id: <20210114081521.11892-2-shawn.lin@rock-chips.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210114081521.11892-1-shawn.lin@rock-chips.com> References: <20210114081521.11892-1-shawn.lin@rock-chips.com> X-Mailman-Approved-At: Thu, 14 Jan 2021 13:25:35 +0100 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.34 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.3 at phobos.denx.de X-Virus-Status: Clean Add the Rockchip Synopsys based PCIe 3.0 PHY driver as part of Generic PHY framework. Signed-off-by: Shawn Lin Reviewed-by: Simon Glass --- drivers/phy/rockchip/Kconfig | 6 + drivers/phy/rockchip/Makefile | 1 + .../phy/rockchip/phy-rockchip-snps-pcie3.c | 146 ++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 drivers/phy/rockchip/phy-rockchip-snps-pcie3.c diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 2318e71f35..b794cdaf6a 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -18,6 +18,12 @@ config PHY_ROCKCHIP_PCIE help Enable this to support the Rockchip PCIe PHY. +config PHY_ROCKCHIP_SNPS_PCIE3 + bool "Rockchip Snps PCIe3 PHY Driver" + depends on PHY && ARCH_ROCKCHIP + help + Support for Rockchip PCIe3 PHY with Synopsys IP block. + config PHY_ROCKCHIP_TYPEC bool "Rockchip TYPEC PHY Driver" depends on ARCH_ROCKCHIP diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile index 44049154f9..f6ad3bf59a 100644 --- a/drivers/phy/rockchip/Makefile +++ b/drivers/phy/rockchip/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o +obj-$(CONFIG_PHY_ROCKCHIP_SNPS_PCIE3) += phy-rockchip-snps-pcie3.o obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c new file mode 100644 index 0000000000..3132f811b9 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip PCIE3.0 phy driver + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GRF_PCIE30PHY_CON1 0x4 +#define GRF_PCIE30PHY_CON6 0x18 +#define GRF_PCIE30PHY_CON9 0x24 + +struct rockchip_p3phy_priv { + void __iomem *mmio; + int mode; + struct regmap *phy_grf; + struct reset_ctl p30phy; + struct clk ref_clk_m; + struct clk ref_clk_n; + struct clk pclk; +}; + +static int rochchip_p3phy_init(struct phy *phy) +{ + struct rockchip_p3phy_priv *priv = dev_get_priv(phy->dev); + int ret; + + ret = clk_enable(&priv->ref_clk_m); + if (ret < 0 && ret != -ENOSYS) + return ret; + + ret = clk_enable(&priv->ref_clk_n); + if (ret < 0 && ret != -ENOSYS) + goto err_ref; + + ret = clk_enable(&priv->pclk); + if (ret < 0 && ret != -ENOSYS) + goto err_pclk; + + reset_assert(&priv->p30phy); + udelay(1); + + /* Deassert PCIe PMA output clamp mode */ + regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON9, + (0x1 << 15) | (0x1 << 31)); + + reset_deassert(&priv->p30phy); + udelay(1); + + return 0; +err_pclk: + clk_disable(&priv->ref_clk_n); +err_ref: + clk_disable(&priv->ref_clk_m); + return ret; +} + +static int rochchip_p3phy_exit(struct phy *phy) +{ + struct rockchip_p3phy_priv *priv = dev_get_priv(phy->dev); + + clk_disable(&priv->ref_clk_m); + clk_disable(&priv->ref_clk_n); + clk_disable(&priv->pclk); + reset_assert(&priv->p30phy); + return 0; +} + +static int rockchip_p3phy_probe(struct udevice *dev) +{ + struct rockchip_p3phy_priv *priv = dev_get_priv(dev); + struct udevice *syscon; + int ret; + + priv->mmio = (void __iomem *)dev_read_addr(dev); + if ((fdt_addr_t)priv->mmio == FDT_ADDR_T_NONE) + return -EINVAL; + + ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, + "rockchip,phy-grf", &syscon); + if (ret) { + pr_err("unable to find syscon device for rockchip,phy-grf\n"); + return ret; + } + + priv->phy_grf = syscon_get_regmap(syscon); + if (IS_ERR(priv->phy_grf)) { + dev_err(dev, "failed to find rockchip,phy_grf regmap\n"); + return PTR_ERR(priv->phy_grf); + } + + ret = reset_get_by_name(dev, "phy", &priv->p30phy); + if (ret) { + dev_err(dev, "no phy reset control specified\n"); + return ret; + } + + ret = clk_get_by_name(dev, "refclk_m", &priv->ref_clk_m); + if (ret) { + dev_err(dev, "failed to find ref clock M\n"); + return PTR_ERR(&priv->ref_clk_m); + } + + ret = clk_get_by_name(dev, "refclk_n", &priv->ref_clk_n); + if (ret) { + dev_err(dev, "failed to find ref clock N\n"); + return PTR_ERR(&priv->ref_clk_n); + } + + ret = clk_get_by_name(dev, "pclk", &priv->pclk); + if (ret) { + dev_err(dev, "failed to find pclk\n"); + return PTR_ERR(&priv->pclk); + } + + return 0; +} + +static struct phy_ops rochchip_p3phy_ops = { + .init = rochchip_p3phy_init, + .exit = rochchip_p3phy_exit, +}; + +static const struct udevice_id rockchip_p3phy_of_match[] = { + { .compatible = "rockchip,rk3568-pcie3-phy" }, + { }, +}; + +U_BOOT_DRIVER(rockchip_pcie3phy) = { + .name = "rockchip_pcie3phy", + .id = UCLASS_PHY, + .of_match = rockchip_p3phy_of_match, + .ops = &rochchip_p3phy_ops, + .probe = rockchip_p3phy_probe, + .priv_auto = sizeof(struct rockchip_p3phy_priv), +}; From patchwork Thu Jan 14 08:15:21 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shawn Lin X-Patchwork-Id: 1426335 X-Patchwork-Delegate: ykai007@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=85.214.62.61; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=rock-chips.com Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4DGkB66bh8z9sVX for ; Thu, 14 Jan 2021 23:28:30 +1100 (AEDT) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 99FF2827BA; Thu, 14 Jan 2021 13:26:29 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=none (p=none dis=none) header.from=rock-chips.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Received: by phobos.denx.de (Postfix, from userid 109) id E3F448271A; Thu, 14 Jan 2021 09:15:49 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.4 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, RCVD_IN_SORBS_WEB, SPF_HELO_NONE autolearn=no autolearn_force=no version=3.4.2 Received: from lucky1.263xmail.com (lucky1.263xmail.com [211.157.147.134]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id C7E9D8271A for ; Thu, 14 Jan 2021 09:15:43 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=none (p=none dis=none) header.from=rock-chips.com Authentication-Results: phobos.denx.de; spf=fail smtp.mailfrom=shawn.lin@rock-chips.com Received: from localhost (unknown [192.168.167.235]) by lucky1.263xmail.com (Postfix) with ESMTP id 40E46C5BA5; Thu, 14 Jan 2021 16:15:34 +0800 (CST) X-MAIL-GRAY: 0 X-MAIL-DELIVERY: 1 X-ADDR-CHECKED4: 1 X-ANTISPAM-LEVEL: 2 X-ABS-CHECKED: 0 Received: from localhost.localdomain (unknown [58.22.7.114]) by smtp.263.net (postfix) whith ESMTP id P25768T139698844464896S1610612129167684_; Thu, 14 Jan 2021 16:15:35 +0800 (CST) X-IP-DOMAINF: 1 X-UNIQUE-TAG: <4edcc160c36b2a02c8f4ad78f956c31f> X-RL-SENDER: shawn.lin@rock-chips.com X-SENDER: lintao@rock-chips.com X-LOGIN-NAME: shawn.lin@rock-chips.com X-FST-TO: sjg@chromium.org X-SENDER-IP: 58.22.7.114 X-ATTACHMENT-NUM: 0 X-System-Flag: 0 From: Shawn Lin To: Simon Glass , Philipp Tomsich , Kever Yang Cc: u-boot@lists.denx.de, Shawn Lin Subject: [PATCH 2/2] pci: Add Rockchip dwc based PCIe controller driver Date: Thu, 14 Jan 2021 16:15:21 +0800 Message-Id: <20210114081521.11892-3-shawn.lin@rock-chips.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210114081521.11892-1-shawn.lin@rock-chips.com> References: <20210114081521.11892-1-shawn.lin@rock-chips.com> X-Mailman-Approved-At: Thu, 14 Jan 2021 13:25:35 +0100 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.34 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.3 at phobos.denx.de X-Virus-Status: Clean Add Rockchip dwc based PCIe controller driver for rk356x platform. Driver support Gen3 by operating as a Root complex. Signed-off-by: Shawn Lin --- drivers/pci/Kconfig | 9 + drivers/pci/Makefile | 1 + drivers/pci/pcie_dw_rockchip.c | 755 +++++++++++++++++++++++++++++++++ 3 files changed, 765 insertions(+) create mode 100644 drivers/pci/pcie_dw_rockchip.c diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 65498bce1d..b1de38f766 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -281,6 +281,15 @@ config PCIE_ROCKCHIP Say Y here if you want to enable PCIe controller support on Rockchip SoCs. +config PCIE_DW_ROCKCHIP + bool "Rockchip DesignWare based PCIe controller" + depends on ARCH_ROCKCHIP + select DM_PCI + select PHY_ROCKCHIP_SNPS_PCIE3 + help + Say Y here if you want to enable DW PCIe controller support on + Rockchip SoCs. + config PCI_BRCMSTB bool "Broadcom STB PCIe controller" depends on DM_PCI diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 8b4d49a590..5ed94bc95c 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -48,5 +48,6 @@ obj-$(CONFIG_PCIE_INTEL_FPGA) += pcie_intel_fpga.o obj-$(CONFIG_PCI_KEYSTONE) += pcie_dw_ti.o obj-$(CONFIG_PCIE_MEDIATEK) += pcie_mediatek.o obj-$(CONFIG_PCIE_ROCKCHIP) += pcie_rockchip.o +obj-$(CONFIG_PCIE_DW_ROCKCHIP) += pcie_dw_rockchip.o obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o obj-$(CONFIG_PCI_OCTEONTX) += pci_octeontx.o diff --git a/drivers/pci/pcie_dw_rockchip.c b/drivers/pci/pcie_dw_rockchip.c new file mode 100644 index 0000000000..aa2cc15e15 --- /dev/null +++ b/drivers/pci/pcie_dw_rockchip.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Rockchip DesignWare based PCIe host controller driver + * + * Copyright (c) 2021 Rockchip, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +struct rk_pcie { + struct udevice *dev; + struct udevice *vpcie3v3; + void *dbi_base; + void *apb_base; + void *cfg_base; + fdt_size_t cfg_size; + struct phy phy; + struct clk_bulk clks; + int first_busno; + struct reset_ctl_bulk rsts; + struct gpio_desc rst_gpio; + struct pci_region io; + struct pci_region mem; +}; + +enum { + PCIBIOS_SUCCESSFUL = 0x0000, + PCIBIOS_UNSUPPORTED = -ENODEV, + PCIBIOS_NODEV = -ENODEV, +}; + +#define msleep(a) udelay((a) * 1000) + +/* Parameters for the waiting for iATU enabled routine */ +#define PCIE_CLIENT_GENERAL_DEBUG 0x104 +#define PCIE_CLIENT_HOT_RESET_CTRL 0x180 +#define PCIE_LTSSM_ENABLE_ENHANCE BIT(4) +#define PCIE_CLIENT_LTSSM_STATUS 0x300 +#define SMLH_LINKUP BIT(16) +#define RDLH_LINKUP BIT(17) +#define PCIE_CLIENT_DBG_FIFO_MODE_CON 0x310 +#define PCIE_CLIENT_DBG_FIFO_PTN_HIT_D0 0x320 +#define PCIE_CLIENT_DBG_FIFO_PTN_HIT_D1 0x324 +#define PCIE_CLIENT_DBG_FIFO_TRN_HIT_D0 0x328 +#define PCIE_CLIENT_DBG_FIFO_TRN_HIT_D1 0x32c +#define PCIE_CLIENT_DBG_FIFO_STATUS 0x350 +#define PCIE_CLIENT_DBG_TRANSITION_DATA 0xffff0000 +#define PCIE_CLIENT_DBF_EN 0xffff0003 +#define RK_PCIE_DBG 0 + +/* PCI DBICS registers */ +#define PCIE_LINK_STATUS_REG 0x80 +#define PCIE_LINK_STATUS_SPEED_OFF 16 +#define PCIE_LINK_STATUS_SPEED_MASK (0xf << PCIE_LINK_STATUS_SPEED_OFF) +#define PCIE_LINK_STATUS_WIDTH_OFF 20 +#define PCIE_LINK_STATUS_WIDTH_MASK (0xf << PCIE_LINK_STATUS_WIDTH_OFF) + +#define PCIE_LINK_CAPABILITY 0x7c +#define PCIE_LINK_CTL_2 0xa0 +#define TARGET_LINK_SPEED_MASK 0xf +#define LINK_SPEED_GEN_1 0x1 +#define LINK_SPEED_GEN_2 0x2 +#define LINK_SPEED_GEN_3 0x3 + +#define PCIE_MISC_CONTROL_1_OFF 0x8bc +#define PCIE_DBI_RO_WR_EN BIT(0) + +#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80c +#define PORT_LOGIC_SPEED_CHANGE BIT(17) + +/* + * iATU Unroll-specific register definitions + * From 4.80 core version the address translation will be made by unroll. + * The registers are offset from atu_base + */ +#define PCIE_ATU_UNR_REGION_CTRL1 0x00 +#define PCIE_ATU_UNR_REGION_CTRL2 0x04 +#define PCIE_ATU_UNR_LOWER_BASE 0x08 +#define PCIE_ATU_UNR_UPPER_BASE 0x0c +#define PCIE_ATU_UNR_LIMIT 0x10 +#define PCIE_ATU_UNR_LOWER_TARGET 0x14 +#define PCIE_ATU_UNR_UPPER_TARGET 0x18 + +#define PCIE_ATU_REGION_INDEX1 (0x1 << 0) +#define PCIE_ATU_REGION_INDEX0 (0x0 << 0) +#define PCIE_ATU_TYPE_MEM (0x0 << 0) +#define PCIE_ATU_TYPE_IO (0x2 << 0) +#define PCIE_ATU_TYPE_CFG0 (0x4 << 0) +#define PCIE_ATU_TYPE_CFG1 (0x5 << 0) +#define PCIE_ATU_ENABLE (0x1 << 31) +#define PCIE_ATU_BAR_MODE_ENABLE (0x1 << 30) +#define PCIE_ATU_BUS(x) (((x) & 0xff) << 24) +#define PCIE_ATU_DEV(x) (((x) & 0x1f) << 19) +#define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) + +/* Register address builder */ +#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \ + ((0x3 << 20) | ((region) << 9)) +#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \ + ((0x3 << 20) | ((region) << 9) | (0x1 << 8)) + +/* Parameters for the waiting for iATU enabled routine */ +#define LINK_WAIT_MAX_IATU_RETRIES 5 +#define LINK_WAIT_IATU 10000 + +static int rk_pcie_read(void __iomem *addr, int size, u32 *val) +{ + if ((uintptr_t)addr & (size - 1)) { + *val = 0; + return PCIBIOS_UNSUPPORTED; + } + + if (size == 4) { + *val = readl(addr); + } else if (size == 2) { + *val = readw(addr); + } else if (size == 1) { + *val = readb(addr); + } else { + *val = 0; + return PCIBIOS_NODEV; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int rk_pcie_write(void __iomem *addr, int size, u32 val) +{ + if ((uintptr_t)addr & (size - 1)) + return PCIBIOS_UNSUPPORTED; + + if (size == 4) + writel(val, addr); + else if (size == 2) + writew(val, addr); + else if (size == 1) + writeb(val, addr); + else + return PCIBIOS_NODEV; + + return PCIBIOS_SUCCESSFUL; +} + +static u32 __rk_pcie_read_apb(struct rk_pcie *rk_pcie, void __iomem *base, + u32 reg, size_t size) +{ + int ret; + u32 val; + + ret = rk_pcie_read(base + reg, size, &val); + if (ret) + dev_err(rk_pcie->dev, "Read APB address failed\n"); + + return val; +} + +static void __rk_pcie_write_apb(struct rk_pcie *rk_pcie, void __iomem *base, + u32 reg, size_t size, u32 val) +{ + int ret; + + ret = rk_pcie_write(base + reg, size, val); + if (ret) + dev_err(rk_pcie->dev, "Write APB address failed\n"); +} + +static inline u32 rk_pcie_readl_apb(struct rk_pcie *rk_pcie, u32 reg) +{ + return __rk_pcie_read_apb(rk_pcie, rk_pcie->apb_base, reg, 0x4); +} + +static inline void rk_pcie_writel_apb(struct rk_pcie *rk_pcie, u32 reg, + u32 val) +{ + __rk_pcie_write_apb(rk_pcie, rk_pcie->apb_base, reg, 0x4, val); +} + +static int rk_pcie_get_link_speed(struct rk_pcie *rk_pcie) +{ + return (readl(rk_pcie->dbi_base + PCIE_LINK_STATUS_REG) & + PCIE_LINK_STATUS_SPEED_MASK) >> PCIE_LINK_STATUS_SPEED_OFF; +} + +static int rk_pcie_get_link_width(struct rk_pcie *rk_pcie) +{ + return (readl(rk_pcie->dbi_base + PCIE_LINK_STATUS_REG) & + PCIE_LINK_STATUS_WIDTH_MASK) >> PCIE_LINK_STATUS_WIDTH_OFF; +} + +static void rk_pcie_writel_ob_unroll(struct rk_pcie *rk_pcie, u32 index, + u32 reg, u32 val) +{ + u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); + void __iomem *base = rk_pcie->dbi_base; + + writel(val, base + offset + reg); +} + +static u32 rk_pcie_readl_ob_unroll(struct rk_pcie *rk_pcie, u32 index, u32 reg) +{ + u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); + void __iomem *base = rk_pcie->dbi_base; + + return readl(base + offset + reg); +} + +static inline void rk_pcie_dbi_write_enable(struct rk_pcie *rk_pcie, bool en) +{ + u32 val; + + val = readl(rk_pcie->dbi_base + PCIE_MISC_CONTROL_1_OFF); + + if (en) + val |= PCIE_DBI_RO_WR_EN; + else + val &= ~PCIE_DBI_RO_WR_EN; + writel(val, rk_pcie->dbi_base + PCIE_MISC_CONTROL_1_OFF); +} + +static void rk_pcie_setup_host(struct rk_pcie *rk_pcie) +{ + u32 val; + + rk_pcie_dbi_write_enable(rk_pcie, true); + + /* setup RC BARs */ + writel(PCI_BASE_ADDRESS_MEM_TYPE_64, + rk_pcie->dbi_base + PCI_BASE_ADDRESS_0); + writel(0x0, rk_pcie->dbi_base + PCI_BASE_ADDRESS_1); + + /* setup interrupt pins */ + val = readl(rk_pcie->dbi_base + PCI_INTERRUPT_LINE); + val &= 0xffff00ff; + val |= 0x00000100; + writel(val, rk_pcie->dbi_base + PCI_INTERRUPT_LINE); + + /* setup bus numbers */ + val = readl(rk_pcie->dbi_base + PCI_PRIMARY_BUS); + val &= 0xff000000; + val |= 0x00ff0100; + writel(val, rk_pcie->dbi_base + PCI_PRIMARY_BUS); + + val = readl(rk_pcie->dbi_base + PCI_PRIMARY_BUS); + + /* setup command register */ + val = readl(rk_pcie->dbi_base + PCI_COMMAND); + val &= 0xffff0000; + val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER | PCI_COMMAND_SERR; + writel(val, rk_pcie->dbi_base + PCI_COMMAND); + + /* program correct class for RC */ + writew(PCI_CLASS_BRIDGE_PCI, rk_pcie->dbi_base + PCI_CLASS_DEVICE); + /* Better disable write permission right after the update */ + + val = readl(rk_pcie->dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL); + val |= PORT_LOGIC_SPEED_CHANGE; + writel(val, rk_pcie->dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL); + + rk_pcie_dbi_write_enable(rk_pcie, false); +} + +static void rk_pcie_configure(struct rk_pcie *pci, u32 cap_speed) +{ + u32 val; + + rk_pcie_dbi_write_enable(pci, true); + + val = readl(pci->dbi_base + PCIE_LINK_CAPABILITY); + val &= ~TARGET_LINK_SPEED_MASK; + val |= cap_speed; + writel(val, pci->dbi_base + PCIE_LINK_CAPABILITY); + + val = readl(pci->dbi_base + PCIE_LINK_CTL_2); + val &= ~TARGET_LINK_SPEED_MASK; + val |= cap_speed; + writel(val, pci->dbi_base + PCIE_LINK_CTL_2); + + rk_pcie_dbi_write_enable(pci, false); +} + +static void rk_pcie_prog_outbound_atu_unroll(struct rk_pcie *pci, int index, + int type, u64 cpu_addr, + u64 pci_addr, u32 size) +{ + u32 retries, val; + + dev_dbg(pci->dev, "ATU programmed with: index: %d, type: %d, cpu addr: %8llx, pci addr: %8llx, size: %8x\n", + index, type, cpu_addr, pci_addr, size); + + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE, + lower_32_bits(cpu_addr)); + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE, + upper_32_bits(cpu_addr)); + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LIMIT, + lower_32_bits(cpu_addr + size - 1)); + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, + lower_32_bits(pci_addr)); + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, + upper_32_bits(pci_addr)); + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, + type); + rk_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, + PCIE_ATU_ENABLE); + + /* + * Make sure ATU enable takes effect before any subsequent config + * and I/O accesses. + */ + for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { + val = rk_pcie_readl_ob_unroll(pci, index, + PCIE_ATU_UNR_REGION_CTRL2); + if (val & PCIE_ATU_ENABLE) + return; + + udelay(LINK_WAIT_IATU); + } + dev_err(pci->dev, "outbound iATU is not being enabled\n"); +} + +static int rk_pcie_addr_valid(pci_dev_t d, int first_busno) +{ + if ((PCI_BUS(d) == first_busno) && (PCI_DEV(d) > 0)) + return 0; + if ((PCI_BUS(d) == first_busno + 1) && (PCI_DEV(d) > 0)) + return 0; + + return 1; +} + +static uintptr_t set_cfg_address(struct rk_pcie *pcie, + pci_dev_t d, uint where) +{ + int bus = PCI_BUS(d) - pcie->first_busno; + uintptr_t va_address; + u32 atu_type; + + /* Use dbi_base for own configuration read and write */ + if (!bus) { + va_address = (uintptr_t)pcie->dbi_base; + goto out; + } + + if (bus == 1) + /* + * For local bus whose primary bus number is root bridge, + * change TLP Type field to 4. + */ + atu_type = PCIE_ATU_TYPE_CFG0; + else + /* Otherwise, change TLP Type field to 5. */ + atu_type = PCIE_ATU_TYPE_CFG1; + + /* + * Not accessing root port configuration space? + * Region #0 is used for Outbound CFG space access. + * Direction = Outbound + * Region Index = 0 + */ + d = PCI_MASK_BUS(d); + d = PCI_ADD_BUS(bus, d); + rk_pcie_prog_outbound_atu_unroll(pcie, PCIE_ATU_REGION_INDEX1, + atu_type, (u64)pcie->cfg_base, + d << 8, pcie->cfg_size); + + va_address = (uintptr_t)pcie->cfg_base; + +out: + va_address += where & ~0x3; + + return va_address; +} + +static int rockchip_pcie_rd_conf(const struct udevice *bus, pci_dev_t bdf, + uint offset, ulong *valuep, + enum pci_size_t size) +{ + struct rk_pcie *pcie = dev_get_priv(bus); + uintptr_t va_address; + ulong value; + + debug("PCIE CFG read: bdf=%2x:%2x:%2x\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + + if (!rk_pcie_addr_valid(bdf, pcie->first_busno)) { + debug("- out of range\n"); + *valuep = pci_get_ff(size); + return 0; + } + + va_address = set_cfg_address(pcie, bdf, offset); + + value = readl(va_address); + + debug("(addr,val)=(0x%04x, 0x%08lx)\n", offset, value); + *valuep = pci_conv_32_to_size(value, offset, size); + + rk_pcie_prog_outbound_atu_unroll(pcie, PCIE_ATU_REGION_INDEX1, + PCIE_ATU_TYPE_IO, pcie->io.phys_start, + pcie->io.bus_start, pcie->io.size); + + return 0; +} + +static int rockchip_pcie_wr_conf(struct udevice *bus, pci_dev_t bdf, + uint offset, ulong value, + enum pci_size_t size) +{ + struct rk_pcie *pcie = dev_get_priv(bus); + uintptr_t va_address; + ulong old; + + debug("PCIE CFG write: (b,d,f)=(%2d,%2d,%2d)\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + debug("(addr,val)=(0x%04x, 0x%08lx)\n", offset, value); + + if (!rk_pcie_addr_valid(bdf, pcie->first_busno)) { + debug("- out of range\n"); + return 0; + } + + va_address = set_cfg_address(pcie, bdf, offset); + + old = readl(va_address); + value = pci_conv_size_to_32(old, value, offset, size); + writel(value, va_address); + + rk_pcie_prog_outbound_atu_unroll(pcie, PCIE_ATU_REGION_INDEX1, + PCIE_ATU_TYPE_IO, pcie->io.phys_start, + pcie->io.bus_start, pcie->io.size); + + return 0; +} + +static void rk_pcie_enable_debug(struct rk_pcie *rk_pcie) +{ +#if RK_PCIE_DBG + rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_PTN_HIT_D0, + PCIE_CLIENT_DBG_TRANSITION_DATA); + rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_PTN_HIT_D1, + PCIE_CLIENT_DBG_TRANSITION_DATA); + rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_TRN_HIT_D0, + PCIE_CLIENT_DBG_TRANSITION_DATA); + rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_TRN_HIT_D1, + PCIE_CLIENT_DBG_TRANSITION_DATA); + rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_MODE_CON, + PCIE_CLIENT_DBF_EN); +#endif +} + +static void rk_pcie_debug_dump(struct rk_pcie *rk_pcie) +{ +#if RK_PCIE_DBG + u32 loop; + + dev_info(rk_pcie->dev, "ltssm = 0x%x\n", + rk_pcie_readl_apb(rk_pcie, PCIE_CLIENT_LTSSM_STATUS)); + for (loop = 0; loop < 64; loop++) + dev_info(rk_pcie->dev, "fifo_status = 0x%x\n", + rk_pcie_readl_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_STATUS)); +#endif +} + +static inline void rk_pcie_link_status_clear(struct rk_pcie *rk_pcie) +{ + rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_GENERAL_DEBUG, 0x0); +} + +static inline void rk_pcie_disable_ltssm(struct rk_pcie *rk_pcie) +{ + rk_pcie_writel_apb(rk_pcie, 0x0, 0xc0008); +} + +static inline void rk_pcie_enable_ltssm(struct rk_pcie *rk_pcie) +{ + rk_pcie_writel_apb(rk_pcie, 0x0, 0xc000c); +} + +static int is_link_up(struct rk_pcie *priv) +{ + u32 val; + + val = rk_pcie_readl_apb(priv, PCIE_CLIENT_LTSSM_STATUS); + if ((val & (RDLH_LINKUP | SMLH_LINKUP)) == 0x30000 && + (val & GENMASK(5, 0)) == 0x11) + return 1; + + return 0; +} + +static int rk_pcie_link_up(struct rk_pcie *priv, u32 cap_speed) +{ + int retries; + + if (is_link_up(priv)) { + printf("PCI Link already up before configuration!\n"); + return 1; + } + + /* DW pre link configurations */ + rk_pcie_configure(priv, cap_speed); + + /* Rest the device */ + if (dm_gpio_is_valid(&priv->rst_gpio)) { + dm_gpio_set_value(&priv->rst_gpio, 0); + msleep(1000); + dm_gpio_set_value(&priv->rst_gpio, 1); + } + + rk_pcie_disable_ltssm(priv); + rk_pcie_link_status_clear(priv); + rk_pcie_enable_debug(priv); + + /* Enable LTSSM */ + rk_pcie_enable_ltssm(priv); + + for (retries = 0; retries < 10000000; retries++) { + if (is_link_up(priv)) { + dev_info(priv->dev, "PCIe Link up, LTSSM is 0x%x\n", + rk_pcie_readl_apb(priv, PCIE_CLIENT_LTSSM_STATUS)); + rk_pcie_debug_dump(priv); + return 0; + } + + dev_info(priv->dev, "PCIe Linking... LTSSM is 0x%x\n", + rk_pcie_readl_apb(priv, PCIE_CLIENT_LTSSM_STATUS)); + rk_pcie_debug_dump(priv); + msleep(1000); + } + + dev_err(priv->dev, "PCIe-%d Link Fail\n", dev_seq(priv->dev)); + /* Link maybe in Gen switch recovery but we need to wait more 1s */ + msleep(1000); + return -EINVAL; +} + +static int rockchip_pcie_init_port(struct udevice *dev) +{ + int ret; + u32 val; + struct rk_pcie *priv = dev_get_priv(dev); + + /* Set power and maybe external ref clk input */ + if (priv->vpcie3v3) { + ret = regulator_set_value(priv->vpcie3v3, 3300000); + if (ret) { + dev_err(priv->dev, "failed to enable vpcie3v3 (ret=%d)\n", + ret); + return ret; + } + } + + msleep(1000); + + ret = generic_phy_init(&priv->phy); + if (ret) { + dev_err(dev, "failed to init phy (ret=%d)\n", ret); + return ret; + } + + ret = generic_phy_power_on(&priv->phy); + if (ret) { + dev_err(dev, "failed to power on phy (ret=%d)\n", ret); + goto err_exit_phy; + } + + ret = reset_deassert_bulk(&priv->rsts); + if (ret) { + dev_err(dev, "failed to deassert resets (ret=%d)\n", ret); + goto err_power_off_phy; + } + + ret = clk_enable_bulk(&priv->clks); + if (ret) { + dev_err(dev, "failed to enable clks (ret=%d)\n", ret); + goto err_deassert_bulk; + } + + /* LTSSM EN ctrl mode */ + val = rk_pcie_readl_apb(priv, PCIE_CLIENT_HOT_RESET_CTRL); + val |= PCIE_LTSSM_ENABLE_ENHANCE | (PCIE_LTSSM_ENABLE_ENHANCE << 16); + rk_pcie_writel_apb(priv, PCIE_CLIENT_HOT_RESET_CTRL, val); + + /* Set RC mode */ + rk_pcie_writel_apb(priv, 0x0, 0xf00040); + rk_pcie_setup_host(priv); + + ret = rk_pcie_link_up(priv, LINK_SPEED_GEN_3); + if (ret < 0) + goto err_link_up; + + return 0; +err_link_up: + clk_disable_bulk(&priv->clks); +err_deassert_bulk: + reset_assert_bulk(&priv->rsts); +err_power_off_phy: + generic_phy_power_off(&priv->phy); +err_exit_phy: + generic_phy_exit(&priv->phy); + return ret; +} + +static int rockchip_pcie_parse_dt(struct udevice *dev) +{ + struct rk_pcie *priv = dev_get_priv(dev); + int ret; + + priv->dbi_base = (void *)dev_read_addr_index(dev, 0); + if (!priv->dbi_base) + return -ENODEV; + + dev_dbg(dev, "DBI address is 0x%p\n", priv->dbi_base); + + priv->apb_base = (void *)dev_read_addr_index(dev, 1); + if (!priv->apb_base) + return -ENODEV; + + dev_dbg(dev, "APB address is 0x%p\n", priv->apb_base); + + ret = gpio_request_by_name(dev, "reset-gpios", 0, + &priv->rst_gpio, GPIOD_IS_OUT); + if (ret) { + dev_err(dev, "failed to find reset-gpios property\n"); + return ret; + } + + ret = reset_get_bulk(dev, &priv->rsts); + if (ret) { + dev_err(dev, "Can't get reset: %d\n", ret); + return ret; + } + + ret = clk_get_bulk(dev, &priv->clks); + if (ret) { + dev_err(dev, "Can't get clock: %d\n", ret); + return ret; + } + + ret = device_get_supply_regulator(dev, "vpcie3v3-supply", + &priv->vpcie3v3); + if (ret && ret != -ENOENT) { + dev_err(dev, "failed to get vpcie3v3 supply (ret=%d)\n", ret); + return ret; + } + + ret = generic_phy_get_by_index(dev, 0, &priv->phy); + if (ret) { + dev_err(dev, "failed to get pcie phy (ret=%d)\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_pcie_probe(struct udevice *dev) +{ + struct rk_pcie *priv = dev_get_priv(dev); + struct udevice *ctlr = pci_get_controller(dev); + struct pci_controller *hose = dev_get_uclass_priv(ctlr); + int ret; + + priv->first_busno = dev_seq(dev); + priv->dev = dev; + + ret = rockchip_pcie_parse_dt(dev); + if (ret) + return ret; + + ret = rockchip_pcie_init_port(dev); + if (ret) + return ret; + + dev_info(dev, "PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n", + dev_seq(dev), rk_pcie_get_link_speed(priv), + rk_pcie_get_link_width(priv), + hose->first_busno); + + for (ret = 0; ret < hose->region_count; ret++) { + if (hose->regions[ret].flags == PCI_REGION_IO) { + priv->io.phys_start = hose->regions[ret].phys_start; /* IO base */ + priv->io.bus_start = hose->regions[ret].bus_start; /* IO_bus_addr */ + priv->io.size = hose->regions[ret].size; /* IO size */ + } else if (hose->regions[ret].flags == PCI_REGION_MEM) { + priv->mem.phys_start = hose->regions[ret].phys_start; /* MEM base */ + priv->mem.bus_start = hose->regions[ret].bus_start; /* MEM_bus_addr */ + priv->mem.size = hose->regions[ret].size; /* MEM size */ + } else if (hose->regions[ret].flags == PCI_REGION_SYS_MEMORY) { + priv->cfg_base = (void *)(priv->io.phys_start - priv->io.size); + priv->cfg_size = priv->io.size; + } else { + dev_err(dev, "invalid flags type!\n"); + } + } + + dev_dbg(dev, "Config space: [0x%p - 0x%p, size 0x%llx]\n", + priv->cfg_base, priv->cfg_base + priv->cfg_size, + priv->cfg_size); + + dev_dbg(dev, "IO space: [0x%llx - 0x%llx, size 0x%lx]\n", + priv->io.phys_start, priv->io.phys_start + priv->io.size, + priv->io.size); + + dev_dbg(dev, "IO bus: [0x%lx - 0x%lx, size 0x%lx]\n", + priv->io.bus_start, priv->io.bus_start + priv->io.size, + priv->io.size); + + dev_dbg(dev, "MEM space: [0x%llx - 0x%llx, size 0x%lx]\n", + priv->mem.phys_start, priv->mem.phys_start + priv->mem.size, + priv->mem.size); + + dev_dbg(dev, "MEM bus: [0x%lx - 0x%lx, size 0x%lx]\n", + priv->mem.bus_start, priv->mem.bus_start + priv->mem.size, + priv->mem.size); + + rk_pcie_prog_outbound_atu_unroll(priv, PCIE_ATU_REGION_INDEX0, + PCIE_ATU_TYPE_MEM, + priv->mem.phys_start, + priv->mem.bus_start, priv->mem.size); + return 0; +} + +static const struct dm_pci_ops rockchip_pcie_ops = { + .read_config = rockchip_pcie_rd_conf, + .write_config = rockchip_pcie_wr_conf, +}; + +static const struct udevice_id rockchip_pcie_ids[] = { + { .compatible = "rockchip,rk3568-pcie" }, + { } +}; + +U_BOOT_DRIVER(rockchip_dw_pcie) = { + .name = "pcie_dw_rockchip", + .id = UCLASS_PCI, + .of_match = rockchip_pcie_ids, + .ops = &rockchip_pcie_ops, + .probe = rockchip_pcie_probe, + .priv_auto = sizeof(struct rk_pcie), +};