18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * arch/arm/plat-orion/pcie.c
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Marvell Orion SoC PCIe handling.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
78c2ecf20Sopenharmony_ci * License version 2.  This program is licensed "as is" without any
88c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/pci.h>
138c2ecf20Sopenharmony_ci#include <linux/mbus.h>
148c2ecf20Sopenharmony_ci#include <asm/mach/pci.h>
158c2ecf20Sopenharmony_ci#include <plat/pcie.h>
168c2ecf20Sopenharmony_ci#include <plat/addr-map.h>
178c2ecf20Sopenharmony_ci#include <linux/delay.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/*
208c2ecf20Sopenharmony_ci * PCIe unit register offsets.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci#define PCIE_DEV_ID_OFF		0x0000
238c2ecf20Sopenharmony_ci#define PCIE_CMD_OFF		0x0004
248c2ecf20Sopenharmony_ci#define PCIE_DEV_REV_OFF	0x0008
258c2ecf20Sopenharmony_ci#define PCIE_BAR_LO_OFF(n)	(0x0010 + ((n) << 3))
268c2ecf20Sopenharmony_ci#define PCIE_BAR_HI_OFF(n)	(0x0014 + ((n) << 3))
278c2ecf20Sopenharmony_ci#define PCIE_HEADER_LOG_4_OFF	0x0128
288c2ecf20Sopenharmony_ci#define PCIE_BAR_CTRL_OFF(n)	(0x1804 + ((n - 1) * 4))
298c2ecf20Sopenharmony_ci#define PCIE_WIN04_CTRL_OFF(n)	(0x1820 + ((n) << 4))
308c2ecf20Sopenharmony_ci#define PCIE_WIN04_BASE_OFF(n)	(0x1824 + ((n) << 4))
318c2ecf20Sopenharmony_ci#define PCIE_WIN04_REMAP_OFF(n)	(0x182c + ((n) << 4))
328c2ecf20Sopenharmony_ci#define PCIE_WIN5_CTRL_OFF	0x1880
338c2ecf20Sopenharmony_ci#define PCIE_WIN5_BASE_OFF	0x1884
348c2ecf20Sopenharmony_ci#define PCIE_WIN5_REMAP_OFF	0x188c
358c2ecf20Sopenharmony_ci#define PCIE_CONF_ADDR_OFF	0x18f8
368c2ecf20Sopenharmony_ci#define  PCIE_CONF_ADDR_EN		0x80000000
378c2ecf20Sopenharmony_ci#define  PCIE_CONF_REG(r)		((((r) & 0xf00) << 16) | ((r) & 0xfc))
388c2ecf20Sopenharmony_ci#define  PCIE_CONF_BUS(b)		(((b) & 0xff) << 16)
398c2ecf20Sopenharmony_ci#define  PCIE_CONF_DEV(d)		(((d) & 0x1f) << 11)
408c2ecf20Sopenharmony_ci#define  PCIE_CONF_FUNC(f)		(((f) & 0x7) << 8)
418c2ecf20Sopenharmony_ci#define PCIE_CONF_DATA_OFF	0x18fc
428c2ecf20Sopenharmony_ci#define PCIE_MASK_OFF		0x1910
438c2ecf20Sopenharmony_ci#define PCIE_CTRL_OFF		0x1a00
448c2ecf20Sopenharmony_ci#define  PCIE_CTRL_X1_MODE		0x0001
458c2ecf20Sopenharmony_ci#define PCIE_STAT_OFF		0x1a04
468c2ecf20Sopenharmony_ci#define  PCIE_STAT_DEV_OFFS		20
478c2ecf20Sopenharmony_ci#define  PCIE_STAT_DEV_MASK		0x1f
488c2ecf20Sopenharmony_ci#define  PCIE_STAT_BUS_OFFS		8
498c2ecf20Sopenharmony_ci#define  PCIE_STAT_BUS_MASK		0xff
508c2ecf20Sopenharmony_ci#define  PCIE_STAT_LINK_DOWN		1
518c2ecf20Sopenharmony_ci#define PCIE_DEBUG_CTRL         0x1a60
528c2ecf20Sopenharmony_ci#define  PCIE_DEBUG_SOFT_RESET		(1<<20)
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ciu32 orion_pcie_dev_id(void __iomem *base)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	return readl(base + PCIE_DEV_ID_OFF) >> 16;
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ciu32 orion_pcie_rev(void __iomem *base)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	return readl(base + PCIE_DEV_REV_OFF) & 0xff;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ciint orion_pcie_link_up(void __iomem *base)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	return !(readl(base + PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ciint __init orion_pcie_x4_mode(void __iomem *base)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	return !(readl(base + PCIE_CTRL_OFF) & PCIE_CTRL_X1_MODE);
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ciint orion_pcie_get_local_bus_nr(void __iomem *base)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	u32 stat = readl(base + PCIE_STAT_OFF);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	return (stat >> PCIE_STAT_BUS_OFFS) & PCIE_STAT_BUS_MASK;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_civoid __init orion_pcie_set_local_bus_nr(void __iomem *base, int nr)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	u32 stat;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	stat = readl(base + PCIE_STAT_OFF);
878c2ecf20Sopenharmony_ci	stat &= ~(PCIE_STAT_BUS_MASK << PCIE_STAT_BUS_OFFS);
888c2ecf20Sopenharmony_ci	stat |= nr << PCIE_STAT_BUS_OFFS;
898c2ecf20Sopenharmony_ci	writel(stat, base + PCIE_STAT_OFF);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_civoid __init orion_pcie_reset(void __iomem *base)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	u32 reg;
958c2ecf20Sopenharmony_ci	int i;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/*
988c2ecf20Sopenharmony_ci	 * MV-S104860-U0, Rev. C:
998c2ecf20Sopenharmony_ci	 * PCI Express Unit Soft Reset
1008c2ecf20Sopenharmony_ci	 * When set, generates an internal reset in the PCI Express unit.
1018c2ecf20Sopenharmony_ci	 * This bit should be cleared after the link is re-established.
1028c2ecf20Sopenharmony_ci	 */
1038c2ecf20Sopenharmony_ci	reg = readl(base + PCIE_DEBUG_CTRL);
1048c2ecf20Sopenharmony_ci	reg |= PCIE_DEBUG_SOFT_RESET;
1058c2ecf20Sopenharmony_ci	writel(reg, base + PCIE_DEBUG_CTRL);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	for (i = 0; i < 20; i++) {
1088c2ecf20Sopenharmony_ci		mdelay(10);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		if (orion_pcie_link_up(base))
1118c2ecf20Sopenharmony_ci			break;
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	reg &= ~(PCIE_DEBUG_SOFT_RESET);
1158c2ecf20Sopenharmony_ci	writel(reg, base + PCIE_DEBUG_CTRL);
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/*
1198c2ecf20Sopenharmony_ci * Setup PCIE BARs and Address Decode Wins:
1208c2ecf20Sopenharmony_ci * BAR[0,2] -> disabled, BAR[1] -> covers all DRAM banks
1218c2ecf20Sopenharmony_ci * WIN[0-3] -> DRAM bank[0-3]
1228c2ecf20Sopenharmony_ci */
1238c2ecf20Sopenharmony_cistatic void __init orion_pcie_setup_wins(void __iomem *base)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	const struct mbus_dram_target_info *dram;
1268c2ecf20Sopenharmony_ci	u32 size;
1278c2ecf20Sopenharmony_ci	int i;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	dram = mv_mbus_dram_info();
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/*
1328c2ecf20Sopenharmony_ci	 * First, disable and clear BARs and windows.
1338c2ecf20Sopenharmony_ci	 */
1348c2ecf20Sopenharmony_ci	for (i = 1; i <= 2; i++) {
1358c2ecf20Sopenharmony_ci		writel(0, base + PCIE_BAR_CTRL_OFF(i));
1368c2ecf20Sopenharmony_ci		writel(0, base + PCIE_BAR_LO_OFF(i));
1378c2ecf20Sopenharmony_ci		writel(0, base + PCIE_BAR_HI_OFF(i));
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	for (i = 0; i < 5; i++) {
1418c2ecf20Sopenharmony_ci		writel(0, base + PCIE_WIN04_CTRL_OFF(i));
1428c2ecf20Sopenharmony_ci		writel(0, base + PCIE_WIN04_BASE_OFF(i));
1438c2ecf20Sopenharmony_ci		writel(0, base + PCIE_WIN04_REMAP_OFF(i));
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	writel(0, base + PCIE_WIN5_CTRL_OFF);
1478c2ecf20Sopenharmony_ci	writel(0, base + PCIE_WIN5_BASE_OFF);
1488c2ecf20Sopenharmony_ci	writel(0, base + PCIE_WIN5_REMAP_OFF);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/*
1518c2ecf20Sopenharmony_ci	 * Setup windows for DDR banks.  Count total DDR size on the fly.
1528c2ecf20Sopenharmony_ci	 */
1538c2ecf20Sopenharmony_ci	size = 0;
1548c2ecf20Sopenharmony_ci	for (i = 0; i < dram->num_cs; i++) {
1558c2ecf20Sopenharmony_ci		const struct mbus_dram_window *cs = dram->cs + i;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		writel(cs->base & 0xffff0000, base + PCIE_WIN04_BASE_OFF(i));
1588c2ecf20Sopenharmony_ci		writel(0, base + PCIE_WIN04_REMAP_OFF(i));
1598c2ecf20Sopenharmony_ci		writel(((cs->size - 1) & 0xffff0000) |
1608c2ecf20Sopenharmony_ci			(cs->mbus_attr << 8) |
1618c2ecf20Sopenharmony_ci			(dram->mbus_dram_target_id << 4) | 1,
1628c2ecf20Sopenharmony_ci				base + PCIE_WIN04_CTRL_OFF(i));
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci		size += cs->size;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	/*
1688c2ecf20Sopenharmony_ci	 * Round up 'size' to the nearest power of two.
1698c2ecf20Sopenharmony_ci	 */
1708c2ecf20Sopenharmony_ci	if ((size & (size - 1)) != 0)
1718c2ecf20Sopenharmony_ci		size = 1 << fls(size);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	/*
1748c2ecf20Sopenharmony_ci	 * Setup BAR[1] to all DRAM banks.
1758c2ecf20Sopenharmony_ci	 */
1768c2ecf20Sopenharmony_ci	writel(dram->cs[0].base, base + PCIE_BAR_LO_OFF(1));
1778c2ecf20Sopenharmony_ci	writel(0, base + PCIE_BAR_HI_OFF(1));
1788c2ecf20Sopenharmony_ci	writel(((size - 1) & 0xffff0000) | 1, base + PCIE_BAR_CTRL_OFF(1));
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_civoid __init orion_pcie_setup(void __iomem *base)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	u16 cmd;
1848c2ecf20Sopenharmony_ci	u32 mask;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	/*
1878c2ecf20Sopenharmony_ci	 * Point PCIe unit MBUS decode windows to DRAM space.
1888c2ecf20Sopenharmony_ci	 */
1898c2ecf20Sopenharmony_ci	orion_pcie_setup_wins(base);
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	/*
1928c2ecf20Sopenharmony_ci	 * Master + slave enable.
1938c2ecf20Sopenharmony_ci	 */
1948c2ecf20Sopenharmony_ci	cmd = readw(base + PCIE_CMD_OFF);
1958c2ecf20Sopenharmony_ci	cmd |= PCI_COMMAND_IO;
1968c2ecf20Sopenharmony_ci	cmd |= PCI_COMMAND_MEMORY;
1978c2ecf20Sopenharmony_ci	cmd |= PCI_COMMAND_MASTER;
1988c2ecf20Sopenharmony_ci	writew(cmd, base + PCIE_CMD_OFF);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	/*
2018c2ecf20Sopenharmony_ci	 * Enable interrupt lines A-D.
2028c2ecf20Sopenharmony_ci	 */
2038c2ecf20Sopenharmony_ci	mask = readl(base + PCIE_MASK_OFF);
2048c2ecf20Sopenharmony_ci	mask |= 0x0f000000;
2058c2ecf20Sopenharmony_ci	writel(mask, base + PCIE_MASK_OFF);
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ciint orion_pcie_rd_conf(void __iomem *base, struct pci_bus *bus,
2098c2ecf20Sopenharmony_ci		       u32 devfn, int where, int size, u32 *val)
2108c2ecf20Sopenharmony_ci{
2118c2ecf20Sopenharmony_ci	writel(PCIE_CONF_BUS(bus->number) |
2128c2ecf20Sopenharmony_ci		PCIE_CONF_DEV(PCI_SLOT(devfn)) |
2138c2ecf20Sopenharmony_ci		PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
2148c2ecf20Sopenharmony_ci		PCIE_CONF_REG(where) | PCIE_CONF_ADDR_EN,
2158c2ecf20Sopenharmony_ci			base + PCIE_CONF_ADDR_OFF);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	*val = readl(base + PCIE_CONF_DATA_OFF);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	if (size == 1)
2208c2ecf20Sopenharmony_ci		*val = (*val >> (8 * (where & 3))) & 0xff;
2218c2ecf20Sopenharmony_ci	else if (size == 2)
2228c2ecf20Sopenharmony_ci		*val = (*val >> (8 * (where & 3))) & 0xffff;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	return PCIBIOS_SUCCESSFUL;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ciint orion_pcie_rd_conf_tlp(void __iomem *base, struct pci_bus *bus,
2288c2ecf20Sopenharmony_ci			   u32 devfn, int where, int size, u32 *val)
2298c2ecf20Sopenharmony_ci{
2308c2ecf20Sopenharmony_ci	writel(PCIE_CONF_BUS(bus->number) |
2318c2ecf20Sopenharmony_ci		PCIE_CONF_DEV(PCI_SLOT(devfn)) |
2328c2ecf20Sopenharmony_ci		PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
2338c2ecf20Sopenharmony_ci		PCIE_CONF_REG(where) | PCIE_CONF_ADDR_EN,
2348c2ecf20Sopenharmony_ci			base + PCIE_CONF_ADDR_OFF);
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	*val = readl(base + PCIE_CONF_DATA_OFF);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	if (bus->number != orion_pcie_get_local_bus_nr(base) ||
2398c2ecf20Sopenharmony_ci	    PCI_FUNC(devfn) != 0)
2408c2ecf20Sopenharmony_ci		*val = readl(base + PCIE_HEADER_LOG_4_OFF);
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	if (size == 1)
2438c2ecf20Sopenharmony_ci		*val = (*val >> (8 * (where & 3))) & 0xff;
2448c2ecf20Sopenharmony_ci	else if (size == 2)
2458c2ecf20Sopenharmony_ci		*val = (*val >> (8 * (where & 3))) & 0xffff;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	return PCIBIOS_SUCCESSFUL;
2488c2ecf20Sopenharmony_ci}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ciint orion_pcie_rd_conf_wa(void __iomem *wa_base, struct pci_bus *bus,
2518c2ecf20Sopenharmony_ci			  u32 devfn, int where, int size, u32 *val)
2528c2ecf20Sopenharmony_ci{
2538c2ecf20Sopenharmony_ci	*val = readl(wa_base + (PCIE_CONF_BUS(bus->number) |
2548c2ecf20Sopenharmony_ci				PCIE_CONF_DEV(PCI_SLOT(devfn)) |
2558c2ecf20Sopenharmony_ci				PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
2568c2ecf20Sopenharmony_ci				PCIE_CONF_REG(where)));
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	if (size == 1)
2598c2ecf20Sopenharmony_ci		*val = (*val >> (8 * (where & 3))) & 0xff;
2608c2ecf20Sopenharmony_ci	else if (size == 2)
2618c2ecf20Sopenharmony_ci		*val = (*val >> (8 * (where & 3))) & 0xffff;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	return PCIBIOS_SUCCESSFUL;
2648c2ecf20Sopenharmony_ci}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ciint orion_pcie_wr_conf(void __iomem *base, struct pci_bus *bus,
2678c2ecf20Sopenharmony_ci		       u32 devfn, int where, int size, u32 val)
2688c2ecf20Sopenharmony_ci{
2698c2ecf20Sopenharmony_ci	int ret = PCIBIOS_SUCCESSFUL;
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	writel(PCIE_CONF_BUS(bus->number) |
2728c2ecf20Sopenharmony_ci		PCIE_CONF_DEV(PCI_SLOT(devfn)) |
2738c2ecf20Sopenharmony_ci		PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
2748c2ecf20Sopenharmony_ci		PCIE_CONF_REG(where) | PCIE_CONF_ADDR_EN,
2758c2ecf20Sopenharmony_ci			base + PCIE_CONF_ADDR_OFF);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	if (size == 4) {
2788c2ecf20Sopenharmony_ci		writel(val, base + PCIE_CONF_DATA_OFF);
2798c2ecf20Sopenharmony_ci	} else if (size == 2) {
2808c2ecf20Sopenharmony_ci		writew(val, base + PCIE_CONF_DATA_OFF + (where & 3));
2818c2ecf20Sopenharmony_ci	} else if (size == 1) {
2828c2ecf20Sopenharmony_ci		writeb(val, base + PCIE_CONF_DATA_OFF + (where & 3));
2838c2ecf20Sopenharmony_ci	} else {
2848c2ecf20Sopenharmony_ci		ret = PCIBIOS_BAD_REGISTER_NUMBER;
2858c2ecf20Sopenharmony_ci	}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	return ret;
2888c2ecf20Sopenharmony_ci}
289