diff options
Diffstat (limited to 'drivers/pci')
22 files changed, 1521 insertions, 184 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 31ec770b433d..4310c7a4212e 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -103,9 +103,9 @@ config PCI_PF_STUB depends on PCI_IOV help Say Y or M here if you want to enable support for devices that - require SR-IOV support, while at the same time the PF itself is - not providing any actual services on the host itself such as - storage or networking. + require SR-IOV support, while at the same time the PF (Physical + Function) itself is not providing any actual services on the + host itself such as storage or networking. When in doubt, say N. diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index 91b0194240a5..548c58223868 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -89,8 +89,8 @@ config PCI_EXYNOS select PCIE_DW_HOST config PCI_IMX6 - bool "Freescale i.MX6 PCIe controller" - depends on SOC_IMX6Q || (ARM && COMPILE_TEST) + bool "Freescale i.MX6/7 PCIe controller" + depends on SOC_IMX6Q || SOC_IMX7D || (ARM && COMPILE_TEST) depends on PCI_MSI_IRQ_DOMAIN select PCIE_DW_HOST @@ -193,4 +193,24 @@ config PCIE_HISI_STB help Say Y here if you want PCIe controller support on HiSilicon STB SoCs +config PCI_MESON + bool "MESON PCIe controller" + depends on PCI_MSI_IRQ_DOMAIN + select PCIE_DW_HOST + help + Say Y here if you want to enable PCI controller support on Amlogic + SoCs. The PCI controller on Amlogic is based on DesignWare hardware + and therefore the driver re-uses the DesignWare core functions to + implement the driver. + +config PCIE_UNIPHIER + bool "Socionext UniPhier PCIe controllers" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on OF && HAS_IOMEM + depends on PCI_MSI_IRQ_DOMAIN + select PCIE_DW_HOST + help + Say Y here if you want PCIe controller support on UniPhier SoCs. + This driver supports LD20 and PXs3 SoCs. + endmenu diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index fcf91eacfc63..7bcdcdf5024e 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -14,6 +14,8 @@ obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o obj-$(CONFIG_PCIE_ARTPEC6) += pcie-artpec6.o obj-$(CONFIG_PCIE_KIRIN) += pcie-kirin.o obj-$(CONFIG_PCIE_HISI_STB) += pcie-histb.o +obj-$(CONFIG_PCI_MESON) += pci-meson.o +obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o # The following drivers are for devices that use the generic ACPI # pci_root.c driver but don't support standard ECAM config access. diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 88af6bff945f..52e47dac028f 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -27,6 +27,8 @@ #include <linux/types.h> #include <linux/interrupt.h> #include <linux/reset.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> #include "pcie-designware.h" @@ -59,6 +61,11 @@ struct imx6_pcie { u32 tx_swing_low; int link_gen; struct regulator *vpcie; + + /* power domain for pcie */ + struct device *pd_pcie; + /* power domain for pcie phy */ + struct device *pd_pcie_phy; }; /* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */ @@ -67,6 +74,7 @@ struct imx6_pcie { #define PHY_PLL_LOCK_WAIT_USLEEP_MAX 200 /* PCIe Root Complex registers (memory-mapped) */ +#define PCIE_RC_IMX6_MSI_CAP 0x50 #define PCIE_RC_LCR 0x7c #define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1 0x1 #define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2 0x2 @@ -290,6 +298,43 @@ static int imx6q_pcie_abort_handler(unsigned long addr, return 1; } +static int imx6_pcie_attach_pd(struct device *dev) +{ + struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev); + struct device_link *link; + + /* Do nothing when in a single power domain */ + if (dev->pm_domain) + return 0; + + imx6_pcie->pd_pcie = dev_pm_domain_attach_by_name(dev, "pcie"); + if (IS_ERR(imx6_pcie->pd_pcie)) + return PTR_ERR(imx6_pcie->pd_pcie); + link = device_link_add(dev, imx6_pcie->pd_pcie, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!link) { + dev_err(dev, "Failed to add device_link to pcie pd.\n"); + return -EINVAL; + } + + imx6_pcie->pd_pcie_phy = dev_pm_domain_attach_by_name(dev, "pcie_phy"); + if (IS_ERR(imx6_pcie->pd_pcie_phy)) + return PTR_ERR(imx6_pcie->pd_pcie_phy); + + device_link_add(dev, imx6_pcie->pd_pcie_phy, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(link)) { + dev_err(dev, "Failed to add device_link to pcie_phy pd: %ld\n", PTR_ERR(link)); + return PTR_ERR(link); + } + + return 0; +} + static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) { struct device *dev = imx6_pcie->pci->dev; @@ -765,8 +810,28 @@ static void imx6_pcie_ltssm_disable(struct device *dev) static void imx6_pcie_pm_turnoff(struct imx6_pcie *imx6_pcie) { - reset_control_assert(imx6_pcie->turnoff_reset); - reset_control_deassert(imx6_pcie->turnoff_reset); + struct device *dev = imx6_pcie->pci->dev; + + /* Some variants have a turnoff reset in DT */ + if (imx6_pcie->turnoff_reset) { + reset_control_assert(imx6_pcie->turnoff_reset); + reset_control_deassert(imx6_pcie->turnoff_reset); + goto pm_turnoff_sleep; + } + + /* Others poke directly at IOMUXC registers */ + switch (imx6_pcie->variant) { + case IMX6SX: + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_PM_TURN_OFF, + IMX6SX_GPR12_PCIE_PM_TURN_OFF); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_PM_TURN_OFF, 0); + break; + default: + dev_err(dev, "PME_Turn_Off not implemented\n"); + return; + } /* * Components with an upstream port must respond to @@ -775,6 +840,7 @@ static void imx6_pcie_pm_turnoff(struct imx6_pcie *imx6_pcie) * The standard recommends a 1-10ms timeout after which to * proceed anyway as if acks were received. */ +pm_turnoff_sleep: usleep_range(1000, 10000); } @@ -784,18 +850,31 @@ static void imx6_pcie_clk_disable(struct imx6_pcie *imx6_pcie) clk_disable_unprepare(imx6_pcie->pcie_phy); clk_disable_unprepare(imx6_pcie->pcie_bus); - if (imx6_pcie->variant == IMX7D) { + switch (imx6_pcie->variant) { + case IMX6SX: + clk_disable_unprepare(imx6_pcie->pcie_inbound_axi); + break; + case IMX7D: regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, IMX7D_GPR12_PCIE_PHY_REFCLK_SEL); + break; + default: + break; } } +static inline bool imx6_pcie_supports_suspend(struct imx6_pcie *imx6_pcie) +{ + return (imx6_pcie->variant == IMX7D || + imx6_pcie->variant == IMX6SX); +} + static int imx6_pcie_suspend_noirq(struct device *dev) { struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev); - if (imx6_pcie->variant != IMX7D) + if (!imx6_pcie_supports_suspend(imx6_pcie)) return 0; imx6_pcie_pm_turnoff(imx6_pcie); @@ -811,7 +890,7 @@ static int imx6_pcie_resume_noirq(struct device *dev) struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev); struct pcie_port *pp = &imx6_pcie->pci->pp; - if (imx6_pcie->variant != IMX7D) + if (!imx6_pcie_supports_suspend(imx6_pcie)) return 0; imx6_pcie_assert_core_reset(imx6_pcie); @@ -840,6 +919,7 @@ static int imx6_pcie_probe(struct platform_device *pdev) struct resource *dbi_base; struct device_node *node = dev->of_node; int ret; + u16 val; imx6_pcie = devm_kzalloc(dev, sizeof(*imx6_pcie), GFP_KERNEL); if (!imx6_pcie) @@ -977,10 +1057,22 @@ static int imx6_pcie_probe(struct platform_device *pdev) platform_set_drvdata(pdev, imx6_pcie); + ret = imx6_pcie_attach_pd(dev); + if (ret) + return ret; + ret = imx6_add_pcie_port(imx6_pcie, pdev); if (ret < 0) return ret; + if (pci_msi_enabled()) { + val = dw_pcie_readw_dbi(pci, PCIE_RC_IMX6_MSI_CAP + + PCI_MSI_FLAGS); + val |= PCI_MSI_FLAGS_ENABLE; + dw_pcie_writew_dbi(pci, PCIE_RC_IMX6_MSI_CAP + PCI_MSI_FLAGS, + val); + } + return 0; } diff --git a/drivers/pci/controller/dwc/pci-layerscape.c b/drivers/pci/controller/dwc/pci-layerscape.c index 7aa9a82b7ebd..ce45bde29bf8 100644 --- a/drivers/pci/controller/dwc/pci-layerscape.c +++ b/drivers/pci/controller/dwc/pci-layerscape.c @@ -222,12 +222,12 @@ static const struct dw_pcie_ops dw_ls_pcie_ops = { .link_up = ls_pcie_link_up, }; -static struct ls_pcie_drvdata ls1021_drvdata = { +static const struct ls_pcie_drvdata ls1021_drvdata = { .ops = &ls1021_pcie_host_ops, .dw_pcie_ops = &dw_ls1021_pcie_ops, }; -static struct ls_pcie_drvdata ls1043_drvdata = { +static const struct ls_pcie_drvdata ls1043_drvdata = { .lut_offset = 0x10000, .ltssm_shift = 24, .lut_dbg = 0x7fc, @@ -235,7 +235,7 @@ static struct ls_pcie_drvdata ls1043_drvdata = { .dw_pcie_ops = &dw_ls_pcie_ops, }; -static struct ls_pcie_drvdata ls1046_drvdata = { +static const struct ls_pcie_drvdata ls1046_drvdata = { .lut_offset = 0x80000, .ltssm_shift = 24, .lut_dbg = 0x407fc, @@ -243,7 +243,7 @@ static struct ls_pcie_drvdata ls1046_drvdata = { .dw_pcie_ops = &dw_ls_pcie_ops, }; -static struct ls_pcie_drvdata ls2080_drvdata = { +static const struct ls_pcie_drvdata ls2080_drvdata = { .lut_offset = 0x80000, .ltssm_shift = 0, .lut_dbg = 0x7fc, @@ -251,7 +251,7 @@ static struct ls_pcie_drvdata ls2080_drvdata = { .dw_pcie_ops = &dw_ls_pcie_ops, }; -static struct ls_pcie_drvdata ls2088_drvdata = { +static const struct ls_pcie_drvdata ls2088_drvdata = { .lut_offset = 0x80000, .ltssm_shift = 0, .lut_dbg = 0x407fc, diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c new file mode 100644 index 000000000000..241ebe0c4505 --- /dev/null +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCIe host controller driver for Amlogic MESON SoCs + * + * Copyright (c) 2018 Amlogic, inc. + * Author: Yue Wang <yue.wang@amlogic.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/resource.h> +#include <linux/types.h> + +#include "pcie-designware.h" + +#define to_meson_pcie(x) dev_get_drvdata((x)->dev) + +/* External local bus interface registers */ +#define PLR_OFFSET 0x700 +#define PCIE_PORT_LINK_CTRL_OFF (PLR_OFFSET + 0x10) +#define FAST_LINK_MODE BIT(7) +#define LINK_CAPABLE_MASK GENMASK(21, 16) +#define LINK_CAPABLE_X1 BIT(16) + +#define PCIE_GEN2_CTRL_OFF (PLR_OFFSET + 0x10c) +#define NUM_OF_LANES_MASK GENMASK(12, 8) +#define NUM_OF_LANES_X1 BIT(8) +#define DIRECT_SPEED_CHANGE BIT(17) + +#define TYPE1_HDR_OFFSET 0x0 +#define PCIE_STATUS_COMMAND (TYPE1_HDR_OFFSET + 0x04) +#define PCI_IO_EN BIT(0) +#define PCI_MEM_SPACE_EN BIT(1) +#define PCI_BUS_MASTER_EN BIT(2) + +#define PCIE_BASE_ADDR0 (TYPE1_HDR_OFFSET + 0x10) +#define PCIE_BASE_ADDR1 (TYPE1_HDR_OFFSET + 0x14) + +#define PCIE_CAP_OFFSET 0x70 +#define PCIE_DEV_CTRL_DEV_STUS (PCIE_CAP_OFFSET + 0x08) +#define PCIE_CAP_MAX_PAYLOAD_MASK GENMASK(7, 5) +#define PCIE_CAP_MAX_PAYLOAD_SIZE(x) ((x) << 5) +#define PCIE_CAP_MAX_READ_REQ_MASK GENMASK(14, 12) +#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 MESON_PCIE_PHY_POWERUP 0x1c +#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 +}; + +struct meson_pcie_mem_res { + void __iomem *elbi_base; + void __iomem *cfg_base; + void __iomem *phy_base; +}; + +struct meson_pcie_clk_res { + struct clk *clk; + struct clk *mipi_gate; + struct clk *port_clk; + struct clk *general_clk; +}; + +struct meson_pcie_rc_reset { + struct reset_control *phy; + struct reset_control *port; + struct reset_control *apb; +}; + +struct meson_pcie { + struct dw_pcie pci; + struct meson_pcie_mem_res mem_res; + struct meson_pcie_clk_res clk_res; + struct meson_pcie_rc_reset mrst; + struct gpio_desc *reset_gpio; +}; + +static struct reset_control *meson_pcie_get_reset(struct meson_pcie *mp, + const char *id, + u32 reset_type) +{ + struct device *dev = mp->pci.dev; + struct reset_control *reset; + + if (reset_type == PCIE_SHARED_RESET) + reset = devm_reset_control_get_shared(dev, id); + else + reset = devm_reset_control_get(dev, id); + + return reset; +} + +static int meson_pcie_get_resets(struct meson_pcie *mp) +{ + struct meson_pcie_rc_reset *mrst = &mp->mrst; + + mrst->phy = meson_pcie_get_reset(mp, "phy", PCIE_SHARED_RESET); + if (IS_ERR(mrst->phy)) + return PTR_ERR(mrst->phy); + reset_control_deassert(mrst->phy); + + mrst->port = meson_pcie_get_reset(mp, "port", PCIE_NORMAL_RESET); + if (IS_ERR(mrst->port)) + return PTR_ERR(mrst->port); + reset_control_deassert(mrst->port); + + mrst->apb = meson_pcie_get_reset(mp, "apb", PCIE_SHARED_RESET); + if (IS_ERR(mrst->apb)) + return PTR_ERR(mrst->apb); + reset_control_deassert(mrst->apb); + + return 0; +} + +static void __iomem *meson_pcie_get_mem(struct platform_device *pdev, + struct meson_pcie *mp, + const char *id) +{ + struct device *dev = mp->pci.dev; + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, id); + + return devm_ioremap_resource(dev, res); +} + +static void __iomem *meson_pcie_get_mem_shared(struct platform_device *pdev, + struct meson_pcie *mp, + const char *id) +{ + struct device *dev = mp->pci.dev; + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, id); + if (!res) { + dev_err(dev, "No REG resource %s\n", id); + return ERR_PTR(-ENXIO); + } + + return devm_ioremap(dev, res->start, resource_size(res)); +} + +static int meson_pcie_get_mems(struct platform_device *pdev, + struct meson_pcie *mp) +{ + mp->mem_res.elbi_base = meson_pcie_get_mem(pdev, mp, "elbi"); + if (IS_ERR(mp->mem_res.elbi_base)) + return PTR_ERR(mp->mem_res.elbi_base); + + mp->mem_res.cfg_base = meson_pcie_get_mem(pdev, mp, "cfg"); + if (IS_ERR(mp->mem_res.cfg_base)) + return PTR_ERR(mp->mem_res.cfg_base); + + /* Meson SoC has two PCI controllers use same phy register*/ + mp->mem_res.phy_base = meson_pcie_get_mem_shared(pdev, mp, "phy"); + if (IS_ERR(mp->mem_res.phy_base)) + return PTR_ERR(mp->mem_res.phy_base); + + return 0; +} + +static void meson_pcie_power_on(struct meson_pcie *mp) +{ + writel(MESON_PCIE_PHY_POWERUP, mp->mem_res.phy_base); +} + +static void meson_pcie_reset(struct meson_pcie *mp) +{ + struct meson_pcie_rc_reset *mrst = &mp->mrst; + + reset_control_assert(mrst->phy); + udelay(PCIE_RESET_DELAY); + reset_control_deassert(mrst->phy); + udelay(PCIE_RESET_DELAY); + + reset_control_assert(mrst->port); + reset_control_assert(mrst->apb); + udelay(PCIE_RESET_DELAY); + reset_control_deassert(mrst->port); + reset_control_deassert(mrst->apb); + udelay(PCIE_RESET_DELAY); +} + +static inline struct clk *meson_pcie_probe_clock(struct device *dev, + const char *id, u64 rate) +{ + struct clk *clk; + int ret; + + clk = devm_clk_get(dev, id); + if (IS_ERR(clk)) + return clk; + + if (rate) { + ret = clk_set_rate(clk, rate); + if (ret) { + dev_err(dev, "set clk rate failed, ret = %d\n", ret); + return ERR_PTR(ret); + } + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(dev, "couldn't enable clk\n"); + return ERR_PTR(ret); + } + + devm_add_action_or_reset(dev, + (void (*) (void *))clk_disable_unprepare, + clk); + + return clk; +} + +static int meson_pcie_probe_clocks(struct meson_pcie *mp) +{ + struct device *dev = mp->pci.dev; + struct meson_pcie_clk_res *res = &mp->clk_res; + + res->port_clk = meson_pcie_probe_clock(dev, "port", PORT_CLK_RATE); + if (IS_ERR(res->port_clk)) + return PTR_ERR(res->port_clk); + + res->mipi_gate = meson_pcie_probe_clock(dev, "pcie_mipi_en", 0); + if (IS_ERR(res->mipi_gate)) + return PTR_ERR(res->mipi_gate); + + res->general_clk = meson_pcie_probe_clock(dev, "pcie_general", 0); + if (IS_ERR(res->general_clk)) + return PTR_ERR(res->general_clk); + + res->clk = meson_pcie_probe_clock(dev, "pcie", 0); + if (IS_ERR(res->clk)) + return PTR_ERR(res->clk); + + return 0; +} + +static inline void meson_elb_writel(struct meson_pcie *mp, u32 val, u32 reg) +{ + writel(val, mp->mem_res.elbi_base + reg); +} + +static inline u32 meson_elb_readl(struct meson_pcie *mp, u32 reg) +{ + return readl(mp->mem_res.elbi_base + reg); +} + +static inline u32 meson_cfg_readl(struct meson_pcie *mp, u32 reg) +{ + return readl(mp->mem_res.cfg_base + reg); +} + +static inline void meson_cfg_writel(struct meson_pcie *mp, u32 val, u32 reg) +{ + writel(val, mp->mem_res.cfg_base + reg); +} + +static void meson_pcie_assert_reset(struct meson_pcie *mp) +{ + gpiod_set_value_cansleep(mp->reset_gpio, 0); + udelay(500); + gpiod_set_value_cansleep(mp->reset_gpio, 1); +} + +static void meson_pcie_init_dw(struct meson_pcie *mp) +{ + u32 val; + + val = meson_cfg_readl(mp, PCIE_CFG0); + val |= APP_LTSSM_ENABLE; + meson_cfg_writel(mp, val, PCIE_CFG0); + + val = meson_elb_readl(mp, PCIE_PORT_LINK_CTRL_OFF); + val &= ~LINK_CAPABLE_MASK; + meson_elb_writel(mp, val, PCIE_PORT_LINK_CTRL_OFF); + + val = meson_elb_readl(mp, PCIE_PORT_LINK_CTRL_OFF); + val |= LINK_CAPABLE_X1 | FAST_LINK_MODE; + meson_elb_writel(mp, val, PCIE_PORT_LINK_CTRL_OFF); + + val = meson_elb_readl(mp, PCIE_GEN2_CTRL_OFF); + val &= ~NUM_OF_LANES_MASK; + meson_elb_writel(mp, val, PCIE_GEN2_CTRL_OFF); + + val = meson_elb_readl(mp, PCIE_GEN2_CTRL_OFF); + val |= NUM_OF_LANES_X1 | DIRECT_SPEED_CHANGE; + meson_elb_writel(mp, val, PCIE_GEN2_CTRL_OFF); + + meson_elb_writel(mp, 0x0, PCIE_BASE_ADDR0); + meson_elb_writel(mp, 0x0, PCIE_BASE_ADDR1); +} + +static int meson_size_to_payload(struct meson_pcie *mp, int size) +{ + struct device *dev = mp->pci.dev; + + /* + * 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) { + dev_warn(dev, "payload size %d, set to default 256\n", size); + return 1; + } + + return fls(size) - 8; +} + +static void meson_set_max_payload(struct meson_pcie *mp, int size) +{ + u32 val; + int max_payload_size = meson_size_to_payload(mp, size); + + val = meson_elb_readl(mp, PCIE_DEV_CTRL_DEV_STUS); + val &= ~PCIE_CAP_MAX_PAYLOAD_MASK; + meson_elb_writel(mp, val, PCIE_DEV_CTRL_DEV_STUS); + + val = meson_elb_readl(mp, PCIE_DEV_CTRL_DEV_STUS); + val |= PCIE_CAP_MAX_PAYLOAD_SIZE(max_payload_size); + meson_elb_writel(mp, val, PCIE_DEV_CTRL_DEV_STUS); +} + +static void meson_set_max_rd_req_size(struct meson_pcie *mp, int size) +{ + u32 val; + int max_rd_req_size = meson_size_to_payload(mp, size); + + val = meson_elb_readl(mp, PCIE_DEV_CTRL_DEV_STUS); + val &= ~PCIE_CAP_MAX_READ_REQ_MASK; + meson_elb_writel(mp, val, PCIE_DEV_CTRL_DEV_STUS); + + val = meson_elb_readl(mp, PCIE_DEV_CTRL_DEV_STUS); + val |= PCIE_CAP_MAX_READ_REQ_SIZE(max_rd_req_size); + meson_elb_writel(mp, val, PCIE_DEV_CTRL_DEV_STUS); +} + +static inline void meson_enable_memory_space(struct meson_pcie *mp) +{ + /* Set the RC Bus Master, Memory Space and I/O Space enables */ + meson_elb_writel(mp, PCI_IO_EN | PCI_MEM_SPACE_EN | PCI_BUS_MASTER_EN, + PCIE_STATUS_COMMAND); +} + +static int meson_pcie_establish_link(struct meson_pcie *mp) +{ + struct dw_pcie *pci = &mp->pci; + struct pcie_port *pp = &pci->pp; + + meson_pcie_init_dw(mp); + meson_set_max_payload(mp, MAX_PAYLOAD_SIZE); + meson_set_max_rd_req_size(mp, MAX_READ_REQ_SIZE); + + dw_pcie_setup_rc(pp); + meson_enable_memory_space(mp); + + meson_pcie_assert_reset(mp); + + return dw_pcie_wait_for_link(pci); +} + +static void meson_pcie_enable_interrupts(struct meson_pcie *mp) +{ + if (IS_ENABLED(CONFIG_PCI_MSI)) + dw_pcie_msi_init(&mp->pci.pp); +} + +static int meson_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, + u32 *val) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + int ret; + + ret = dw_pcie_read(pci->dbi_base + where, size, val); + if (ret != PCIBIOS_SUCCESSFUL) + return ret; + + /* + * There is a bug in the MESON AXG PCIe controller whereby software + * cannot program the PCI_CLASS_DEVICE register, so we must fabricate + * the return value in the config accessors. + */ + if (where == PCI_CLASS_REVISION && size == 4) + *val = (PCI_CLASS_BRIDGE_PCI << 16) | (*val & 0xffff); + else if (where == PCI_CLASS_DEVICE && size == 2) + *val = PCI_CLASS_BRIDGE_PCI; + else if (where == PCI_CLASS_DEVICE && size == 1) + *val = PCI_CLASS_BRIDGE_PCI & 0xff; + else if (where == PCI_CLASS_DEVICE + 1 && size == 1) + *val = (PCI_CLASS_BRIDGE_PCI >> 8) & 0xff; + + return PCIBIOS_SUCCESSFUL; +} + +static int meson_pcie_wr_own_conf(struct pcie_port *pp, int where, + int size, u32 val) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + + return dw_pcie_write(pci->dbi_base + where, size, val); +} + +static int meson_pcie_link_up(struct dw_pcie *pci) +{ + struct meson_pcie *mp = to_meson_pcie(pci); + struct device *dev = pci->dev; + u32 speed_okay = 0; + u32 cnt = 0; + u32 state12, state17, smlh_up, ltssm_up, rdlh_up; + + do { + state12 = meson_cfg_readl(mp, PCIE_CFG_STATUS12); + state17 = meson_cfg_readl(mp, 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) + dev_dbg(dev, "smlh_link_up is on\n"); + if (rdlh_up) + dev_dbg(dev, "rdlh_link_up is on\n"); + if (ltssm_up) + dev_dbg(dev, "ltssm_up is on\n"); + if (speed_okay) + dev_dbg(dev, "speed_okay\n"); + + if (smlh_up && rdlh_up && ltssm_up && speed_okay) + return 1; + + cnt++; + + udelay(10); + } while (cnt < WAIT_LINKUP_TIMEOUT); + + dev_err(dev, "error: wait linkup timeout\n"); + return 0; +} + +static int meson_pcie_host_init(struct pcie_port *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct meson_pcie *mp = to_meson_pcie(pci); + int ret; + + ret = meson_pcie_establish_link(mp); + if (ret) + return ret; + + meson_pcie_enable_interrupts(mp); + + return 0; +} + +static const struct dw_pcie_host_ops meson_pcie_host_ops = { + .rd_own_conf = meson_pcie_rd_own_conf, + .wr_own_conf = meson_pcie_wr_own_conf, + .host_init = meson_pcie_host_init, +}; + +static int meson_add_pcie_port(struct meson_pcie *mp, + struct platform_device *pdev) +{ + struct dw_pcie *pci = &mp->pci; + struct pcie_port *pp = &pci->pp; + struct device *dev = &pdev->dev; + int ret; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + pp->msi_irq = platform_get_irq(pdev, 0); + if (pp->msi_irq < 0) { + dev_err(dev, "failed to get MSI IRQ\n"); + return pp->msi_irq; + } + } + + pp->ops = &meson_pcie_host_ops; + pci->dbi_base = mp->mem_res.elbi_base; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} + +static const struct dw_pcie_ops dw_pcie_ops = { + .link_up = meson_pcie_link_up, +}; + +static int meson_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dw_pcie *pci; + struct meson_pcie *mp; + int ret; + + mp = devm_kzalloc(dev, sizeof(*mp), GFP_KERNEL); + if (!mp) + return -ENOMEM; + + pci = &mp->pci; + pci->dev = dev; + pci->ops = &dw_pcie_ops; + + mp->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(mp->reset_gpio)) { + dev_err(dev, "get reset gpio failed\n"); + return PTR_ERR(mp->reset_gpio); + } + + ret = meson_pcie_get_resets(mp); + if (ret) { + dev_err(dev, "get reset resource failed, %d\n", ret); + return ret; + } + + ret = meson_pcie_get_mems(pdev, mp); + if (ret) { + dev_err(dev, "get memory resource failed, %d\n", ret); + return ret; + } + + meson_pcie_power_on(mp); + meson_pcie_reset(mp); + + ret = meson_pcie_probe_clocks(mp); + if (ret) { + dev_err(dev, "init clock resources failed, %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, mp); + + ret = meson_add_pcie_port(mp, pdev); + if (ret < 0) { + dev_err(dev, "Add PCIe port failed, %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id meson_pcie_of_match[] = { + { + .compatible = "amlogic,axg-pcie", + }, + {}, +}; + +static struct platform_driver meson_pcie_driver = { + .probe = meson_pcie_probe, + .driver = { + .name = "meson-pcie", + .of_match_table = meson_pcie_of_match, + }, +}; + +builtin_platform_driver(meson_pcie_driver); diff --git a/drivers/pci/controller/dwc/pcie-armada8k.c b/drivers/pci/controller/dwc/pcie-armada8k.c index 0c389a30ef5d..b171b6bc15c8 100644 --- a/drivers/pci/controller/dwc/pcie-armada8k.c +++ b/drivers/pci/controller/dwc/pcie-armada8k.c @@ -22,6 +22,7 @@ #include <linux/resource.h> #include <linux/of_pci.h> #include <linux/of_irq.h> +#include <linux/gpio/consumer.h> #include "pcie-designware.h" @@ -29,6 +30,7 @@ struct armada8k_pcie { struct dw_pcie *pci; struct clk *clk; struct clk *clk_reg; + struct gpio_desc *reset_gpio; }; #define PCIE_VENDOR_REGS_OFFSET 0x8000 @@ -137,6 +139,12 @@ static int armada8k_pcie_host_init(struct pcie_port *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct armada8k_pcie *pcie = to_armada8k_pcie(pci); + if (pcie->reset_gpio) { + /* assert and then deassert the reset signal */ + gpiod_set_value_cansleep(pcie->reset_gpio, 1); + msleep(100); + gpiod_set_value_cansleep(pcie->reset_gpio, 0); + } dw_pcie_setup_rc(pp); armada8k_pcie_establish_link(pcie); @@ -249,6 +257,14 @@ static int armada8k_pcie_probe(struct platform_device *pdev) goto fail_clkreg; } + /* Get reset gpio signal and hold asserted (logically high) */ + pcie->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(pcie->re |