diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 8465d4a..be5c7bd 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -93,6 +93,7 @@ select GPIOLIB select HAS_DEBUG_LL select HAVE_PBL_MULTI_IMAGES + select HW_HAS_PCI select MVEBU_MBUS select OFTREE select OF_ADDRESS_PCI diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 9e46592..d17a151 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -24,6 +24,12 @@ When in doubt, say N. +config PCI_MVEBU + bool "Marvell EBU PCIe driver" + depends on ARCH_MVEBU + select OF_PCI + select PCI + endmenu endif diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index edac1a5..4423531 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -6,3 +6,5 @@ ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG CPPFLAGS += $(ccflags-y) + +obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o pci-mvebu-phy.o diff --git a/drivers/pci/pci-mvebu-phy.c b/drivers/pci/pci-mvebu-phy.c new file mode 100644 index 0000000..55a1d39 --- /dev/null +++ b/drivers/pci/pci-mvebu-phy.c @@ -0,0 +1,208 @@ +/* + * SoC specific PCIe PHY setup for Marvell MVEBU SoCs + * + * Sebastian Hesselbarth + * + * 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 +#include +#include + +#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; +} diff --git a/drivers/pci/pci-mvebu.c b/drivers/pci/pci-mvebu.c new file mode 100644 index 0000000..45befbb --- /dev/null +++ b/drivers/pci/pci-mvebu.c @@ -0,0 +1,446 @@ +/* + * PCIe driver for Marvell MVEBU SoCs + * + * Based on Linux drivers/pci/host/pci-mvebu.c + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pci-mvebu.h" + +/* PCIe unit register offsets */ +#define PCIE_DEV_ID_OFF 0x0000 +#define PCIE_CMD_OFF 0x0004 +#define PCIE_DEV_REV_OFF 0x0008 +#define PCIE_BAR_LO_OFF(n) (0x0010 + ((n) << 3)) +#define PCIE_BAR_HI_OFF(n) (0x0014 + ((n) << 3)) +#define PCIE_HEADER_LOG_4_OFF 0x0128 +#define PCIE_BAR_CTRL_OFF(n) (0x1804 + (((n) - 1) * 4)) +#define PCIE_WIN04_CTRL_OFF(n) (0x1820 + ((n) << 4)) +#define PCIE_WIN04_BASE_OFF(n) (0x1824 + ((n) << 4)) +#define PCIE_WIN04_REMAP_OFF(n) (0x182c + ((n) << 4)) +#define PCIE_WIN5_CTRL_OFF 0x1880 +#define PCIE_WIN5_BASE_OFF 0x1884 +#define PCIE_WIN5_REMAP_OFF 0x188c +#define PCIE_CONF_ADDR_OFF 0x18f8 +#define PCIE_CONF_ADDR_EN BIT(31) +#define PCIE_CONF_REG(r) ((((r) & 0xf00) << 16) | ((r) & 0xfc)) +#define PCIE_CONF_BUS(b) (((b) & 0xff) << 16) +#define PCIE_CONF_DEV(d) (((d) & 0x1f) << 11) +#define PCIE_CONF_FUNC(f) (((f) & 0x7) << 8) +#define PCIE_CONF_ADDR(bus, devfn, where) \ + (PCIE_CONF_BUS(bus) | PCIE_CONF_DEV(PCI_SLOT(devfn)) | \ + PCIE_CONF_FUNC(PCI_FUNC(devfn)) | PCIE_CONF_REG(where) | \ + PCIE_CONF_ADDR_EN) +#define PCIE_CONF_DATA_OFF 0x18fc +#define PCIE_MASK_OFF 0x1910 +#define PCIE_MASK_ENABLE_INTS (0xf << 24) +#define PCIE_CTRL_OFF 0x1a00 +#define PCIE_CTRL_X1_MODE BIT(0) +#define PCIE_STAT_OFF 0x1a04 +#define PCIE_STAT_BUS (0xff << 8) +#define PCIE_STAT_DEV (0x1f << 16) +#define PCIE_STAT_LINK_DOWN BIT(0) +#define PCIE_DEBUG_CTRL 0x1a60 +#define PCIE_DEBUG_SOFT_RESET BIT(20) + +#define to_pcie(_hc) container_of(_hc, struct mvebu_pcie, pci) + +/* + * MVEBU PCIe controller needs MEMORY and I/O BARs to be mapped + * into SoCs address space. Each controller will map 32M of MEM + * and 64K of I/O space when registered. + */ +static void __iomem *mvebu_pcie_membase = IOMEM(0xe0000000); +static void __iomem *mvebu_pcie_iobase = IOMEM(0xffe00000); + +static inline bool mvebu_pcie_link_up(struct mvebu_pcie *pcie) +{ + return !(readl(pcie->base + PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN); +} + +static void mvebu_pcie_set_local_bus_nr(struct pci_controller *host, int busno) +{ + struct mvebu_pcie *pcie = to_pcie(host); + u32 stat; + + stat = readl(pcie->base + PCIE_STAT_OFF); + stat &= ~PCIE_STAT_BUS; + stat |= busno << 8; + writel(stat, pcie->base + PCIE_STAT_OFF); +} + +static void mvebu_pcie_set_local_dev_nr(struct mvebu_pcie *pcie, int devno) +{ + u32 stat; + + stat = readl(pcie->base + PCIE_STAT_OFF); + stat &= ~PCIE_STAT_DEV; + stat |= devno << 16; + writel(stat, pcie->base + PCIE_STAT_OFF); +} + +static int mvebu_pcie_indirect_rd_conf(struct pci_bus *bus, + unsigned int devfn, int where, int size, u32 *val) +{ + struct mvebu_pcie *pcie = to_pcie(bus->host); + + /* Skip all requests not directed to device behind bridge */ + if (devfn != pcie->devfn || !mvebu_pcie_link_up(pcie)) { + *val = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + writel(PCIE_CONF_ADDR(bus->number, devfn, where), + pcie->base + PCIE_CONF_ADDR_OFF); + + *val = readl(pcie->base + PCIE_CONF_DATA_OFF); + + if (size == 1) + *val = (*val >> (8 * (where & 3))) & 0xff; + else if (size == 2) + *val = (*val >> (8 * (where & 3))) & 0xffff; + + return PCIBIOS_SUCCESSFUL; +} + +static int mvebu_pcie_indirect_wr_conf(struct pci_bus *bus, + unsigned int devfn, int where, int size, u32 val) +{ + struct mvebu_pcie *pcie = to_pcie(bus->host); + u32 _val, shift = 8 * (where & 3); + + /* Skip all requests not directed to device behind bridge */ + if (devfn != pcie->devfn || !mvebu_pcie_link_up(pcie)) + return PCIBIOS_DEVICE_NOT_FOUND; + + writel(PCIE_CONF_ADDR(bus->number, devfn, where), + pcie->base + PCIE_CONF_ADDR_OFF); + _val = readl(pcie->base + PCIE_CONF_DATA_OFF); + + if (size == 4) + _val = val; + else if (size == 2) + _val = (_val & ~(0xffff << shift)) | ((val & 0xffff) << shift); + else if (size == 1) + _val = (_val & ~(0xff << shift)) | ((val & 0xff) << shift); + else + return PCIBIOS_BAD_REGISTER_NUMBER; + + writel(_val, pcie->base + PCIE_CONF_DATA_OFF); + + return PCIBIOS_SUCCESSFUL; +} + +static int mvebu_pcie_res_start(struct pci_bus *bus, resource_size_t res_addr) +{ + struct mvebu_pcie *pcie = to_pcie(bus->host); + + return (int)pcie->membase + (res_addr & (resource_size(&pcie->mem)-1)); +} + +static struct pci_ops mvebu_pcie_indirect_ops = { + .read = mvebu_pcie_indirect_rd_conf, + .write = mvebu_pcie_indirect_wr_conf, + .res_start = mvebu_pcie_res_start, +}; + +/* + * Setup PCIE BARs and Address Decode Wins: + * BAR[0,2] -> disabled, BAR[1] -> covers all DRAM banks + * WIN[0-3] -> DRAM bank[0-3] + */ +static void mvebu_pcie_setup_wins(struct mvebu_pcie *pcie) +{ + const struct mbus_dram_target_info *dram = mvebu_mbus_dram_info(); + u32 size; + int i; + + /* First, disable and clear BARs and windows. */ + for (i = 1; i < 3; i++) { + writel(0, pcie->base + PCIE_BAR_CTRL_OFF(i)); + writel(0, pcie->base + PCIE_BAR_LO_OFF(i)); + writel(0, pcie->base + PCIE_BAR_HI_OFF(i)); + } + + for (i = 0; i < 5; i++) { + writel(0, pcie->base + PCIE_WIN04_CTRL_OFF(i)); + writel(0, pcie->base + PCIE_WIN04_BASE_OFF(i)); + writel(0, pcie->base + PCIE_WIN04_REMAP_OFF(i)); + } + + writel(0, pcie->base + PCIE_WIN5_CTRL_OFF); + writel(0, pcie->base + PCIE_WIN5_BASE_OFF); + writel(0, pcie->base + PCIE_WIN5_REMAP_OFF); + + /* Setup windows for DDR banks. Count total DDR size on the fly. */ + size = 0; + for (i = 0; i < dram->num_cs; i++) { + const struct mbus_dram_window *cs = dram->cs + i; + + writel(cs->base & 0xffff0000, + pcie->base + PCIE_WIN04_BASE_OFF(i)); + writel(0, pcie->base + PCIE_WIN04_REMAP_OFF(i)); + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + pcie->base + PCIE_WIN04_CTRL_OFF(i)); + + size += cs->size; + } + + /* Round up 'size' to the nearest power of two. */ + if ((size & (size - 1)) != 0) + size = 1 << fls(size); + + /* Setup BAR[1] to all DRAM banks. */ + writel(dram->cs[0].base, pcie->base + PCIE_BAR_LO_OFF(1)); + writel(0, pcie->base + PCIE_BAR_HI_OFF(1)); + writel(((size - 1) & 0xffff0000) | 1, + pcie->base + PCIE_BAR_CTRL_OFF(1)); +} + +#define DT_FLAGS_TO_TYPE(flags) (((flags) >> 24) & 0x03) +#define DT_TYPE_IO 0x1 +#define DT_TYPE_MEM32 0x2 +#define DT_CPUADDR_TO_TARGET(cpuaddr) (((cpuaddr) >> 56) & 0xFF) +#define DT_CPUADDR_TO_ATTR(cpuaddr) (((cpuaddr) >> 48) & 0xFF) + +static int mvebu_get_target_attr(struct device_node *np, int devfn, + unsigned long type, unsigned int *target, unsigned int *attr) +{ + const int na = 3, ns = 2; + const __be32 *range; + int rlen, nranges, rangesz, pna, i; + + *target = -1; + *attr = -1; + + range = of_get_property(np, "ranges", &rlen); + if (!range) + return -EINVAL; + + pna = of_n_addr_cells(np); + rangesz = pna + na + ns; + nranges = rlen / sizeof(__be32) / rangesz; + + for (i = 0; i < nranges; i++) { + u32 flags = of_read_number(range, 1); + u32 slot = of_read_number(range + 1, 1); + u64 cpuaddr = of_read_number(range + na, pna); + unsigned long rtype; + + if (DT_FLAGS_TO_TYPE(flags) == DT_TYPE_IO) + rtype = IORESOURCE_IO; + else if (DT_FLAGS_TO_TYPE(flags) == DT_TYPE_MEM32) + rtype = IORESOURCE_MEM; + + if (slot == PCI_SLOT(devfn) && type == rtype) { + *target = DT_CPUADDR_TO_TARGET(cpuaddr); + *attr = DT_CPUADDR_TO_ATTR(cpuaddr); + return 0; + } + + range += rangesz; + } + + return -ENOENT; +} + +static struct mvebu_pcie *mvebu_pcie_port_probe(struct device_d *dev, + struct device_node *np) +{ + struct mvebu_pcie *pcie; + struct clk *clk; + enum of_gpio_flags flags; + struct property *prop; + const __be32 *p; + int reset_gpio; + u32 u, port, lane, lane_mask, devfn; + int mem_target, mem_attr; + int io_target, io_attr; + int ret; + + if (of_property_read_u32(np, "marvell,pcie-port", &port)) { + dev_err(dev, "missing pcie-port property\n"); + return ERR_PTR(-EINVAL); + } + + lane_mask = 0; + of_property_for_each_u32(np, "marvell,pcie-lane", prop, p, u) + lane_mask |= BIT(u); + lane = ffs(lane_mask)-1; + + devfn = of_pci_get_devfn(np); + if (devfn < 0) { + dev_err(dev, "unable to parse devfn\n"); + return ERR_PTR(-EINVAL); + } + + if (mvebu_get_target_attr(dev->device_node, devfn, IORESOURCE_MEM, + &mem_target, &mem_attr)) { + dev_err(dev, "unable to get target/attr for mem window\n"); + return ERR_PTR(-EINVAL); + } + + /* I/O windows are optional */ + mvebu_get_target_attr(dev->device_node, devfn, IORESOURCE_IO, + &io_target, &io_attr); + + reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &flags); + if (gpio_is_valid(reset_gpio)) { + int reset_active_low = flags & OF_GPIO_ACTIVE_LOW; + char *reset_name = asprintf("pcie%d.%d-reset", port, lane); + u32 reset_udelay = 20000; + + of_property_read_u32(np, "reset-delay-us", &reset_udelay); + + ret = gpio_request_one(reset_gpio, GPIOF_DIR_OUT, reset_name); + if (ret) + return ERR_PTR(ret); + + /* Ensure a full reset cycle*/ + gpio_set_value(reset_gpio, 1 ^ reset_active_low); + udelay(reset_udelay); + gpio_set_value(reset_gpio, 0 ^ reset_active_low); + udelay(reset_udelay); + } + + pcie = xzalloc(sizeof(*pcie)); + pcie->port = port; + pcie->lane = lane; + pcie->lane_mask = lane_mask; + pcie->name = asprintf("pcie%d.%d", port, lane); + pcie->devfn = devfn; + + pcie->base = of_iomap(np, 0); + if (!pcie->base) { + dev_err(dev, "PCIe%d.%d unable to map registers\n", port, lane); + free(pcie); + return ERR_PTR(-ENOMEM); + } + + pcie->membase = mvebu_pcie_membase; + pcie->mem.start = (u32)mvebu_pcie_membase; + pcie->mem.end = pcie->mem.start + SZ_32M - 1; + if (mvebu_mbus_add_window_remap_by_id(mem_target, mem_attr, + (resource_size_t)pcie->membase, resource_size(&pcie->mem), + (u32)pcie->mem.start)) { + dev_err(dev, "PCIe%d.%d unable to add mbus window for mem at %08x+%08x", + port, lane, (u32)pcie->mem.start, resource_size(&pcie->mem)); + + free(pcie); + return ERR_PTR(-EBUSY); + } + mvebu_pcie_membase += SZ_32M; + + if (io_target >= 0 && io_attr >= 0) { + pcie->iobase = mvebu_pcie_iobase; + pcie->io.start = (u32)mvebu_pcie_iobase; + pcie->io.end = pcie->io.start + SZ_64K - 1; + + mvebu_mbus_add_window_remap_by_id(io_target, io_attr, + (resource_size_t)pcie->iobase, resource_size(&pcie->io), + (u32)pcie->io.start); + mvebu_pcie_iobase += SZ_64K; + } + + clk = of_clk_get(np, 0); + if (!IS_ERR(clk)) + clk_enable(clk); + + pcie->pci.set_busno = mvebu_pcie_set_local_bus_nr; + pcie->pci.pci_ops = &mvebu_pcie_indirect_ops; + pcie->pci.mem_resource = &pcie->mem; + pcie->pci.io_resource = &pcie->io; + + return pcie; +} + +static struct mvebu_pcie_ops __maybe_unused armada_370_ops = { + .phy_setup = armada_370_phy_setup, +}; + +static struct mvebu_pcie_ops __maybe_unused armada_xp_ops = { + .phy_setup = armada_xp_phy_setup, +}; + +static struct of_device_id mvebu_pcie_dt_ids[] = { +#if defined(CONFIG_ARCH_ARMADA_XP) + { .compatible = "marvell,armada-xp-pcie", .data = (u32)&armada_xp_ops, }, +#endif +#if defined(CONFIG_ARCH_ARMADA_370) + { .compatible = "marvell,armada-370-pcie", .data = (u32)&armada_370_ops, }, +#endif +#if defined(CONFIG_ARCH_DOVE) + { .compatible = "marvell,dove-pcie", }, +#endif +#if defined(CONFIG_ARCH_KIRKWOOD) + { .compatible = "marvell,kirkwood-pcie", }, +#endif + { }, +}; + +static int mvebu_pcie_probe(struct device_d *dev) +{ + struct device_node *np = dev->device_node; + const struct of_device_id *match = of_match_node(mvebu_pcie_dt_ids, np); + struct mvebu_pcie_ops *ops = (struct mvebu_pcie_ops *)match->data; + struct device_node *pnp; + + for_each_child_of_node(np, pnp) { + struct mvebu_pcie *pcie; + u32 reg; + + if (!of_device_is_available(pnp)) + continue; + + pcie = mvebu_pcie_port_probe(dev, pnp); + if (IS_ERR(pcie)) + continue; + + if (ops && ops->phy_setup) + ops->phy_setup(pcie); + + mvebu_pcie_set_local_dev_nr(pcie, 0); + mvebu_pcie_setup_wins(pcie); + + /* Master + slave enable. */ + reg = readl(pcie->base + PCIE_CMD_OFF); + reg |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + reg |= PCI_COMMAND_MASTER; + writel(reg, pcie->base + PCIE_CMD_OFF); + + /* Disable interrupts */ + reg = readl(pcie->base + PCIE_MASK_OFF); + reg &= ~PCIE_MASK_ENABLE_INTS; + writel(reg, pcie->base + PCIE_MASK_OFF); + + register_pci_controller(&pcie->pci); + } + + return 0; +} + +static struct driver_d mvebu_pcie_driver = { + .name = "mvebu-pcie", + .probe = mvebu_pcie_probe, + .of_compatible = mvebu_pcie_dt_ids, +}; +device_platform_driver(mvebu_pcie_driver); diff --git a/drivers/pci/pci-mvebu.h b/drivers/pci/pci-mvebu.h new file mode 100644 index 0000000..8ced9fe --- /dev/null +++ b/drivers/pci/pci-mvebu.h @@ -0,0 +1,37 @@ +/* + * PCIe include for Marvell MVEBU SoCs + * + * Sebastian Hesselbarth + * + * 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. + */ + +#ifndef __MVEBU_PCI_H +#define __MVEBU_PCI_H + +#include + +struct mvebu_pcie { + struct pci_controller pci; + char *name; + void __iomem *base; + void __iomem *membase; + struct resource mem; + void __iomem *iobase; + struct resource io; + u32 port; + u32 lane; + u32 lane_mask; + int devfn; +}; + +struct mvebu_pcie_ops { + int (*phy_setup)(struct mvebu_pcie *pcie); +}; + +int armada_370_phy_setup(struct mvebu_pcie *pcie); +int armada_xp_phy_setup(struct mvebu_pcie *pcie); + +#endif