aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/pci/Kconfig8
-rw-r--r--drivers/pci/Makefile1
-rw-r--r--drivers/pci/pcie_dw_meson.c459
3 files changed, 468 insertions, 0 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index cacfc4bd253..cdcdd8f456b 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -276,6 +276,14 @@ config PCIE_MEDIATEK
Say Y here if you want to enable Gen2 PCIe controller,
which could be found on MT7623 SoC family.
+config PCIE_DW_MESON
+ bool "Amlogic Meson DesignWare based PCIe controller"
+ depends on ARCH_MESON
+ select PCIE_DW_COMMON
+ help
+ Say Y here if you want to enable DW PCIe controller support on
+ Amlogic SoCs.
+
config PCIE_ROCKCHIP
bool "Enable Rockchip PCIe driver"
depends on ARCH_ROCKCHIP
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index e3ca8b27e48..96d61821fe3 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -50,5 +50,6 @@ 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_PCIE_DW_MESON) += pcie_dw_meson.o
obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o
obj-$(CONFIG_PCI_OCTEONTX) += pci_octeontx.o
diff --git a/drivers/pci/pcie_dw_meson.c b/drivers/pci/pcie_dw_meson.c
new file mode 100644
index 00000000000..0525ecbea64
--- /dev/null
+++ b/drivers/pci/pcie_dw_meson.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Amlogic DesignWare based PCIe host controller driver
+ *
+ * Copyright (c) 2021 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ *
+ * Based on pcie_dw_rockchip.c
+ * Copyright (c) 2021 Rockchip, Inc.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <pci.h>
+#include <power-domain.h>
+#include <reset.h>
+#include <syscon.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <asm-generic/gpio.h>
+#include <dm/device_compat.h>
+#include <linux/iopoll.h>
+#include <linux/delay.h>
+#include <linux/log2.h>
+#include <linux/bitfield.h>
+
+#include "pcie_dw_common.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * struct meson_pcie - Amlogic Meson DW PCIe controller state
+ *
+ * @pci: The common PCIe DW structure
+ * @meson_cfg_base: The base address of vendor regs
+ * @phy
+ * @clk_port
+ * @clk_general
+ * @clk_pclk
+ * @rsts
+ * @rst_gpio: The #PERST signal for slot
+ */
+struct meson_pcie {
+ /* Must be first member of the struct */
+ struct pcie_dw dw;
+ void *meson_cfg_base;
+ struct phy phy;
+ struct clk clk_port;
+ struct clk clk_general;
+ struct clk clk_pclk;
+ struct reset_ctl_bulk rsts;
+ struct gpio_desc rst_gpio;
+};
+
+#define PCI_EXP_DEVCTL_PAYLOAD 0x00e0 /* Max_Payload_Size */
+
+#define PCIE_CAP_MAX_PAYLOAD_SIZE(x) ((x) << 5)
+#define PCIE_CAP_MAX_READ_REQ_SIZE(x) ((x) << 12)
+
+/* PCIe specific config registers */
+#define PCIE_CFG0 0x0
+#define APP_LTSSM_ENABLE BIT(7)
+
+#define PCIE_CFG_STATUS12 0x30
+#define IS_SMLH_LINK_UP(x) ((x) & (1 << 6))
+#define IS_RDLH_LINK_UP(x) ((x) & (1 << 16))
+#define IS_LTSSM_UP(x) ((((x) >> 10) & 0x1f) == 0x11)
+
+#define PCIE_CFG_STATUS17 0x44
+#define PM_CURRENT_STATE(x) (((x) >> 7) & 0x1)
+
+#define WAIT_LINKUP_TIMEOUT 4000
+#define PORT_CLK_RATE 100000000UL
+#define MAX_PAYLOAD_SIZE 256
+#define MAX_READ_REQ_SIZE 256
+#define PCIE_RESET_DELAY 500
+#define PCIE_SHARED_RESET 1
+#define PCIE_NORMAL_RESET 0
+
+enum pcie_data_rate {
+ PCIE_GEN1,
+ PCIE_GEN2,
+ PCIE_GEN3,
+ PCIE_GEN4
+};
+
+/* Parameters for the waiting for #perst signal */
+#define PERST_WAIT_US 1000000
+
+static inline u32 meson_cfg_readl(struct meson_pcie *priv, u32 reg)
+{
+ return readl(priv->meson_cfg_base + reg);
+}
+
+static inline void meson_cfg_writel(struct meson_pcie *priv, u32 val, u32 reg)
+{
+ writel(val, priv->meson_cfg_base + reg);
+}
+
+/**
+ * meson_pcie_configure() - Configure link
+ *
+ * @meson_pcie: Pointer to the PCI controller state
+ *
+ * Configure the link mode and width
+ */
+static void meson_pcie_configure(struct meson_pcie *priv)
+{
+ u32 val;
+
+ dw_pcie_dbi_write_enable(&priv->dw, true);
+
+ val = readl(priv->dw.dbi_base + PCIE_PORT_LINK_CONTROL);
+ val &= ~PORT_LINK_FAST_LINK_MODE;
+ val |= PORT_LINK_DLL_LINK_EN;
+ val &= ~PORT_LINK_MODE_MASK;
+ val |= PORT_LINK_MODE_1_LANES;
+ writel(val, priv->dw.dbi_base + PCIE_PORT_LINK_CONTROL);
+
+ val = readl(priv->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
+ val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
+ writel(val, priv->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL);
+
+ dw_pcie_dbi_write_enable(&priv->dw, false);
+}
+
+static inline void meson_pcie_enable_ltssm(struct meson_pcie *priv)
+{
+ u32 val;
+
+ val = meson_cfg_readl(priv, PCIE_CFG0);
+ val |= APP_LTSSM_ENABLE;
+ meson_cfg_writel(priv, val, PCIE_CFG0);
+}
+
+static int meson_pcie_wait_link_up(struct meson_pcie *priv)
+{
+ u32 speed_okay = 0;
+ u32 cnt = 0;
+ u32 state12, state17, smlh_up, ltssm_up, rdlh_up;
+
+ do {
+ state12 = meson_cfg_readl(priv, PCIE_CFG_STATUS12);
+ state17 = meson_cfg_readl(priv, PCIE_CFG_STATUS17);
+ smlh_up = IS_SMLH_LINK_UP(state12);
+ rdlh_up = IS_RDLH_LINK_UP(state12);
+ ltssm_up = IS_LTSSM_UP(state12);
+
+ if (PM_CURRENT_STATE(state17) < PCIE_GEN3)
+ speed_okay = 1;
+
+ if (smlh_up)
+ debug("%s: smlh_link_up is on\n", __func__);
+ if (rdlh_up)
+ debug("%s: rdlh_link_up is on\n", __func__);
+ if (ltssm_up)
+ debug("%s: ltssm_up is on\n", __func__);
+ if (speed_okay)
+ debug("%s: speed_okay\n", __func__);
+
+ if (smlh_up && rdlh_up && ltssm_up && speed_okay)
+ return 0;
+
+ cnt++;
+
+ udelay(10);
+ } while (cnt < WAIT_LINKUP_TIMEOUT);
+
+ printf("%s: error: wait linkup timeout\n", __func__);
+ return -EIO;
+}
+
+/**
+ * meson_pcie_link_up() - Wait for the link to come up
+ *
+ * @meson_pcie: Pointer to the PCI controller state
+ * @cap_speed: Desired link speed
+ *
+ * Return: 1 (true) for active line and negative (false) for no link (timeout)
+ */
+static int meson_pcie_link_up(struct meson_pcie *priv, u32 cap_speed)
+{
+ /* DW link configurations */
+ meson_pcie_configure(priv);
+
+ /* Reset the device */
+ if (dm_gpio_is_valid(&priv->rst_gpio)) {
+ dm_gpio_set_value(&priv->rst_gpio, 1);
+ /*
+ * Minimal is 100ms from spec but we see
+ * some wired devices need much more, such as 600ms.
+ * Add a enough delay to cover all cases.
+ */
+ udelay(PERST_WAIT_US);
+ dm_gpio_set_value(&priv->rst_gpio, 0);
+ }
+
+ /* Enable LTSSM */
+ meson_pcie_enable_ltssm(priv);
+
+ return meson_pcie_wait_link_up(priv);
+}
+
+static int meson_size_to_payload(int size)
+{
+ /*
+ * dwc supports 2^(val+7) payload size, which val is 0~5 default to 1.
+ * So if input size is not 2^order alignment or less than 2^7 or bigger
+ * than 2^12, just set to default size 2^(1+7).
+ */
+ if (!is_power_of_2(size) || size < 128 || size > 4096) {
+ debug("%s: payload size %d, set to default 256\n", __func__, size);
+ return 1;
+ }
+
+ return fls(size) - 8;
+}
+
+static void meson_set_max_payload(struct meson_pcie *priv, int size)
+{
+ u32 val;
+ u16 offset = dm_pci_find_capability(priv->dw.dev, PCI_CAP_ID_EXP);
+ int max_payload_size = meson_size_to_payload(size);
+
+ dw_pcie_dbi_write_enable(&priv->dw, true);
+
+ val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
+ val &= ~PCI_EXP_DEVCTL_PAYLOAD;
+ writel(val, priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
+
+ val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
+ val |= PCIE_CAP_MAX_PAYLOAD_SIZE(max_payload_size);
+ writel(val, priv->dw.dbi_base + PCI_EXP_DEVCTL);
+
+ dw_pcie_dbi_write_enable(&priv->dw, false);
+}
+
+static void meson_set_max_rd_req_size(struct meson_pcie *priv, int size)
+{
+ u32 val;
+ u16 offset = dm_pci_find_capability(priv->dw.dev, PCI_CAP_ID_EXP);
+ int max_rd_req_size = meson_size_to_payload(size);
+
+ dw_pcie_dbi_write_enable(&priv->dw, true);
+
+ val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
+ val &= ~PCI_EXP_DEVCTL_PAYLOAD;
+ writel(val, priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
+
+ val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
+ val |= PCIE_CAP_MAX_READ_REQ_SIZE(max_rd_req_size);
+ writel(val, priv->dw.dbi_base + PCI_EXP_DEVCTL);
+
+ dw_pcie_dbi_write_enable(&priv->dw, false);
+}
+
+static int meson_pcie_init_port(struct udevice *dev)
+{
+ int ret;
+ struct meson_pcie *priv = dev_get_priv(dev);
+
+ 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 = generic_phy_reset(&priv->phy);
+ if (ret) {
+ dev_err(dev, "failed to reset phy (ret=%d)\n", ret);
+ goto err_exit_phy;
+ }
+
+ ret = reset_assert_bulk(&priv->rsts);
+ if (ret) {
+ dev_err(dev, "failed to assert resets (ret=%d)\n", ret);
+ goto err_power_off_phy;
+ }
+
+ udelay(PCIE_RESET_DELAY);
+
+ ret = reset_deassert_bulk(&priv->rsts);
+ if (ret) {
+ dev_err(dev, "failed to deassert resets (ret=%d)\n", ret);
+ goto err_power_off_phy;
+ }
+
+ udelay(PCIE_RESET_DELAY);
+
+ ret = clk_set_rate(&priv->clk_port, PORT_CLK_RATE);
+ if (ret) {
+ dev_err(dev, "failed to set port clk rate (ret=%d)\n", ret);
+ goto err_deassert_bulk;
+ }
+
+ ret = clk_enable(&priv->clk_general);
+ if (ret) {
+ dev_err(dev, "failed to enable clk general (ret=%d)\n", ret);
+ goto err_deassert_bulk;
+ }
+
+ ret = clk_enable(&priv->clk_pclk);
+ if (ret) {
+ dev_err(dev, "failed to enable pclk (ret=%d)\n", ret);
+ goto err_deassert_bulk;
+ }
+
+ meson_set_max_payload(priv, MAX_PAYLOAD_SIZE);
+ meson_set_max_rd_req_size(priv, MAX_READ_REQ_SIZE);
+
+ pcie_dw_setup_host(&priv->dw);
+
+ ret = meson_pcie_link_up(priv, LINK_SPEED_GEN_2);
+ if (ret < 0)
+ goto err_link_up;
+
+ return 0;
+err_link_up:
+ clk_disable(&priv->clk_port);
+ clk_disable(&priv->clk_general);
+ clk_disable(&priv->clk_pclk);
+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 meson_pcie_parse_dt(struct udevice *dev)
+{
+ struct meson_pcie *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->dw.dbi_base = (void *)dev_read_addr_index(dev, 0);
+ if (!priv->dw.dbi_base)
+ return -ENODEV;
+
+ dev_dbg(dev, "ELBI address is 0x%p\n", priv->dw.dbi_base);
+
+ priv->meson_cfg_base = (void *)dev_read_addr_index(dev, 1);
+ if (!priv->meson_cfg_base)
+ return -ENODEV;
+
+ dev_dbg(dev, "CFG address is 0x%p\n", priv->meson_cfg_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_by_name(dev, "port", &priv->clk_port);
+ if (ret) {
+ dev_err(dev, "Can't get port clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_get_by_name(dev, "general", &priv->clk_general);
+ if (ret) {
+ dev_err(dev, "Can't get port clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_get_by_name(dev, "pclk", &priv->clk_pclk);
+ if (ret) {
+ dev_err(dev, "Can't get port clock: %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;
+}
+
+/**
+ * meson_pcie_probe() - Probe the PCIe bus for active link
+ *
+ * @dev: A pointer to the device being operated on
+ *
+ * Probe for an active link on the PCIe bus and configure the controller
+ * to enable this port.
+ *
+ * Return: 0 on success, else -ENODEV
+ */
+static int meson_pcie_probe(struct udevice *dev)
+{
+ struct meson_pcie *priv = dev_get_priv(dev);
+ struct udevice *ctlr = pci_get_controller(dev);
+ struct pci_controller *hose = dev_get_uclass_priv(ctlr);
+ int ret = 0;
+
+ priv->dw.first_busno = dev_seq(dev);
+ priv->dw.dev = dev;
+
+ ret = meson_pcie_parse_dt(dev);
+ if (ret)
+ return ret;
+
+ ret = meson_pcie_init_port(dev);
+ if (ret) {
+ dm_gpio_free(dev, &priv->rst_gpio);
+ return ret;
+ }
+
+ printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n",
+ dev_seq(dev), pcie_dw_get_link_speed(&priv->dw),
+ pcie_dw_get_link_width(&priv->dw),
+ hose->first_busno);
+
+ return pcie_dw_prog_outbound_atu_unroll(&priv->dw,
+ PCIE_ATU_REGION_INDEX0,
+ PCIE_ATU_TYPE_MEM,
+ priv->dw.mem.phys_start,
+ priv->dw.mem.bus_start,
+ priv->dw.mem.size);
+}
+
+static const struct dm_pci_ops meson_pcie_ops = {
+ .read_config = pcie_dw_read_config,
+ .write_config = pcie_dw_write_config,
+};
+
+static const struct udevice_id meson_pcie_ids[] = {
+ { .compatible = "amlogic,axg-pcie" },
+ { .compatible = "amlogic,g12a-pcie" },
+ { }
+};
+
+U_BOOT_DRIVER(meson_dw_pcie) = {
+ .name = "pcie_dw_meson",
+ .id = UCLASS_PCI,
+ .of_match = meson_pcie_ids,
+ .ops = &meson_pcie_ops,
+ .probe = meson_pcie_probe,
+ .priv_auto = sizeof(struct meson_pcie),
+};