This adds a PCI driver for the controllers found on Marvell MVEBU SoCs. Besides the functional driver itself, it also adds SoC specific PHY setup required for PCIe. Currently, only Armada 370 is fully supported. Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> Acked-by: Lucas Stach <l.stach@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
209 lines
6.4 KiB
C
209 lines
6.4 KiB
C
/*
|
|
* SoC specific PCIe PHY setup for Marvell MVEBU SoCs
|
|
*
|
|
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
|
|
*
|
|
* based on Marvell BSP code (C) Marvell International Ltd.
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <of.h>
|
|
#include <of_address.h>
|
|
|
|
#include "pci-mvebu.h"
|
|
|
|
static u32 mvebu_pcie_phy_indirect(void __iomem *phybase, u8 lane,
|
|
u8 off, u16 val, bool is_read)
|
|
{
|
|
u32 reg = (lane << 24) | (off << 16) | val;
|
|
|
|
if (is_read)
|
|
reg |= BIT(31);
|
|
writel(reg, phybase);
|
|
|
|
return (is_read) ? readl(phybase) & 0xffff : 0;
|
|
}
|
|
|
|
static inline u32 mvebu_pcie_phy_read(void __iomem *phybase, u8 lane,
|
|
u8 off)
|
|
{
|
|
return mvebu_pcie_phy_indirect(phybase, lane, off, 0, true);
|
|
}
|
|
|
|
static inline void mvebu_pcie_phy_write(void __iomem *phybase, u8 lane,
|
|
u8 off, u16 val)
|
|
{
|
|
mvebu_pcie_phy_indirect(phybase, lane, off, val, false);
|
|
}
|
|
|
|
/* PCIe registers */
|
|
#define ARMADA_370_XP_PCIE_LINK_CAPS 0x6c
|
|
#define MAX_LINK_WIDTH_MASK MAX_LINK_WIDTH(0x3f)
|
|
#define MAX_LINK_WIDTH(x) ((x) << 4)
|
|
#define MAX_LINK_SPEED_MASK 0xf
|
|
#define MAX_LINK_SPEED_5G0 0x2
|
|
#define MAX_LINK_SPEED_2G5 0x1
|
|
#define ARMADA_370_XP_PHY_OFFSET 0x1b00
|
|
/* System Control registers */
|
|
#define ARMADA_370_XP_SOC_CTRL 0x04
|
|
#define PCIE1_QUADX1_EN BIT(8) /* Armada XP */
|
|
#define PCIE0_QUADX1_EN BIT(7) /* Armada XP */
|
|
#define PCIE0_EN BIT(0)
|
|
#define ARMADA_370_XP_SERDES03_SEL 0x70
|
|
#define ARMADA_370_XP_SERDES47_SEL 0x74
|
|
#define SERDES(x, v) ((v) << ((x) * 0x4))
|
|
#define SERDES_MASK(x) SERDES((x), 0xf)
|
|
|
|
int armada_370_phy_setup(struct mvebu_pcie *pcie)
|
|
{
|
|
struct device_node *np = of_find_compatible_node(NULL, NULL,
|
|
"marvell,armada-370-xp-system-controller");
|
|
void __iomem *sysctrl = of_iomap(np, 0);
|
|
void __iomem *phybase = pcie->base + ARMADA_370_XP_PHY_OFFSET;
|
|
u32 reg;
|
|
|
|
if (!sysctrl)
|
|
return -ENODEV;
|
|
|
|
/* Enable PEX */
|
|
reg = readl(sysctrl + ARMADA_370_XP_SOC_CTRL);
|
|
reg |= PCIE0_EN << pcie->port;
|
|
writel(reg, sysctrl + ARMADA_370_XP_SOC_CTRL);
|
|
|
|
/* Set SERDES selector */
|
|
reg = readl(sysctrl + ARMADA_370_XP_SERDES03_SEL);
|
|
reg &= ~SERDES_MASK(pcie->port);
|
|
reg |= SERDES(pcie->port, 0x1);
|
|
writel(reg, sysctrl + ARMADA_370_XP_SERDES03_SEL);
|
|
|
|
/* BTS #232 - PCIe clock (undocumented) */
|
|
writel(0x00000077, sysctrl + 0x2f0);
|
|
|
|
/* Set x1 Link Capability */
|
|
reg = readl(pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);
|
|
reg &= ~(MAX_LINK_WIDTH_MASK | MAX_LINK_SPEED_MASK);
|
|
reg |= MAX_LINK_WIDTH(0x1) | MAX_LINK_SPEED_5G0;
|
|
writel(reg, pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);
|
|
|
|
/* PEX pipe configuration */
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0025);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc3, 0x000f);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc8, 0x0005);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xd0, 0x0100);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xd1, 0x3014);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc5, 0x011f);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x80, 0x1000);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x81, 0x0011);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x0f, 0x2a21);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x45, 0x00df);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x4f, 0x6219);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x01, 0xfc60);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x46, 0x0000);
|
|
|
|
reg = mvebu_pcie_phy_read(phybase, pcie->lane, 0x48) & ~0x4;
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x48, reg & 0xffff);
|
|
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x02, 0x0040);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0024);
|
|
|
|
mdelay(15);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* MV78230: 2 PCIe units Gen2.0, one unit 1x4 or 4x1, one unit 1x1
|
|
* MV78260: 3 PCIe units Gen2.0, two units 1x4 or 4x1, one unit 1x1/1x4
|
|
* MV78460: 4 PCIe units Gen2.0, two units 1x4 or 4x1, two units 1x1/1x4
|
|
*/
|
|
#define ARMADA_XP_COMM_PHY_REFCLK_ALIGN 0xf8
|
|
#define REFCLK_ALIGN(x) (0xf << ((x) * 0x4))
|
|
int armada_xp_phy_setup(struct mvebu_pcie *pcie)
|
|
{
|
|
struct device_node *np = of_find_compatible_node(NULL, NULL,
|
|
"marvell,armada-370-xp-system-controller");
|
|
void __iomem *sysctrl = of_iomap(np, 0);
|
|
void __iomem *phybase = pcie->base + ARMADA_370_XP_PHY_OFFSET;
|
|
u32 serdes_off = (pcie->port < 2) ? ARMADA_370_XP_SERDES03_SEL :
|
|
ARMADA_370_XP_SERDES47_SEL;
|
|
bool single_x4 = (pcie->lane_mask == 0xf);
|
|
u32 reg, mask;
|
|
|
|
if (!sysctrl)
|
|
return -ENODEV;
|
|
|
|
/* Prepare PEX */
|
|
reg = readl(sysctrl + ARMADA_370_XP_SOC_CTRL);
|
|
reg &= ~(PCIE0_EN << pcie->port);
|
|
writel(reg, sysctrl + ARMADA_370_XP_SOC_CTRL);
|
|
|
|
if (pcie->port < 2) {
|
|
mask = PCIE0_QUADX1_EN << pcie->port;
|
|
if (single_x4)
|
|
reg &= ~mask;
|
|
else
|
|
reg |= mask;
|
|
}
|
|
reg |= PCIE0_EN << pcie->port;
|
|
writel(reg, sysctrl + ARMADA_370_XP_SOC_CTRL);
|
|
|
|
/* Set SERDES selector */
|
|
reg = readl(sysctrl + serdes_off);
|
|
for (mask = pcie->lane_mask; mask;) {
|
|
u32 l = ffs(mask)-1;
|
|
u32 off = 4 * (pcie->port % 2);
|
|
reg &= ~SERDES_MASK(off + l);
|
|
reg |= SERDES(off + l, 0x1);
|
|
mask &= ~BIT(l);
|
|
}
|
|
reg &= ~SERDES_MASK(pcie->port % 2);
|
|
reg |= SERDES(pcie->port % 2, 0x1);
|
|
writel(reg, sysctrl + serdes_off);
|
|
|
|
/* Reference Clock Alignment for 1x4 */
|
|
reg = readl(sysctrl + ARMADA_XP_COMM_PHY_REFCLK_ALIGN);
|
|
if (single_x4)
|
|
reg |= REFCLK_ALIGN(pcie->port);
|
|
else
|
|
reg &= ~REFCLK_ALIGN(pcie->port);
|
|
writel(reg, sysctrl + ARMADA_XP_COMM_PHY_REFCLK_ALIGN);
|
|
|
|
/* Set x1/x4 Link Capability */
|
|
reg = readl(pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);
|
|
reg &= ~(MAX_LINK_WIDTH_MASK | MAX_LINK_SPEED_MASK);
|
|
if (single_x4)
|
|
reg |= MAX_LINK_WIDTH(0x4);
|
|
else
|
|
reg |= MAX_LINK_WIDTH(0x1);
|
|
reg |= MAX_LINK_SPEED_5G0;
|
|
writel(reg, pcie->base + ARMADA_370_XP_PCIE_LINK_CAPS);
|
|
|
|
/* PEX pipe configuration */
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0025);
|
|
if (single_x4) {
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc2, 0x0200);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc3, 0x0001);
|
|
} else {
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc2, 0x0000);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc3, 0x000f);
|
|
}
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc8, 0x0005);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x01, 0xfc60);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x46, 0x0000);
|
|
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x02, 0x0040);
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0xc1, 0x0024);
|
|
if (single_x4)
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x48, 0x1080);
|
|
else
|
|
mvebu_pcie_phy_write(phybase, pcie->lane, 0x48, 0x9080);
|
|
|
|
mdelay(15);
|
|
|
|
return 0;
|
|
}
|