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 <linux/io.h> 158c2ecf20Sopenharmony_ci#include <asm/pci-bridge.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic int 188c2ecf20Sopenharmony_ciindirect_read_config(struct pci_bus *bus, unsigned int devfn, int offset, 198c2ecf20Sopenharmony_ci int len, u32 *val) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci struct pci_controller *hose = pci_bus_to_host(bus); 228c2ecf20Sopenharmony_ci volatile void __iomem *cfg_data; 238c2ecf20Sopenharmony_ci u8 cfg_type = 0; 248c2ecf20Sopenharmony_ci u32 bus_no, reg; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_NO_PCIE_LINK) { 278c2ecf20Sopenharmony_ci if (bus->number != hose->first_busno) 288c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 298c2ecf20Sopenharmony_ci if (devfn != 0) 308c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 318c2ecf20Sopenharmony_ci } 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_SET_CFG_TYPE) 348c2ecf20Sopenharmony_ci if (bus->number != hose->first_busno) 358c2ecf20Sopenharmony_ci cfg_type = 1; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci bus_no = (bus->number == hose->first_busno) ? 388c2ecf20Sopenharmony_ci hose->self_busno : bus->number; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_EXT_REG) 418c2ecf20Sopenharmony_ci reg = ((offset & 0xf00) << 16) | (offset & 0xfc); 428c2ecf20Sopenharmony_ci else 438c2ecf20Sopenharmony_ci reg = offset & 0xfc; /* Only 3 bits for function */ 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_BIG_ENDIAN) 468c2ecf20Sopenharmony_ci out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 478c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 488c2ecf20Sopenharmony_ci else 498c2ecf20Sopenharmony_ci out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 508c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* 538c2ecf20Sopenharmony_ci * Note: the caller has already checked that offset is 548c2ecf20Sopenharmony_ci * suitably aligned and that len is 1, 2 or 4. 558c2ecf20Sopenharmony_ci */ 568c2ecf20Sopenharmony_ci cfg_data = hose->cfg_data + (offset & 3); /* Only 3 bits for function */ 578c2ecf20Sopenharmony_ci switch (len) { 588c2ecf20Sopenharmony_ci case 1: 598c2ecf20Sopenharmony_ci *val = in_8(cfg_data); 608c2ecf20Sopenharmony_ci break; 618c2ecf20Sopenharmony_ci case 2: 628c2ecf20Sopenharmony_ci *val = in_le16(cfg_data); 638c2ecf20Sopenharmony_ci break; 648c2ecf20Sopenharmony_ci default: 658c2ecf20Sopenharmony_ci *val = in_le32(cfg_data); 668c2ecf20Sopenharmony_ci break; 678c2ecf20Sopenharmony_ci } 688c2ecf20Sopenharmony_ci return PCIBIOS_SUCCESSFUL; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int 728c2ecf20Sopenharmony_ciindirect_write_config(struct pci_bus *bus, unsigned int devfn, int offset, 738c2ecf20Sopenharmony_ci int len, u32 val) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct pci_controller *hose = pci_bus_to_host(bus); 768c2ecf20Sopenharmony_ci volatile void __iomem *cfg_data; 778c2ecf20Sopenharmony_ci u8 cfg_type = 0; 788c2ecf20Sopenharmony_ci u32 bus_no, reg; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_NO_PCIE_LINK) { 818c2ecf20Sopenharmony_ci if (bus->number != hose->first_busno) 828c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 838c2ecf20Sopenharmony_ci if (devfn != 0) 848c2ecf20Sopenharmony_ci return PCIBIOS_DEVICE_NOT_FOUND; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_SET_CFG_TYPE) 888c2ecf20Sopenharmony_ci if (bus->number != hose->first_busno) 898c2ecf20Sopenharmony_ci cfg_type = 1; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci bus_no = (bus->number == hose->first_busno) ? 928c2ecf20Sopenharmony_ci hose->self_busno : bus->number; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_EXT_REG) 958c2ecf20Sopenharmony_ci reg = ((offset & 0xf00) << 16) | (offset & 0xfc); 968c2ecf20Sopenharmony_ci else 978c2ecf20Sopenharmony_ci reg = offset & 0xfc; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_BIG_ENDIAN) 1008c2ecf20Sopenharmony_ci out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 1018c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 1028c2ecf20Sopenharmony_ci else 1038c2ecf20Sopenharmony_ci out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) | 1048c2ecf20Sopenharmony_ci (devfn << 8) | reg | cfg_type)); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* suppress setting of PCI_PRIMARY_BUS */ 1078c2ecf20Sopenharmony_ci if (hose->indirect_type & INDIRECT_TYPE_SURPRESS_PRIMARY_BUS) 1088c2ecf20Sopenharmony_ci if ((offset == PCI_PRIMARY_BUS) && 1098c2ecf20Sopenharmony_ci (bus->number == hose->first_busno)) 1108c2ecf20Sopenharmony_ci val &= 0xffffff00; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* Workaround for PCI_28 Errata in 440EPx/GRx */ 1138c2ecf20Sopenharmony_ci if ((hose->indirect_type & INDIRECT_TYPE_BROKEN_MRM) && 1148c2ecf20Sopenharmony_ci offset == PCI_CACHE_LINE_SIZE) { 1158c2ecf20Sopenharmony_ci val = 0; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci /* 1198c2ecf20Sopenharmony_ci * Note: the caller has already checked that offset is 1208c2ecf20Sopenharmony_ci * suitably aligned and that len is 1, 2 or 4. 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_ci cfg_data = hose->cfg_data + (offset & 3); 1238c2ecf20Sopenharmony_ci switch (len) { 1248c2ecf20Sopenharmony_ci case 1: 1258c2ecf20Sopenharmony_ci out_8(cfg_data, val); 1268c2ecf20Sopenharmony_ci break; 1278c2ecf20Sopenharmony_ci case 2: 1288c2ecf20Sopenharmony_ci out_le16(cfg_data, val); 1298c2ecf20Sopenharmony_ci break; 1308c2ecf20Sopenharmony_ci default: 1318c2ecf20Sopenharmony_ci out_le32(cfg_data, val); 1328c2ecf20Sopenharmony_ci break; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return PCIBIOS_SUCCESSFUL; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic struct pci_ops indirect_pci_ops = { 1398c2ecf20Sopenharmony_ci .read = indirect_read_config, 1408c2ecf20Sopenharmony_ci .write = indirect_write_config, 1418c2ecf20Sopenharmony_ci}; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_civoid __init 1448c2ecf20Sopenharmony_cisetup_indirect_pci(struct pci_controller *hose, 1458c2ecf20Sopenharmony_ci resource_size_t cfg_addr, 1468c2ecf20Sopenharmony_ci resource_size_t cfg_data, u32 flags) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci resource_size_t base = cfg_addr & PAGE_MASK; 1498c2ecf20Sopenharmony_ci void __iomem *mbase; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci mbase = ioremap(base, PAGE_SIZE); 1528c2ecf20Sopenharmony_ci hose->cfg_addr = mbase + (cfg_addr & ~PAGE_MASK); 1538c2ecf20Sopenharmony_ci if ((cfg_data & PAGE_MASK) != base) 1548c2ecf20Sopenharmony_ci mbase = ioremap(cfg_data & PAGE_MASK, PAGE_SIZE); 1558c2ecf20Sopenharmony_ci hose->cfg_data = mbase + (cfg_data & ~PAGE_MASK); 1568c2ecf20Sopenharmony_ci hose->ops = &indirect_pci_ops; 1578c2ecf20Sopenharmony_ci hose->indirect_type = flags; 1588c2ecf20Sopenharmony_ci} 159