| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Support for PCI on Celleb platform. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * (C) Copyright 2006-2007 TOSHIBA CORPORATION | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This code is based on arch/powerpc/kernel/rtas_pci.c: | 
					
						
							|  |  |  |  *  Copyright (C) 2001 Dave Engebretsen, IBM Corporation | 
					
						
							|  |  |  |  *  Copyright (C) 2003 Anton Blanchard <anton@au.ibm.com>, IBM | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation; either version 2 of the License, or | 
					
						
							|  |  |  |  * (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License along | 
					
						
							|  |  |  |  * with this program; if not, write to the Free Software Foundation, Inc., | 
					
						
							|  |  |  |  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #undef DEBUG
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/threads.h>
 | 
					
						
							|  |  |  | #include <linux/pci.h>
 | 
					
						
							|  |  |  | #include <linux/string.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/bootmem.h>
 | 
					
						
							|  |  |  | #include <linux/pci_regs.h>
 | 
					
						
							| 
									
										
										
										
											2008-01-09 06:20:40 +11:00
										 |  |  | #include <linux/of.h>
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | #include <linux/of_device.h>
 | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <asm/io.h>
 | 
					
						
							|  |  |  | #include <asm/irq.h>
 | 
					
						
							|  |  |  | #include <asm/prom.h>
 | 
					
						
							|  |  |  | #include <asm/pci-bridge.h>
 | 
					
						
							|  |  |  | #include <asm/ppc-pci.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-24 19:25:16 +10:00
										 |  |  | #include "io-workarounds.h"
 | 
					
						
							|  |  |  | #include "celleb_pci.h"
 | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | #define MAX_PCI_DEVICES    32
 | 
					
						
							|  |  |  | #define MAX_PCI_FUNCTIONS   8
 | 
					
						
							|  |  |  | #define MAX_PCI_BASE_ADDRS  3 /* use 64 bit address */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* definition for fake pci configuration area for GbE, .... ,and etc. */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct celleb_pci_resource { | 
					
						
							|  |  |  | 	struct resource r[MAX_PCI_BASE_ADDRS]; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct celleb_pci_private { | 
					
						
							|  |  |  | 	unsigned char *fake_config[MAX_PCI_DEVICES][MAX_PCI_FUNCTIONS]; | 
					
						
							|  |  |  | 	struct celleb_pci_resource *res[MAX_PCI_DEVICES][MAX_PCI_FUNCTIONS]; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline u8 celleb_fake_config_readb(void *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u8 *p = addr; | 
					
						
							|  |  |  | 	return *p; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline u16 celleb_fake_config_readw(void *addr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2007-02-09 16:39:50 +00:00
										 |  |  | 	__le16 *p = addr; | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	return le16_to_cpu(*p); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline u32 celleb_fake_config_readl(void *addr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2007-02-09 16:39:50 +00:00
										 |  |  | 	__le32 *p = addr; | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	return le32_to_cpu(*p); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void celleb_fake_config_writeb(u32 val, void *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u8 *p = addr; | 
					
						
							|  |  |  | 	*p = val; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void celleb_fake_config_writew(u32 val, void *addr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2007-02-09 16:39:50 +00:00
										 |  |  | 	__le16 val16; | 
					
						
							|  |  |  | 	__le16 *p = addr; | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	val16 = cpu_to_le16(val); | 
					
						
							|  |  |  | 	*p = val16; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void celleb_fake_config_writel(u32 val, void *addr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2007-02-09 16:39:50 +00:00
										 |  |  | 	__le32 val32; | 
					
						
							|  |  |  | 	__le32 *p = addr; | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	val32 = cpu_to_le32(val); | 
					
						
							|  |  |  | 	*p = val32; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static unsigned char *get_fake_config_start(struct pci_controller *hose, | 
					
						
							|  |  |  | 					    int devno, int fn) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct celleb_pci_private *private = hose->private_data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (private == NULL) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return private->fake_config[devno][fn]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct celleb_pci_resource *get_resource_start( | 
					
						
							|  |  |  | 				struct pci_controller *hose, | 
					
						
							|  |  |  | 				int devno, int fn) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct celleb_pci_private *private = hose->private_data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (private == NULL) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return private->res[devno][fn]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void celleb_config_read_fake(unsigned char *config, int where, | 
					
						
							|  |  |  | 				    int size, u32 *val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char *p = config + where; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (size) { | 
					
						
							|  |  |  | 	case 1: | 
					
						
							|  |  |  | 		*val = celleb_fake_config_readb(p); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 2: | 
					
						
							|  |  |  | 		*val = celleb_fake_config_readw(p); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 4: | 
					
						
							|  |  |  | 		*val = celleb_fake_config_readl(p); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void celleb_config_write_fake(unsigned char *config, int where, | 
					
						
							|  |  |  | 				     int size, u32 val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char *p = config + where; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (size) { | 
					
						
							|  |  |  | 	case 1: | 
					
						
							|  |  |  | 		celleb_fake_config_writeb(val, p); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 2: | 
					
						
							|  |  |  | 		celleb_fake_config_writew(val, p); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 4: | 
					
						
							|  |  |  | 		celleb_fake_config_writel(val, p); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int celleb_fake_pci_read_config(struct pci_bus *bus, | 
					
						
							|  |  |  | 		unsigned int devfn, int where, int size, u32 *val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char *config; | 
					
						
							| 
									
										
										
										
											2009-04-30 03:10:15 +00:00
										 |  |  | 	struct pci_controller *hose = pci_bus_to_host(bus); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	unsigned int devno = devfn >> 3; | 
					
						
							|  |  |  | 	unsigned int fn = devfn & 0x7; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* allignment check */ | 
					
						
							|  |  |  | 	BUG_ON(where % size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("    fake read: bus=0x%x, ", bus->number); | 
					
						
							|  |  |  | 	config = get_fake_config_start(hose, devno, fn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("devno=0x%x, where=0x%x, size=0x%x, ", devno, where, size); | 
					
						
							|  |  |  | 	if (!config) { | 
					
						
							|  |  |  | 		pr_debug("failed\n"); | 
					
						
							|  |  |  | 		return PCIBIOS_DEVICE_NOT_FOUND; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	celleb_config_read_fake(config, where, size, val); | 
					
						
							|  |  |  | 	pr_debug("val=0x%x\n", *val); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return PCIBIOS_SUCCESSFUL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int celleb_fake_pci_write_config(struct pci_bus *bus, | 
					
						
							| 
									
										
										
										
											2008-04-24 19:25:16 +10:00
										 |  |  | 		unsigned int devfn, int where, int size, u32 val) | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | { | 
					
						
							|  |  |  | 	char *config; | 
					
						
							| 
									
										
										
										
											2009-04-30 03:10:15 +00:00
										 |  |  | 	struct pci_controller *hose = pci_bus_to_host(bus); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	struct celleb_pci_resource *res; | 
					
						
							|  |  |  | 	unsigned int devno = devfn >> 3; | 
					
						
							|  |  |  | 	unsigned int fn = devfn & 0x7; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* allignment check */ | 
					
						
							|  |  |  | 	BUG_ON(where % size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	config = get_fake_config_start(hose, devno, fn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!config) | 
					
						
							|  |  |  | 		return PCIBIOS_DEVICE_NOT_FOUND; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (val == ~0) { | 
					
						
							|  |  |  | 		int i = (where - PCI_BASE_ADDRESS_0) >> 3; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch (where) { | 
					
						
							|  |  |  | 		case PCI_BASE_ADDRESS_0: | 
					
						
							|  |  |  | 		case PCI_BASE_ADDRESS_2: | 
					
						
							|  |  |  | 			if (size != 4) | 
					
						
							|  |  |  | 				return PCIBIOS_DEVICE_NOT_FOUND; | 
					
						
							|  |  |  | 			res = get_resource_start(hose, devno, fn); | 
					
						
							|  |  |  | 			if (!res) | 
					
						
							|  |  |  | 				return PCIBIOS_DEVICE_NOT_FOUND; | 
					
						
							|  |  |  | 			celleb_config_write_fake(config, where, size, | 
					
						
							|  |  |  | 					(res->r[i].end - res->r[i].start)); | 
					
						
							|  |  |  | 			return PCIBIOS_SUCCESSFUL; | 
					
						
							|  |  |  | 		case PCI_BASE_ADDRESS_1: | 
					
						
							|  |  |  | 		case PCI_BASE_ADDRESS_3: | 
					
						
							|  |  |  | 		case PCI_BASE_ADDRESS_4: | 
					
						
							|  |  |  | 		case PCI_BASE_ADDRESS_5: | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	celleb_config_write_fake(config, where, size, val); | 
					
						
							|  |  |  | 	pr_debug("    fake write: where=%x, size=%d, val=%x\n", | 
					
						
							|  |  |  | 		 where, size, val); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return PCIBIOS_SUCCESSFUL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct pci_ops celleb_fake_pci_ops = { | 
					
						
							| 
									
										
										
										
											2007-08-10 05:18:37 +10:00
										 |  |  | 	.read = celleb_fake_pci_read_config, | 
					
						
							|  |  |  | 	.write = celleb_fake_pci_write_config, | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void celleb_setup_pci_base_addrs(struct pci_controller *hose, | 
					
						
							|  |  |  | 					unsigned int devno, unsigned int fn, | 
					
						
							|  |  |  | 					unsigned int num_base_addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u32 val; | 
					
						
							|  |  |  | 	unsigned char *config; | 
					
						
							|  |  |  | 	struct celleb_pci_resource *res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	config = get_fake_config_start(hose, devno, fn); | 
					
						
							|  |  |  | 	res = get_resource_start(hose, devno, fn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!config || !res) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (num_base_addr) { | 
					
						
							|  |  |  | 	case 3: | 
					
						
							|  |  |  | 		val = (res->r[2].start & 0xfffffff0) | 
					
						
							|  |  |  | 		    | PCI_BASE_ADDRESS_MEM_TYPE_64; | 
					
						
							|  |  |  | 		celleb_config_write_fake(config, PCI_BASE_ADDRESS_4, 4, val); | 
					
						
							|  |  |  | 		val = res->r[2].start >> 32; | 
					
						
							|  |  |  | 		celleb_config_write_fake(config, PCI_BASE_ADDRESS_5, 4, val); | 
					
						
							|  |  |  | 		/* FALLTHROUGH */ | 
					
						
							|  |  |  | 	case 2: | 
					
						
							|  |  |  | 		val = (res->r[1].start & 0xfffffff0) | 
					
						
							|  |  |  | 		    | PCI_BASE_ADDRESS_MEM_TYPE_64; | 
					
						
							|  |  |  | 		celleb_config_write_fake(config, PCI_BASE_ADDRESS_2, 4, val); | 
					
						
							|  |  |  | 		val = res->r[1].start >> 32; | 
					
						
							|  |  |  | 		celleb_config_write_fake(config, PCI_BASE_ADDRESS_3, 4, val); | 
					
						
							|  |  |  | 		/* FALLTHROUGH */ | 
					
						
							|  |  |  | 	case 1: | 
					
						
							|  |  |  | 		val = (res->r[0].start & 0xfffffff0) | 
					
						
							|  |  |  | 		    | PCI_BASE_ADDRESS_MEM_TYPE_64; | 
					
						
							|  |  |  | 		celleb_config_write_fake(config, PCI_BASE_ADDRESS_0, 4, val); | 
					
						
							|  |  |  | 		val = res->r[0].start >> 32; | 
					
						
							|  |  |  | 		celleb_config_write_fake(config, PCI_BASE_ADDRESS_1, 4, val); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	val = PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; | 
					
						
							|  |  |  | 	celleb_config_write_fake(config, PCI_COMMAND, 2, val); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-07-26 19:59:17 +10:00
										 |  |  | static int __init celleb_setup_fake_pci_device(struct device_node *node, | 
					
						
							|  |  |  | 					       struct pci_controller *hose) | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned int rlen; | 
					
						
							|  |  |  | 	int num_base_addr = 0; | 
					
						
							|  |  |  | 	u32 val; | 
					
						
							|  |  |  | 	const u32 *wi0, *wi1, *wi2, *wi3, *wi4; | 
					
						
							|  |  |  | 	unsigned int devno, fn; | 
					
						
							|  |  |  | 	struct celleb_pci_private *private = hose->private_data; | 
					
						
							|  |  |  | 	unsigned char **config = NULL; | 
					
						
							|  |  |  | 	struct celleb_pci_resource **res = NULL; | 
					
						
							|  |  |  | 	const char *name; | 
					
						
							|  |  |  | 	const unsigned long *li; | 
					
						
							|  |  |  | 	int size, result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (private == NULL) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "PCI: " | 
					
						
							|  |  |  | 		       "memory space for pci controller is not assigned\n"); | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-04-03 22:26:41 +10:00
										 |  |  | 	name = of_get_property(node, "model", &rlen); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	if (!name) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "PCI: model property not found.\n"); | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-04-03 22:26:41 +10:00
										 |  |  | 	wi4 = of_get_property(node, "reg", &rlen); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	if (wi4 == NULL) | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	devno = ((wi4[0] >> 8) & 0xff) >> 3; | 
					
						
							|  |  |  | 	fn = (wi4[0] >> 8) & 0x7; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("PCI: celleb_setup_fake_pci() %s devno=%x fn=%x\n", name, | 
					
						
							|  |  |  | 		 devno, fn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	size = 256; | 
					
						
							|  |  |  | 	config = &private->fake_config[devno][fn]; | 
					
						
							| 
									
										
										
										
											2007-09-17 14:08:06 +10:00
										 |  |  | 	*config = alloc_maybe_bootmem(size, GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	if (*config == NULL) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "PCI: " | 
					
						
							|  |  |  | 		       "not enough memory for fake configuration space\n"); | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	pr_debug("PCI: fake config area assigned 0x%016lx\n", | 
					
						
							|  |  |  | 		 (unsigned long)*config); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	size = sizeof(struct celleb_pci_resource); | 
					
						
							|  |  |  | 	res = &private->res[devno][fn]; | 
					
						
							| 
									
										
										
										
											2007-09-17 14:08:06 +10:00
										 |  |  | 	*res = alloc_maybe_bootmem(size, GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	if (*res == NULL) { | 
					
						
							|  |  |  | 		printk(KERN_ERR | 
					
						
							|  |  |  | 		       "PCI: not enough memory for resource data space\n"); | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	pr_debug("PCI: res assigned 0x%016lx\n", (unsigned long)*res); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-04-03 22:26:41 +10:00
										 |  |  | 	wi0 = of_get_property(node, "device-id", NULL); | 
					
						
							|  |  |  | 	wi1 = of_get_property(node, "vendor-id", NULL); | 
					
						
							|  |  |  | 	wi2 = of_get_property(node, "class-code", NULL); | 
					
						
							|  |  |  | 	wi3 = of_get_property(node, "revision-id", NULL); | 
					
						
							| 
									
										
										
										
											2007-11-29 18:44:18 +11:00
										 |  |  | 	if (!wi0 || !wi1 || !wi2 || !wi3) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "PCI: Missing device tree properties.\n"); | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_DEVICE_ID, 2, wi0[0] & 0xffff); | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_VENDOR_ID, 2, wi1[0] & 0xffff); | 
					
						
							|  |  |  | 	pr_debug("class-code = 0x%08x\n", wi2[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_CLASS_PROG, 1, wi2[0] & 0xff); | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_CLASS_DEVICE, 2, | 
					
						
							|  |  |  | 				 (wi2[0] >> 8) & 0xffff); | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_REVISION_ID, 1, wi3[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (num_base_addr < MAX_PCI_BASE_ADDRS) { | 
					
						
							|  |  |  | 		result = of_address_to_resource(node, | 
					
						
							|  |  |  | 				num_base_addr, &(*res)->r[num_base_addr]); | 
					
						
							|  |  |  | 		if (result) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		num_base_addr++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	celleb_setup_pci_base_addrs(hose, devno, fn, num_base_addr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-04-03 22:26:41 +10:00
										 |  |  | 	li = of_get_property(node, "interrupts", &rlen); | 
					
						
							| 
									
										
										
										
											2007-11-29 18:44:18 +11:00
										 |  |  | 	if (!li) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "PCI: interrupts not found.\n"); | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	val = li[0]; | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_INTERRUPT_PIN, 1, 1); | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_INTERRUPT_LINE, 1, val); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG
 | 
					
						
							|  |  |  | 	pr_debug("PCI: %s irq=%ld\n", name, li[0]); | 
					
						
							|  |  |  | 	for (i = 0; i < 6; i++) { | 
					
						
							|  |  |  | 		celleb_config_read_fake(*config, | 
					
						
							|  |  |  | 					PCI_BASE_ADDRESS_0 + 0x4 * i, 4, | 
					
						
							|  |  |  | 					&val); | 
					
						
							|  |  |  | 		pr_debug("PCI: %s fn=%d base_address_%d=0x%x\n", | 
					
						
							|  |  |  | 			 name, fn, i, val); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	celleb_config_write_fake(*config, PCI_HEADER_TYPE, 1, | 
					
						
							|  |  |  | 				 PCI_HEADER_TYPE_NORMAL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | error: | 
					
						
							|  |  |  | 	if (mem_init_done) { | 
					
						
							|  |  |  | 		if (config && *config) | 
					
						
							|  |  |  | 			kfree(*config); | 
					
						
							|  |  |  | 		if (res && *res) | 
					
						
							|  |  |  | 			kfree(*res); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		if (config && *config) { | 
					
						
							|  |  |  | 			size = 256; | 
					
						
							|  |  |  | 			free_bootmem((unsigned long)(*config), size); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (res && *res) { | 
					
						
							|  |  |  | 			size = sizeof(struct celleb_pci_resource); | 
					
						
							|  |  |  | 			free_bootmem((unsigned long)(*res), size); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-07-26 19:59:17 +10:00
										 |  |  | static int __init phb_set_bus_ranges(struct device_node *dev, | 
					
						
							|  |  |  | 				     struct pci_controller *phb) | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | { | 
					
						
							|  |  |  | 	const int *bus_range; | 
					
						
							|  |  |  | 	unsigned int len; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-04-03 22:26:41 +10:00
										 |  |  | 	bus_range = of_get_property(dev, "bus-range", &len); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 	if (bus_range == NULL || len < 2 * sizeof(int)) | 
					
						
							|  |  |  | 		return 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	phb->first_busno = bus_range[0]; | 
					
						
							|  |  |  | 	phb->last_busno = bus_range[1]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-07-26 19:59:17 +10:00
										 |  |  | static void __init celleb_alloc_private_mem(struct pci_controller *hose) | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2007-09-17 14:08:06 +10:00
										 |  |  | 	hose->private_data = | 
					
						
							|  |  |  | 		alloc_maybe_bootmem(sizeof(struct celleb_pci_private), | 
					
						
							|  |  |  | 			GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | static int __init celleb_setup_fake_pci(struct device_node *dev, | 
					
						
							|  |  |  | 					struct pci_controller *phb) | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct device_node *node; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 	phb->ops = &celleb_fake_pci_ops; | 
					
						
							|  |  |  | 	celleb_alloc_private_mem(phb); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 	for (node = of_get_next_child(dev, NULL); | 
					
						
							|  |  |  | 	     node != NULL; node = of_get_next_child(dev, node)) | 
					
						
							|  |  |  | 		celleb_setup_fake_pci_device(node, phb); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-24 19:24:13 +10:00
										 |  |  | static struct celleb_phb_spec celleb_fake_pci_spec __initdata = { | 
					
						
							|  |  |  | 	.setup = celleb_setup_fake_pci, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | static struct of_device_id celleb_phb_match[] __initdata = { | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		.name = "pci-pseudo", | 
					
						
							| 
									
										
										
										
											2008-04-24 19:24:13 +10:00
										 |  |  | 		.data = &celleb_fake_pci_spec, | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 	}, { | 
					
						
							|  |  |  | 		.name = "epci", | 
					
						
							| 
									
										
										
										
											2008-04-24 19:24:13 +10:00
										 |  |  | 		.data = &celleb_epci_spec, | 
					
						
							| 
									
										
										
										
											2008-04-24 20:27:39 +10:00
										 |  |  | 	}, { | 
					
						
							|  |  |  | 		.name = "pcie", | 
					
						
							|  |  |  | 		.data = &celleb_pciex_spec, | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 	}, { | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-24 19:24:13 +10:00
										 |  |  | static int __init celleb_io_workaround_init(struct pci_controller *phb, | 
					
						
							|  |  |  | 					    struct celleb_phb_spec *phb_spec) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (phb_spec->ops) { | 
					
						
							|  |  |  | 		iowa_register_bus(phb, phb_spec->ops, phb_spec->iowa_init, | 
					
						
							|  |  |  | 				  phb_spec->iowa_data); | 
					
						
							|  |  |  | 		io_workaround_init(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | int __init celleb_setup_phb(struct pci_controller *phb) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2007-12-10 14:33:21 +11:00
										 |  |  | 	struct device_node *dev = phb->dn; | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 	const struct of_device_id *match; | 
					
						
							| 
									
										
										
										
											2008-04-24 19:24:13 +10:00
										 |  |  | 	struct celleb_phb_spec *phb_spec; | 
					
						
							|  |  |  | 	int rc; | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	match = of_match_node(celleb_phb_match, dev); | 
					
						
							|  |  |  | 	if (!match) | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | 		return 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-02 18:26:53 +10:00
										 |  |  | 	phb_set_bus_ranges(dev, phb); | 
					
						
							|  |  |  | 	phb->buid = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-24 19:24:13 +10:00
										 |  |  | 	phb_spec = match->data; | 
					
						
							|  |  |  | 	rc = (*phb_spec->setup)(dev, phb); | 
					
						
							|  |  |  | 	if (rc) | 
					
						
							|  |  |  | 		return 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return celleb_io_workaround_init(phb, phb_spec); | 
					
						
							| 
									
										
										
										
											2007-01-12 10:03:28 +09:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int celleb_pci_probe_mode(struct pci_bus *bus) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return PCI_PROBE_DEVTREE; | 
					
						
							|  |  |  | } |