/* * Moschip MCS8140 PCI support * * Copyright (C) 2003 Moschip Semiconductors Ltd. * Copyright (C) 2003 Artec Design Ltd. * Copyright (C) 2012 Florian Fainelli * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MCS8140_PCI_CONFIG_SIZE SZ_64M #define MCS8140_PCI_IOMISC_SIZE SZ_64M #define MCS8140_PCI_HOST_BASE 0x80000000 #define MCS8140_PCI_IOMISC_BASE 0x00000000 #define MCS8140_PCI_PRE_BASE 0x10000000 #define MCS8140_PCI_NONPRE_BASE 0x30000000 #define MCS8140_PCI_CFG_BASE (MCS8140_PCI_HOST_BASE + 0x04000000) #define MCS8140_PCI_IO_BASE (MCS8140_PCI_HOST_BASE) #define MCS8140_PCI_IO_VIRT_BASE (MCS814X_IO_BASE - MCS8140_PCI_CONFIG_SIZE - \ MCS8140_PCI_IOMISC_SIZE) #define MCS8140_PCI_CFG_VIRT_BASE (MCS814X_IO_BASE - MCS8140_PCI_CONFIG_SIZE) #define PCI_FATAL_ERROR 1 #define EXTERNAL_ABORT_NON_LINE_FETCH 8 #define EPRM_DONE 0x80 #define EPRM_SDRAM_FUNC0 0xAC #define PCI_INTD 4 #define MCS8140_PCI_DEVICE_ID 0xA0009710 #define MCS8140_PCI_CLASS_ID 0x02000011 /* Host-Class id :0x0600 */ #define PCI_IF_CONFIG 0x200 static void __iomem *mcs8140_pci_master_base; static void __iomem *mcs8140_eeprom_emu_base; static unsigned long __pci_addr(struct pci_bus *bus, unsigned int devfn, int offset) { unsigned int busnr = bus->number; unsigned int slot; /* we only support bus 0 */ if (busnr != 0) return 0; /* * Trap out illegal values */ BUG_ON(devfn > 255 || busnr > 255 || devfn > 255); /* Scan 3 slots */ slot = PCI_SLOT(devfn); switch (slot) { case 1: case 2: case 3: if (PCI_FUNC(devfn) >= 4) return 0; return MCS8140_PCI_CFG_VIRT_BASE | (PCI_SLOT(devfn) << 11) | (PCI_FUNC(devfn) << 8) | offset; default: pr_warn("Ignoring: PCI Slot is %x\n", PCI_SLOT(devfn)); return 0; } } static int mcs8140_pci_host_status(void) { u32 host_status; host_status = readl_relaxed(mcs8140_pci_master_base + PCI_IF_CONFIG); if (host_status & PCI_FATAL_ERROR) { writel_relaxed(host_status & 0xfffffff0, mcs8140_pci_master_base + PCI_IF_CONFIG); /* flush write */ host_status = readl_relaxed(mcs8140_pci_master_base + PCI_IF_CONFIG); return 1; } return 0; } static int mcs8140_pci_read_config(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val) { unsigned long v = 0xFFFFFFFF; unsigned long addr = __pci_addr(bus, devfn, where); if (addr != 0) { switch (size) { case 1: v = readb_relaxed(addr); break; case 2: addr &= ~1; v = readw_relaxed(addr); break; default: addr &= ~3; v = readl_relaxed(addr); break; } } else v = 0xffffffff; if (mcs8140_pci_host_status()) v = 0xffffffff; *val = v; return PCIBIOS_SUCCESSFUL; } static void mcs8140_eeprom_emu_init(void) { writel_relaxed(0x0000000F, mcs8140_eeprom_emu_base + EPRM_SDRAM_FUNC0); writel_relaxed(0x08000000, MCS8140_PCI_CFG_VIRT_BASE + 0x10); /* Set the DONE bit of the EEPROM emulator */ writel_relaxed(0x01, mcs8140_eeprom_emu_base + EPRM_DONE); } static int mcs8140_pci_write_config(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val) { unsigned long addr = __pci_addr(bus, devfn, where); if (addr != 0) { switch (size) { case 1: writeb_relaxed((u8)val, addr); break; case 2: writew_relaxed((u16)val, addr); break; case 4: writel_relaxed(val, addr); break; } } return PCIBIOS_SUCCESSFUL; } static struct pci_ops pci_mcs8140_ops = { .read = mcs8140_pci_read_config, .write = mcs8140_pci_write_config, }; static struct resource io_mem = { .name = "PCI I/O space", .start = MCS8140_PCI_HOST_BASE + MCS8140_PCI_IOMISC_BASE, .end = MCS8140_PCI_HOST_BASE + MCS8140_PCI_IOMISC_BASE + SZ_64M, .flags = IORESOURCE_IO, }; static struct resource pre_mem = { .name = "PCI prefetchable", .start = MCS8140_PCI_HOST_BASE + MCS8140_PCI_PRE_BASE, .end = MCS8140_PCI_HOST_BASE + MCS8140_PCI_PRE_BASE + SZ_512M, .flags = IORESOURCE_MEM | IORESOURCE_PREFETCH, }; static struct resource non_mem = { .name = "PCI non-prefetchable", .start = MCS8140_PCI_HOST_BASE + MCS8140_PCI_NONPRE_BASE, .end = MCS8140_PCI_HOST_BASE + MCS8140_PCI_NONPRE_BASE + SZ_256M, .flags = IORESOURCE_MEM, }; int __init pci_mcs8140_setup_resources(struct pci_sys_data *sys) { int ret = 0; ret = request_resource(&iomem_resource, &io_mem); if (ret) { pr_err("PCI: unable to allocate I/O " "memory region (%d)\n", ret); goto out; } ret = request_resource(&iomem_resource, &non_mem); if (ret) { pr_err("PCI: unable to allocate non-prefetchable " "memory region (%d)\n", ret); goto release_io_mem; } ret = request_resource(&iomem_resource, &pre_mem); if (ret) { pr_err("PCI: unable to allocate prefetchable " "memory region (%d)\n", ret); goto release_non_mem; } mcs8140_eeprom_emu_init(); pci_add_resource(&sys->resources, &io_mem); pci_add_resource(&sys->resources, &non_mem); pci_add_resource(&sys->resources, &pre_mem); return ret; release_non_mem: release_resource(&non_mem); release_io_mem: release_resource(&io_mem); out: return ret; } struct pci_bus *pci_mcs8140_scan_bus(int nr, struct pci_sys_data *sys) { return pci_scan_bus(sys->busnr, &pci_mcs8140_ops, sys); } int __init pci_mcs8140_setup(int nr, struct pci_sys_data *sys) { int ret = 0; u32 val; if (nr > 0) return 0; sys->mem_offset = MCS8140_PCI_IO_VIRT_BASE - MCS8140_PCI_IO_BASE; sys->io_offset = 0; ret = pci_mcs8140_setup_resources(sys); if (ret < 0) { pr_err("unable to setup mcs8140 resources\n"); goto out; } val = readl_relaxed(MCS8140_PCI_CFG_VIRT_BASE); if (val != MCS8140_PCI_DEVICE_ID) { pr_err("cannot find MCS8140 PCI Core: %08x\n", val); ret = -EIO; goto out; } pr_info("MCS8140 PCI core found\n"); val = readl_relaxed(MCS8140_PCI_CFG_VIRT_BASE + PCI_COMMAND); /* Added to support wireless cards */ writel_relaxed(0, MCS8140_PCI_CFG_VIRT_BASE + 0x40); writel_relaxed(val | 0x147, MCS8140_PCI_CFG_VIRT_BASE + PCI_COMMAND); val = readl_relaxed(MCS8140_PCI_CFG_VIRT_BASE + PCI_COMMAND); ret = 1; out: return ret; } static int __init mcs8140_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) { int line = IRQ_PCI_INTA; if (pin != 0) { /* IRQ_PCIA - 22 */ if (pin == PCI_INTD) line = IRQ_PCI_INTA + pin; /* IRQ_PCIA - 22 */ else line = IRQ_PCI_INTA + pin - 1; /* IRQ_PCIA - 22 */ } pr_info("PCI: Map interrupt slot 0x%02x pin 0x%02x line 0x%02x\n", slot, pin, line); return line; } static irqreturn_t mcs8140_pci_abort_interrupt(int irq, void *dummy) { u32 word; word = readl_relaxed(mcs8140_pci_master_base + PCI_IF_CONFIG); if (!(word & (1 << 24))) return IRQ_NONE; writel_relaxed(word & 0xfffffff0, mcs8140_pci_master_base + PCI_IF_CONFIG); /* flush write */ word = readl_relaxed(mcs8140_pci_master_base + PCI_IF_CONFIG); return IRQ_HANDLED; } static int mcs8140_pci_abort_irq_init(int irq) { u32 word; /* Enable Interrupt in PCI Master Core */ word = readl_relaxed(mcs8140_pci_master_base + PCI_IF_CONFIG); word |= (1 << 24); writel_relaxed(word, mcs8140_pci_master_base + PCI_IF_CONFIG); /* flush write */ word = readl_relaxed(mcs8140_pci_master_base + PCI_IF_CONFIG); return request_irq(irq, mcs8140_pci_abort_interrupt, 0, "PCI abort", NULL); } static int mcs8140_pci_host_abort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { pr_warn("PCI Data abort: address = 0x%08lx fsr = 0x%03x" "PC = 0x%08lx LR = 0x%08lx\n", addr, fsr, regs->ARM_pc, regs->ARM_lr); /* * If it was an imprecise abort, then we need to correct the * return address to be _after_ the instruction. */ if (fsr & (1 << 10) || mcs8140_pci_host_status()) regs->ARM_pc += 4; return 0; } static void mcs8140_data_abort_init(void) { hook_fault_code(EXTERNAL_ABORT_NON_LINE_FETCH, mcs8140_pci_host_abort, SIGBUS, 0, "external abort on non-line fetch"); } static struct hw_pci mcs8140_pci __initdata = { .map_irq = mcs8140_map_irq, .nr_controllers = 1, .setup = pci_mcs8140_setup, .scan = pci_mcs8140_scan_bus, }; static struct map_desc mcs8140_pci_io_desc[] __initdata = { { .virtual = MCS8140_PCI_CFG_VIRT_BASE, .pfn = __phys_to_pfn(MCS8140_PCI_CFG_BASE), .length = MCS8140_PCI_CONFIG_SIZE, .type = MT_DEVICE }, { .virtual = MCS8140_PCI_IO_VIRT_BASE, .pfn = __phys_to_pfn(MCS8140_PCI_IO_BASE), .length = MCS8140_PCI_IOMISC_SIZE, .type = MT_DEVICE }, }; static int __devinit mcs8140_pci_probe(struct platform_device *pdev) { struct resource *res; int ret, irq; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "failed to get mem resource 0\n"); return -ENODEV; } mcs8140_pci_master_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!mcs8140_pci_master_base) { dev_err(&pdev->dev, "failed to remap PCI master regs\n"); return -ENODEV; } res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { dev_err(&pdev->dev, "failed to get mem resource 1\n"); return -ENOMEM; } mcs8140_eeprom_emu_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!mcs8140_eeprom_emu_base) { dev_err(&pdev->dev, "failed to remap EEPROM regs\n"); return -ENOMEM; } irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "failed to get pci abort irq\n"); return -ENODEV; } /* Setup static mappins for PCI CFG space */ iotable_init(mcs8140_pci_io_desc, ARRAY_SIZE(mcs8140_pci_io_desc)); pcibios_min_io = MCS8140_PCI_HOST_BASE; pcibios_min_mem = MCS8140_PCI_HOST_BASE + MCS8140_PCI_PRE_BASE; mcs8140_data_abort_init(); ret = mcs8140_pci_abort_irq_init(irq); if (ret) { dev_err(&pdev->dev, "failed to setup abort irq\n"); return ret; } pci_common_init(&mcs8140_pci); return 0; } static struct of_device_id mcs8140_of_ids[] __devinitdata = { { .compatible = "moschip,mcs8140-pci" }, { .compatible = "moschip,mcs814x-pci" }, { /* sentinel */ }, }; static struct platform_driver mcs8140_pci_driver = { .driver = { .name = "mcs8140-pci", .of_match_table = mcs8140_of_ids, }, .probe = mcs8140_pci_probe, }; static int __init mcs8140_pci_init(void) { return platform_driver_register(&mcs8140_pci_driver); } subsys_initcall(mcs8140_pci_init);