linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 11/19] powerpc: Supporting PCI bus and base of I/O for Celleb
@ 2006-12-14  2:38 Ishizaki Kou
  2006-12-14  4:54 ` Benjamin Herrenschmidt
  0 siblings, 1 reply; 2+ messages in thread
From: Ishizaki Kou @ 2006-12-14  2:38 UTC (permalink / raw)
  To: paulus; +Cc: linuxppc-dev

This patch includes support for pci buses, base of Celleb specific
devices, and etc. It works on of_platform bus.

Signed-off-by: Kou Ishizaki <kou.ishizaki@toshiba.co.jp>
---

Index: linux-powerpc-git/arch/powerpc/platforms/celleb/pci.c
diff -u /dev/null linux-powerpc-git/arch/powerpc/platforms/celleb/pci.c:1.8
--- /dev/null	Wed Dec 13 21:32:03 2006
+++ linux-powerpc-git/arch/powerpc/platforms/celleb/pci.c	Wed Dec 13 18:13:44 2006
@@ -0,0 +1,581 @@
+/*
+ * Support for PCI on Celleb platform.
+ *
+ * (C) Copyright 2006 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>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/pci-bridge.h>
+#include <asm/ppc-pci.h>
+
+#include "pci.h"
+#include "interrupt.h"
+
+#define MAX_PCI_DEVICES    32
+#define MAX_PCI_FUNCTIONS   8
+#define MAX_PCI_BASE_ADDRS  6
+
+
+/* definition for fake pci configuration area for GbE, .... ,and etc. */
+
+struct celleb_pci_iodata {
+	unsigned long iobase[MAX_PCI_BASE_ADDRS];
+	unsigned long iosize[MAX_PCI_BASE_ADDRS];
+};
+
+struct celleb_pci_private {
+	unsigned char *fake_config[MAX_PCI_DEVICES][MAX_PCI_FUNCTIONS];
+	struct celleb_pci_iodata *iodata[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)
+{
+	u16 *p = addr;
+	return le16_to_cpu(*p);
+}
+
+static inline u32 celleb_fake_config_readl(void *addr)
+{
+	u32 *p = addr;
+	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)
+{
+	u16 val16;
+	u16 *p = addr;
+	val16 = cpu_to_le16(val);
+	*p = val16;
+}
+
+static inline void celleb_fake_config_writel(u32 val, void *addr)
+{
+	u32 val32;
+	u32 *p = addr;
+	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 unsigned long *get_iobase_start(struct pci_controller *hose,
+				       int devno, int fn)
+{
+	struct celleb_pci_private *private = hose->private_data;
+
+	if (private == NULL || private->iodata[devno][fn] == NULL)
+		return NULL;
+
+	return private->iodata[devno][fn]->iobase;
+}
+
+static unsigned long *get_iosize_start(struct pci_controller *hose,
+				       int devno, int fn)
+{
+	struct celleb_pci_private *private = hose->private_data;
+
+	if (private == NULL || private->iodata[devno][fn] == NULL)
+		return NULL;
+
+	return private->iodata[devno][fn]->iosize;
+}
+
+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;
+	}
+
+	return;
+}
+
+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;
+	}
+	return;
+}
+
+static int celleb_fake_pci_read_config(struct pci_bus *bus,
+		unsigned int devfn, int where, int size, u32 *val)
+{
+	char *config;
+	struct device_node *node;
+	struct pci_controller *hose;
+	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);
+	node = (struct device_node *)bus->sysdata;
+	hose = pci_find_hose_for_OF_device(node);
+	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,
+		 unsigned int devfn, int where, int size, u32 val)
+{
+	char *config;
+	struct device_node *node;
+	struct pci_controller *hose;
+	unsigned long *iosize;
+	unsigned int devno = devfn >> 3;
+	unsigned int fn = devfn & 0x7;
+
+	/* allignment check */
+	BUG_ON(where % size);
+
+	node = (struct device_node *)bus->sysdata;
+	hose = pci_find_hose_for_OF_device(node);
+	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;
+			iosize = get_iosize_start(hose, devno, fn);
+			if (!iosize)
+				return PCIBIOS_DEVICE_NOT_FOUND;
+			celleb_config_write_fake(config, where, size,
+						 iosize[i]);
+			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 = {
+	celleb_fake_pci_read_config,
+	celleb_fake_pci_write_config
+};
+
+static 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;
+	unsigned long *iobase;
+
+	config = get_fake_config_start(hose, devno, fn);
+	iobase = get_iobase_start(hose, devno, fn);
+
+	if (!config || !iobase)
+		return;
+
+	switch (num_base_addr) {
+	case 3:
+		val = (iobase[2] & 0xfffffff0)
+		    | PCI_BASE_ADDRESS_MEM_TYPE_64;
+		celleb_config_write_fake(config, PCI_BASE_ADDRESS_4, 4,
+					 val);
+		val = iobase[2] >> 32;
+		celleb_config_write_fake(config, PCI_BASE_ADDRESS_5, 4,
+					 val);
+		/* FALLTHROUGH */
+	case 2:
+		val = (iobase[1] & 0xfffffff0)
+		    | PCI_BASE_ADDRESS_MEM_TYPE_64;
+		celleb_config_write_fake(config, PCI_BASE_ADDRESS_2, 4,
+					 val);
+		val = iobase[1] >> 32;
+		celleb_config_write_fake(config, PCI_BASE_ADDRESS_3, 4,
+					 val);
+		/* FALLTHROUGH */
+	case 1:
+		val = (iobase[0] & 0xfffffff0)
+		    | PCI_BASE_ADDRESS_MEM_TYPE_64;
+		celleb_config_write_fake(config, PCI_BASE_ADDRESS_0, 4,
+					 val);
+		val = iobase[0] >> 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);
+}
+
+
+static int __devinit celleb_setup_fake_pci_device(struct device_node *node,
+						  struct pci_controller *hose)
+{
+	unsigned int rlen, num_base_addr;
+	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_iodata **iodata = NULL;
+	unsigned long *iobase, *iosize;
+	const char *name;
+	const unsigned long *li;
+	int size;
+	int i;
+
+	if (private == NULL) {
+		printk(KERN_ERR "PCI: "
+		       "memory space for pci controller is not assigned\n");
+		goto error;
+	}
+
+	name = get_property(node, "model", &rlen);
+	if (!name) {
+		printk(KERN_ERR "PCI: model property not found.\n");
+		goto error;
+	}
+
+	wi4 = get_property(node, "reg", &rlen);
+	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];
+	if (mem_init_done)
+		*config = kzalloc(size, GFP_KERNEL);
+	else
+		*config = alloc_bootmem(size);
+	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_iodata);
+	iodata = &private->iodata[devno][fn];
+	if (mem_init_done)
+		*iodata = kzalloc(size, GFP_KERNEL);
+	else
+		*iodata = alloc_bootmem(size);
+	if (*iodata == NULL) {
+		printk(KERN_ERR
+		       "PCI: not enough memory for iobase data space\n");
+		goto error;
+	}
+	pr_debug("PCI: iodata assigned 0x%016lx\n", (unsigned long)*iodata);
+
+	iobase = get_iobase_start(hose, devno, fn);
+	iosize = get_iosize_start(hose, devno, fn);
+	pr_debug("PCI: "
+		 "iobase info addr = 0x%016lx, iosize info addr=0x%016lx\n",
+		 (unsigned long)iobase, (unsigned long)iosize);
+
+	wi0 = get_property(node, "device-id", NULL);
+	wi1 = get_property(node, "vendor-id", NULL);
+	wi2 = get_property(node, "class-code", NULL);
+	wi3 = get_property(node, "revision-id", NULL);
+
+	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]);
+
+	li = get_property(node, "toshiba,reg", &rlen);
+
+	if (li) {
+		num_base_addr = rlen >> 4;
+
+		if (num_base_addr > 3)
+			num_base_addr = 3;
+		pr_debug("PCI: %s: number of base addresses is %d\n",
+			 name, num_base_addr);
+
+		for (i = 0; i < num_base_addr; i++) {
+			iobase[i] = li[i * 2];
+			iosize[i] = li[i * 2 + 1];
+			pr_debug("PCI: base address %d: 0x%lx-0x%lx\n",
+				 i, li[i * 2], li[i * 2 + 1]);
+		}
+
+	} else {
+		printk(KERN_ERR "PCI: No toshiba,reg property\n");
+		goto error;
+	}
+
+	celleb_setup_pci_base_addrs(hose, devno, fn, num_base_addr);
+
+	li = get_property(node, "interrupts", &rlen);
+	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 (iodata && *iodata)
+			kfree(*iodata);
+
+	} else {
+		if (config && *config) {
+			size = 256;
+			free_bootmem((unsigned long)(*config), size);
+		}
+		if (iodata && *iodata) {
+			size = sizeof(struct celleb_pci_iodata);
+			free_bootmem((unsigned long)(*iodata), size);
+		}
+	}
+
+	return 1;
+}
+
+
+void __devinit celleb_pcibios_fixup(void)
+{
+	struct pci_dev *dev = NULL;
+	struct pci_bus *bus;
+	struct list_head *ln;
+
+	for_each_pci_dev(dev) {
+		bus = dev->bus;
+		if (bus && ppc_md.pci_probe_mode(bus) == PCI_PROBE_NORMAL)
+			continue;
+
+		pci_read_irq_line(dev);
+	}
+
+	list_for_each(ln, &pci_root_buses) {
+		bus = pci_bus_b(ln);
+		if (ppc_md.pci_probe_mode(bus) == PCI_PROBE_NORMAL) {
+			dev = NULL;
+
+#if 1				/* XXX: This is for SYSCON workaround */
+			list_for_each_entry(dev, &bus->devices, bus_list) {
+				int i;
+				if ((dev->class >> 8) != PCI_CLASS_BRIDGE_HOST)
+					continue;
+
+				for (i = 0; i < PCI_NUM_RESOURCES; i++) {
+					struct resource *r = NULL;
+					struct resource *root = NULL;
+					r = &dev->resource[i];
+					root = pcibios_select_root(dev, r);
+					if (!(r->flags) || r->parent)
+						continue;
+
+					insert_resource(root, r);
+				}
+			}
+#endif
+			pci_bus_assign_resources(bus);
+			pci_enable_bridges(bus);
+		}
+	}
+
+	return;
+}
+
+static int __devinit phb_set_bus_ranges(struct device_node *dev,
+					struct pci_controller *phb)
+{
+	const int *bus_range;
+	unsigned int len;
+
+	bus_range = get_property(dev, "bus-range", &len);
+	if (bus_range == NULL || len < 2 * sizeof(int))
+		return 1;
+
+	phb->first_busno = bus_range[0];
+	phb->last_busno = bus_range[1];
+
+	return 0;
+}
+
+static void __devinit celleb_alloc_private_mem(struct pci_controller *hose)
+{
+	if (mem_init_done)
+		hose->private_data =
+			kzalloc(sizeof(struct celleb_pci_private), GFP_KERNEL);
+	else
+		hose->private_data =
+			alloc_bootmem(sizeof(struct celleb_pci_private));
+}
+
+int __devinit celleb_setup_phb(struct pci_controller *phb)
+{
+	const char *name;
+	struct device_node *dev = phb->arch_data;
+	struct device_node *node;
+	unsigned int rlen;
+	
+	name = get_property(dev, "name", &rlen);
+	if (!name)
+		return 1;
+
+	pr_debug("PCI: celleb_setup_phb() %s\n", name);
+	phb_set_bus_ranges(dev, phb);
+
+	if (strcmp(name, "epci") == 0) {
+		phb->ops = &celleb_epci_ops;
+		celleb_setup_epci(dev, phb);
+
+	} else if (strcmp(name, "pci-pseudo") == 0) {
+		phb->ops = &celleb_fake_pci_ops;
+		celleb_alloc_private_mem(phb);
+		for (node = of_get_next_child(dev, NULL);
+		     node != NULL; node = of_get_next_child(dev, node))
+			celleb_setup_fake_pci_device(node, phb);
+
+	} else
+		return 1;
+
+	return 0;
+}
+
+/*
+ * To be fixed: watch <n> bit of "reg" property
+ *              -- and set proper property to "reg" via bootloader.
+ */
+int celleb_pci_probe_mode(struct pci_bus *bus)
+{
+	struct device_node *node;
+	const char *name;
+
+	node = (struct device_node *)bus->sysdata;
+	name = get_property(node, "name", NULL);
+
+	if (strcmp(name, "pci-pseudo") == 0)
+		return PCI_PROBE_DEVTREE;
+
+	return PCI_PROBE_NORMAL;
+}
Index: linux-powerpc-git/arch/powerpc/platforms/celleb/pci.h
diff -u /dev/null linux-powerpc-git/arch/powerpc/platforms/celleb/pci.h:1.2
--- /dev/null	Wed Dec 13 21:32:03 2006
+++ linux-powerpc-git/arch/powerpc/platforms/celleb/pci.h	Wed Dec 13 18:37:14 2006
@@ -0,0 +1,36 @@
+/*
+ * pci prototypes for Celleb platform
+ *
+ * (C) Copyright 2006 TOSHIBA CORPORATION
+ *
+ * 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.
+ */
+
+#ifndef _CELLEB_PCI_H
+#define _CELLEB_PCI_H
+
+#include <linux/pci.h>
+
+#include <asm/pci-bridge.h>
+#include <asm/prom.h>
+
+extern int celleb_setup_phb(struct pci_controller *);
+extern void celleb_pcibios_fixup(void);
+extern int celleb_pci_probe_mode(struct pci_bus *);
+
+extern struct pci_ops celleb_epci_ops;
+extern int celleb_setup_epci(struct device_node *, struct pci_controller *);
+
+#endif /* _CELLEB_PCI_H */
Index: linux-powerpc-git/arch/powerpc/platforms/celleb/scc_epci.c
diff -u /dev/null linux-powerpc-git/arch/powerpc/platforms/celleb/scc_epci.c:1.6
--- /dev/null	Wed Dec 13 21:32:03 2006
+++ linux-powerpc-git/arch/powerpc/platforms/celleb/scc_epci.c	Wed Dec 13 18:13:44 2006
@@ -0,0 +1,547 @@
+/*
+ * Support for SCC external PCI
+ *
+ * (C) Copyright 2004-2006 TOSHIBA CORPORATION
+ *
+ * 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/init.h>
+#include <linux/pci_regs.h>
+#include <linux/bootmem.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/pci-bridge.h>
+#include <asm/ppc-pci.h>
+
+#include "scc.h"
+#include "pci.h"
+#include "interrupt.h"
+
+#define MAX_PCI_DEVICES   32
+#define MAX_PCI_FUNCTIONS  8
+
+#define iob()  __asm__ __volatile__("eieio; sync":::"memory")
+
+
+struct celleb_epci_private {
+	unsigned int epci_ext_irq;
+	unsigned int epci_int_irq;
+};
+
+
+#if 0
+static void celleb_epci_dummy_read(struct pci_dev *dev)
+{
+	unsigned long epci_base;
+	struct device_node *node;
+	struct pci_controller *hose;
+	u32 val;
+
+	node = (struct device_node *)dev->bus->sysdata;
+	hose = pci_find_hose_for_OF_device(node);
+
+	if (!hose)
+		return;
+
+	epci_base = (unsigned long)hose->cfg_addr;
+
+	val = in_be32((void *)(epci_base + SCC_EPCI_WATRP));
+	iosync();
+
+	return;
+}
+#endif
+
+static inline void clear_and_disable_master_abort_interrupt(
+					struct pci_controller *hose)
+{
+	unsigned long addr;
+	addr = (unsigned long)hose->cfg_addr + PCI_COMMAND;
+	out_be32((void *)addr,
+		 in_be32((void *)addr) | (PCI_STATUS_REC_MASTER_ABORT << 16));
+}
+
+static int celleb_epci_check_abort(struct pci_controller *hose,
+				   unsigned long addr)
+{
+	unsigned long reg;
+	unsigned long epci_base;
+	u32 val;
+
+	iob();
+	epci_base = (unsigned long)hose->cfg_addr;
+
+	reg = epci_base + PCI_COMMAND;
+	val = in_be32((void *)reg);
+
+	if (val & (PCI_STATUS_REC_MASTER_ABORT << 16)) {
+		out_be32((void *)reg,
+			 (val & 0xffff) | (PCI_STATUS_REC_MASTER_ABORT << 16));
+
+		/* clear PCI Controller error, FRE, PMFE */
+		reg = epci_base + SCC_EPCI_STATUS;
+		out_be32((void *)reg, SCC_EPCI_INT_PAI);
+
+		reg = epci_base + SCC_EPCI_VCSR;
+		val = in_be32((void *)reg) & 0xffff;
+		val |= SCC_EPCI_VCSR_FRE;
+		out_be32((void *)reg, val);
+
+		reg = epci_base + SCC_EPCI_VISTAT;
+		out_be32((void *)reg, SCC_EPCI_VISTAT_PMFE);
+		return PCIBIOS_DEVICE_NOT_FOUND;
+	}
+
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int celleb_epci_subordinate_read_config(struct pci_controller *hose,
+					       int bus, unsigned int devfn,
+					       int where, int size, u32 *val)
+{
+	unsigned long addr;
+
+	if (!hose->cfg_data)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	if (where == PCI_INTERRUPT_LINE) {
+		struct celleb_epci_private *private = hose->private_data;
+		if (private == NULL)
+			return PCIBIOS_DEVICE_NOT_FOUND;
+		*val = irq_create_mapping(NULL, private->epci_ext_irq);
+		return PCIBIOS_SUCCESSFUL;
+	} else if (where == PCI_INTERRUPT_PIN) {
+		*val = 1;
+		return PCIBIOS_SUCCESSFUL;
+	}
+
+	clear_and_disable_master_abort_interrupt(hose);
+
+	/* address for PCI configuration access */
+	addr = (unsigned long)hose->cfg_data +
+		(((bus & 0xff) << 16)
+		 | ((devfn & 0xff) << 8)
+		 | (where & 0xff)
+		 | 0x01000000);
+
+	switch (size) {
+	case 1:
+		*val = in_8((u8 *)addr);
+		break;
+	case 2:
+		*val = in_le16((u16 *)addr);
+		break;
+	case 4:
+		*val = in_le32((u32 *)addr);
+		break;
+	default:
+		return PCIBIOS_DEVICE_NOT_FOUND;
+	}
+
+	return celleb_epci_check_abort(hose, 0);
+}
+
+static int celleb_epci_subordinate_write_config(struct pci_controller *hose,
+						int bus, unsigned int devfn,
+						int where, int size, u32 val)
+{
+	unsigned long addr;
+
+	if (!hose->cfg_data)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	clear_and_disable_master_abort_interrupt(hose);
+
+	/* address for PCI configuration access */
+	addr = (unsigned long)hose->cfg_data +
+		(((bus & 0xff) << 16)
+		 | ((devfn & 0xff) << 8)
+		 | (where & 0xff)
+		 | 0x01000000);
+
+	switch (size) {
+	case 1:
+		out_8((u8 *)addr, val);
+		break;
+	case 2:
+		out_le16((u16 *)addr, val);
+		break;
+	case 4:
+		out_le32((u32 *)addr, val);
+		break;
+	default:
+		return PCIBIOS_DEVICE_NOT_FOUND;
+	}
+
+	return celleb_epci_check_abort(hose, addr);
+}
+
+static int celleb_epci_read_config(struct pci_bus *bus,
+			unsigned int devfn, int where, int size, u32 * val)
+{
+	unsigned long addr;
+	struct device_node *node;
+	struct pci_controller *hose;
+
+	/* allignment check */
+	BUG_ON(where % size);
+
+	node = (struct device_node *)bus->sysdata;
+	hose = pci_find_hose_for_OF_device(node);
+
+	if (bus->self)
+		return celleb_epci_subordinate_read_config(hose,
+					bus->number, devfn, where, size, val);
+
+	if (!hose->cfg_data)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	if (bus->number == hose->first_busno && devfn == 0) {
+		/* EPCI controller self */
+
+		if (where == PCI_INTERRUPT_LINE) {
+			struct celleb_epci_private *private = hose->private_data;
+			if (private == NULL)
+				return PCIBIOS_DEVICE_NOT_FOUND;
+			*val = irq_create_mapping(NULL, private->epci_int_irq);
+			return PCIBIOS_SUCCESSFUL;
+		} else if (where == PCI_INTERRUPT_PIN) {
+			*val = 1;
+			return PCIBIOS_SUCCESSFUL;
+		}
+
+		addr = (unsigned long)hose->cfg_addr + where;
+
+		switch (size) {
+		case 1:
+			*val = in_8((u8 *)addr);
+			break;
+		case 2:
+			*val = in_be16((u16 *)addr);
+			break;
+		case 4:
+			*val = in_be32((u32 *)addr);
+			break;
+		default:
+			return PCIBIOS_DEVICE_NOT_FOUND;
+		}
+
+	} else {
+
+		if (where == PCI_INTERRUPT_LINE) {
+			struct celleb_epci_private *private = hose->private_data;
+			if (private == NULL)
+				return PCIBIOS_DEVICE_NOT_FOUND;
+			*val = irq_create_mapping(NULL, private->epci_ext_irq);
+			return PCIBIOS_SUCCESSFUL;
+		} else if (where == PCI_INTERRUPT_PIN) {
+			*val = 1;
+			return PCIBIOS_SUCCESSFUL;
+		}
+
+		clear_and_disable_master_abort_interrupt(hose);
+
+		/* address for PCI configuration access */
+		addr = (unsigned long)hose->cfg_data +
+			(((devfn & 0xff) << 8) | (where & 0xff));
+
+		switch (size) {
+		case 1:
+			*val = in_8((u8 *)addr);
+			break;
+		case 2:
+			*val = in_le16((u16 *)addr);
+			break;
+		case 4:
+			*val = in_le32((u32 *)addr);
+			break;
+		default:
+			return PCIBIOS_DEVICE_NOT_FOUND;
+		}
+	}
+
+	pr_debug("    epci read: "
+		 "addr=0x%lx, devfn=0x%x, where=0x%x, size=0x%x, val=0x%x\n",
+		 addr, devfn, where, size, *val);
+
+	return celleb_epci_check_abort(hose, 0);
+}
+
+static int celleb_epci_write_config(struct pci_bus *bus,
+			unsigned int devfn, int where, int size, u32 val)
+{
+	unsigned long addr;
+	struct device_node *node;
+	struct pci_controller *hose;
+
+	/* allignment check */
+	BUG_ON(where % size);
+
+	node = (struct device_node *)bus->sysdata;
+	hose = pci_find_hose_for_OF_device(node);
+
+	if (bus->self)
+		return celleb_epci_subordinate_write_config(hose,
+					bus->number, devfn, where, size, val);
+
+	if (!hose->cfg_data)
+		return PCIBIOS_DEVICE_NOT_FOUND;
+
+	if (bus->number == hose->first_busno && devfn == 0) {
+		/* EPCI controller self */
+
+		addr = (unsigned long)hose->cfg_addr + where;
+
+		switch (size) {
+		case 1:
+			out_8((u8 *)addr, val);
+			break;
+		case 2:
+			out_be16((u16 *)addr, val);
+			break;
+		case 4:
+			out_be32((u32 *)addr, val);
+			break;
+		default:
+			return PCIBIOS_DEVICE_NOT_FOUND;
+		}
+
+	} else {
+
+		clear_and_disable_master_abort_interrupt(hose);
+
+		/* address for PCI configuration access */
+		addr = (unsigned long)hose->cfg_data +
+			(((devfn & 0xff) << 8) | (where & 0xff));
+
+		switch (size) {
+		case 1:
+			out_8((u8 *)addr, val);
+			break;
+		case 2:
+			out_le16((u16 *)addr, val);
+			break;
+		case 4:
+			out_le32((u32 *)addr, val);
+			break;
+		default:
+			return PCIBIOS_DEVICE_NOT_FOUND;
+		}
+	}
+
+	return celleb_epci_check_abort(hose, addr);
+}
+
+struct pci_ops celleb_epci_ops = {
+	celleb_epci_read_config,
+	celleb_epci_write_config,
+};
+
+static int __devinit celleb_epci_init(struct pci_controller *hose)
+{
+	u32 val;
+	unsigned long reg;
+	unsigned long epci_base;
+	int hwres = 0;
+
+	epci_base = (unsigned long)hose->cfg_addr;
+
+	/* PCI core reset(Internal bus and PCI clock) */
+	reg = epci_base + SCC_EPCI_CKCTRL;
+	val = in_be32((void *)reg);
+	if (val == 0x00030101)
+		hwres = 1;
+	else {
+		val &= ~(SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1);
+		out_be32((void *)reg, val);
+
+		/* set PCI core clock */
+		val = in_be32((void *)reg);
+		val |= (SCC_EPCI_CKCTRL_OCLKEN | SCC_EPCI_CKCTRL_LCLKEN);
+		out_be32((void *)reg, val);
+
+		/* release PCI core reset (internal bus) */
+		val = in_be32((void *)reg);
+		val |= SCC_EPCI_CKCTRL_CRST0;
+		out_be32((void *)reg, val);
+
+		/* set PCI clock select */
+		reg = epci_base + SCC_EPCI_CLKRST;
+		val = in_be32((void *)reg);
+		val &= ~SCC_EPCI_CLKRST_CKS_MASK;
+		val |= SCC_EPCI_CLKRST_CKS_2;
+		out_be32((void *)reg, val);
+
+		/* set arbiter */
+		reg = epci_base + SCC_EPCI_ABTSET;
+		out_be32((void *)reg, 0x0f1f001f);	/* temporary value */
+
+		/* buffer on */
+		reg = epci_base + SCC_EPCI_CLKRST;
+		val = in_be32((void *)reg);
+		val |= SCC_EPCI_CLKRST_BC;
+		out_be32((void *)reg, val);
+
+		/* PCI clock enable */
+		val = in_be32((void *)reg);
+		val |= SCC_EPCI_CLKRST_PCKEN;
+		out_be32((void *)reg, val);
+
+		/* release PCI core reset (all) */
+		reg = epci_base + SCC_EPCI_CKCTRL;
+		val = in_be32((void *)reg);
+		val |= (SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1);
+		out_be32((void *)reg, val);
+
+		/* set base translation registers. (already set by Beat) */
+
+		/* set base address masks. (already set by Beat) */
+	}
+
+	/* release interrupt masks and clear all interrupts */
+	reg = epci_base + SCC_EPCI_INTSET;
+	out_be32((void *)reg, 0x013f011f);	/* all interrupts enable */
+	reg = epci_base + SCC_EPCI_VIENAB;
+	val = SCC_EPCI_VIENAB_PMPEE | SCC_EPCI_VIENAB_PMFEE;
+	out_be32((void *)reg, val);
+	reg = epci_base + SCC_EPCI_STATUS;
+	out_be32((void *)reg, 0xffffffff);
+	reg = epci_base + SCC_EPCI_VISTAT;
+	out_be32((void *)reg, 0xffffffff);
+
+	/* disable PCI->IB address translation */
+	reg = epci_base + SCC_EPCI_VCSR;
+	val = in_be32((void *)reg);
+	val &= ~(SCC_EPCI_VCSR_DR | SCC_EPCI_VCSR_AT);
+	out_be32((void *)reg, val);
+
+	/* set base addresses. (no need to set?) */
+
+	/* memory space, bus master enable */
+	reg = epci_base + PCI_COMMAND;
+	val = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
+	out_be32((void *)reg, val);
+
+	/* endian mode setup */
+	reg = epci_base + SCC_EPCI_ECMODE;
+	//val = 0x00ff03ff;
+	val = 0x00550155;
+	//val = 0x00550000;
+	out_be32((void *)reg, val);
+
+	/* set control option */
+	reg = epci_base + SCC_EPCI_CNTOPT;
+	val = in_be32((void *)reg);
+	val |= SCC_EPCI_CNTOPT_O2PMB;
+	out_be32((void *)reg, val);
+
+	/* XXX: temporay: set registers for address conversion setup */
+	reg = epci_base + SCC_EPCI_CNF10_REG;
+	out_be32((void *)reg, 0x80000008);
+	reg = epci_base + SCC_EPCI_CNF14_REG;
+	out_be32((void *)reg, 0x40000008);
+
+	reg = epci_base + SCC_EPCI_BAM0;
+	out_be32((void *)reg, 0x80000000);
+	reg = epci_base + SCC_EPCI_BAM1;
+	out_be32((void *)reg, 0xe0000000);
+
+	reg = epci_base + SCC_EPCI_PVBAT;
+	out_be32((void *)reg, 0x80000000);
+
+	if (!hwres) {
+		/* release external PCI reset */
+		reg = epci_base + SCC_EPCI_CLKRST;
+		val = in_be32((void *)reg);
+		val |= SCC_EPCI_CLKRST_PCIRST;
+		out_be32((void *)reg, val);
+	}
+
+	return 0;
+}
+
+int __devinit celleb_setup_epci(struct device_node *node,
+				struct pci_controller *hose)
+{
+	const unsigned long *li, *li2;
+	unsigned int rlen;
+	struct celleb_epci_private *private;
+
+
+	pr_debug("PCI: celleb_setup_epci()\n");
+
+	li = get_property(node, "toshiba,reg", &rlen);
+
+	if (!li || ((rlen >> 4) != 4)) {
+		printk(KERN_ERR "PCI: No toshiba,reg property\n");
+		goto error;
+	}
+
+	hose->cfg_addr = ioremap(li[0], li[1]);
+	if (!hose->cfg_addr)
+		goto error;
+
+	hose->cfg_data = ioremap(li[4], li[5]);
+	if (!hose->cfg_data)
+		goto error;
+
+	if (mem_init_done)
+		hose->private_data = private =
+		    kzalloc(sizeof(struct celleb_epci_private), GFP_KERNEL);
+	else
+		hose->private_data = private =
+		    alloc_bootmem(sizeof(struct celleb_epci_private));
+
+
+	li2 = get_property(node, "interrupts", &rlen);
+	if (!li2) {
+		printk(KERN_ERR "PCI: No interrupts property");
+		goto error;
+	}
+
+	if (private){
+		private->epci_ext_irq = li2[0];
+		private->epci_int_irq = li2[1];
+	}
+
+	pr_debug("PCI: epci_ext_irq %x epci_int_irq %x\n",
+		 private->epci_ext_irq, private->epci_int_irq);
+	pr_debug("PCI: cfg_addr 0x%016lx->0x%016lx + 0x%016lx\n",
+		 li[0], (unsigned long)hose->cfg_addr, li[1]);
+	pr_debug("PCI: cfg_data map 0x%016lx->0x%016lx + 0x%016lx\n",
+		 li[4], (unsigned long)hose->cfg_data, li[5]);
+
+	celleb_epci_init(hose);
+
+	return 0;
+
+error:
+	if (hose->cfg_addr)
+		iounmap((void *)hose->cfg_addr);
+
+	if (hose->cfg_data)
+		iounmap((void *)hose->cfg_data);
+
+	return 1;
+}
Index: linux-powerpc-git/arch/powerpc/platforms/celleb/scc_uhc.c
diff -u /dev/null linux-powerpc-git/arch/powerpc/platforms/celleb/scc_uhc.c:1.3
--- /dev/null	Wed Dec 13 21:32:03 2006
+++ linux-powerpc-git/arch/powerpc/platforms/celleb/scc_uhc.c	Wed Dec 13 18:37:14 2006
@@ -0,0 +1,90 @@
+/*
+ * SCC (Super Companion Chip) UHC setup
+ *
+ * (C) Copyright 2006 TOSHIBA CORPORATION
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include <asm/delay.h>
+#include <asm/io.h>
+
+#include "scc.h"
+
+#define UHC_RESET_WAIT_MAX 10000
+
+static inline int uhc_clkctrl_ready(u32 val)
+{
+	const u32 mask = SCC_UHC_USBCEN | SCC_UHC_USBCEN;
+	return((val & mask) == mask);
+}
+
+/*
+ * UHC(usb host controler) enable function.
+ * affect to both of OHCI and EHCI core module.
+ */
+static void quirk_scc_uhc_enable(struct pci_dev *dev)
+{
+	void __iomem *uhc_base;
+	u32 __iomem *uhc_clkctrl;
+	u32 __iomem *uhc_ecmode;
+	u32 val = 0;
+	int i;
+
+	uhc_base = ioremap(pci_resource_start(dev, 0),
+			   pci_resource_len(dev, 0));
+	if (!uhc_base) {
+		printk(KERN_ERR "failed to map UHC register base.\n");
+		return;
+	}
+	uhc_clkctrl = uhc_base + SCC_UHC_CKRCTRL; 
+	uhc_ecmode  = uhc_base + SCC_UHC_ECMODE; 
+
+	/* setup for normal mode */
+	val |= SCC_UHC_F48MCKLEN;
+	out_be32(uhc_clkctrl, val);
+	val |= SCC_UHC_PHY_SUSPEND_SEL;
+	out_be32(uhc_clkctrl, val);
+	udelay(10);
+	val |= SCC_UHC_PHYEN;
+	out_be32(uhc_clkctrl, val);
+	udelay(50);
+
+	/* disable reset */
+	val |= SCC_UHC_HCLKEN;
+	out_be32(uhc_clkctrl, val);
+	val |= (SCC_UHC_USBCEN | SCC_UHC_USBEN);
+	out_be32(uhc_clkctrl, val);
+	i = 0;
+	while (!uhc_clkctrl_ready(in_be32(uhc_clkctrl))) {
+		udelay(10);
+		if (i++ > UHC_RESET_WAIT_MAX) {
+			printk(KERN_ERR "Failed to disable UHC reset %x\n",
+			       in_be32(uhc_clkctrl));
+			break;
+		}
+	}
+
+	/* Endian Conversion Mode for Master ALL area */
+	out_be32(uhc_ecmode, SCC_UHC_ECMODE_BY_BYTE);
+
+	iounmap(uhc_base);
+}
+
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA_2,
+		 PCI_DEVICE_ID_TOSHIBA_SCC_USB, quirk_scc_uhc_enable);

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH 11/19] powerpc: Supporting PCI bus and base of I/O for Celleb
  2006-12-14  2:38 [PATCH 11/19] powerpc: Supporting PCI bus and base of I/O for Celleb Ishizaki Kou
@ 2006-12-14  4:54 ` Benjamin Herrenschmidt
  0 siblings, 0 replies; 2+ messages in thread
From: Benjamin Herrenschmidt @ 2006-12-14  4:54 UTC (permalink / raw)
  To: Ishizaki Kou; +Cc: linuxppc-dev, paulus

On Thu, 2006-12-14 at 11:38 +0900, Ishizaki Kou wrote:
> This patch includes support for pci buses, base of Celleb specific
> devices, and etc. It works on of_platform bus.

There is still a few serious issues unfortunately with that PCI code...

> +static int celleb_epci_subordinate_read_config(struct pci_controller *hose,
> +					       int bus, unsigned int devfn,
> +					       int where, int size, u32 *val)
> +{
> +	unsigned long addr;
> +
> +	if (!hose->cfg_data)
> +		return PCIBIOS_DEVICE_NOT_FOUND;
> +
> +	if (where == PCI_INTERRUPT_LINE) {
> +		struct celleb_epci_private *private = hose->private_data;
> +		if (private == NULL)
> +			return PCIBIOS_DEVICE_NOT_FOUND;
> +		*val = irq_create_mapping(NULL, private->epci_ext_irq);
> +		return PCIBIOS_SUCCESSFUL;
> +	} else if (where == PCI_INTERRUPT_PIN) {
> +		*val = 1;
> +		return PCIBIOS_SUCCESSFUL;
> +	}

Why the above ? You have 3 copies of the code here that does the IRQ
mapping inside of read_config, and I don't see this making any sense.

The IRQ mapping should be in the device-tree, in the form of an
interrupt-map property, or at -worse- as a fixup pass to setup
pci_dev->irq in ppc_md, not some weird code in the config space. Nobody
actually cares what is in PCI_INTERRUPT_LINE of a PCI device in fact.
 
> +	clear_and_disable_master_abort_interrupt(hose);
> +
> +	/* address for PCI configuration access */
> +	addr = (unsigned long)hose->cfg_data +
> +		(((bus & 0xff) << 16)
> +		 | ((devfn & 0xff) << 8)
> +		 | (where & 0xff)
> +		 | 0x01000000);
> +
> +	switch (size) {
> +	case 1:
> +		*val = in_8((u8 *)addr);
> +		break;
> +	case 2:
> +		*val = in_le16((u16 *)addr);
> +		break;
> +	case 4:
> +		*val = in_le32((u32 *)addr);
> +		break;
> +	default:
> +		return PCIBIOS_DEVICE_NOT_FOUND;
> +	}
> +
> +	return celleb_epci_check_abort(hose, 0);
> +}

The above code is duplicated several times (+/- endian) and I don't see
why. You definitely do not need special functions that are 90% duplicate
to handle wether you are doing a type 0 or type 1 config access :-) Best
is you look at how other platforms do it, it's mostly a single if in the
first few lines of the function to calculate the cfg address.

The host bridge case must stay separate due to different endian, though
it should still shrink the size of the code significantly by moving the
IRQ bits elsewhere.

> +int __devinit celleb_setup_epci(struct device_node *node,
> +				struct pci_controller *hose)
> +{
> +	const unsigned long *li, *li2;
> +	unsigned int rlen;
> +	struct celleb_epci_private *private;
> +
> +
> +	pr_debug("PCI: celleb_setup_epci()\n");
> +
> +	li = get_property(node, "toshiba,reg", &rlen);

Why that ? Why not use a normal "reg" property with proper ranges etc...
in the device tree, and then here, use the standard
of_address_to_resource() or similar ?

We are getting critical short on time for the 2.6.20 merge window
unfortunately. I hope we might still be able to get in, though I can't
guarantee it at this point.

Cheers,
Ben.

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2006-12-14  4:54 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-12-14  2:38 [PATCH 11/19] powerpc: Supporting PCI bus and base of I/O for Celleb Ishizaki Kou
2006-12-14  4:54 ` Benjamin Herrenschmidt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).