162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Support for indirect PCI bridges.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 1998 Gabriel Paubert.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/pci.h>
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/string.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <asm/io.h>
1562306a36Sopenharmony_ci#include <asm/pci-bridge.h>
1662306a36Sopenharmony_ci#include <asm/machdep.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ciint __indirect_read_config(struct pci_controller *hose,
1962306a36Sopenharmony_ci			   unsigned char bus_number, unsigned int devfn,
2062306a36Sopenharmony_ci			   int offset, int len, u32 *val)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	volatile void __iomem *cfg_data;
2362306a36Sopenharmony_ci	u8 cfg_type = 0;
2462306a36Sopenharmony_ci	u32 bus_no, reg;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_NO_PCIE_LINK) {
2762306a36Sopenharmony_ci		if (bus_number != hose->first_busno)
2862306a36Sopenharmony_ci			return PCIBIOS_DEVICE_NOT_FOUND;
2962306a36Sopenharmony_ci		if (devfn != 0)
3062306a36Sopenharmony_ci			return PCIBIOS_DEVICE_NOT_FOUND;
3162306a36Sopenharmony_ci	}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	if (ppc_md.pci_exclude_device)
3462306a36Sopenharmony_ci		if (ppc_md.pci_exclude_device(hose, bus_number, devfn))
3562306a36Sopenharmony_ci			return PCIBIOS_DEVICE_NOT_FOUND;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_SET_CFG_TYPE)
3862306a36Sopenharmony_ci		if (bus_number != hose->first_busno)
3962306a36Sopenharmony_ci			cfg_type = 1;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	bus_no = (bus_number == hose->first_busno) ?
4262306a36Sopenharmony_ci			hose->self_busno : bus_number;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_EXT_REG)
4562306a36Sopenharmony_ci		reg = ((offset & 0xf00) << 16) | (offset & 0xfc);
4662306a36Sopenharmony_ci	else
4762306a36Sopenharmony_ci		reg = offset & 0xfc;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_BIG_ENDIAN)
5062306a36Sopenharmony_ci		out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
5162306a36Sopenharmony_ci			 (devfn << 8) | reg | cfg_type));
5262306a36Sopenharmony_ci	else
5362306a36Sopenharmony_ci		out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
5462306a36Sopenharmony_ci			 (devfn << 8) | reg | cfg_type));
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	/*
5762306a36Sopenharmony_ci	 * Note: the caller has already checked that offset is
5862306a36Sopenharmony_ci	 * suitably aligned and that len is 1, 2 or 4.
5962306a36Sopenharmony_ci	 */
6062306a36Sopenharmony_ci	cfg_data = hose->cfg_data + (offset & 3);
6162306a36Sopenharmony_ci	switch (len) {
6262306a36Sopenharmony_ci	case 1:
6362306a36Sopenharmony_ci		*val = in_8(cfg_data);
6462306a36Sopenharmony_ci		break;
6562306a36Sopenharmony_ci	case 2:
6662306a36Sopenharmony_ci		*val = in_le16(cfg_data);
6762306a36Sopenharmony_ci		break;
6862306a36Sopenharmony_ci	default:
6962306a36Sopenharmony_ci		*val = in_le32(cfg_data);
7062306a36Sopenharmony_ci		break;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci	return PCIBIOS_SUCCESSFUL;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ciint indirect_read_config(struct pci_bus *bus, unsigned int devfn,
7662306a36Sopenharmony_ci			 int offset, int len, u32 *val)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct pci_controller *hose = pci_bus_to_host(bus);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return __indirect_read_config(hose, bus->number, devfn, offset, len,
8162306a36Sopenharmony_ci				      val);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ciint indirect_write_config(struct pci_bus *bus, unsigned int devfn,
8562306a36Sopenharmony_ci			  int offset, int len, u32 val)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct pci_controller *hose = pci_bus_to_host(bus);
8862306a36Sopenharmony_ci	volatile void __iomem *cfg_data;
8962306a36Sopenharmony_ci	u8 cfg_type = 0;
9062306a36Sopenharmony_ci	u32 bus_no, reg;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_NO_PCIE_LINK) {
9362306a36Sopenharmony_ci		if (bus->number != hose->first_busno)
9462306a36Sopenharmony_ci			return PCIBIOS_DEVICE_NOT_FOUND;
9562306a36Sopenharmony_ci		if (devfn != 0)
9662306a36Sopenharmony_ci			return PCIBIOS_DEVICE_NOT_FOUND;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (ppc_md.pci_exclude_device)
10062306a36Sopenharmony_ci		if (ppc_md.pci_exclude_device(hose, bus->number, devfn))
10162306a36Sopenharmony_ci			return PCIBIOS_DEVICE_NOT_FOUND;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_SET_CFG_TYPE)
10462306a36Sopenharmony_ci		if (bus->number != hose->first_busno)
10562306a36Sopenharmony_ci			cfg_type = 1;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	bus_no = (bus->number == hose->first_busno) ?
10862306a36Sopenharmony_ci			hose->self_busno : bus->number;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_EXT_REG)
11162306a36Sopenharmony_ci		reg = ((offset & 0xf00) << 16) | (offset & 0xfc);
11262306a36Sopenharmony_ci	else
11362306a36Sopenharmony_ci		reg = offset & 0xfc;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_BIG_ENDIAN)
11662306a36Sopenharmony_ci		out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
11762306a36Sopenharmony_ci			 (devfn << 8) | reg | cfg_type));
11862306a36Sopenharmony_ci	else
11962306a36Sopenharmony_ci		out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
12062306a36Sopenharmony_ci			 (devfn << 8) | reg | cfg_type));
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	/* suppress setting of PCI_PRIMARY_BUS */
12362306a36Sopenharmony_ci	if (hose->indirect_type & PPC_INDIRECT_TYPE_SURPRESS_PRIMARY_BUS)
12462306a36Sopenharmony_ci		if ((offset == PCI_PRIMARY_BUS) &&
12562306a36Sopenharmony_ci			(bus->number == hose->first_busno))
12662306a36Sopenharmony_ci		val &= 0xffffff00;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* Workaround for PCI_28 Errata in 440EPx/GRx */
12962306a36Sopenharmony_ci	if ((hose->indirect_type & PPC_INDIRECT_TYPE_BROKEN_MRM) &&
13062306a36Sopenharmony_ci			offset == PCI_CACHE_LINE_SIZE) {
13162306a36Sopenharmony_ci		val = 0;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/*
13562306a36Sopenharmony_ci	 * Note: the caller has already checked that offset is
13662306a36Sopenharmony_ci	 * suitably aligned and that len is 1, 2 or 4.
13762306a36Sopenharmony_ci	 */
13862306a36Sopenharmony_ci	cfg_data = hose->cfg_data + (offset & 3);
13962306a36Sopenharmony_ci	switch (len) {
14062306a36Sopenharmony_ci	case 1:
14162306a36Sopenharmony_ci		out_8(cfg_data, val);
14262306a36Sopenharmony_ci		break;
14362306a36Sopenharmony_ci	case 2:
14462306a36Sopenharmony_ci		out_le16(cfg_data, val);
14562306a36Sopenharmony_ci		break;
14662306a36Sopenharmony_ci	default:
14762306a36Sopenharmony_ci		out_le32(cfg_data, val);
14862306a36Sopenharmony_ci		break;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci	return PCIBIOS_SUCCESSFUL;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic struct pci_ops indirect_pci_ops =
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	.read = indirect_read_config,
15662306a36Sopenharmony_ci	.write = indirect_write_config,
15762306a36Sopenharmony_ci};
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_civoid setup_indirect_pci(struct pci_controller *hose, resource_size_t cfg_addr,
16062306a36Sopenharmony_ci			resource_size_t cfg_data, u32 flags)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	resource_size_t base = cfg_addr & PAGE_MASK;
16362306a36Sopenharmony_ci	void __iomem *mbase;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	mbase = ioremap(base, PAGE_SIZE);
16662306a36Sopenharmony_ci	hose->cfg_addr = mbase + (cfg_addr & ~PAGE_MASK);
16762306a36Sopenharmony_ci	if ((cfg_data & PAGE_MASK) != base)
16862306a36Sopenharmony_ci		mbase = ioremap(cfg_data & PAGE_MASK, PAGE_SIZE);
16962306a36Sopenharmony_ci	hose->cfg_data = mbase + (cfg_data & ~PAGE_MASK);
17062306a36Sopenharmony_ci	hose->ops = &indirect_pci_ops;
17162306a36Sopenharmony_ci	hose->indirect_type = flags;
17262306a36Sopenharmony_ci}
173