| /* |
| * 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> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| |
| #include <asm/io.h> |
| #include <asm/irq.h> |
| #include <asm/prom.h> |
| #include <asm/pci-bridge.h> |
| #include <asm/ppc-pci.h> |
| |
| #include "io-workarounds.h" |
| #include "celleb_pci.h" |
| |
| #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) |
| { |
| __le16 *p = addr; |
| return le16_to_cpu(*p); |
| } |
| |
| static inline u32 celleb_fake_config_readl(void *addr) |
| { |
| __le32 *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) |
| { |
| __le16 val16; |
| __le16 *p = addr; |
| val16 = cpu_to_le16(val); |
| *p = val16; |
| } |
| |
| static inline void celleb_fake_config_writel(u32 val, void *addr) |
| { |
| __le32 val32; |
| __le32 *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 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; |
| struct pci_controller *hose = pci_bus_to_host(bus); |
| 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, |
| unsigned int devfn, int where, int size, u32 val) |
| { |
| char *config; |
| struct pci_controller *hose = pci_bus_to_host(bus); |
| 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 = { |
| .read = celleb_fake_pci_read_config, |
| .write = celleb_fake_pci_write_config, |
| }; |
| |
| 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); |
| } |
| |
| static int __init celleb_setup_fake_pci_device(struct device_node *node, |
| struct pci_controller *hose) |
| { |
| 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; |
| } |
| |
| name = of_get_property(node, "model", &rlen); |
| if (!name) { |
| printk(KERN_ERR "PCI: model property not found.\n"); |
| goto error; |
| } |
| |
| wi4 = of_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]; |
| *config = alloc_maybe_bootmem(size, GFP_KERNEL); |
| 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]; |
| *res = alloc_maybe_bootmem(size, GFP_KERNEL); |
| 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); |
| |
| 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); |
| if (!wi0 || !wi1 || !wi2 || !wi3) { |
| printk(KERN_ERR "PCI: Missing device tree properties.\n"); |
| goto error; |
| } |
| |
| 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); |
| |
| li = of_get_property(node, "interrupts", &rlen); |
| if (!li) { |
| printk(KERN_ERR "PCI: interrupts not found.\n"); |
| goto error; |
| } |
| 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; |
| } |
| |
| static int __init phb_set_bus_ranges(struct device_node *dev, |
| struct pci_controller *phb) |
| { |
| const int *bus_range; |
| unsigned int len; |
| |
| bus_range = of_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 __init celleb_alloc_private_mem(struct pci_controller *hose) |
| { |
| hose->private_data = |
| alloc_maybe_bootmem(sizeof(struct celleb_pci_private), |
| GFP_KERNEL); |
| } |
| |
| static int __init celleb_setup_fake_pci(struct device_node *dev, |
| struct pci_controller *phb) |
| { |
| struct device_node *node; |
| |
| 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); |
| |
| return 0; |
| } |
| |
| static struct celleb_phb_spec celleb_fake_pci_spec __initdata = { |
| .setup = celleb_setup_fake_pci, |
| }; |
| |
| static struct of_device_id celleb_phb_match[] __initdata = { |
| { |
| .name = "pci-pseudo", |
| .data = &celleb_fake_pci_spec, |
| }, { |
| .name = "epci", |
| .data = &celleb_epci_spec, |
| }, { |
| .name = "pcie", |
| .data = &celleb_pciex_spec, |
| }, { |
| }, |
| }; |
| |
| 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; |
| } |
| |
| int __init celleb_setup_phb(struct pci_controller *phb) |
| { |
| struct device_node *dev = phb->dn; |
| const struct of_device_id *match; |
| struct celleb_phb_spec *phb_spec; |
| int rc; |
| |
| match = of_match_node(celleb_phb_match, dev); |
| if (!match) |
| return 1; |
| |
| phb_set_bus_ranges(dev, phb); |
| phb->buid = 1; |
| |
| phb_spec = match->data; |
| rc = (*phb_spec->setup)(dev, phb); |
| if (rc) |
| return 1; |
| |
| return celleb_io_workaround_init(phb, phb_spec); |
| } |
| |
| int celleb_pci_probe_mode(struct pci_bus *bus) |
| { |
| return PCI_PROBE_DEVTREE; |
| } |