pcie.c 7.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * arch/arm/plat-orion/pcie.c
 *
 * Marvell Orion SoC PCIe handling.
 *
 * 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 <linux/kernel.h>
#include <linux/pci.h>
#include <linux/mbus.h>
#include <asm/mach/pci.h>
15
#include <plat/pcie.h>
16
#include <plat/addr-map.h>
17
#include <linux/delay.h>
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

/*
 * 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		0x80000000
#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)
40
#define  PCIE_CONF_FUNC(f)		(((f) & 0x7) << 8)
41 42 43
#define PCIE_CONF_DATA_OFF	0x18fc
#define PCIE_MASK_OFF		0x1910
#define PCIE_CTRL_OFF		0x1a00
44
#define  PCIE_CTRL_X1_MODE		0x0001
45 46 47 48 49 50
#define PCIE_STAT_OFF		0x1a04
#define  PCIE_STAT_DEV_OFFS		20
#define  PCIE_STAT_DEV_MASK		0x1f
#define  PCIE_STAT_BUS_OFFS		8
#define  PCIE_STAT_BUS_MASK		0xff
#define  PCIE_STAT_LINK_DOWN		1
51 52
#define PCIE_DEBUG_CTRL         0x1a60
#define  PCIE_DEBUG_SOFT_RESET		(1<<20)
53 54 55 56 57 58 59 60 61 62 63 64


u32 __init orion_pcie_dev_id(void __iomem *base)
{
	return readl(base + PCIE_DEV_ID_OFF) >> 16;
}

u32 __init orion_pcie_rev(void __iomem *base)
{
	return readl(base + PCIE_DEV_REV_OFF) & 0xff;
}

65
int orion_pcie_link_up(void __iomem *base)
66 67 68 69
{
	return !(readl(base + PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
}

70 71 72 73 74
int __init orion_pcie_x4_mode(void __iomem *base)
{
	return !(readl(base + PCIE_CTRL_OFF) & PCIE_CTRL_X1_MODE);
}

75
int orion_pcie_get_local_bus_nr(void __iomem *base)
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
{
	u32 stat = readl(base + PCIE_STAT_OFF);

	return (stat >> PCIE_STAT_BUS_OFFS) & PCIE_STAT_BUS_MASK;
}

void __init orion_pcie_set_local_bus_nr(void __iomem *base, int nr)
{
	u32 stat;

	stat = readl(base + PCIE_STAT_OFF);
	stat &= ~(PCIE_STAT_BUS_MASK << PCIE_STAT_BUS_OFFS);
	stat |= nr << PCIE_STAT_BUS_OFFS;
	writel(stat, base + PCIE_STAT_OFF);
}

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
void __init orion_pcie_reset(void __iomem *base)
{
	u32 reg;
	int i;

	/*
	 * MV-S104860-U0, Rev. C:
	 * PCI Express Unit Soft Reset
	 * When set, generates an internal reset in the PCI Express unit.
	 * This bit should be cleared after the link is re-established.
	 */
	reg = readl(base + PCIE_DEBUG_CTRL);
	reg |= PCIE_DEBUG_SOFT_RESET;
	writel(reg, base + PCIE_DEBUG_CTRL);

	for (i = 0; i < 20; i++) {
		mdelay(10);

		if (orion_pcie_link_up(base))
			break;
	}

	reg &= ~(PCIE_DEBUG_SOFT_RESET);
	writel(reg, base + PCIE_DEBUG_CTRL);
}

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
/*
 * 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 __init orion_pcie_setup_wins(void __iomem *base,
					 struct mbus_dram_target_info *dram)
{
	u32 size;
	int i;

	/*
	 * First, disable and clear BARs and windows.
	 */
	for (i = 1; i <= 2; i++) {
		writel(0, base + PCIE_BAR_CTRL_OFF(i));
		writel(0, base + PCIE_BAR_LO_OFF(i));
		writel(0, base + PCIE_BAR_HI_OFF(i));
	}

	for (i = 0; i < 5; i++) {
		writel(0, base + PCIE_WIN04_CTRL_OFF(i));
		writel(0, base + PCIE_WIN04_BASE_OFF(i));
		writel(0, base + PCIE_WIN04_REMAP_OFF(i));
	}

	writel(0, base + PCIE_WIN5_CTRL_OFF);
	writel(0, base + PCIE_WIN5_BASE_OFF);
	writel(0, 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++) {
		struct mbus_dram_window *cs = dram->cs + i;

		writel(cs->base & 0xffff0000, base + PCIE_WIN04_BASE_OFF(i));
		writel(0, base + PCIE_WIN04_REMAP_OFF(i));
		writel(((cs->size - 1) & 0xffff0000) |
			(cs->mbus_attr << 8) |
			(dram->mbus_dram_target_id << 4) | 1,
				base + PCIE_WIN04_CTRL_OFF(i));

		size += cs->size;
	}

165 166 167 168 169 170
	/*
	 * Round up 'size' to the nearest power of two.
	 */
	if ((size & (size - 1)) != 0)
		size = 1 << fls(size);

171 172 173 174 175 176 177 178
	/*
	 * Setup BAR[1] to all DRAM banks.
	 */
	writel(dram->cs[0].base, base + PCIE_BAR_LO_OFF(1));
	writel(0, base + PCIE_BAR_HI_OFF(1));
	writel(((size - 1) & 0xffff0000) | 1, base + PCIE_BAR_CTRL_OFF(1));
}

179
void __init orion_pcie_setup(void __iomem *base)
180 181 182 183 184 185 186
{
	u16 cmd;
	u32 mask;

	/*
	 * Point PCIe unit MBUS decode windows to DRAM space.
	 */
187
	orion_pcie_setup_wins(base, &orion_mbus_dram_info);
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

	/*
	 * Master + slave enable.
	 */
	cmd = readw(base + PCIE_CMD_OFF);
	cmd |= PCI_COMMAND_IO;
	cmd |= PCI_COMMAND_MEMORY;
	cmd |= PCI_COMMAND_MASTER;
	writew(cmd, base + PCIE_CMD_OFF);

	/*
	 * Enable interrupt lines A-D.
	 */
	mask = readl(base + PCIE_MASK_OFF);
	mask |= 0x0f000000;
	writel(mask, base + PCIE_MASK_OFF);
}

int orion_pcie_rd_conf(void __iomem *base, struct pci_bus *bus,
		       u32 devfn, int where, int size, u32 *val)
{
	writel(PCIE_CONF_BUS(bus->number) |
		PCIE_CONF_DEV(PCI_SLOT(devfn)) |
		PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
		PCIE_CONF_REG(where) | PCIE_CONF_ADDR_EN,
			base + PCIE_CONF_ADDR_OFF);

	*val = readl(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;
}

int orion_pcie_rd_conf_tlp(void __iomem *base, struct pci_bus *bus,
			   u32 devfn, int where, int size, u32 *val)
{
	writel(PCIE_CONF_BUS(bus->number) |
		PCIE_CONF_DEV(PCI_SLOT(devfn)) |
		PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
		PCIE_CONF_REG(where) | PCIE_CONF_ADDR_EN,
			base + PCIE_CONF_ADDR_OFF);

	*val = readl(base + PCIE_CONF_DATA_OFF);

	if (bus->number != orion_pcie_get_local_bus_nr(base) ||
	    PCI_FUNC(devfn) != 0)
		*val = readl(base + PCIE_HEADER_LOG_4_OFF);

	if (size == 1)
		*val = (*val >> (8 * (where & 3))) & 0xff;
	else if (size == 2)
		*val = (*val >> (8 * (where & 3))) & 0xffff;

	return PCIBIOS_SUCCESSFUL;
}

int orion_pcie_rd_conf_wa(void __iomem *wa_base, struct pci_bus *bus,
			  u32 devfn, int where, int size, u32 *val)
{
	*val = readl(wa_base + (PCIE_CONF_BUS(bus->number) |
				PCIE_CONF_DEV(PCI_SLOT(devfn)) |
				PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
				PCIE_CONF_REG(where)));

	if (size == 1)
		*val = (*val >> (8 * (where & 3))) & 0xff;
	else if (size == 2)
		*val = (*val >> (8 * (where & 3))) & 0xffff;

	return PCIBIOS_SUCCESSFUL;
}

int orion_pcie_wr_conf(void __iomem *base, struct pci_bus *bus,
		       u32 devfn, int where, int size, u32 val)
{
	int ret = PCIBIOS_SUCCESSFUL;

	writel(PCIE_CONF_BUS(bus->number) |
		PCIE_CONF_DEV(PCI_SLOT(devfn)) |
		PCIE_CONF_FUNC(PCI_FUNC(devfn)) |
		PCIE_CONF_REG(where) | PCIE_CONF_ADDR_EN,
			base + PCIE_CONF_ADDR_OFF);

	if (size == 4) {
		writel(val, base + PCIE_CONF_DATA_OFF);
	} else if (size == 2) {
		writew(val, base + PCIE_CONF_DATA_OFF + (where & 3));
	} else if (size == 1) {
		writeb(val, base + PCIE_CONF_DATA_OFF + (where & 3));
	} else {
		ret = PCIBIOS_BAD_REGISTER_NUMBER;
	}

	return ret;
}