From 127dbec39a5754346ca211460451b11412fe2cb3 Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Wed, 26 May 2021 17:59:34 +0200 Subject: arm: a37xx: pci: Don't put link into LTSSM Recovery state during probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During our debugging of the Aardvark driver in Linux we have discovered that the PCIE_CORE_LINK_CTRL_STAT_REG register in fact controls standard PCIe Link Control Register for PCIe Root Bridge. This led us to discover that the name of the PCIE_CORE_LINK_TRAINING macro and the corresponding comment by this macro's usage is misleading; this bit in fact controls Retrain Link, which, according to PCIe base spec is defined as: A write of 1b to this bit initiates Link retraining by directing the Physical Layer LTSSM to the Recovery state. If the LTSSM is already in Recovery or Configuration, re-entering Recovery is permitted but not required. Entering Recovery state is normally done from LTSSM L0, L0s and L1 states. But since the pci-aardvark.c driver enables Link Training just a few lines above, the controller is not in L0 ready state yet. So setting aardvark bit PCIE_CORE_LINK_TRAINING does not actually enter Recovery state at this place. Moreover, trying to enter LTSSM Recovery state without other configuration is causing issues for some cards (e.g. Atheros AR9xxx and QCA9xxx). Since Recovery state is not entered, these issues are not triggered. Remove code which tries to enter LTSSM Recovery state completely. Signed-off-by: Pali Rohár Reviewed-by: Marek Behún Reviewed-by: Stefan Roese --- drivers/pci/pci-aardvark.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-aardvark.c b/drivers/pci/pci-aardvark.c index c43d4f309b1..06c567e236f 100644 --- a/drivers/pci/pci-aardvark.c +++ b/drivers/pci/pci-aardvark.c @@ -613,11 +613,6 @@ static int pcie_advk_setup_hw(struct pcie_advk *pcie) reg |= PIO_CTRL_ADDR_WIN_DISABLE; advk_writel(pcie, reg, PIO_CTRL); - /* Start link training */ - reg = advk_readl(pcie, PCIE_CORE_LINK_CTRL_STAT_REG); - reg |= PCIE_CORE_LINK_TRAINING; - advk_writel(pcie, reg, PCIE_CORE_LINK_CTRL_STAT_REG); - /* Wait for PCIe link up */ if (pcie_advk_wait_for_link(pcie)) return -ENXIO; -- cgit v1.3.1 From 7b85aefd4ba502158aceae44c1d03eb853bf89be Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Wed, 26 May 2021 17:59:35 +0200 Subject: arm: a37xx: pci: Disable bus mastering when unloading driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disable Root Bridge I/O space, memory space and bus mastering in Aardvark's remove method, which is called before booting Linux kernel. This ensures that PCIe device which was initialized and used by U-Boot cannot do new DMA transfers until Linux initializes PCI subsystem and loads appropriate drivers for the device. During initialization of PCI subsystem Linux in fact disables this bus mastering on Root Bridge (and later enables it when driver is loaded and configured), but there is a possibility of a small window after U-Boot boots Linux when bus mastering is enabled, which is not correct. Signed-off-by: Pali Rohár Reviewed-by: Marek Behún Reviewed-by: Stefan Roese --- drivers/pci/pci-aardvark.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pci-aardvark.c b/drivers/pci/pci-aardvark.c index 06c567e236f..ee81b2ea46d 100644 --- a/drivers/pci/pci-aardvark.c +++ b/drivers/pci/pci-aardvark.c @@ -675,6 +675,12 @@ static int pcie_advk_remove(struct udevice *dev) struct pcie_advk *pcie = dev_get_priv(dev); u32 reg; + reg = advk_readl(pcie, PCIE_CORE_CMD_STATUS_REG); + reg &= ~(PCIE_CORE_CMD_MEM_ACCESS_EN | + PCIE_CORE_CMD_IO_ACCESS_EN | + PCIE_CORE_CMD_MEM_IO_REQ_EN); + advk_writel(pcie, reg, PCIE_CORE_CMD_STATUS_REG); + reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG); reg &= ~LINK_TRAINING_EN; advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG); -- cgit v1.3.1 From a544d65f1ddf0a14c49ca137aea1f2407edddb4c Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Wed, 26 May 2021 17:59:36 +0200 Subject: arm: a37xx: pci: Fix DT compatible string to Linux' DT compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change DT compatible string for A3700 PCIe from 'marvell,armada-37xx-pcie' to 'marvell,armada-3700-pcie' to make U-Boot A3700 PCIe DT node compatible with Linux' DT node. Signed-off-by: Pali Rohár Reviewed-by: Marek Behún Reviewed-by: Stefan Roese --- arch/arm/dts/armada-37xx.dtsi | 2 +- drivers/pci/pci-aardvark.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/arch/arm/dts/armada-37xx.dtsi b/arch/arm/dts/armada-37xx.dtsi index a1052add0cc..b7d325b4057 100644 --- a/arch/arm/dts/armada-37xx.dtsi +++ b/arch/arm/dts/armada-37xx.dtsi @@ -323,7 +323,7 @@ }; pcie0: pcie@d0070000 { - compatible = "marvell,armada-37xx-pcie"; + compatible = "marvell,armada-3700-pcie"; reg = <0 0xd0070000 0 0x20000>; #address-cells = <3>; #size-cells = <2>; diff --git a/drivers/pci/pci-aardvark.c b/drivers/pci/pci-aardvark.c index ee81b2ea46d..ae1a20551fe 100644 --- a/drivers/pci/pci-aardvark.c +++ b/drivers/pci/pci-aardvark.c @@ -717,7 +717,7 @@ static const struct dm_pci_ops pcie_advk_ops = { }; static const struct udevice_id pcie_advk_ids[] = { - { .compatible = "marvell,armada-37xx-pcie" }, + { .compatible = "marvell,armada-3700-pcie" }, { } }; -- cgit v1.3.1 From b321722f2215c5012194929d20f00d29ce8015be Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Wed, 26 May 2021 17:59:40 +0200 Subject: arm: a37xx: pci: Fix configuring PCIe resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `ranges` DT property of the PCIe node is currently ignored by Aardvark driver - all entries are used as transparent PCIe MEM, despite some of them being defined for IO in DT. This is because the driver does not setup PCIe outbound windows and thus a default configuration is used. This can cause an external abort on CPU when a device driver tries to access non-MEM space. Setup the PCIe windows according to the `ranges` property for all non-MEM resources (currently only IO) and also non-transparent MEM resources. Because Linux expects that bootloader does not setup Aardvark PCIe windows, disable them before booting Linux. Signed-off-by: Pali Rohár Reviewed-by: Marek Behún Reviewed-by: Stefan Roese --- drivers/pci/pci-aardvark.c | 158 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pci-aardvark.c b/drivers/pci/pci-aardvark.c index ae1a20551fe..96aa039bdc2 100644 --- a/drivers/pci/pci-aardvark.c +++ b/drivers/pci/pci-aardvark.c @@ -99,6 +99,46 @@ #define PCIE_CORE_CTRL2_STRICT_ORDER_ENABLE BIT(5) #define PCIE_CORE_CTRL2_ADDRWIN_MAP_ENABLE BIT(6) +/* PCIe window configuration */ +#define OB_WIN_BASE_ADDR 0x4c00 +#define OB_WIN_BLOCK_SIZE 0x20 +#define OB_WIN_COUNT 8 +#define OB_WIN_REG_ADDR(win, offset) (OB_WIN_BASE_ADDR + \ + OB_WIN_BLOCK_SIZE * (win) + \ + (offset)) +#define OB_WIN_MATCH_LS(win) OB_WIN_REG_ADDR(win, 0x00) +#define OB_WIN_ENABLE BIT(0) +#define OB_WIN_MATCH_MS(win) OB_WIN_REG_ADDR(win, 0x04) +#define OB_WIN_REMAP_LS(win) OB_WIN_REG_ADDR(win, 0x08) +#define OB_WIN_REMAP_MS(win) OB_WIN_REG_ADDR(win, 0x0c) +#define OB_WIN_MASK_LS(win) OB_WIN_REG_ADDR(win, 0x10) +#define OB_WIN_MASK_MS(win) OB_WIN_REG_ADDR(win, 0x14) +#define OB_WIN_ACTIONS(win) OB_WIN_REG_ADDR(win, 0x18) +#define OB_WIN_DEFAULT_ACTIONS (OB_WIN_ACTIONS(OB_WIN_COUNT-1) + 0x4) +#define OB_WIN_FUNC_NUM_MASK GENMASK(31, 24) +#define OB_WIN_FUNC_NUM_SHIFT 24 +#define OB_WIN_FUNC_NUM_ENABLE BIT(23) +#define OB_WIN_BUS_NUM_BITS_MASK GENMASK(22, 20) +#define OB_WIN_BUS_NUM_BITS_SHIFT 20 +#define OB_WIN_MSG_CODE_ENABLE BIT(22) +#define OB_WIN_MSG_CODE_MASK GENMASK(21, 14) +#define OB_WIN_MSG_CODE_SHIFT 14 +#define OB_WIN_MSG_PAYLOAD_LEN BIT(12) +#define OB_WIN_ATTR_ENABLE BIT(11) +#define OB_WIN_ATTR_TC_MASK GENMASK(10, 8) +#define OB_WIN_ATTR_TC_SHIFT 8 +#define OB_WIN_ATTR_RELAXED BIT(7) +#define OB_WIN_ATTR_NOSNOOP BIT(6) +#define OB_WIN_ATTR_POISON BIT(5) +#define OB_WIN_ATTR_IDO BIT(4) +#define OB_WIN_TYPE_MASK GENMASK(3, 0) +#define OB_WIN_TYPE_SHIFT 0 +#define OB_WIN_TYPE_MEM 0x0 +#define OB_WIN_TYPE_IO 0x4 +#define OB_WIN_TYPE_CONFIG_TYPE0 0x8 +#define OB_WIN_TYPE_CONFIG_TYPE1 0x9 +#define OB_WIN_TYPE_MSG 0xc + /* LMI registers base address and register offsets */ #define LMI_BASE_ADDR 0x6000 #define CFG_REG (LMI_BASE_ADDR + 0x0) @@ -522,6 +562,86 @@ static int pcie_advk_wait_for_link(struct pcie_advk *pcie) return -ETIMEDOUT; } +/* + * Set PCIe address window register which could be used for memory + * mapping. + */ +static void pcie_advk_set_ob_win(struct pcie_advk *pcie, u8 win_num, + phys_addr_t match, phys_addr_t remap, + phys_addr_t mask, u32 actions) +{ + advk_writel(pcie, OB_WIN_ENABLE | + lower_32_bits(match), OB_WIN_MATCH_LS(win_num)); + advk_writel(pcie, upper_32_bits(match), OB_WIN_MATCH_MS(win_num)); + advk_writel(pcie, lower_32_bits(remap), OB_WIN_REMAP_LS(win_num)); + advk_writel(pcie, upper_32_bits(remap), OB_WIN_REMAP_MS(win_num)); + advk_writel(pcie, lower_32_bits(mask), OB_WIN_MASK_LS(win_num)); + advk_writel(pcie, upper_32_bits(mask), OB_WIN_MASK_MS(win_num)); + advk_writel(pcie, actions, OB_WIN_ACTIONS(win_num)); +} + +static void pcie_advk_disable_ob_win(struct pcie_advk *pcie, u8 win_num) +{ + advk_writel(pcie, 0, OB_WIN_MATCH_LS(win_num)); + advk_writel(pcie, 0, OB_WIN_MATCH_MS(win_num)); + advk_writel(pcie, 0, OB_WIN_REMAP_LS(win_num)); + advk_writel(pcie, 0, OB_WIN_REMAP_MS(win_num)); + advk_writel(pcie, 0, OB_WIN_MASK_LS(win_num)); + advk_writel(pcie, 0, OB_WIN_MASK_MS(win_num)); + advk_writel(pcie, 0, OB_WIN_ACTIONS(win_num)); +} + +static void pcie_advk_set_ob_region(struct pcie_advk *pcie, int *wins, + struct pci_region *region, u32 actions) +{ + phys_addr_t phys_start = region->phys_start; + pci_addr_t bus_start = region->bus_start; + pci_size_t size = region->size; + phys_addr_t win_mask; + u64 win_size; + + if (*wins == -1) + return; + + /* + * The n-th PCIe window is configured by tuple (match, remap, mask) + * and an access to address A uses this window it if A matches the + * match with given mask. + * So every PCIe window size must be a power of two and every start + * address must be aligned to window size. Minimal size is 64 KiB + * because lower 16 bits of mask must be zero. + */ + while (*wins < OB_WIN_COUNT && size > 0) { + /* Calculate the largest aligned window size */ + win_size = (1ULL << (fls64(size) - 1)) | + (phys_start ? (1ULL << __ffs64(phys_start)) : 0); + win_size = 1ULL << __ffs64(win_size); + if (win_size < 0x10000) + break; + + dev_dbg(pcie->dev, + "Configuring PCIe window %d: [0x%llx-0x%llx] as 0x%x\n", + *wins, (u64)phys_start, (u64)phys_start + win_size, + actions); + win_mask = ~(win_size - 1) & ~0xffff; + pcie_advk_set_ob_win(pcie, *wins, phys_start, bus_start, + win_mask, actions); + + phys_start += win_size; + bus_start += win_size; + size -= win_size; + (*wins)++; + } + + if (size > 0) { + *wins = -1; + dev_err(pcie->dev, + "Invalid PCIe region [0x%llx-0x%llx]\n", + (u64)region->phys_start, + (u64)region->phys_start + region->size); + } +} + /** * pcie_advk_setup_hw() - PCIe initailzation * @@ -531,6 +651,8 @@ static int pcie_advk_wait_for_link(struct pcie_advk *pcie) */ static int pcie_advk_setup_hw(struct pcie_advk *pcie) { + struct pci_region *io, *mem, *pref; + int i, wins; u32 reg; /* Set to Direct mode */ @@ -597,7 +719,9 @@ static int pcie_advk_setup_hw(struct pcie_advk *pcie) * configurations (Default User Field: 0xD0074CFC) * are used to transparent address translation for * the outbound transactions. Thus, PCIe address - * windows are not required. + * windows are not required for transparent memory + * access when default outbound window configuration + * is set for memory access. */ reg = advk_readl(pcie, PCIE_CORE_CTRL2_REG); reg |= PCIE_CORE_CTRL2_ADDRWIN_MAP_ENABLE; @@ -613,6 +737,34 @@ static int pcie_advk_setup_hw(struct pcie_advk *pcie) reg |= PIO_CTRL_ADDR_WIN_DISABLE; advk_writel(pcie, reg, PIO_CTRL); + /* + * Set memory access in Default User Field so it + * is not required to configure PCIe address for + * transparent memory access. + */ + advk_writel(pcie, OB_WIN_TYPE_MEM, OB_WIN_DEFAULT_ACTIONS); + + /* + * Configure PCIe address windows for non-memory or + * non-transparent access as by default PCIe uses + * transparent memory access. + */ + wins = 0; + pci_get_regions(pcie->dev, &io, &mem, &pref); + if (io) + pcie_advk_set_ob_region(pcie, &wins, io, OB_WIN_TYPE_IO); + if (mem && mem->phys_start != mem->bus_start) + pcie_advk_set_ob_region(pcie, &wins, mem, OB_WIN_TYPE_MEM); + if (pref && pref->phys_start != pref->bus_start) + pcie_advk_set_ob_region(pcie, &wins, pref, OB_WIN_TYPE_MEM); + + /* Disable remaining PCIe outbound windows */ + for (i = ((wins >= 0) ? wins : 0); i < OB_WIN_COUNT; i++) + pcie_advk_disable_ob_win(pcie, i); + + if (wins == -1) + return -EINVAL; + /* Wait for PCIe link up */ if (pcie_advk_wait_for_link(pcie)) return -ENXIO; @@ -674,6 +826,10 @@ static int pcie_advk_remove(struct udevice *dev) { struct pcie_advk *pcie = dev_get_priv(dev); u32 reg; + int i; + + for (i = 0; i < OB_WIN_COUNT; i++) + pcie_advk_disable_ob_win(pcie, i); reg = advk_readl(pcie, PCIE_CORE_CMD_STATUS_REG); reg &= ~(PCIE_CORE_CMD_MEM_ACCESS_EN | -- cgit v1.3.1