| /* |
| * Support for SCC external PCI |
| * |
| * (C) Copyright 2004-2007 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 epci_private { |
| dma_addr_t dummy_page_da; |
| }; |
| |
| static inline PCI_IO_ADDR celleb_epci_get_epci_base( |
| struct pci_controller *hose) |
| { |
| /* |
| * Note: |
| * Celleb epci uses cfg_addr as a base address for |
| * epci control registers. |
| */ |
| |
| return hose->cfg_addr; |
| } |
| |
| static inline PCI_IO_ADDR celleb_epci_get_epci_cfg( |
| struct pci_controller *hose) |
| { |
| /* |
| * Note: |
| * Celleb epci uses cfg_data as a base address for |
| * configuration area for epci devices. |
| */ |
| |
| return hose->cfg_data; |
| } |
| |
| static void scc_epci_dummy_read(struct pci_controller *hose) |
| { |
| PCI_IO_ADDR epci_base; |
| u32 val; |
| |
| epci_base = celleb_epci_get_epci_base(hose); |
| |
| val = in_be32(epci_base + SCC_EPCI_WATRP); |
| iosync(); |
| |
| return; |
| } |
| |
| void __init epci_workaround_init(struct pci_controller *hose) |
| { |
| PCI_IO_ADDR epci_base; |
| PCI_IO_ADDR reg; |
| struct epci_private *private = hose->private_data; |
| |
| BUG_ON(!private); |
| |
| private->dummy_page_da = dma_map_single(hose->parent, |
| celleb_dummy_page_va, PAGE_SIZE, DMA_FROM_DEVICE); |
| if (private->dummy_page_da == DMA_ERROR_CODE) { |
| printk(KERN_ERR "EPCI: dummy read disabled. " |
| "Map dummy page failed.\n"); |
| return; |
| } |
| |
| celleb_pci_add_one(hose, scc_epci_dummy_read); |
| epci_base = celleb_epci_get_epci_base(hose); |
| |
| reg = epci_base + SCC_EPCI_DUMYRADR; |
| out_be32(reg, private->dummy_page_da); |
| } |
| |
| static inline void clear_and_disable_master_abort_interrupt( |
| struct pci_controller *hose) |
| { |
| PCI_IO_ADDR epci_base; |
| PCI_IO_ADDR reg; |
| epci_base = celleb_epci_get_epci_base(hose); |
| reg = epci_base + PCI_COMMAND; |
| out_be32(reg, in_be32(reg) | (PCI_STATUS_REC_MASTER_ABORT << 16)); |
| } |
| |
| static int celleb_epci_check_abort(struct pci_controller *hose, |
| PCI_IO_ADDR addr) |
| { |
| PCI_IO_ADDR reg; |
| PCI_IO_ADDR epci_base; |
| u32 val; |
| |
| iob(); |
| epci_base = celleb_epci_get_epci_base(hose); |
| |
| reg = epci_base + PCI_COMMAND; |
| val = in_be32(reg); |
| |
| if (val & (PCI_STATUS_REC_MASTER_ABORT << 16)) { |
| out_be32(reg, |
| (val & 0xffff) | (PCI_STATUS_REC_MASTER_ABORT << 16)); |
| |
| /* clear PCI Controller error, FRE, PMFE */ |
| reg = epci_base + SCC_EPCI_STATUS; |
| out_be32(reg, SCC_EPCI_INT_PAI); |
| |
| reg = epci_base + SCC_EPCI_VCSR; |
| val = in_be32(reg) & 0xffff; |
| val |= SCC_EPCI_VCSR_FRE; |
| out_be32(reg, val); |
| |
| reg = epci_base + SCC_EPCI_VISTAT; |
| out_be32(reg, SCC_EPCI_VISTAT_PMFE); |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| static PCI_IO_ADDR celleb_epci_make_config_addr( |
| struct pci_bus *bus, |
| struct pci_controller *hose, |
| unsigned int devfn, int where) |
| { |
| PCI_IO_ADDR addr; |
| |
| if (bus != hose->bus) |
| addr = celleb_epci_get_epci_cfg(hose) + |
| (((bus->number & 0xff) << 16) |
| | ((devfn & 0xff) << 8) |
| | (where & 0xff) |
| | 0x01000000); |
| else |
| addr = celleb_epci_get_epci_cfg(hose) + |
| (((devfn & 0xff) << 8) | (where & 0xff)); |
| |
| pr_debug("EPCI: config_addr = 0x%p\n", addr); |
| |
| return addr; |
| } |
| |
| static int celleb_epci_read_config(struct pci_bus *bus, |
| unsigned int devfn, int where, int size, u32 * val) |
| { |
| PCI_IO_ADDR epci_base; |
| PCI_IO_ADDR 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 (!celleb_epci_get_epci_cfg(hose)) |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| |
| if (bus->number == hose->first_busno && devfn == 0) { |
| /* EPCI controller self */ |
| |
| epci_base = celleb_epci_get_epci_base(hose); |
| addr = epci_base + where; |
| |
| switch (size) { |
| case 1: |
| *val = in_8(addr); |
| break; |
| case 2: |
| *val = in_be16(addr); |
| break; |
| case 4: |
| *val = in_be32(addr); |
| break; |
| default: |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| |
| } else { |
| |
| clear_and_disable_master_abort_interrupt(hose); |
| addr = celleb_epci_make_config_addr(bus, hose, devfn, where); |
| |
| switch (size) { |
| case 1: |
| *val = in_8(addr); |
| break; |
| case 2: |
| *val = in_le16(addr); |
| break; |
| case 4: |
| *val = in_le32(addr); |
| break; |
| default: |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| } |
| |
| pr_debug("EPCI: " |
| "addr=0x%p, devfn=0x%x, where=0x%x, size=0x%x, val=0x%x\n", |
| addr, devfn, where, size, *val); |
| |
| return celleb_epci_check_abort(hose, NULL); |
| } |
| |
| static int celleb_epci_write_config(struct pci_bus *bus, |
| unsigned int devfn, int where, int size, u32 val) |
| { |
| PCI_IO_ADDR epci_base; |
| PCI_IO_ADDR 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 (!celleb_epci_get_epci_cfg(hose)) |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| |
| if (bus->number == hose->first_busno && devfn == 0) { |
| /* EPCI controller self */ |
| |
| epci_base = celleb_epci_get_epci_base(hose); |
| addr = epci_base + where; |
| |
| switch (size) { |
| case 1: |
| out_8(addr, val); |
| break; |
| case 2: |
| out_be16(addr, val); |
| break; |
| case 4: |
| out_be32(addr, val); |
| break; |
| default: |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| |
| } else { |
| |
| clear_and_disable_master_abort_interrupt(hose); |
| addr = celleb_epci_make_config_addr(bus, hose, devfn, where); |
| |
| switch (size) { |
| case 1: |
| out_8(addr, val); |
| break; |
| case 2: |
| out_le16(addr, val); |
| break; |
| case 4: |
| out_le32(addr, val); |
| break; |
| default: |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| } |
| |
| return celleb_epci_check_abort(hose, addr); |
| } |
| |
| struct pci_ops celleb_epci_ops = { |
| .read = celleb_epci_read_config, |
| .write = celleb_epci_write_config, |
| }; |
| |
| /* to be moved in FW */ |
| static int __init celleb_epci_init(struct pci_controller *hose) |
| { |
| u32 val; |
| PCI_IO_ADDR reg; |
| PCI_IO_ADDR epci_base; |
| int hwres = 0; |
| |
| epci_base = celleb_epci_get_epci_base(hose); |
| |
| /* PCI core reset(Internal bus and PCI clock) */ |
| reg = epci_base + SCC_EPCI_CKCTRL; |
| val = in_be32(reg); |
| if (val == 0x00030101) |
| hwres = 1; |
| else { |
| val &= ~(SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1); |
| out_be32(reg, val); |
| |
| /* set PCI core clock */ |
| val = in_be32(reg); |
| val |= (SCC_EPCI_CKCTRL_OCLKEN | SCC_EPCI_CKCTRL_LCLKEN); |
| out_be32(reg, val); |
| |
| /* release PCI core reset (internal bus) */ |
| val = in_be32(reg); |
| val |= SCC_EPCI_CKCTRL_CRST0; |
| out_be32(reg, val); |
| |
| /* set PCI clock select */ |
| reg = epci_base + SCC_EPCI_CLKRST; |
| val = in_be32(reg); |
| val &= ~SCC_EPCI_CLKRST_CKS_MASK; |
| val |= SCC_EPCI_CLKRST_CKS_2; |
| out_be32(reg, val); |
| |
| /* set arbiter */ |
| reg = epci_base + SCC_EPCI_ABTSET; |
| out_be32(reg, 0x0f1f001f); /* temporary value */ |
| |
| /* buffer on */ |
| reg = epci_base + SCC_EPCI_CLKRST; |
| val = in_be32(reg); |
| val |= SCC_EPCI_CLKRST_BC; |
| out_be32(reg, val); |
| |
| /* PCI clock enable */ |
| val = in_be32(reg); |
| val |= SCC_EPCI_CLKRST_PCKEN; |
| out_be32(reg, val); |
| |
| /* release PCI core reset (all) */ |
| reg = epci_base + SCC_EPCI_CKCTRL; |
| val = in_be32(reg); |
| val |= (SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1); |
| out_be32(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(reg, 0x013f011f); /* all interrupts enable */ |
| reg = epci_base + SCC_EPCI_VIENAB; |
| val = SCC_EPCI_VIENAB_PMPEE | SCC_EPCI_VIENAB_PMFEE; |
| out_be32(reg, val); |
| reg = epci_base + SCC_EPCI_STATUS; |
| out_be32(reg, 0xffffffff); |
| reg = epci_base + SCC_EPCI_VISTAT; |
| out_be32(reg, 0xffffffff); |
| |
| /* disable PCI->IB address translation */ |
| reg = epci_base + SCC_EPCI_VCSR; |
| val = in_be32(reg); |
| val &= ~(SCC_EPCI_VCSR_DR | SCC_EPCI_VCSR_AT); |
| out_be32(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(reg, val); |
| |
| /* endian mode setup */ |
| reg = epci_base + SCC_EPCI_ECMODE; |
| val = 0x00550155; |
| out_be32(reg, val); |
| |
| /* set control option */ |
| reg = epci_base + SCC_EPCI_CNTOPT; |
| val = in_be32(reg); |
| val |= SCC_EPCI_CNTOPT_O2PMB; |
| out_be32(reg, val); |
| |
| /* XXX: temporay: set registers for address conversion setup */ |
| reg = epci_base + SCC_EPCI_CNF10_REG; |
| out_be32(reg, 0x80000008); |
| reg = epci_base + SCC_EPCI_CNF14_REG; |
| out_be32(reg, 0x40000008); |
| |
| reg = epci_base + SCC_EPCI_BAM0; |
| out_be32(reg, 0x80000000); |
| reg = epci_base + SCC_EPCI_BAM1; |
| out_be32(reg, 0xe0000000); |
| |
| reg = epci_base + SCC_EPCI_PVBAT; |
| out_be32(reg, 0x80000000); |
| |
| if (!hwres) { |
| /* release external PCI reset */ |
| reg = epci_base + SCC_EPCI_CLKRST; |
| val = in_be32(reg); |
| val |= SCC_EPCI_CLKRST_PCIRST; |
| out_be32(reg, val); |
| } |
| |
| return 0; |
| } |
| |
| int __init celleb_setup_epci(struct device_node *node, |
| struct pci_controller *hose) |
| { |
| struct resource r; |
| |
| pr_debug("PCI: celleb_setup_epci()\n"); |
| |
| /* |
| * Note: |
| * Celleb epci uses cfg_addr and cfg_data member of |
| * pci_controller structure in irregular way. |
| * |
| * cfg_addr is used to map for control registers of |
| * celleb epci. |
| * |
| * cfg_data is used for configuration area of devices |
| * on Celleb epci buses. |
| */ |
| |
| if (of_address_to_resource(node, 0, &r)) |
| goto error; |
| hose->cfg_addr = ioremap(r.start, (r.end - r.start + 1)); |
| if (!hose->cfg_addr) |
| goto error; |
| pr_debug("EPCI: cfg_addr map 0x%016lx->0x%016lx + 0x%016lx\n", |
| r.start, (unsigned long)hose->cfg_addr, |
| (r.end - r.start + 1)); |
| |
| if (of_address_to_resource(node, 2, &r)) |
| goto error; |
| hose->cfg_data = ioremap(r.start, (r.end - r.start + 1)); |
| if (!hose->cfg_data) |
| goto error; |
| pr_debug("EPCI: cfg_data map 0x%016lx->0x%016lx + 0x%016lx\n", |
| r.start, (unsigned long)hose->cfg_data, |
| (r.end - r.start + 1)); |
| |
| hose->private_data = kzalloc(sizeof(struct epci_private), GFP_KERNEL); |
| if (hose->private_data == NULL) { |
| printk(KERN_ERR "EPCI: no memory for private data.\n"); |
| goto error; |
| } |
| |
| hose->ops = &celleb_epci_ops; |
| celleb_epci_init(hose); |
| |
| return 0; |
| |
| error: |
| kfree(hose->private_data); |
| |
| if (hose->cfg_addr) |
| iounmap(hose->cfg_addr); |
| |
| if (hose->cfg_data) |
| iounmap(hose->cfg_data); |
| return 1; |
| } |