18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Support for indirect PCI bridges. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 1998 Gabriel Paubert. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/pci.h> 108c2ecf20Sopenharmony_ci#include <linux/delay.h> 118c2ecf20Sopenharmony_ci#include <linux/string.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <asm/io.h> 158c2ecf20Sopenharmony_ci#include <asm/prom.h> 168c2ecf20Sopenharmony_ci#include <asm/pci-bridge.h> 178c2ecf20Sopenharmony_ci#include <asm/machdep.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ciint __indirect_read_config(struct pci_controller *hose, 208c2ecf20Sopenharmony_ci unsigned char bus_number, unsigned int devfn, 218c2ecf20Sopenharmony_ci int offset, int len, u32 *val) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci volatile void __iomem *cfg_data; 248c2ecf20Sopenharmony_ci u8 cfg_type = 0; 258c2ecf20Sopenharmony_ci u32 bus_no, reg; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_NO_PCIE_LINK) { 288c2ecf20Sopenharmony_ci if (bus_number != hose->first_busno) 298c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 308c2ecf20Sopenharmony_ci if (devfn != 0) 318c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 328c2ecf20Sopenharmony_ci } 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci if (ppc_md.pci_exclude_device) 358c2ecf20Sopenharmony_ci if (ppc_md.pci_exclude_device(hose, bus_number, devfn)) 368c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_SET_CFG_TYPE) 398c2ecf20Sopenharmony_ci if (bus_number != hose->first_busno) 408c2ecf20Sopenharmony_ci cfg_type = 1; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci bus_no = (bus_number == hose->first_busno) ? 438c2ecf20Sopenharmony_ci hose->self_busno : bus_number; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_EXT_REG) 468c2ecf20Sopenharmony_ci reg = ((offset & 0xf00) << 16) | (offset & 0xfc); 478c2ecf20Sopenharmony_ci else 488c2ecf20Sopenharmony_ci reg = offset & 0xfc; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_BIG_ENDIAN) 518c2ecf20Sopenharmony_ci out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 528c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 538c2ecf20Sopenharmony_ci else 548c2ecf20Sopenharmony_ci out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 558c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* 588c2ecf20Sopenharmony_ci * Note: the caller has already checked that offset is 598c2ecf20Sopenharmony_ci * suitably aligned and that len is 1, 2 or 4. 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_ci cfg_data = hose->cfg_data + (offset & 3); 628c2ecf20Sopenharmony_ci switch (len) { 638c2ecf20Sopenharmony_ci case 1: 648c2ecf20Sopenharmony_ci *val = in_8(cfg_data); 658c2ecf20Sopenharmony_ci break; 668c2ecf20Sopenharmony_ci case 2: 678c2ecf20Sopenharmony_ci *val = in_le16(cfg_data); 688c2ecf20Sopenharmony_ci break; 698c2ecf20Sopenharmony_ci default: 708c2ecf20Sopenharmony_ci *val = in_le32(cfg_data); 718c2ecf20Sopenharmony_ci break; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci return PCIBIOS_SUCCESSFUL; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ciint indirect_read_config(struct pci_bus *bus, unsigned int devfn, 778c2ecf20Sopenharmony_ci int offset, int len, u32 *val) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct pci_controller *hose = pci_bus_to_host(bus); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return __indirect_read_config(hose, bus->number, devfn, offset, len, 828c2ecf20Sopenharmony_ci val); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ciint indirect_write_config(struct pci_bus *bus, unsigned int devfn, 868c2ecf20Sopenharmony_ci int offset, int len, u32 val) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct pci_controller *hose = pci_bus_to_host(bus); 898c2ecf20Sopenharmony_ci volatile void __iomem *cfg_data; 908c2ecf20Sopenharmony_ci u8 cfg_type = 0; 918c2ecf20Sopenharmony_ci u32 bus_no, reg; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_NO_PCIE_LINK) { 948c2ecf20Sopenharmony_ci if (bus->number != hose->first_busno) 958c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 968c2ecf20Sopenharmony_ci if (devfn != 0) 978c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (ppc_md.pci_exclude_device) 1018c2ecf20Sopenharmony_ci if (ppc_md.pci_exclude_device(hose, bus->number, devfn)) 1028c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_SET_CFG_TYPE) 1058c2ecf20Sopenharmony_ci if (bus->number != hose->first_busno) 1068c2ecf20Sopenharmony_ci cfg_type = 1; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci bus_no = (bus->number == hose->first_busno) ? 1098c2ecf20Sopenharmony_ci hose->self_busno : bus->number; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_EXT_REG) 1128c2ecf20Sopenharmony_ci reg = ((offset & 0xf00) << 16) | (offset & 0xfc); 1138c2ecf20Sopenharmony_ci else 1148c2ecf20Sopenharmony_ci reg = offset & 0xfc; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_BIG_ENDIAN) 1178c2ecf20Sopenharmony_ci out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 1188c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 1198c2ecf20Sopenharmony_ci else 1208c2ecf20Sopenharmony_ci out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 1218c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* suppress setting of PCI_PRIMARY_BUS */ 1248c2ecf20Sopenharmony_ci if (hose->indirect_type & PPC_INDIRECT_TYPE_SURPRESS_PRIMARY_BUS) 1258c2ecf20Sopenharmony_ci if ((offset == PCI_PRIMARY_BUS) && 1268c2ecf20Sopenharmony_ci (bus->number == hose->first_busno)) 1278c2ecf20Sopenharmony_ci val &= 0xffffff00; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci /* Workaround for PCI_28 Errata in 440EPx/GRx */ 1308c2ecf20Sopenharmony_ci if ((hose->indirect_type & PPC_INDIRECT_TYPE_BROKEN_MRM) && 1318c2ecf20Sopenharmony_ci offset == PCI_CACHE_LINE_SIZE) { 1328c2ecf20Sopenharmony_ci val = 0; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* 1368c2ecf20Sopenharmony_ci * Note: the caller has already checked that offset is 1378c2ecf20Sopenharmony_ci * suitably aligned and that len is 1, 2 or 4. 1388c2ecf20Sopenharmony_ci */ 1398c2ecf20Sopenharmony_ci cfg_data = hose->cfg_data + (offset & 3); 1408c2ecf20Sopenharmony_ci switch (len) { 1418c2ecf20Sopenharmony_ci case 1: 1428c2ecf20Sopenharmony_ci out_8(cfg_data, val); 1438c2ecf20Sopenharmony_ci break; 1448c2ecf20Sopenharmony_ci case 2: 1458c2ecf20Sopenharmony_ci out_le16(cfg_data, val); 1468c2ecf20Sopenharmony_ci break; 1478c2ecf20Sopenharmony_ci default: 1488c2ecf20Sopenharmony_ci out_le32(cfg_data, val); 1498c2ecf20Sopenharmony_ci break; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci return PCIBIOS_SUCCESSFUL; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic struct pci_ops indirect_pci_ops = 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci .read = indirect_read_config, 1578c2ecf20Sopenharmony_ci .write = indirect_write_config, 1588c2ecf20Sopenharmony_ci}; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_civoid setup_indirect_pci(struct pci_controller *hose, resource_size_t cfg_addr, 1618c2ecf20Sopenharmony_ci resource_size_t cfg_data, u32 flags) 1628c2ecf20Sopenharmony_ci{ 1638c2ecf20Sopenharmony_ci resource_size_t base = cfg_addr & PAGE_MASK; 1648c2ecf20Sopenharmony_ci void __iomem *mbase; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci mbase = ioremap(base, PAGE_SIZE); 1678c2ecf20Sopenharmony_ci hose->cfg_addr = mbase + (cfg_addr & ~PAGE_MASK); 1688c2ecf20Sopenharmony_ci if ((cfg_data & PAGE_MASK) != base) 1698c2ecf20Sopenharmony_ci mbase = ioremap(cfg_data & PAGE_MASK, PAGE_SIZE); 1708c2ecf20Sopenharmony_ci hose->cfg_data = mbase + (cfg_data & ~PAGE_MASK); 1718c2ecf20Sopenharmony_ci hose->ops = &indirect_pci_ops; 1728c2ecf20Sopenharmony_ci hose->indirect_type = flags; 1738c2ecf20Sopenharmony_ci} 174