| /* |
| * |
| * (C) Copyright 2003 |
| * Wolfgang Denk, DENX Software Engineering, wd@denx.de. |
| * |
| * (C) Copyright 2004 Red Hat, Inc. |
| * |
| * 2005 (c) MontaVista Software, Inc. |
| * Vitaly Bordug <vbordug@ru.mvista.com> |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/irq.h> |
| #include <linux/interrupt.h> |
| |
| #include <asm/byteorder.h> |
| #include <asm/io.h> |
| #include <asm/irq.h> |
| #include <asm/uaccess.h> |
| #include <asm/machdep.h> |
| #include <asm/pci-bridge.h> |
| #include <asm/immap_cpm2.h> |
| #include <asm/mpc8260.h> |
| #include <asm/cpm2.h> |
| |
| #include "m82xx_pci.h" |
| |
| /* |
| * Interrupt routing |
| */ |
| |
| static inline int |
| pq2pci_map_irq(struct pci_dev *dev, unsigned char idsel, unsigned char pin) |
| { |
| static char pci_irq_table[][4] = |
| /* |
| * PCI IDSEL/INTPIN->INTLINE |
| * A B C D |
| */ |
| { |
| { PIRQA, PIRQB, PIRQC, PIRQD }, /* IDSEL 22 - PCI slot 0 */ |
| { PIRQD, PIRQA, PIRQB, PIRQC }, /* IDSEL 23 - PCI slot 1 */ |
| { PIRQC, PIRQD, PIRQA, PIRQB }, /* IDSEL 24 - PCI slot 2 */ |
| }; |
| |
| const long min_idsel = 22, max_idsel = 24, irqs_per_slot = 4; |
| return PCI_IRQ_TABLE_LOOKUP; |
| } |
| |
| static void |
| pq2pci_mask_irq(unsigned int irq) |
| { |
| int bit = irq - NR_CPM_INTS; |
| |
| *(volatile unsigned long *) PCI_INT_MASK_REG |= (1 << (31 - bit)); |
| return; |
| } |
| |
| static void |
| pq2pci_unmask_irq(unsigned int irq) |
| { |
| int bit = irq - NR_CPM_INTS; |
| |
| *(volatile unsigned long *) PCI_INT_MASK_REG &= ~(1 << (31 - bit)); |
| return; |
| } |
| |
| static void |
| pq2pci_mask_and_ack(unsigned int irq) |
| { |
| int bit = irq - NR_CPM_INTS; |
| |
| *(volatile unsigned long *) PCI_INT_MASK_REG |= (1 << (31 - bit)); |
| return; |
| } |
| |
| static void |
| pq2pci_end_irq(unsigned int irq) |
| { |
| int bit = irq - NR_CPM_INTS; |
| |
| *(volatile unsigned long *) PCI_INT_MASK_REG &= ~(1 << (31 - bit)); |
| return; |
| } |
| |
| struct hw_interrupt_type pq2pci_ic = { |
| "PQ2 PCI", |
| NULL, |
| NULL, |
| pq2pci_unmask_irq, |
| pq2pci_mask_irq, |
| pq2pci_mask_and_ack, |
| pq2pci_end_irq, |
| 0 |
| }; |
| |
| static irqreturn_t |
| pq2pci_irq_demux(int irq, void *dev_id) |
| { |
| unsigned long stat, mask, pend; |
| int bit; |
| |
| for(;;) { |
| stat = *(volatile unsigned long *) PCI_INT_STAT_REG; |
| mask = *(volatile unsigned long *) PCI_INT_MASK_REG; |
| pend = stat & ~mask & 0xf0000000; |
| if (!pend) |
| break; |
| for (bit = 0; pend != 0; ++bit, pend <<= 1) { |
| if (pend & 0x80000000) |
| __do_IRQ(NR_CPM_INTS + bit); |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct irqaction pq2pci_irqaction = { |
| .handler = pq2pci_irq_demux, |
| .flags = IRQF_DISABLED, |
| .mask = CPU_MASK_NONE, |
| .name = "PQ2 PCI cascade", |
| }; |
| |
| |
| void |
| pq2pci_init_irq(void) |
| { |
| int irq; |
| volatile cpm2_map_t *immap = cpm2_immr; |
| for (irq = NR_CPM_INTS; irq < NR_CPM_INTS + 4; irq++) |
| irq_desc[irq].chip = &pq2pci_ic; |
| |
| /* make PCI IRQ level sensitive */ |
| immap->im_intctl.ic_siexr &= |
| ~(1 << (14 - (PCI_INT_TO_SIU - SIU_INT_IRQ1))); |
| |
| /* mask all PCI interrupts */ |
| *(volatile unsigned long *) PCI_INT_MASK_REG |= 0xfff00000; |
| |
| /* install the demultiplexer for the PCI cascade interrupt */ |
| setup_irq(PCI_INT_TO_SIU, &pq2pci_irqaction); |
| return; |
| } |
| |
| static int |
| pq2pci_exclude_device(u_char bus, u_char devfn) |
| { |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| /* PCI bus configuration registers. |
| */ |
| static void |
| pq2ads_setup_pci(struct pci_controller *hose) |
| { |
| __u32 val; |
| volatile cpm2_map_t *immap = cpm2_immr; |
| bd_t* binfo = (bd_t*) __res; |
| u32 sccr = immap->im_clkrst.car_sccr; |
| uint pci_div,freq,time; |
| /* PCI int lowest prio */ |
| /* Each 4 bits is a device bus request and the MS 4bits |
| is highest priority */ |
| /* Bus 4bit value |
| --- ---------- |
| CPM high 0b0000 |
| CPM middle 0b0001 |
| CPM low 0b0010 |
| PCI request 0b0011 |
| Reserved 0b0100 |
| Reserved 0b0101 |
| Internal Core 0b0110 |
| External Master 1 0b0111 |
| External Master 2 0b1000 |
| External Master 3 0b1001 |
| The rest are reserved |
| */ |
| immap->im_siu_conf.siu_82xx.sc_ppc_alrh = 0x61207893; |
| /* park bus on core */ |
| immap->im_siu_conf.siu_82xx.sc_ppc_acr = PPC_ACR_BUS_PARK_CORE; |
| /* |
| * Set up master windows that allow the CPU to access PCI space. These |
| * windows are set up using the two SIU PCIBR registers. |
| */ |
| |
| immap->im_memctl.memc_pcimsk0 = M82xx_PCI_PRIM_WND_SIZE; |
| immap->im_memctl.memc_pcibr0 = M82xx_PCI_PRIM_WND_BASE | PCIBR_ENABLE; |
| |
| #ifdef M82xx_PCI_SEC_WND_SIZE |
| immap->im_memctl.memc_pcimsk1 = M82xx_PCI_SEC_WND_SIZE; |
| immap->im_memctl.memc_pcibr1 = M82xx_PCI_SEC_WND_BASE | PCIBR_ENABLE; |
| #endif |
| |
| /* Enable PCI */ |
| immap->im_pci.pci_gcr = cpu_to_le32(PCIGCR_PCI_BUS_EN); |
| |
| pci_div = ( (sccr & SCCR_PCI_MODCK) ? 2 : 1) * |
| ( ( (sccr & SCCR_PCIDF_MSK) >> SCCR_PCIDF_SHIFT) + 1); |
| freq = (uint)((2*binfo->bi_cpmfreq)/(pci_div)); |
| time = (int)66666666/freq; |
| |
| /* due to PCI Local Bus spec, some devices needs to wait such a long |
| time after RST deassertion. More specifically, 0.508s for 66MHz & twice more for 33 */ |
| printk("%s: The PCI bus is %d Mhz.\nWaiting %s after deasserting RST...\n",__FILE__,freq, |
| (time==1) ? "0.5 seconds":"1 second" ); |
| |
| { |
| int i; |
| for(i=0;i<(500*time);i++) |
| udelay(1000); |
| } |
| |
| /* setup ATU registers */ |
| immap->im_pci.pci_pocmr0 = cpu_to_le32(POCMR_ENABLE | POCMR_PCI_IO | |
| ((~(M82xx_PCI_IO_SIZE - 1U)) >> POTA_ADDR_SHIFT)); |
| immap->im_pci.pci_potar0 = cpu_to_le32(M82xx_PCI_LOWER_IO >> POTA_ADDR_SHIFT); |
| immap->im_pci.pci_pobar0 = cpu_to_le32(M82xx_PCI_IO_BASE >> POTA_ADDR_SHIFT); |
| |
| /* Set-up non-prefetchable window */ |
| immap->im_pci.pci_pocmr1 = cpu_to_le32(POCMR_ENABLE | ((~(M82xx_PCI_MMIO_SIZE-1U)) >> POTA_ADDR_SHIFT)); |
| immap->im_pci.pci_potar1 = cpu_to_le32(M82xx_PCI_LOWER_MMIO >> POTA_ADDR_SHIFT); |
| immap->im_pci.pci_pobar1 = cpu_to_le32((M82xx_PCI_LOWER_MMIO - M82xx_PCI_MMIO_OFFSET) >> POTA_ADDR_SHIFT); |
| |
| /* Set-up prefetchable window */ |
| immap->im_pci.pci_pocmr2 = cpu_to_le32(POCMR_ENABLE |POCMR_PREFETCH_EN | |
| (~(M82xx_PCI_MEM_SIZE-1U) >> POTA_ADDR_SHIFT)); |
| immap->im_pci.pci_potar2 = cpu_to_le32(M82xx_PCI_LOWER_MEM >> POTA_ADDR_SHIFT); |
| immap->im_pci.pci_pobar2 = cpu_to_le32((M82xx_PCI_LOWER_MEM - M82xx_PCI_MEM_OFFSET) >> POTA_ADDR_SHIFT); |
| |
| /* Inbound transactions from PCI memory space */ |
| immap->im_pci.pci_picmr0 = cpu_to_le32(PICMR_ENABLE | PICMR_PREFETCH_EN | |
| ((~(M82xx_PCI_SLAVE_MEM_SIZE-1U)) >> PITA_ADDR_SHIFT)); |
| immap->im_pci.pci_pibar0 = cpu_to_le32(M82xx_PCI_SLAVE_MEM_BUS >> PITA_ADDR_SHIFT); |
| immap->im_pci.pci_pitar0 = cpu_to_le32(M82xx_PCI_SLAVE_MEM_LOCAL>> PITA_ADDR_SHIFT); |
| |
| /* park bus on PCI */ |
| immap->im_siu_conf.siu_82xx.sc_ppc_acr = PPC_ACR_BUS_PARK_PCI; |
| |
| /* Enable bus mastering and inbound memory transactions */ |
| early_read_config_dword(hose, hose->first_busno, 0, PCI_COMMAND, &val); |
| val &= 0xffff0000; |
| val |= PCI_COMMAND_MEMORY|PCI_COMMAND_MASTER; |
| early_write_config_dword(hose, hose->first_busno, 0, PCI_COMMAND, val); |
| |
| } |
| |
| void __init pq2_find_bridges(void) |
| { |
| extern int pci_assign_all_buses; |
| struct pci_controller * hose; |
| int host_bridge; |
| |
| pci_assign_all_buses = 1; |
| |
| hose = pcibios_alloc_controller(); |
| |
| if (!hose) |
| return; |
| |
| ppc_md.pci_swizzle = common_swizzle; |
| |
| hose->first_busno = 0; |
| hose->bus_offset = 0; |
| hose->last_busno = 0xff; |
| |
| setup_m8260_indirect_pci(hose, |
| (unsigned long)&cpm2_immr->im_pci.pci_cfg_addr, |
| (unsigned long)&cpm2_immr->im_pci.pci_cfg_data); |
| |
| /* Make sure it is a supported bridge */ |
| early_read_config_dword(hose, |
| 0, |
| PCI_DEVFN(0,0), |
| PCI_VENDOR_ID, |
| &host_bridge); |
| switch (host_bridge) { |
| case PCI_DEVICE_ID_MPC8265: |
| break; |
| case PCI_DEVICE_ID_MPC8272: |
| break; |
| default: |
| printk("Attempting to use unrecognized host bridge ID" |
| " 0x%08x.\n", host_bridge); |
| break; |
| } |
| |
| pq2ads_setup_pci(hose); |
| |
| hose->io_space.start = M82xx_PCI_LOWER_IO; |
| hose->io_space.end = M82xx_PCI_UPPER_IO; |
| hose->mem_space.start = M82xx_PCI_LOWER_MEM; |
| hose->mem_space.end = M82xx_PCI_UPPER_MMIO; |
| hose->pci_mem_offset = M82xx_PCI_MEM_OFFSET; |
| |
| isa_io_base = |
| (unsigned long) ioremap(M82xx_PCI_IO_BASE, |
| M82xx_PCI_IO_SIZE); |
| hose->io_base_virt = (void *) isa_io_base; |
| |
| /* setup resources */ |
| pci_init_resource(&hose->mem_resources[0], |
| M82xx_PCI_LOWER_MEM, |
| M82xx_PCI_UPPER_MEM, |
| IORESOURCE_MEM|IORESOURCE_PREFETCH, "PCI prefetchable memory"); |
| |
| pci_init_resource(&hose->mem_resources[1], |
| M82xx_PCI_LOWER_MMIO, |
| M82xx_PCI_UPPER_MMIO, |
| IORESOURCE_MEM, "PCI memory"); |
| |
| pci_init_resource(&hose->io_resource, |
| M82xx_PCI_LOWER_IO, |
| M82xx_PCI_UPPER_IO, |
| IORESOURCE_IO | 1, "PCI I/O"); |
| |
| ppc_md.pci_exclude_device = pq2pci_exclude_device; |
| hose->last_busno = pciauto_bus_scan(hose, hose->first_busno); |
| |
| ppc_md.pci_map_irq = pq2pci_map_irq; |
| ppc_md.pcibios_fixup = NULL; |
| ppc_md.pcibios_fixup_bus = NULL; |
| |
| } |