1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
23cc30d07SMichael Ellerman /*
33cc30d07SMichael Ellerman  * Support PCI IO workaround
43cc30d07SMichael Ellerman  *
53cc30d07SMichael Ellerman  *  Copyright (C) 2006 Benjamin Herrenschmidt <benh@kernel.crashing.org>
63cc30d07SMichael Ellerman  *		       IBM, Corp.
73cc30d07SMichael Ellerman  *  (C) Copyright 2007-2008 TOSHIBA CORPORATION
83cc30d07SMichael Ellerman  */
93cc30d07SMichael Ellerman #undef DEBUG
103cc30d07SMichael Ellerman 
113cc30d07SMichael Ellerman #include <linux/kernel.h>
12589ee628SIngo Molnar #include <linux/sched/mm.h>	/* for init_mm */
1365fddcfcSMike Rapoport #include <linux/pgtable.h>
143cc30d07SMichael Ellerman 
153cc30d07SMichael Ellerman #include <asm/io.h>
163cc30d07SMichael Ellerman #include <asm/machdep.h>
173cc30d07SMichael Ellerman #include <asm/ppc-pci.h>
183cc30d07SMichael Ellerman #include <asm/io-workarounds.h>
1994171b19SAneesh Kumar K.V #include <asm/pte-walk.h>
2094171b19SAneesh Kumar K.V 
213cc30d07SMichael Ellerman 
223cc30d07SMichael Ellerman #define IOWA_MAX_BUS	8
233cc30d07SMichael Ellerman 
243cc30d07SMichael Ellerman static struct iowa_bus iowa_busses[IOWA_MAX_BUS];
253cc30d07SMichael Ellerman static unsigned int iowa_bus_count;
263cc30d07SMichael Ellerman 
273cc30d07SMichael Ellerman static struct iowa_bus *iowa_pci_find(unsigned long vaddr, unsigned long paddr)
283cc30d07SMichael Ellerman {
293cc30d07SMichael Ellerman 	int i, j;
303cc30d07SMichael Ellerman 	struct resource *res;
313cc30d07SMichael Ellerman 	unsigned long vstart, vend;
323cc30d07SMichael Ellerman 
333cc30d07SMichael Ellerman 	for (i = 0; i < iowa_bus_count; i++) {
343cc30d07SMichael Ellerman 		struct iowa_bus *bus = &iowa_busses[i];
353cc30d07SMichael Ellerman 		struct pci_controller *phb = bus->phb;
363cc30d07SMichael Ellerman 
373cc30d07SMichael Ellerman 		if (vaddr) {
383cc30d07SMichael Ellerman 			vstart = (unsigned long)phb->io_base_virt;
393cc30d07SMichael Ellerman 			vend = vstart + phb->pci_io_size - 1;
403cc30d07SMichael Ellerman 			if ((vaddr >= vstart) && (vaddr <= vend))
413cc30d07SMichael Ellerman 				return bus;
423cc30d07SMichael Ellerman 		}
433cc30d07SMichael Ellerman 
443cc30d07SMichael Ellerman 		if (paddr)
453cc30d07SMichael Ellerman 			for (j = 0; j < 3; j++) {
463cc30d07SMichael Ellerman 				res = &phb->mem_resources[j];
473cc30d07SMichael Ellerman 				if (paddr >= res->start && paddr <= res->end)
483cc30d07SMichael Ellerman 					return bus;
493cc30d07SMichael Ellerman 			}
503cc30d07SMichael Ellerman 	}
513cc30d07SMichael Ellerman 
523cc30d07SMichael Ellerman 	return NULL;
533cc30d07SMichael Ellerman }
543cc30d07SMichael Ellerman 
55ecd73cc5SBenjamin Herrenschmidt #ifdef CONFIG_PPC_INDIRECT_MMIO
563cc30d07SMichael Ellerman struct iowa_bus *iowa_mem_find_bus(const PCI_IO_ADDR addr)
573cc30d07SMichael Ellerman {
5812bc9f6fSAneesh Kumar K.V 	unsigned hugepage_shift;
593cc30d07SMichael Ellerman 	struct iowa_bus *bus;
603cc30d07SMichael Ellerman 	int token;
613cc30d07SMichael Ellerman 
623cc30d07SMichael Ellerman 	token = PCI_GET_ADDR_TOKEN(addr);
633cc30d07SMichael Ellerman 
643cc30d07SMichael Ellerman 	if (token && token <= iowa_bus_count)
653cc30d07SMichael Ellerman 		bus = &iowa_busses[token - 1];
663cc30d07SMichael Ellerman 	else {
673cc30d07SMichael Ellerman 		unsigned long vaddr, paddr;
683cc30d07SMichael Ellerman 		pte_t *ptep;
693cc30d07SMichael Ellerman 
703cc30d07SMichael Ellerman 		vaddr = (unsigned long)PCI_FIX_ADDR(addr);
713cc30d07SMichael Ellerman 		if (vaddr < PHB_IO_BASE || vaddr >= PHB_IO_END)
723cc30d07SMichael Ellerman 			return NULL;
73691e95fdSAneesh Kumar K.V 		/*
74691e95fdSAneesh Kumar K.V 		 * We won't find huge pages here (iomem). Also can't hit
75691e95fdSAneesh Kumar K.V 		 * a page table free due to init_mm
76691e95fdSAneesh Kumar K.V 		 */
7794171b19SAneesh Kumar K.V 		ptep = find_init_mm_pte(vaddr, &hugepage_shift);
783cc30d07SMichael Ellerman 		if (ptep == NULL)
793cc30d07SMichael Ellerman 			paddr = 0;
8012bc9f6fSAneesh Kumar K.V 		else {
8112bc9f6fSAneesh Kumar K.V 			WARN_ON(hugepage_shift);
823cc30d07SMichael Ellerman 			paddr = pte_pfn(*ptep) << PAGE_SHIFT;
8312bc9f6fSAneesh Kumar K.V 		}
843cc30d07SMichael Ellerman 		bus = iowa_pci_find(vaddr, paddr);
853cc30d07SMichael Ellerman 
863cc30d07SMichael Ellerman 		if (bus == NULL)
873cc30d07SMichael Ellerman 			return NULL;
883cc30d07SMichael Ellerman 	}
893cc30d07SMichael Ellerman 
903cc30d07SMichael Ellerman 	return bus;
913cc30d07SMichael Ellerman }
92ecd73cc5SBenjamin Herrenschmidt #else /* CONFIG_PPC_INDIRECT_MMIO */
93ecd73cc5SBenjamin Herrenschmidt struct iowa_bus *iowa_mem_find_bus(const PCI_IO_ADDR addr)
94ecd73cc5SBenjamin Herrenschmidt {
95ecd73cc5SBenjamin Herrenschmidt 	return NULL;
96ecd73cc5SBenjamin Herrenschmidt }
97ecd73cc5SBenjamin Herrenschmidt #endif /* !CONFIG_PPC_INDIRECT_MMIO */
983cc30d07SMichael Ellerman 
99ecd73cc5SBenjamin Herrenschmidt #ifdef CONFIG_PPC_INDIRECT_PIO
1003cc30d07SMichael Ellerman struct iowa_bus *iowa_pio_find_bus(unsigned long port)
1013cc30d07SMichael Ellerman {
1023cc30d07SMichael Ellerman 	unsigned long vaddr = (unsigned long)pci_io_base + port;
1033cc30d07SMichael Ellerman 	return iowa_pci_find(vaddr, 0);
1043cc30d07SMichael Ellerman }
105ecd73cc5SBenjamin Herrenschmidt #else
106ecd73cc5SBenjamin Herrenschmidt struct iowa_bus *iowa_pio_find_bus(unsigned long port)
107ecd73cc5SBenjamin Herrenschmidt {
108ecd73cc5SBenjamin Herrenschmidt 	return NULL;
109ecd73cc5SBenjamin Herrenschmidt }
110ecd73cc5SBenjamin Herrenschmidt #endif
1113cc30d07SMichael Ellerman 
1123cc30d07SMichael Ellerman #define DEF_PCI_AC_RET(name, ret, at, al, space, aa)		\
1133cc30d07SMichael Ellerman static ret iowa_##name at					\
1143cc30d07SMichael Ellerman {								\
1153cc30d07SMichael Ellerman 	struct iowa_bus *bus;					\
1163cc30d07SMichael Ellerman 	bus = iowa_##space##_find_bus(aa);			\
1173cc30d07SMichael Ellerman 	if (bus && bus->ops && bus->ops->name)			\
1183cc30d07SMichael Ellerman 		return bus->ops->name al;			\
1193cc30d07SMichael Ellerman 	return __do_##name al;					\
1203cc30d07SMichael Ellerman }
1213cc30d07SMichael Ellerman 
1223cc30d07SMichael Ellerman #define DEF_PCI_AC_NORET(name, at, al, space, aa)		\
1233cc30d07SMichael Ellerman static void iowa_##name at					\
1243cc30d07SMichael Ellerman {								\
1253cc30d07SMichael Ellerman 	struct iowa_bus *bus;					\
1263cc30d07SMichael Ellerman 	bus = iowa_##space##_find_bus(aa);			\
1273cc30d07SMichael Ellerman 	if (bus && bus->ops && bus->ops->name) {		\
1283cc30d07SMichael Ellerman 		bus->ops->name al;				\
1293cc30d07SMichael Ellerman 		return;						\
1303cc30d07SMichael Ellerman 	}							\
1313cc30d07SMichael Ellerman 	__do_##name al;						\
1323cc30d07SMichael Ellerman }
1333cc30d07SMichael Ellerman 
1343cc30d07SMichael Ellerman #include <asm/io-defs.h>
1353cc30d07SMichael Ellerman 
1363cc30d07SMichael Ellerman #undef DEF_PCI_AC_RET
1373cc30d07SMichael Ellerman #undef DEF_PCI_AC_NORET
1383cc30d07SMichael Ellerman 
139cad5cef6SGreg Kroah-Hartman static const struct ppc_pci_io iowa_pci_io = {
1403cc30d07SMichael Ellerman 
1413cc30d07SMichael Ellerman #define DEF_PCI_AC_RET(name, ret, at, al, space, aa)	.name = iowa_##name,
1423cc30d07SMichael Ellerman #define DEF_PCI_AC_NORET(name, at, al, space, aa)	.name = iowa_##name,
1433cc30d07SMichael Ellerman 
1443cc30d07SMichael Ellerman #include <asm/io-defs.h>
1453cc30d07SMichael Ellerman 
1463cc30d07SMichael Ellerman #undef DEF_PCI_AC_RET
1473cc30d07SMichael Ellerman #undef DEF_PCI_AC_NORET
1483cc30d07SMichael Ellerman 
1493cc30d07SMichael Ellerman };
1503cc30d07SMichael Ellerman 
151ecd73cc5SBenjamin Herrenschmidt #ifdef CONFIG_PPC_INDIRECT_MMIO
15214b4d976SChristophe Leroy void __iomem *iowa_ioremap(phys_addr_t addr, unsigned long size,
153c766ee72SChristophe Leroy 			   pgprot_t prot, void *caller)
1543cc30d07SMichael Ellerman {
1553cc30d07SMichael Ellerman 	struct iowa_bus *bus;
156c766ee72SChristophe Leroy 	void __iomem *res = __ioremap_caller(addr, size, prot, caller);
1573cc30d07SMichael Ellerman 	int busno;
1583cc30d07SMichael Ellerman 
1593cc30d07SMichael Ellerman 	bus = iowa_pci_find(0, (unsigned long)addr);
1603cc30d07SMichael Ellerman 	if (bus != NULL) {
1613cc30d07SMichael Ellerman 		busno = bus - iowa_busses;
1623cc30d07SMichael Ellerman 		PCI_SET_ADDR_TOKEN(res, busno + 1);
1633cc30d07SMichael Ellerman 	}
1643cc30d07SMichael Ellerman 	return res;
1653cc30d07SMichael Ellerman }
166ecd73cc5SBenjamin Herrenschmidt #endif /* !CONFIG_PPC_INDIRECT_MMIO */
1673cc30d07SMichael Ellerman 
16814b4d976SChristophe Leroy bool io_workaround_inited;
16914b4d976SChristophe Leroy 
170d1109b75SMichael Ellerman /* Enable IO workaround */
171cad5cef6SGreg Kroah-Hartman static void io_workaround_init(void)
172d1109b75SMichael Ellerman {
173d1109b75SMichael Ellerman 	if (io_workaround_inited)
174d1109b75SMichael Ellerman 		return;
175d1109b75SMichael Ellerman 	ppc_pci_io = iowa_pci_io;
17614b4d976SChristophe Leroy 	io_workaround_inited = true;
177d1109b75SMichael Ellerman }
178d1109b75SMichael Ellerman 
179d1109b75SMichael Ellerman /* Register new bus to support workaround */
180cad5cef6SGreg Kroah-Hartman void iowa_register_bus(struct pci_controller *phb, struct ppc_pci_io *ops,
1813cc30d07SMichael Ellerman 		       int (*initfunc)(struct iowa_bus *, void *), void *data)
1823cc30d07SMichael Ellerman {
1833cc30d07SMichael Ellerman 	struct iowa_bus *bus;
1843cc30d07SMichael Ellerman 	struct device_node *np = phb->dn;
1853cc30d07SMichael Ellerman 
186d1109b75SMichael Ellerman 	io_workaround_init();
187d1109b75SMichael Ellerman 
1883cc30d07SMichael Ellerman 	if (iowa_bus_count >= IOWA_MAX_BUS) {
1893cc30d07SMichael Ellerman 		pr_err("IOWA:Too many pci bridges, "
190b7c670d6SRob Herring 		       "workarounds disabled for %pOF\n", np);
1913cc30d07SMichael Ellerman 		return;
1923cc30d07SMichael Ellerman 	}
1933cc30d07SMichael Ellerman 
1943cc30d07SMichael Ellerman 	bus = &iowa_busses[iowa_bus_count];
1953cc30d07SMichael Ellerman 	bus->phb = phb;
1963cc30d07SMichael Ellerman 	bus->ops = ops;
19769b12368SMichael Ellerman 	bus->private = data;
1983cc30d07SMichael Ellerman 
1993cc30d07SMichael Ellerman 	if (initfunc)
2003cc30d07SMichael Ellerman 		if ((*initfunc)(bus, data))
2013cc30d07SMichael Ellerman 			return;
2023cc30d07SMichael Ellerman 
2033cc30d07SMichael Ellerman 	iowa_bus_count++;
2043cc30d07SMichael Ellerman 
205b7c670d6SRob Herring 	pr_debug("IOWA:[%d]Add bus, %pOF.\n", iowa_bus_count-1, np);
2063cc30d07SMichael Ellerman }
2073cc30d07SMichael Ellerman 
208