| /* |
| * linux/arch/arm/mach-comcerto/pcie-c2000.c |
| * |
| * Copyright (C) 2004,2005 Mindspeed Technologies, Inc. |
| * |
| * 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/version.h> |
| #include <linux/ratelimit.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/spinlock.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/sched.h> |
| #if defined(CONFIG_PCI_MSI) |
| #include <linux/msi.h> |
| #endif |
| |
| #include <asm/irq.h> |
| #include <asm/delay.h> |
| #include <asm/sizes.h> |
| #include <asm/mach/pci.h> |
| #include <linux/irq.h> |
| #include <asm/io.h> |
| #include <asm/mach/irq.h> |
| #include <mach/pcie-c2000.h> |
| #include <mach/serdes-c2000.h> |
| #include <mach/reset.h> |
| #include <mach/hardware.h> |
| |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <mach/comcerto-2000/pm.h> |
| //#define COMCERTO_PCIE_DEBUG |
| |
| #ifdef CONFIG_PCI_MSI |
| static DECLARE_BITMAP(msi_irq_in_use[NUM_PCIE_PORTS], PCIE_NUM_MSI_IRQS); |
| static int comcerto_msi_init(struct pcie_port *pp); |
| #endif |
| |
| int pcie_external_clk = 0; |
| static int __init get_pcie_clk_mode(char *str) |
| { |
| if (!strcmp(str, "yes")) |
| pcie_external_clk = 1; |
| |
| return 1; |
| } |
| |
| __setup("pcie_external_clk=", get_pcie_clk_mode); |
| |
| int pcie_gen1_only = 0; |
| static int __init get_pcie_gen_mode(char *str) |
| { |
| if (!strcmp(str, "yes")) |
| pcie_gen1_only = 1; |
| |
| return 1; |
| } |
| |
| __setup("pcie_gen1_only=", get_pcie_gen_mode); |
| |
| |
| /* DWC PCIEe configuration register offsets on APB */ |
| struct pcie_app_reg app_regs[MAX_PCIE_PORTS] = { |
| /* PCIe0 */ |
| { |
| 0x00000000, |
| 0x00000004, |
| 0x00000008, |
| 0x0000000C, |
| 0x00000010, |
| 0x00000014, |
| 0x00000018, |
| 0x00000040, |
| 0x00000044, |
| 0x00000048, |
| 0x00000058, |
| 0x00000080, |
| 0x00000084, |
| 0x000000C0, |
| 0x000000C4, |
| 0x00000100, |
| 0x00000104, |
| 0x00000108, |
| 0x0000010C |
| }, |
| /* PCIe1 */ |
| { |
| 0x00000020, |
| 0x00000024, |
| 0x00000028, |
| 0x0000002C, |
| 0x00000030, |
| 0x00000034, |
| 0x00000038, |
| 0x0000004C, |
| 0x00000050, |
| 0x00000054, |
| 0x0000005C, |
| 0x00000088, |
| 0x0000008C, |
| 0x000000C8, |
| 0x000000CC, |
| 0x00000110, |
| 0x00000114, |
| 0x00000118, |
| 0x0000011C |
| } |
| }; |
| |
| /* Keeping all DDR area of 512MB accesible for inbound transaction */ |
| #define INBOUND_ADDR_MASK 0x1FFFFFFF |
| |
| |
| #define PCIE_SETUP_iATU_IB_ENTRY( _pp, _view_port, _base, _limit, _ctl1, _ctl2, _target ) \ |
| {\ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_VIEW_PORT, 4, (u32)(_view_port|iATU_VIEW_PORT_IN_BOUND)); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_CTRL2, 4, 0); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_SRC_LOW, 4, (u32)_base); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_SRC_HIGH, 4, 0); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_LIMIT, 4, (u32)((_base)+(_limit))); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_TRGT_LOW, 4, (u32)_target); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_TRGT_HIGH, 4, (u32)0); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_CTRL1, 4, (u32)_ctl1); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_CTRL2, 4, (u32)(_ctl2 |iATU_CTRL2_ID_EN) ); \ |
| } |
| |
| #define PCIE_SETUP_iATU_OB_ENTRY( _pp, _view_port, _base, _limit, _ctl1, _ctl2, _target ) \ |
| {\ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_VIEW_PORT, 4, (u32)_view_port); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_CTRL2, 4, 0); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_SRC_LOW, 4, (u32)_base); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_SRC_HIGH, 4, (u32)0); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_LIMIT, 4, ((u32)((_base)+(_limit)))); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_TRGT_LOW, 4, (u32)_target); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_TRGT_HIGH, 4, (u32)0); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_CTRL1, 4, (u32)_ctl1); \ |
| comcerto_dbi_write_reg(_pp, PCIE_iATU_CTRL2, 4, (u32)(_ctl2 |iATU_CTRL2_ID_EN) ); \ |
| } |
| |
| #define MAX_LINK_UP_WAIT_JIFFIES HZ /* 1 Second */ |
| |
| static unsigned long pcie_cnf_base_addr[MAX_PCIE_PORTS] = |
| { COMCERTO_AXI_PCIe0_BASE, COMCERTO_AXI_PCIe1_BASE }; |
| static unsigned long pcie_remote_base_addr[MAX_PCIE_PORTS] = |
| { COMCERTO_AXI_PCIe0_SLAVE_BASE, COMCERTO_AXI_PCIe1_SLAVE_BASE }; |
| static int pcie_msi_base[MAX_PCIE_PORTS] = |
| { PCIE0_MSI_INT_BASE, PCIE1_MSI_INT_BASE }; |
| static int pcie_intx_base[MAX_PCIE_PORTS] = |
| { PCIE0_INTX_BASE, PCIE1_INTX_BASE }; |
| static int pcie_irqs[MAX_PCIE_PORTS] = |
| { IRQ_PCIe0, IRQ_PCIe1 }; |
| |
| |
| static struct pcie_port pcie_port[MAX_PCIE_PORTS]; |
| |
| static int pcie_port_is_host( int nr ) |
| { |
| struct pcie_port *pp= &pcie_port[nr]; |
| |
| return ( pp->port_mode == PCIE_PORT_MODE_RC ) ? 1 : 0; |
| } |
| |
| /** |
| * This function sets PCIe port mode. |
| */ |
| static void pcie_port_set_mode( int nr, int mode ) |
| { |
| struct pcie_port *pp= &pcie_port[nr]; |
| |
| writel( (readl(pp->va_app_base + pp->app_regs->cfg0) & ~0xf) | mode, pp->va_app_base + pp->app_regs->cfg0); |
| } |
| |
| /** |
| * This function returns the given port mode. |
| * @param nr PCIe Port number. |
| */ |
| static int pcie_port_get_mode( int nr ) |
| { |
| struct pcie_port *pp= &pcie_port[nr]; |
| |
| return ( readl(pp->va_app_base + pp->app_regs->cfg0) & |
| DWC_CFG0_DEV_TYPE_MASK ); |
| } |
| |
| /** |
| * This function checks whether link is up or not. |
| * Returns true if link is up otherwise returns false. |
| * @param pp Pointer to PCIe Port control block. |
| */ |
| static int comcerto_pcie_link_up( struct pcie_port *pp ) |
| { |
| unsigned long deadline = jiffies + MAX_LINK_UP_WAIT_JIFFIES; |
| |
| do { |
| if (readl( pp->va_app_base + pp->app_regs->sts0 ) & STS0_RDLH_LINK_UP) { |
| return 1; |
| } |
| |
| cond_resched(); |
| } while (!time_after_eq(jiffies, deadline)); |
| |
| return 0; |
| } |
| |
| /** |
| * bus_to_port(). |
| * |
| */ |
| static struct pcie_port *bus_to_port(int bus) |
| { |
| int i; |
| |
| for (i = NUM_PCIE_PORTS - 1; i >= 0; i--) { |
| int rbus = pcie_port[i].root_bus_nr; |
| if ( !pcie_port_is_host(i) ) |
| continue; |
| if (rbus != -1 && rbus <= bus) |
| break; |
| } |
| |
| return i >= 0 ? pcie_port + i : NULL; |
| } |
| |
| /** |
| * This function is used to read DBI registers. |
| */ |
| |
| static void comcerto_dbi_read_reg(struct pcie_port *pp, int where, int size, |
| u32 *val) |
| { |
| u32 va_address; |
| |
| va_address = (u32)pp->va_dbi_base + (where & ~0x3); |
| |
| *val = readl_relaxed(va_address); |
| |
| if (size == 1) |
| *val = (*val >> (8 * (where & 3))) & 0xff; |
| else if (size == 2) |
| *val = (*val >> (8 * (where & 3))) & 0xffff; |
| } |
| |
| /** |
| * This function is used to write into DBI registers. |
| */ |
| static void comcerto_dbi_write_reg(struct pcie_port *pp, int where, int size, |
| u32 val) |
| { |
| u32 va_address; |
| int pos, val1, mask = 0; |
| |
| va_address = (u32)pp->va_dbi_base + (where & ~0x3); |
| |
| pos = (where & 0x3) << 3; |
| |
| if (size == 4) |
| val1 = val; |
| else |
| { |
| if (size == 2) |
| mask = 0xffff; |
| else if (size == 1) |
| mask = 0xff; |
| |
| val1 = readl_relaxed(va_address); |
| val1 = ( val1 & ~( mask << pos ) ) | ( (val & mask) << pos ); |
| } |
| |
| writel_relaxed(val1, va_address); |
| } |
| |
| static inline void nop_delay(void) |
| { |
| int k; |
| for(k = 0 ; k < 1000; k++) |
| nop(); |
| } |
| |
| static int comcerto_pcie_rd_conf(struct pcie_port *pp, int bus_nr, |
| u32 devfn, int where, int size, u32 *val) |
| { |
| u32 address; |
| u32 target_address = (u32)(bus_nr << 24) | (PCI_SLOT(devfn) << 19) | (PCI_FUNC(devfn) << 16); |
| |
| /* Initialize iATU */ |
| if (bus_nr != pp->root_bus_nr) { |
| |
| if (pp->cfg1_prev_taddr != target_address) { |
| /* Type1 configuration request */ |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_CNF1, (u32)iATU_GET_CFG1_BASE(pp->remote_mem_baseaddr), |
| iATU_CFG1_SIZE - 1, (AXI_OP_TYPE_CONFIG_RDRW_TYPE1 & iATU_CTRL1_TYPE_MASK), |
| 0, target_address ); |
| pp->cfg1_prev_taddr = target_address; |
| } |
| |
| address = (u32)pp->va_cfg1_base |(where & 0xFFFC); |
| } else { |
| if (pp->cfg0_prev_taddr != target_address) { |
| /* Type0 configuration request */ |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_CNF0, (u32)iATU_GET_CFG0_BASE(pp->remote_mem_baseaddr), |
| iATU_CFG0_SIZE - 1, (AXI_OP_TYPE_CONFIG_RDRW_TYPE0 & iATU_CTRL1_TYPE_MASK), |
| 0, target_address ); |
| pp->cfg0_prev_taddr = target_address; |
| } |
| |
| address = (u32)pp->va_cfg0_base |(where & 0xFFFC); |
| } |
| |
| |
| *val = readl_relaxed(address); |
| |
| /* Because of the imprecise external abort the processor is not able to get the exact instruction |
| which caused the abort and hence when the abort handler tries to restore the PC to the next |
| instruction to resume it is often wrong and it results in skipping few instruction after the |
| readl_relaxed which has caused abort. So nop instructions are added after readl so that even |
| if the some instructions are missed out it will miss the nop instruction only. |
| */ |
| nop_delay(); |
| |
| if (size == 1) |
| *val = (*val >> (8 * (where & 3))) & 0xff; |
| else if (size == 2) |
| *val = (*val >> (8 * (where & 3))) & 0xffff; |
| |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: bus:%d dev:%d where:%d, size:%d addr : %x value:%x\n", __func__, bus_nr, devfn, where, size, address, *val); |
| #endif |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| static int pcie_read_conf(struct pci_bus *bus, u32 devfn, int where, int size, u32 *val) |
| { |
| struct pcie_port *pp = bus_to_port(bus->number); |
| unsigned long flags; |
| int ret; |
| |
| |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: bus:%d dev:%d where:%d, size:%d\n", __func__, bus->number, devfn, where, size); |
| #endif |
| |
| |
| /* Make sure that link is up. |
| * Filter device numbers, unless it's a type1 access |
| */ |
| if ( (!pp->link_state)|| |
| ((bus->number == pp->root_bus_nr) && (PCI_SLOT(devfn) > 0)) ) { |
| *val = 0xffffffff; |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| |
| BUG_ON (((where & 0x3) + size) > 4); |
| |
| /* Enter critical section. */ |
| spin_lock_irqsave(&pp->conf_lock, flags); |
| ret = comcerto_pcie_rd_conf(pp, bus->number, devfn, where, size, val); |
| /* Exit critical section. */ |
| spin_unlock_irqrestore(&pp->conf_lock, flags); |
| |
| return ret; |
| } |
| |
| static int comcerto_pcie_wr_conf(struct pcie_port *pp, int bus_nr, |
| u32 devfn, int where, int size, u32 val) |
| { |
| int ret = PCIBIOS_SUCCESSFUL; |
| u32 address; |
| u32 target_address = (u32)(bus_nr << 24) | (PCI_SLOT(devfn) << 19) | (PCI_FUNC(devfn) << 16); |
| |
| /* Initialize iATU */ |
| if (bus_nr != pp->root_bus_nr) { |
| if (pp->cfg1_prev_taddr != target_address) { |
| /* Type1 configuration request */ |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_CNF1, (u32)iATU_GET_CFG1_BASE(pp->remote_mem_baseaddr), |
| iATU_CFG1_SIZE - 1, (AXI_OP_TYPE_CONFIG_RDRW_TYPE1 & iATU_CTRL1_TYPE_MASK), |
| 0, target_address ); |
| pp->cfg1_prev_taddr = target_address; |
| } |
| |
| address = (u32)pp->va_cfg1_base |(where & 0xFFFC); |
| } else { |
| if (pp->cfg0_prev_taddr != target_address) { |
| /* Type0 configuration request */ |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_CNF0, (u32)iATU_GET_CFG0_BASE(pp->remote_mem_baseaddr), |
| iATU_CFG0_SIZE - 1, (AXI_OP_TYPE_CONFIG_RDRW_TYPE0 & iATU_CTRL1_TYPE_MASK), |
| 0, target_address ); |
| pp->cfg0_prev_taddr = target_address; |
| } |
| |
| address = (u32)pp->va_cfg0_base |(where & 0xFFFC); |
| } |
| |
| |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: bus:%d dev:%d where:%d, size:%d addr : %x value:%x\n", __func__, bus_nr, devfn, where, size, address, val); |
| #endif |
| if (size == 4) |
| writel_relaxed(val, address); |
| else if (size == 2) |
| writew_relaxed(val, address + (where & 2)); |
| else if (size == 1) |
| writeb_relaxed(val, address + (where & 3)); |
| else |
| ret = PCIBIOS_BAD_REGISTER_NUMBER; |
| |
| return ret; |
| } |
| |
| static int pcie_write_conf(struct pci_bus *bus, u32 devfn, int where, int size, u32 val) |
| { |
| struct pcie_port *pp = bus_to_port(bus->number); |
| unsigned long flags; |
| int ret; |
| |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: bus:%d dev:%d where:%d, size:%d val:%d\n", __func__, bus->number, devfn, where, size, val); |
| #endif |
| |
| /* Make sure that link is up. |
| * Filter device numbers, unless it's a type1 access |
| */ |
| if ( (!pp->link_state)|| |
| ((bus->number == pp->root_bus_nr) && (PCI_SLOT(devfn) > 0)) ) { |
| return PCIBIOS_DEVICE_NOT_FOUND; |
| } |
| |
| BUG_ON (((where & 0x3) + size) > 4); |
| |
| /* Enter critical section. */ |
| spin_lock_irqsave(&pp->conf_lock, flags); |
| ret = comcerto_pcie_wr_conf(pp, bus->number, devfn, where, size, val); |
| if (where == PCI_COMMAND) |
| pp->cmd_reg_val = val & 0xffff; |
| /* Exit critical section. */ |
| spin_unlock_irqrestore(&pp->conf_lock, flags); |
| |
| return ret; |
| } |
| |
| |
| |
| static u8 __init comcerto_pcie_swizzle(struct pci_dev *dev, u8 *pin) |
| { |
| return PCI_SLOT(dev->devfn); |
| } |
| |
| static int __init comcerto_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) |
| { |
| struct pcie_port *pp = bus_to_port(dev->bus->number); |
| int irq = (PCIE0_INTX_BASE + pp->port * PCIE_NUM_INTX_IRQS + pin - 1); |
| |
| return irq; |
| } |
| |
| static int __init comcerto_pcie_setup(int nr, struct pci_sys_data *sys) |
| { |
| struct pcie_port *pp; |
| struct pcie_app_reg *app_reg; |
| u32 val; |
| |
| |
| if ((nr >= NUM_PCIE_PORTS) || !pcie_port_is_host(nr)) |
| return 0; |
| |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s:%d nr:%d\n", __func__, __LINE__, nr); |
| #endif |
| |
| |
| pp = &pcie_port[nr]; |
| |
| if (!pp->link_state) |
| return 0; |
| |
| pp->root_bus_nr = sys->busnr; |
| app_reg = pp->app_regs; |
| |
| /* Allocate device memory mapped and IO mapped regions */ |
| snprintf(pp->mem_space_name, sizeof(pp->mem_space_name), |
| "PCIe %d MEM", pp->port); |
| pp->mem_space_name[sizeof(pp->mem_space_name) - 1] = 0; |
| pp->res[0].name = pp->mem_space_name; |
| pp->res[0].start = iATU_GET_MEM_BASE(pp->remote_mem_baseaddr); |
| pp->res[0].end = pp->res[0].start + iATU_MEM_SIZE - 1; |
| pp->res[0].flags = IORESOURCE_MEM; |
| |
| snprintf(pp->io_space_name, sizeof(pp->io_space_name), |
| "PCIe %d I/O", pp->port); |
| pp->io_space_name[sizeof(pp->io_space_name) - 1] = 0; |
| pp->res[1].name = pp->io_space_name; |
| pp->res[1].start = iATU_GET_IO_BASE(pp->remote_mem_baseaddr); |
| pp->res[1].end = pp->res[1].start + iATU_IO_SIZE - 1; |
| pp->res[1].flags = IORESOURCE_IO; |
| |
| |
| if (request_resource(&iomem_resource, &pp->res[0])) |
| { |
| printk(KERN_ERR "%s:can't allocate PCIe Memory space", __func__); |
| return 0; |
| } |
| |
| if (request_resource(&iomem_resource, &pp->res[1])) |
| { |
| printk(KERN_ERR "%s:can't allocate PCIe IO space", __func__); |
| return 0; |
| } |
| |
| /* Generic PCIe unit setup.*/ |
| |
| /* Enable own BME. It is necessary to enable own BME to do a |
| * memory transaction on a downstream device |
| */ |
| comcerto_dbi_read_reg(pp, PCI_COMMAND, 2, &val); |
| val |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
| | PCI_COMMAND_PARITY | PCI_COMMAND_SERR); |
| comcerto_dbi_write_reg(pp, PCI_COMMAND, 2, val); |
| |
| /* Need to come back here*/ |
| |
| sys->resource[0] = &pp->res[0]; |
| sys->resource[1] = &pp->res[1]; |
| sys->resource[2] = NULL; |
| |
| pp->cfg0_prev_taddr = 0xffffffff; |
| pp->cfg1_prev_taddr = 0xffffffff; |
| |
| return 1; |
| } |
| |
| static struct pci_ops pcie_ops = { |
| .read = pcie_read_conf, |
| .write = pcie_write_conf, |
| }; |
| |
| static struct pci_bus *__init comcerto_pcie_scan_bus(int nr, struct pci_sys_data *sys) |
| { |
| struct pci_bus *bus; |
| |
| if ((nr < NUM_PCIE_PORTS) && (pcie_port_is_host(nr))) { |
| |
| bus = pci_scan_bus(sys->busnr, &pcie_ops, sys); |
| } else { |
| bus = NULL; |
| BUG(); |
| } |
| |
| return bus; |
| } |
| |
| static struct hw_pci comcerto_pcie __initdata = { |
| .nr_controllers = NUM_PCIE_PORTS, |
| .swizzle = comcerto_pcie_swizzle, |
| .map_irq = comcerto_pcie_map_irq, |
| .setup = comcerto_pcie_setup, |
| .scan = comcerto_pcie_scan_bus, |
| }; |
| |
| |
| |
| #ifdef CONFIG_PCI_MSI |
| /* MSI int handler |
| */ |
| static void handle_msi(struct pcie_port *pp) |
| { |
| unsigned long val, mask; |
| unsigned int pos, mask0; |
| |
| |
| val = readl_relaxed(pp->va_dbi_base + PCIE_MSI_INTR0_STATUS); |
| |
| continue_handle: |
| |
| pos = 0; |
| |
| while (val) { |
| mask0 = 1 << pos; |
| |
| if (val & mask0) { |
| /* FIXME : WA for bz69520 |
| * To avoid race condition during avk the interrupt disabling interrupt before |
| * Ack and enabling after Ack. |
| */ |
| spin_lock(&pp->intr_lock); |
| mask = readl_relaxed(pp->va_dbi_base + PCIE_MSI_INTR0_ENABLE); |
| writel_relaxed(mask & ~mask0, pp->va_dbi_base + PCIE_MSI_INTR0_ENABLE); |
| writel_relaxed(mask0, pp->va_dbi_base + PCIE_MSI_INTR0_STATUS); |
| writel_relaxed(mask, pp->va_dbi_base + PCIE_MSI_INTR0_ENABLE); |
| spin_unlock(&pp->intr_lock); |
| generic_handle_irq(pp->msi_base + pos); |
| val = val & ~mask0; |
| } |
| pos++; |
| } |
| |
| val = readl_relaxed(pp->va_dbi_base + PCIE_MSI_INTR0_STATUS); |
| if(val) |
| goto continue_handle; |
| |
| #if 0 |
| for (i = 0; i < (PCIE_NUM_MSI_IRQS >> 5); i++) { |
| val = readl_relaxed(pp->va_dbi_base + PCIE_MSI_INTR0_STATUS + (i * 12)); |
| if (val) { |
| pos = 0; |
| while ((pos = find_next_bit(&val, 32, pos)) != 32) { |
| /* FIXME : WA for bz69520 |
| * To avoid race condition during avk the interrupt disabling interrupt before |
| * Ack and enabling after Ack. |
| */ |
| spin_lock(&pp->intr_lock); |
| mask = readl_relaxed(pp->va_dbi_base + PCIE_MSI_INTR0_ENABLE + (i * 12)); |
| writel_relaxed(mask & ~(1 << pos), pp->va_dbi_base + PCIE_MSI_INTR0_ENABLE + (i * 12)); |
| writel_relaxed((1 << pos), pp->va_dbi_base + PCIE_MSI_INTR0_STATUS + (i * 12)); |
| writel_relaxed(mask & (1 << pos), pp->va_dbi_base + PCIE_MSI_INTR0_ENABLE + (i * 12)); |
| spin_unlock(&pp->intr_lock); |
| generic_handle_irq(pp->msi_base + (i * 32) + pos); |
| pos++; |
| } |
| } |
| } |
| #endif |
| } |
| #else |
| static void handle_msi(struct pcie_port *pp) |
| { |
| } |
| #endif |
| |
| static void comcerto_pcie_int_handler(unsigned int irq, struct irq_desc *desc) |
| { |
| struct pcie_port *pp = irq_get_handler_data(irq); |
| struct pcie_app_reg *app_reg = pp->app_regs; |
| struct irq_chip *chip; |
| unsigned int status; |
| int pos, ii; |
| |
| chip = irq_desc_get_chip(desc); |
| chained_irq_enter(chip, desc); |
| status = readl_relaxed(pp->va_app_base + app_reg->intr_sts); |
| |
| if (status & INTR_CTRL_MSI) { |
| writel_relaxed(INTR_CTRL_MSI, (pp->va_app_base + app_reg->intr_sts)); |
| status &= ~(INTR_CTRL_MSI); |
| handle_msi(pp); |
| } |
| |
| for (ii = 0; (ii < PCIE_NUM_INTX_IRQS) && status; ii++) { |
| pos = ii << 1; |
| |
| /* Handle INTx Assert */ |
| if (status & (INTR_CTRL_INTA_ASSERT << pos)) { |
| status &= ~(INTR_CTRL_INTA_ASSERT << pos); |
| writel_relaxed((INTR_CTRL_INTA_ASSERT << pos), (pp->va_app_base + app_reg->intr_sts)); |
| generic_handle_irq(pp->intx_base + ii); |
| } |
| |
| /*FIXME : No need to handle DEASSERT, simply clear the interrupt */ |
| status &= ~(INTR_CTRL_INTA_DEASSERT << pos); |
| #if 0 |
| /* Handle INTx Deasert */ |
| if (status & (INTR_CTRL_INTA_DEASSERT << pos)) { |
| status &= ~(INTR_CTRL_INTA_DEASSERT << pos); |
| writel_relaxed((INTR_CTRL_INTA_DEASSERT << pos), (pp->va_app_base + app_reg->intr_sts)); |
| } |
| #endif |
| } |
| |
| if (status) { |
| printk(KERN_INFO "%s:Unhandled interrupt %x\n", __func__, status); |
| /* FIXME: HP, AER, PME interrupts need to be handled */ |
| writel_relaxed(status, (pp->va_app_base + app_reg->intr_sts)); |
| } |
| |
| chained_irq_exit(chip, desc); |
| } |
| |
| |
| static void pcie_nop_intx_irq(struct irq_data *data ) |
| { |
| return; |
| } |
| |
| static void pcie_unmask_intx_irq( struct irq_data *data ) |
| { |
| struct pcie_port *pp = data->chip_data; |
| |
| spin_lock(&pp->conf_lock); |
| comcerto_pcie_wr_conf(pp, pp->root_bus_nr, 0, PCI_COMMAND, 2, (pp->cmd_reg_val & ~PCI_COMMAND_INTX_DISABLE)); |
| spin_unlock(&pp->conf_lock); |
| } |
| |
| static void pcie_enable_intx_irq( struct irq_data *data ) |
| { |
| int irq = data->irq; |
| struct pcie_port *pp = data->chip_data; |
| int irq_offset = irq - pp->intx_base; |
| struct pcie_app_reg *app_reg = (struct pcie_app_reg *)pp->app_regs; |
| unsigned long flags; |
| |
| if( irq_offset >= PCIE_NUM_INTX_IRQS ) |
| return; |
| |
| /* |
| * In interrupt sts/mask register each interrupt has two |
| * consecutive bits, one for assert and another for dessert. |
| */ |
| irq_offset = irq_offset << 1; |
| |
| spin_lock_irqsave(&pp->intr_lock, flags); |
| writel_relaxed(readl_relaxed(pp->va_app_base + app_reg->intr_en) | ( INTR_CTRL_INTA_ASSERT << irq_offset), |
| (pp->va_app_base + app_reg->intr_en) ); |
| spin_unlock_irqrestore(&pp->intr_lock, flags); |
| } |
| |
| static void pcie_mask_intx_irq( struct irq_data *data ) |
| { |
| struct pcie_port *pp = data->chip_data; |
| |
| spin_lock(&pp->conf_lock); |
| comcerto_pcie_wr_conf(pp, pp->root_bus_nr, 0, PCI_COMMAND, 2, (pp->cmd_reg_val | PCI_COMMAND_INTX_DISABLE)); |
| spin_unlock(&pp->conf_lock); |
| } |
| |
| static void pcie_disable_intx_irq( struct irq_data *data ) |
| { |
| int irq = data->irq; |
| struct pcie_port *pp = data->chip_data; |
| int irq_offset = irq - pp->intx_base; |
| struct pcie_app_reg *app_reg = (struct pcie_app_reg *)pp->app_regs; |
| unsigned long flags; |
| |
| if( irq_offset >= PCIE_NUM_INTX_IRQS ) |
| return; |
| |
| /* |
| * In interrupt sts/mask register each interrupt has two |
| * consecutive bits, one for assert and another for dessert. |
| */ |
| irq_offset = irq_offset << 1; |
| |
| spin_lock_irqsave(&pp->intr_lock, flags); |
| writel_relaxed( readl_relaxed(pp->va_app_base + app_reg->intr_en) & ~( INTR_CTRL_INTA_ASSERT << irq_offset), |
| (pp->va_app_base + app_reg->intr_en) ); |
| spin_unlock_irqrestore(&pp->intr_lock, flags); |
| } |
| |
| static struct irq_chip pcie_intx_chip = { |
| .name = "PCIe INTx", |
| .irq_ack = pcie_nop_intx_irq, |
| .irq_enable = pcie_enable_intx_irq, |
| .irq_disable = pcie_disable_intx_irq, |
| .irq_mask = pcie_mask_intx_irq, |
| .irq_unmask = pcie_unmask_intx_irq, |
| }; |
| |
| static int comcerto_pcie_intx_init(struct pcie_port *pp) |
| { |
| int i, irq; |
| struct pcie_app_reg *app_reg; |
| |
| /* Disable INTX interrupt*/ |
| app_reg = pp->app_regs; |
| |
| writel(readl(pp->va_app_base + app_reg->intr_en) & |
| ~(INTR_CTRL_INTA_ASSERT | |
| INTR_CTRL_INTB_ASSERT | |
| INTR_CTRL_INTC_ASSERT | |
| INTR_CTRL_INTD_ASSERT), |
| pp->va_app_base + app_reg->intr_en ); |
| |
| /* initilize INTX chip here only. MSI chip will be |
| * initilized dynamically.*/ |
| irq = pp->intx_base; |
| for (i = 0; i < PCIE_NUM_INTX_IRQS; i++) { |
| irq_set_chip_data(irq + i, pp); |
| irq_set_chip_and_handler(irq + i, &pcie_intx_chip, |
| handle_level_irq); |
| set_irq_flags(irq + i, IRQF_VALID); |
| } |
| |
| irq_set_handler_data(pp->irq, pp); |
| irq_set_chained_handler(pp->irq, comcerto_pcie_int_handler); |
| |
| /* FIXME Added for debuging */ |
| writel(readl(pp->va_app_base + app_reg->intr_en) & |
| ~(INTR_CTRL_AER | INTR_CTRL_PME | |
| INTR_CTRL_HP | INTR_CTRL_LINK_AUTO_BW ), |
| pp->va_app_base + app_reg->intr_en ); |
| |
| |
| return 0; |
| } |
| |
| static int comcerto_pcie_rc_int_init( struct pcie_port *pp ) |
| { |
| |
| printk(KERN_INFO "%s\n",__func__); |
| |
| comcerto_pcie_intx_init(pp); |
| |
| #ifdef CONFIG_PCI_MSI |
| comcerto_msi_init(pp); |
| #endif |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PCI_MSI |
| static int find_valid_pos0(int port, int nvec, int pos, int *pos0) |
| { |
| int flag = 1; |
| do { |
| pos = find_next_zero_bit(msi_irq_in_use[port], |
| PCIE_NUM_MSI_IRQS, pos); |
| /*if you have reached to the end then get out from here.*/ |
| if (pos == PCIE_NUM_MSI_IRQS) |
| return -ENOSPC; |
| /* Check if this position is at correct offset.nvec is always a |
| * power of two. pos0 must be nvec bit alligned. |
| */ |
| if (pos % nvec) |
| pos += nvec - (pos % nvec); |
| else |
| flag = 0; |
| } while (flag); |
| |
| *pos0 = pos; |
| return 0; |
| } |
| |
| #define GET_MSI_INT_REG_POS(_irq) ((_irq >> 5) * 12) |
| #define GET_MSI_INT_OFST(_irq) (_irq & 0x1F) |
| |
| static void comcerto_msi_nop(struct irq_data *data) |
| { |
| return; |
| } |
| |
| static void comcerto_msi_unmask(struct irq_data *data) |
| { |
| struct pcie_port *pp = data->chip_data; |
| int irq = data->irq - pp->msi_base; |
| int ofst, pos; |
| unsigned int val; |
| unsigned long flags; |
| |
| if( irq >= PCIE_NUM_MSI_IRQS ) |
| return; |
| |
| pos = GET_MSI_INT_REG_POS(irq); |
| ofst = GET_MSI_INT_OFST(irq); |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: %x:%x\n",__func__, pos, ofst); |
| #endif |
| |
| spin_lock_irqsave(&pp->intr_lock, flags); |
| comcerto_dbi_read_reg(pp, PCIE_MSI_INTR0_MASK + pos, 4, &val); |
| val &= ~(1 << ofst); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_INTR0_MASK + pos, 4, val); |
| spin_unlock_irqrestore(&pp->intr_lock, flags); |
| |
| return; |
| } |
| |
| static void comcerto_msi_mask(struct irq_data *data) |
| { |
| struct pcie_port *pp = data->chip_data; |
| int irq = data->irq - pp->msi_base; |
| int ofst, pos; |
| unsigned int val; |
| unsigned long flags; |
| |
| if( irq >= PCIE_NUM_MSI_IRQS ) |
| return; |
| |
| pos = GET_MSI_INT_REG_POS(irq); |
| ofst = GET_MSI_INT_OFST(irq); |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: %x:%x\n",__func__, pos, ofst); |
| #endif |
| |
| spin_lock_irqsave(&pp->intr_lock, flags); |
| comcerto_dbi_read_reg(pp, PCIE_MSI_INTR0_MASK + pos, 4, &val); |
| val |= (1 << ofst); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_INTR0_MASK + pos, 4, val); |
| spin_unlock_irqrestore(&pp->intr_lock, flags); |
| |
| return; |
| } |
| |
| |
| static void comcerto_msi_enable(struct irq_data *data) |
| { |
| struct pcie_port *pp = data->chip_data; |
| int irq = data->irq - pp->msi_base; |
| int ofst, pos; |
| unsigned int val; |
| unsigned long flags; |
| |
| if( irq >= PCIE_NUM_MSI_IRQS ) |
| return; |
| |
| pos = GET_MSI_INT_REG_POS(irq); |
| ofst = GET_MSI_INT_OFST(irq); |
| |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: %x:%x\n",__func__, pos, ofst); |
| #endif |
| |
| spin_lock_irqsave(&pp->intr_lock, flags); |
| comcerto_dbi_read_reg(pp, PCIE_MSI_INTR0_ENABLE + pos, 4, &val); |
| val |= (1 << ofst); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_INTR0_ENABLE + pos, 4, val); |
| spin_unlock_irqrestore(&pp->intr_lock, flags); |
| |
| return; |
| } |
| |
| |
| static void comcerto_msi_disable(struct irq_data *data) |
| { |
| struct pcie_port *pp = data->chip_data; |
| int irq = data->irq - pp->msi_base; |
| int ofst, pos; |
| unsigned int val; |
| unsigned long flags; |
| |
| if( irq >= PCIE_NUM_MSI_IRQS ) |
| return; |
| |
| pos = GET_MSI_INT_REG_POS(irq); |
| ofst = GET_MSI_INT_OFST(irq); |
| #ifdef COMCERTO_PCIE_DEBUG |
| printk(KERN_DEBUG "%s: %x:%x\n",__func__, pos, ofst); |
| #endif |
| |
| spin_lock_irqsave(&pp->intr_lock, flags); |
| comcerto_dbi_read_reg(pp, PCIE_MSI_INTR0_ENABLE + pos, 4, &val); |
| val &= ~(1 << ofst); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_INTR0_ENABLE + pos, 4, val); |
| spin_unlock_irqrestore(&pp->intr_lock, flags); |
| |
| return; |
| } |
| |
| static struct irq_chip comcerto_msi_chip = { |
| .name = "PCI-MSI", |
| .irq_ack = comcerto_msi_nop, |
| .irq_enable = comcerto_msi_enable, |
| .irq_disable = comcerto_msi_disable, |
| .irq_mask = comcerto_msi_mask, |
| .irq_unmask = comcerto_msi_unmask, |
| }; |
| |
| /* |
| * Dynamic irq allocate and deallocation |
| */ |
| static int get_irq(int nvec, struct msi_desc *desc, int *pos) |
| { |
| int irq, pos0, pos1, i; |
| struct pcie_port *pp = bus_to_port(desc->dev->bus->number); |
| |
| pos0 = find_first_zero_bit(msi_irq_in_use[pp->port], |
| PCIE_NUM_MSI_IRQS); |
| if (pos0 % nvec) { |
| if (find_valid_pos0(pp->port, nvec, pos0, &pos0)) |
| goto no_valid_irq; |
| } |
| if (nvec > 1) { |
| pos1 = find_next_bit(msi_irq_in_use[pp->port], |
| PCIE_NUM_MSI_IRQS, pos0); |
| /* there must be nvec number of consecutive free bits */ |
| while ((pos1 - pos0) < nvec) { |
| if (find_valid_pos0(pp->port, nvec, pos1, &pos0)) |
| goto no_valid_irq; |
| pos1 = find_next_bit(msi_irq_in_use[pp->port], |
| PCIE_NUM_MSI_IRQS, pos0); |
| } |
| } |
| |
| irq = pp->msi_base + pos0; |
| |
| if ((irq + nvec) > (pp->msi_base + PCIE_NUM_MSI_IRQS)) |
| goto no_valid_irq; |
| |
| i = 0; |
| while (i < nvec) { |
| set_bit(pos0 + i, msi_irq_in_use[pp->port]); |
| dynamic_irq_init(irq + i); |
| irq_set_chip_data(irq + i, pp); |
| irq_set_chip_and_handler(irq + i, &comcerto_msi_chip, |
| handle_simple_irq); |
| set_irq_flags(irq + i, IRQF_VALID); |
| i++; |
| } |
| |
| irq_set_msi_desc(irq, desc); |
| |
| *pos = pos0; |
| return irq; |
| no_valid_irq: |
| printk(KERN_ERR "%s : MSI interrupt allocate failed\n", __func__); |
| *pos = pos0; |
| return -ENOSPC; |
| } |
| |
| static void clean_irq(unsigned int irq) |
| { |
| int res, bit, val, pos; |
| struct irq_data *data = irq_get_irq_data(irq); |
| struct pcie_port *pp = data->chip_data; |
| unsigned long flags; |
| |
| if( !pp ) |
| return; |
| |
| pos = irq - pp->msi_base; |
| |
| dynamic_irq_cleanup(irq); |
| |
| spin_lock_irqsave(&pp->msi_map_lock, flags); |
| clear_bit(pos, msi_irq_in_use[pp->port]); |
| |
| /* Disable corresponding interrupt on MSI interrupt |
| * controller. |
| */ |
| res = (pos / 32) * 12; |
| bit = pos % 32; |
| comcerto_dbi_read_reg(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); |
| val &= ~(1 << bit); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); |
| spin_unlock_irqrestore(&pp->msi_map_lock, flags); |
| |
| } |
| |
| int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) |
| { |
| int irq, pos; |
| struct msi_msg msg; |
| struct pcie_port *pp = bus_to_port(pdev->bus->number); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pp->msi_map_lock, flags); |
| irq = get_irq(1, desc, &pos); |
| spin_unlock_irqrestore(&pp->msi_map_lock, flags); |
| |
| |
| if (irq < 0) |
| return irq; |
| |
| desc->msi_attrib.multiple = 0; |
| |
| /* An EP will modify lower 8 bits(max) of msi data while |
| * sending any msi interrupt |
| */ |
| msg.address_hi = 0x0; |
| msg.address_lo = pp->msi_mbox_handle; |
| msg.data = pos; |
| write_msi_msg(irq, &msg); |
| |
| return 0; |
| } |
| |
| void arch_teardown_msi_irq(unsigned int irq) |
| { |
| clean_irq(irq); |
| } |
| |
| static int comcerto_msi_init(struct pcie_port *pp) |
| { |
| struct pcie_app_reg *app_reg = (struct pcie_app_reg *)pp->app_regs; |
| |
| pp->msi_mbox_baseaddr = dma_alloc_coherent(NULL, sizeof(u32), &pp->msi_mbox_handle, GFP_KERNEL); |
| if (!pp->msi_mbox_baseaddr) { |
| printk(KERN_ERR "PCIe(%d): failed to allocate msi mailbox coherent memory\n", pp->port); |
| goto err; |
| } |
| |
| comcerto_dbi_write_reg(pp, PCIE_MSI_ADDR_LO, 4, pp->msi_mbox_handle); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_ADDR_HI, 4, 0); |
| /* Enbale MSI interrupt*/ |
| writel(readl(pp->va_app_base + app_reg->intr_en) | INTR_CTRL_MSI, |
| pp->va_app_base + app_reg->intr_en); |
| return 0; |
| |
| err: |
| return -1; |
| } |
| #endif |
| |
| static void comcerto_pcie_rc_init(struct pcie_port *pp) |
| { |
| struct pcie_app_reg *app_reg = pp->app_regs; |
| unsigned int val; |
| |
| |
| //FIXME : Bit:27 definition is not clear from document |
| // This register setting is copied from simulation code. |
| writel(readl(pp->va_app_base + app_reg->cfg0) | 0x08007FF0, |
| pp->va_app_base + app_reg->cfg0); |
| |
| |
| /** |
| *FIXME : Bit:15 definition is not clear from document |
| * This register setting is copied from simulation code. |
| */ |
| writel(readl(pp->va_app_base + app_reg->cfg4) | 0x00008000, |
| pp->va_app_base + app_reg->cfg4); |
| |
| comcerto_dbi_read_reg(pp, PCIE_AFL0L1_REG, 4, &val); |
| val &= ~(0x00FFFF00); |
| val |= 0x00F1F100; |
| comcerto_dbi_write_reg(pp, PCIE_AFL0L1_REG, 4, val); |
| |
| if(pcie_gen1_only) |
| { |
| comcerto_dbi_write_reg(pp, PCIE_LCNT2_REG, 4, 0x1); |
| comcerto_dbi_write_reg(pp, PCIE_LCAP_REG, 4, 0x1); |
| } |
| else |
| { |
| comcerto_dbi_read_reg(pp, PCIE_G2CTRL_REG, 4, &val); |
| val &= ~(0xFF); |
| val |= 0xF1; |
| comcerto_dbi_write_reg(pp, PCIE_G2CTRL_REG, 4, val); |
| |
| // instruct pcie to switch to gen2 after init |
| comcerto_dbi_read_reg(pp, PCIE_G2CTRL_REG, 4, &val); |
| val |= (1 << 17); |
| comcerto_dbi_write_reg(pp, PCIE_G2CTRL_REG, 4, val); |
| } |
| |
| /*setup iATU for outbound translation */ |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_MEM, iATU_GET_MEM_BASE(pp->remote_mem_baseaddr), |
| iATU_MEM_SIZE - 1, 0, 0, pp->remote_mem_baseaddr ); |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_IO, iATU_GET_IO_BASE(pp->remote_mem_baseaddr), |
| iATU_IO_SIZE - 1, (AXI_OP_TYPE_IO_RDRW & iATU_CTRL1_TYPE_MASK), |
| 0, iATU_GET_IO_BASE(pp->remote_mem_baseaddr) ); |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_MSG, iATU_GET_MSG_BASE(pp->remote_mem_baseaddr), |
| iATU_MSG_SIZE - 1, (AXI_OP_TYPE_MSG_REQ & iATU_CTRL1_TYPE_MASK), |
| 0, iATU_GET_MSG_BASE(pp->remote_mem_baseaddr) ); |
| PCIE_SETUP_iATU_IB_ENTRY( pp, 0, 0, |
| INBOUND_ADDR_MASK, 0, 0, COMCERTO_AXI_DDR_BASE); |
| |
| } |
| |
| static int pcie_app_init(struct pcie_port *pp, int nr, int mode) |
| { |
| |
| if (nr > MAX_PCIE_PORTS) { |
| printk(KERN_ERR "%s: Invalid port\n", __func__); |
| goto err0; |
| } |
| |
| if (mode != CFG0_DEV_TYPE_RC) { |
| printk(KERN_ERR "%s: Unsupported mode selected mode: %d. \n", __func__, mode); |
| goto err0; |
| } |
| |
| memset(pp, 0, sizeof(struct pcie_port)); |
| |
| pp->port = nr; |
| pp->app_regs = &app_regs[nr]; |
| pp->root_bus_nr = nr; |
| pp->base = pcie_cnf_base_addr[nr]; |
| pp->app_base = (unsigned long)COMCERTO_APB_PCI_SATA_USB_CTRL_BASE; |
| pp->va_app_base = (void __iomem *)APB_VADDR(COMCERTO_APB_PCI_SATA_USB_CTRL_BASE); |
| pp->remote_mem_baseaddr = pcie_remote_base_addr[nr]; |
| |
| if (!nr) |
| pp->va_dbi_base = (void __iomem *) COMCERTO_AXI_PCIe0_VADDR_BASE; |
| else |
| pp->va_dbi_base = (void __iomem *) COMCERTO_AXI_PCIe1_VADDR_BASE; |
| |
| pp->va_cfg0_base = (void __iomem *) |
| ioremap( iATU_GET_CFG0_BASE(pp->remote_mem_baseaddr), iATU_CFG0_SIZE); |
| if (!pp->va_cfg0_base) { |
| pr_err("error with ioremap in function %s\n", __func__); |
| goto err0; |
| } |
| pp->va_cfg1_base = (void __iomem *) |
| ioremap( iATU_GET_CFG1_BASE(pp->remote_mem_baseaddr) , iATU_CFG1_SIZE); |
| if (!pp->va_cfg1_base) { |
| pr_err("error with ioremap in function %s\n", __func__); |
| goto err1; |
| } |
| |
| pp->intx_base = pcie_intx_base[nr]; |
| pp->msi_base = pcie_msi_base[nr]; |
| pp->irq = pcie_irqs[nr]; |
| spin_lock_init(&pp->conf_lock); |
| spin_lock_init(&pp->intr_lock); |
| spin_lock_init(&pp->msi_map_lock); |
| memset(pp->res, 0, sizeof(pp->res)); |
| |
| /* Get the reference clock to PCIe port*/ |
| switch (nr) { |
| case 0: |
| pp->ref_clock = clk_get(NULL, "pcie0"); |
| break; |
| case 1: |
| pp->ref_clock = clk_get(NULL, "pcie1"); |
| break; |
| default: |
| printk(KERN_ERR "%s: Invalid port\n", __func__); |
| goto err2; |
| } |
| if (IS_ERR(pp->ref_clock)) { |
| pr_err("%s: Unable to obtain pcie%d clock: %ld\n", __func__, nr, PTR_ERR(pcie_port[nr].ref_clock)); |
| goto err2; |
| } |
| |
| /* Enable the PCIE_OCC clock */ |
| #if defined(CONFIG_COMCERTO_PCIE_OCC_CLOCK) |
| pp->occ_clock = clk_get(NULL,"pcie_occ"); |
| if (IS_ERR(pp->occ_clock)) { |
| pr_err("%s: Unable to obtain pcie_occ clock: %ld\n", __func__, PTR_ERR(pp->occ_clock)); |
| goto err_occ_clock; |
| } |
| #endif |
| pcie_port_set_mode(nr, mode); |
| |
| return 0; |
| |
| #if defined(CONFIG_COMCERTO_PCIE_OCC_CLOCK) |
| err_occ_clock: |
| clk_put(pp->occ_clock); |
| #endif |
| |
| err2: |
| iounmap(pp->va_cfg1_base); |
| err1: |
| iounmap(pp->va_cfg0_base); |
| err0: |
| return -1; |
| |
| } |
| |
| #if defined(CONFIG_C2K_EVM) || defined(CONFIG_C2K_ASIC) |
| #define PCIE_DEV_EXT_RESET_ASSERT(_id) \ |
| writel(readl(COMCERTO_GPIO_OUTPUT_REG) & ~(GPIO_PIN_27), COMCERTO_GPIO_OUTPUT_REG); |
| |
| #define PCIE_DEV_EXT_RESET_DEASSERT(_id) \ |
| writel(readl(COMCERTO_GPIO_OUTPUT_REG) | (GPIO_PIN_27), COMCERTO_GPIO_OUTPUT_REG); |
| |
| #elif defined(CONFIG_GOOGLE_FIBER_OPTIMUS) |
| /* Older revisions of optimus boards had only a single reset line, like |
| * C2K_EVM. Newer ones have two. But it's harmless to twiddle both even |
| * on older boards. |
| */ |
| #define PCIE_DEV_EXT_RESET_ASSERT(_id) do { \ |
| writel(readl(COMCERTO_GPIO_OUTPUT_REG) & ~(GPIO_PIN_27), COMCERTO_GPIO_OUTPUT_REG); \ |
| writel(readl(COMCERTO_GPIO_63_32_PIN_OUTPUT) & ~(PCIE_ADDITIONAL_RESET_PIN), \ |
| COMCERTO_GPIO_63_32_PIN_OUTPUT); \ |
| } while(0) |
| |
| #define PCIE_DEV_EXT_RESET_DEASSERT(_id) do { \ |
| writel(readl(COMCERTO_GPIO_OUTPUT_REG) | (GPIO_PIN_27), COMCERTO_GPIO_OUTPUT_REG); \ |
| writel(readl(COMCERTO_GPIO_63_32_PIN_OUTPUT) | (PCIE_ADDITIONAL_RESET_PIN), \ |
| COMCERTO_GPIO_63_32_PIN_OUTPUT); \ |
| } while(0) |
| #else |
| /* Board specific */ |
| #error "you must define proper PCIE_DEV_EXT_RESET_* macros for this board" |
| #endif |
| |
| static int comcerto_pcie_device_reset(struct pcie_port *pp) |
| { |
| int ii; |
| struct pcie_port *l_pp = pp; |
| struct pci_dev *pci_dev = NULL; |
| |
| printk(KERN_INFO "ENTER: Bringing PCIe%d device reset\n", pp->port); |
| if (!pp->link_state) |
| return -1; |
| |
| /* On C2KEVM and ASIC same reset is connected to PCIe0/1. |
| * So, device might have kept in reset, using other PCIe host. |
| */ |
| if (!comcerto_pcie_link_up(pp)) { |
| printk(KERN_INFO "%s : Device is already link down state\n", __func__); |
| return 0; |
| } |
| |
| |
| /*FIXME : Below code might be required if we want to reset pcie device, without |
| * invoking pcie device supend. If we invoke pcie device suspend it will |
| * take care of saving pcie config space. |
| */ |
| #if 1 |
| /* Now save the PCIe device configuration space.*/ |
| while((pci_dev = pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, pci_dev))) { |
| if (pp->root_bus_nr == pci_dev->bus->number) { |
| pci_save_state(pci_dev); |
| } |
| } |
| |
| /* On C2KEVM/ASIC, since same GPIO27 is used to reset PCIe0/PCIe1 devices, |
| * save configuration of devices on other PCIe also. |
| * This may not be applicable for other cutomer boards. |
| */ |
| #if defined(CONFIG_GOOGLE_FIBER_OPTIMUS) || defined(CONFIG_C2K_EVM) || defined(CONFIG_C2K_ASIC) |
| l_pp = &pcie_port[!pp->port]; |
| pci_dev = NULL; |
| |
| if (l_pp->link_state) { |
| while((pci_dev = pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, pci_dev))) { |
| if (l_pp->root_bus_nr == pci_dev->bus->number) { |
| pci_save_state(pci_dev); |
| } |
| } |
| } |
| #endif |
| #endif |
| |
| /************** Ready to issue rest *****************/ |
| /* assert external reset (GPIO-27) */ |
| PCIE_DEV_EXT_RESET_ASSERT(pp->port); |
| printk(KERN_INFO "EXIT: Bringing PCIe%d device reset\n", pp->port); |
| |
| return 0; |
| |
| } |
| |
| |
| static int comcerto_pcie_device_reset_exit(struct pcie_port *pp) |
| { |
| unsigned int val; |
| struct pci_dev *pci_dev; |
| |
| printk(KERN_INFO "ENTER: Bringing PCIe%d device out-of-reset\n", pp->port); |
| |
| if (!pp->link_state && pp->reset) |
| return -1; |
| |
| /* deassert external reset (GPIO-27) */ |
| PCIE_DEV_EXT_RESET_DEASSERT(pp->port); |
| |
| udelay(1000); |
| udelay(1000); |
| udelay(1000); |
| udelay(1000); |
| udelay(1000); |
| udelay(1000); |
| udelay(1000); |
| |
| /* Restore the RC configuration */ |
| comcerto_dbi_read_reg(pp, PCIE_AFL0L1_REG, 4, &val); |
| val &= ~(0x00FFFF00); |
| val |= 0x00F1F100; |
| comcerto_dbi_write_reg(pp, PCIE_AFL0L1_REG, 4, val); |
| |
| if(pcie_gen1_only) |
| { |
| comcerto_dbi_write_reg(pp, PCIE_LCNT2_REG, 4, 0x1); |
| comcerto_dbi_write_reg(pp, PCIE_LCAP_REG, 4, 0x1); |
| } |
| else |
| { |
| comcerto_dbi_read_reg(pp, PCIE_G2CTRL_REG, 4, &val); |
| val &= ~(0xFF); |
| val |= 0xF1; |
| comcerto_dbi_write_reg(pp, PCIE_G2CTRL_REG, 4, val); |
| } |
| |
| // instruct pcie to switch to gen2 after init |
| comcerto_dbi_read_reg(pp, PCIE_G2CTRL_REG, 4, &val); |
| val |= (1 << 17); |
| comcerto_dbi_write_reg(pp, PCIE_G2CTRL_REG, 4, val); |
| |
| /*setup iATU for outbound translation */ |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_MEM, iATU_GET_MEM_BASE(pp->remote_mem_baseaddr), |
| iATU_MEM_SIZE - 1, 0, 0, pp->remote_mem_baseaddr ); |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_IO, iATU_GET_IO_BASE(pp->remote_mem_baseaddr), |
| iATU_IO_SIZE - 1, (AXI_OP_TYPE_IO_RDRW & iATU_CTRL1_TYPE_MASK), |
| 0, iATU_GET_IO_BASE(pp->remote_mem_baseaddr) ); |
| PCIE_SETUP_iATU_OB_ENTRY( pp, iATU_ENTRY_MSG, iATU_GET_MSG_BASE(pp->remote_mem_baseaddr), |
| iATU_MSG_SIZE - 1, (AXI_OP_TYPE_MSG_REQ & iATU_CTRL1_TYPE_MASK), |
| 0, iATU_GET_MSG_BASE(pp->remote_mem_baseaddr) ); |
| PCIE_SETUP_iATU_IB_ENTRY( pp, 0, 0, |
| INBOUND_ADDR_MASK, 0, 0, COMCERTO_AXI_DDR_BASE); |
| |
| comcerto_dbi_write_reg(pp, PCIE_MSI_ADDR_LO, 4, pp->msi_mbox_handle); |
| comcerto_dbi_write_reg(pp, PCIE_MSI_ADDR_HI, 4, 0); |
| |
| writel_relaxed(0x7, pp->va_app_base + pp->app_regs->cfg5); |
| |
| /* Generic PCIe unit setup.*/ |
| |
| /* Enable own BME. It is necessary to enable own BME to do a |
| * memory transaction on a downstream device |
| */ |
| comcerto_dbi_read_reg(pp, PCI_COMMAND, 2, &val); |
| val |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
| | PCI_COMMAND_PARITY | PCI_COMMAND_SERR); |
| comcerto_dbi_write_reg(pp, PCI_COMMAND, 2, val); |
| |
| pp->cfg0_prev_taddr = 0xffffffff; |
| pp->cfg1_prev_taddr = 0xffffffff; |
| |
| //udelay(1000); |
| |
| if(comcerto_pcie_link_up(pp)) { |
| printk(KERN_INFO " Bringing PCIe%d device out-of-reset : Link Up\n", pp->port); |
| /*FIXME : Below code might be required if we want to bring pcie device out-of-reset, |
| * without invoking pcie device supend. If we invoke pcie device resume it will |
| * take care of restoring, saved pcie config space. |
| */ |
| #if 1 |
| pci_dev = NULL; |
| while((pci_dev = pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, pci_dev))) { |
| if (pp->root_bus_nr == pci_dev->bus->number) { |
| pci_restore_state(pci_dev); |
| } |
| } |
| #endif |
| } |
| |
| printk(KERN_INFO "EXIT: Bringing PCIe%d device out-of-reset\n", pp->port); |
| return 0; |
| |
| } |
| |
| static ssize_t comcerto_pcie_show_reset(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct pcie_port *pp = &pcie_port[pdev->id]; |
| |
| return sprintf(buf, "%d\n", pp->reset); |
| } |
| |
| static ssize_t comcerto_pcie_set_reset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct pcie_port *pp = &pcie_port[pdev->id]; |
| int reset = 0, rc; |
| |
| if (!pcie_port_is_host(pdev->id) || !(pp->link_state)) |
| return count; |
| |
| sscanf(buf, "%d", &reset); |
| |
| reset = reset ? 1:0; |
| |
| if (pp->reset == reset) { |
| printk(KERN_INFO "%s: Already in same state\n", __func__); |
| return count; |
| } |
| |
| |
| if (reset) { |
| printk(KERN_INFO "ENTER : Putting PCIe%d device into reset\n", pdev->id); |
| |
| if (!comcerto_pcie_device_reset(pp)) { |
| int ii = 10; |
| |
| /* Wait for link_req_rst_not, to be de-asseted */ |
| while (ii--) { |
| |
| if (!(readl( pp->va_app_base + pp->app_regs->sts0 ) & STS0_LINK_REQ_RST_NOT)) { |
| printk(KERN_INFO "%s : (PCIe%d) link_req_rst_not is de-asseted\n", __func__, pp->port); |
| break; |
| } |
| |
| udelay(1000); |
| } |
| |
| if (ii == 10) |
| printk(KERN_WARNING "%s : (PCIe%d) link_req_rst_not is not de-asseted \n", __func__, pp->port); |
| |
| pp->reset = 1; |
| /* Disable LTSSM and initiate linkdown reset */ |
| writel((readl(pp->va_app_base + pp->app_regs->cfg5) & |
| ~(CFG5_LTSSM_ENABLE)) | CFG5_LINK_DOWN_RST, |
| pp->va_app_base + pp->app_regs->cfg5); |
| udelay(1000); |
| } |
| |
| printk(KERN_INFO "Disabling PCIe%d Controler Clock\n", pdev->id); |
| |
| if (pcie_port[pdev->id].port_mode != PCIE_PORT_MODE_NONE) |
| clk_disable(pcie_port[pdev->id].ref_clock); |
| |
| printk(KERN_INFO "EXIT : Putting PCIe%d device into reset\n", pdev->id); |
| } |
| else { |
| |
| printk(KERN_INFO "ENTER: Bringing PCIe%d device outof reset\n", pdev->id); |
| |
| printk(KERN_INFO "Enabling PCIe%d Controler Clock\n", pdev->id); |
| |
| if(pcie_port[pdev->id].port_mode != PCIE_PORT_MODE_NONE) { |
| rc = clk_enable(pcie_port[pdev->id].ref_clock); |
| if (rc) |
| pr_err("%s: PCIe%d clock enable failed\n", __func__, pdev->id); |
| } |
| |
| if (!comcerto_pcie_device_reset_exit(pp)) { |
| pp->reset = 0; |
| } |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(device_reset, 0644, comcerto_pcie_show_reset, comcerto_pcie_set_reset); |
| |
| |
| static ssize_t comcerto_pcie_serdes_pd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct pcie_port *pp = &pcie_port[pdev->id]; |
| int reset = 0, rc; |
| |
| sscanf(buf, "%d", &reset); |
| |
| reset = reset ? 1:0; |
| |
| if (reset) { |
| printk(KERN_INFO "%s: Putting Serdes to Low Power and CMU Power Off\n", __func__); |
| |
| if (pdev->id) |
| writel(readl(USBPHY_SERDES_STAT_BASE+0x44) | ((0x3 << 2)|(0x1 << 7)) , USBPHY_SERDES_STAT_BASE+0x44); |
| else |
| writel(readl(USBPHY_SERDES_STAT_BASE+0x34) | ((0x3 << 2)|(0x1 << 7)) , USBPHY_SERDES_STAT_BASE+0x34); |
| } else { |
| printk(KERN_INFO "%s: Getting Serdes out of Low Power and CMU Power On\n", __func__); |
| |
| if (pdev->id) |
| writel(readl(USBPHY_SERDES_STAT_BASE+0x44) & ~((0x3 << 2)|(0x1 << 7)) , USBPHY_SERDES_STAT_BASE+0x44); |
| else |
| writel(readl(USBPHY_SERDES_STAT_BASE+0x34) & ~((0x3 << 2)|(0x1 << 7)) , USBPHY_SERDES_STAT_BASE+0x34); |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(serdes_pd, 0644, NULL, comcerto_pcie_serdes_pd); |
| |
| |
| #ifdef CONFIG_PM |
| static int no_irq_resume ; |
| |
| static int comcerto_pcie_suspend(struct device *dev) |
| { |
| unsigned int val, i; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| printk(KERN_INFO "%s: pcie device %p (id = %d):\n", |
| __func__, pdev, pdev->id); |
| |
| /* Check for the BitMask bit for PCIe, if it is enabled |
| * then we are not going suspend the PCIe device , as by |
| * this device , we will wake from System Resume. |
| */ |
| if ( !(host_utilpe_shared_pmu_bitmask & PCIe0_IRQ) || !(host_utilpe_shared_pmu_bitmask & PCIe1_IRQ) ){ |
| /* We will Just return from here */ |
| return 0; |
| } |
| if (pcie_port[pdev->id].port_mode != PCIE_PORT_MODE_NONE) { |
| if (comcerto_pcie_link_up(&pcie_port[pdev->id])){ |
| /* Enable PME to root Port */ |
| comcerto_dbi_read_reg(&pcie_port[pdev->id], (PCI_CAP_PM + PCI_PM_CTRL), 4, &val); |
| comcerto_dbi_write_reg(&pcie_port[pdev->id], (PCI_CAP_PM + PCI_PM_CTRL), 4, val | PCI_PM_CTRL_STATE_MASK); |
| /* Required PM Delay */ |
| for (i = 0 ; i < 40 ; i++) |
| udelay(500); |
| } |
| } |
| no_irq_resume =0 ; |
| return 0; |
| } |
| |
| static int comcerto_pcie_resume(struct device *dev) |
| { |
| unsigned int val, i; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| printk(KERN_INFO "%s: pcie device %p (id = %d)\n", |
| __func__, pdev, pdev->id); |
| /* Check for the Bit_Mask bit for PCIe, if it is enabled |
| * then we are not going suspend the PCIe device , as by |
| * this device , we will wake from System Resume. |
| */ |
| if ( !(host_utilpe_shared_pmu_bitmask & PCIe0_IRQ) || !(host_utilpe_shared_pmu_bitmask & PCIe1_IRQ) ){ |
| |
| /* We will Just return |
| */ |
| return 0; |
| } |
| if( no_irq_resume == 0) |
| { |
| if(pcie_port[pdev->id].port_mode != PCIE_PORT_MODE_NONE) { |
| if (comcerto_pcie_link_up(&pcie_port[pdev->id])){ |
| /* Put In D0 State */ |
| comcerto_dbi_read_reg(&pcie_port[pdev->id], (PCI_CAP_PM + PCI_PM_CTRL), 4, &val); |
| comcerto_dbi_write_reg(&pcie_port[pdev->id], (PCI_CAP_PM + PCI_PM_CTRL), 4, val & (~PCI_PM_CTRL_STATE_MASK)); |
| |
| /* Required PM Delay */ |
| for (i = 0 ; i < 40 ; i++) |
| udelay(500); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int comcerto_pcie_noirq_resume(struct device *dev) |
| { |
| int val,i; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| printk(KERN_INFO "%s: pcie device %p (id = %d)\n", |
| __func__, pdev, pdev->id); |
| |
| /* Check for the Bit_Mask bit for PCIe, if it is enabled |
| * then we are not going suspend the PCIe device , as by |
| * this device , we will wake from System Resume. |
| */ |
| if ( !(host_utilpe_shared_pmu_bitmask & PCIe0_IRQ) || !(host_utilpe_shared_pmu_bitmask & PCIe1_IRQ) ){ |
| /* We will Just return |
| */ |
| return 0; |
| } |
| if(pcie_port[pdev->id].port_mode != PCIE_PORT_MODE_NONE) { |
| if (comcerto_pcie_link_up(&pcie_port[pdev->id])){ |
| /* Put In D0 State */ |
| comcerto_dbi_read_reg(&pcie_port[pdev->id], (PCI_CAP_PM + PCI_PM_CTRL), 4, &val); |
| comcerto_dbi_write_reg(&pcie_port[pdev->id], (PCI_CAP_PM + PCI_PM_CTRL), 4, val & (~PCI_PM_CTRL_STATE_MASK)); |
| /* Required PM Delay */ |
| for (i = 0 ; i < 40 ; i++) |
| udelay(500); |
| } |
| } |
| return 0; |
| } |
| |
| static const struct dev_pm_ops pcie_platform_pm_ops = { |
| .suspend = comcerto_pcie_suspend, |
| .resume = comcerto_pcie_resume, |
| .resume_noirq = comcerto_pcie_noirq_resume, |
| }; |
| |
| |
| static struct platform_driver comcerto_pcie_driver = { |
| .driver = { |
| .name = "pcie", |
| .pm = &pcie_platform_pm_ops, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static struct platform_device pcie_pwr0 = { |
| .name = "pcie", |
| .id = 0, |
| }; |
| |
| static struct platform_device pcie_pwr1 = { |
| .name = "pcie", |
| .id = 1, |
| }; |
| #endif |
| |
| static void comcerto_serdes_set_polarity(struct serdes_regs_s *p_pcie_phy_reg_file, int current_polarity) |
| { |
| switch(current_polarity) |
| { |
| case 0: |
| p_pcie_phy_reg_file[0x73].val = 0x0; |
| p_pcie_phy_reg_file[0x75].val = 0x0; |
| break; |
| case 1: |
| p_pcie_phy_reg_file[0x73].val = 0x8; |
| p_pcie_phy_reg_file[0x75].val = 0x2; |
| break; |
| case 2: |
| p_pcie_phy_reg_file[0x73].val = 0x0; |
| p_pcie_phy_reg_file[0x75].val = 0x2; |
| break; |
| case 3: |
| p_pcie_phy_reg_file[0x73].val = 0x8; |
| p_pcie_phy_reg_file[0x75].val = 0x0; |
| break; |
| } |
| |
| } |
| |
| static int comcerto_pcie_bsp_link_init(struct pcie_port *pp, int nr, struct serdes_regs_s *p_pcie_phy_reg_file, int serdes_reg_size, int current_polarity) |
| { |
| int axi_pcie_component; |
| int pcie_component; |
| int serdes_component; |
| int rc; |
| int if_err = 1; |
| |
| comcerto_serdes_set_polarity(p_pcie_phy_reg_file, current_polarity); |
| |
| if(nr == 0) |
| { |
| axi_pcie_component = COMPONENT_AXI_PCIE0; |
| pcie_component = COMPONENT_SERDES_PCIE0; |
| serdes_component = COMPONENT_SERDES0; |
| } |
| else |
| { |
| axi_pcie_component = COMPONENT_AXI_PCIE1; |
| pcie_component = COMPONENT_SERDES_PCIE1; |
| serdes_component = COMPONENT_SERDES1; |
| } |
| |
| //Bring serdes out of reset |
| c2000_block_reset(axi_pcie_component,0); |
| c2000_block_reset(serdes_component,0); |
| |
| /* SW select for ck_soc_div_i SOC clock */ |
| writel(0xFF3C, COMCERTO_SERDES_DWC_CFG_REG( nr, SD_PHY_CTRL3_REG_OFST )); |
| writel(readl(COMCERTO_SERDES_DWC_CFG_REG( nr, SD_PHY_CTRL2_REG_OFST )) & ~0x3, |
| COMCERTO_SERDES_DWC_CFG_REG( nr, SD_PHY_CTRL2_REG_OFST )); |
| |
| rc = clk_enable(pp->ref_clock); |
| if (rc){ |
| pr_err("%s: PCIe%d clock enable failed\n", __func__, nr); |
| goto err1; |
| } |
| |
| /* Enable the PCIE_OCC clock */ |
| #if defined(CONFIG_COMCERTO_PCIE_OCC_CLOCK) |
| rc = clk_enable(pp->occ_clock); |
| if (rc){ |
| pr_err("%s: PCIe_occ clock enable failed\n", __func__); |
| goto err_occ_clock; |
| } |
| #endif |
| |
| /* Serdes Initialization. */ |
| if( serdes_phy_init(nr, p_pcie_phy_reg_file, |
| serdes_reg_size/ sizeof(serdes_regs_t), |
| SD_DEV_TYPE_PCIE) ) |
| { |
| pp->port_mode = PCIE_PORT_MODE_NONE; |
| pr_err("%s: Failed to initialize serdes (%d)\n", __func__, nr ); |
| goto err_phy_link; |
| } |
| |
| mdelay(1); //After CMU locks wait for sometime |
| |
| #if defined(CONFIG_C2K_MFCN_EVM) |
| if(nr == 0){ |
| GPIO_reset_external_device(COMPONENT_PCIE0,0); |
| }else{ |
| GPIO_reset_external_device(COMPONENT_PCIE1,0); |
| } |
| |
| mdelay(1); |
| #endif |
| //Bring PCIe out of reset |
| c2000_block_reset(pcie_component,0); |
| |
| //Hold the LTSSM in detect state |
| writel(readl(pp->va_app_base + pp->app_regs->cfg5) & ~CFG5_LTSSM_ENABLE, |
| pp->va_app_base + pp->app_regs->cfg5); |
| |
| comcerto_pcie_rc_init(pp); |
| |
| //Enable LTSSM to start link initialization |
| writel(readl(pp->va_app_base + pp->app_regs->cfg5) | (CFG5_APP_INIT_RST | CFG5_LTSSM_ENABLE), |
| pp->va_app_base + pp->app_regs->cfg5); |
| |
| pp->link_state = comcerto_pcie_link_up( &pcie_port[nr] ); |
| |
| if(!pp->link_state) |
| { |
| if_err = 0; |
| goto err_phy_link; |
| } |
| |
| return 0; |
| |
| err_phy_link: |
| clk_disable(pp->ref_clock); |
| clk_put(pp->ref_clock); |
| #if defined(CONFIG_COMCERTO_PCIE_OCC_CLOCK) |
| err_occ_clock: |
| clk_disable(pp->occ_clock); |
| clk_put(pp->occ_clock); |
| #endif |
| |
| err1: |
| //Put all to reset |
| c2000_block_reset(axi_pcie_component,1); |
| c2000_block_reset(serdes_component,1); |
| c2000_block_reset(pcie_component,1); |
| |
| if(if_err) |
| return -1; |
| else |
| return 0; |
| |
| } |
| |
| static int comcerto_pcie_bsp_init(struct pcie_port *pp, int nr) |
| { |
| struct serdes_regs_s *p_pcie_phy_reg_file; |
| int serdes_regs_size; |
| int polarity_max = 4; |
| int polarity; |
| int ret; |
| |
| |
| if (nr >= NUM_PCIE_PORTS) { |
| printk("%s : Invalid PCIe port number\n", __func__); |
| goto err0; |
| } |
| |
| ret = pcie_app_init(pp, nr, CFG0_DEV_TYPE_RC); |
| if(ret == -1) |
| goto err0;; |
| |
| pp->port_mode = pcie_port_get_mode(nr); |
| |
| if(pcie_external_clk) |
| { |
| p_pcie_phy_reg_file = &pcie_phy_reg_file_100[0]; |
| serdes_regs_size = sizeof(pcie_phy_reg_file_100); |
| } |
| else |
| { |
| if(HAL_get_ref_clk() == REF_CLK_24MHZ) |
| { |
| p_pcie_phy_reg_file = &pcie_phy_reg_file_24[0]; |
| serdes_regs_size = sizeof(pcie_phy_reg_file_24); |
| printk(KERN_INFO "PCIe: Ref clk 24Mhz\n"); |
| } |
| else |
| { |
| p_pcie_phy_reg_file = &pcie_phy_reg_file_48[0]; |
| serdes_regs_size = sizeof(pcie_phy_reg_file_48); |
| printk(KERN_INFO "PCIe: Ref clk 48Mhz\n"); |
| } |
| } |
| |
| if (system_rev == 1) { |
| // C2K RevA1 devices use a different serdes clk divider |
| p_pcie_phy_reg_file[0x61].val = 0x6; |
| } |
| |
| for(polarity = 0 ; polarity < polarity_max; polarity++) |
| { |
| |
| ret = comcerto_pcie_bsp_link_init(pp, nr, p_pcie_phy_reg_file, serdes_regs_size, polarity); |
| if(ret == -1) |
| goto err1;; |
| |
| if (pp->link_state) |
| goto linkup; |
| |
| if(!pcie_port_is_host(nr)) |
| return 0; //Endpoint, so no need to change polarity |
| |
| } |
| |
| printk(KERN_INFO "PCIe%d: Link Up Failed \n",nr); |
| |
| err1: |
| iounmap(pp->va_cfg0_base); |
| iounmap(pp->va_cfg1_base); |
| pp->port_mode = PCIE_PORT_MODE_NONE; |
| err0: |
| return -1; |
| |
| linkup: |
| printk(KERN_INFO "PCIe%d: Link Up Success \n",nr); |
| printk(KERN_INFO "PCIe%d: Polarity: %d Gen1 mode: %d External Clk: %d\n",nr, polarity, pcie_gen1_only,pcie_external_clk); |
| if (pcie_port_is_host(nr)) |
| comcerto_pcie_rc_int_init(pp); |
| return 0; |
| |
| } |
| |
| static int comcerto_pcie_abort_handler(unsigned long addr, unsigned int fsr, |
| struct pt_regs *regs) |
| { |
| static DEFINE_RATELIMIT_STATE(rs, 5 * HZ, 5); |
| if (__ratelimit(&rs)) { |
| pr_err("PCIe external abort detected at 0x%08lx\n", addr); |
| } else { |
| /* |
| * Things are getting out of control. Give up before we |
| * just freeze the CPU and can't report about it. |
| */ |
| panic("PCIe: too many external aborts in a short time"); |
| } |
| return 0; |
| } |
| |
| |
| static int __init comcerto_pcie_init(void) |
| { |
| struct pcie_port *pp; |
| int i, rc; |
| int num_pcie_port = 1; |
| struct pci_dev *pdev = NULL; |
| |
| |
| pp = &pcie_port[0]; |
| comcerto_pcie_bsp_init(pp, 0); |
| |
| if ( (NUM_PCIE_PORTS == 2) && |
| !(readl(COMCERTO_GPIO_SYSTEM_CONFIG) & BOOT_SERDES1_CNF_SATA0) ) |
| { |
| num_pcie_port = 2; |
| pp = &pcie_port[1]; |
| comcerto_pcie_bsp_init(pp, 1); |
| } |
| |
| comcerto_pcie.nr_controllers = num_pcie_port; |
| |
| pcibios_min_io = iATU_GET_IO_BASE(COMCERTO_AXI_PCIe0_SLAVE_BASE); |
| pcibios_min_mem = COMCERTO_AXI_PCIe0_SLAVE_BASE; |
| pci_add_flags(PCI_REASSIGN_ALL_RSRC); |
| |
| hook_fault_code(16 + 6, comcerto_pcie_abort_handler, SIGBUS, 0, "imprecise external abort"); |
| |
| pci_common_init(&comcerto_pcie); |
| |
| for ( i = 0; i < num_pcie_port; i++ ) |
| { |
| |
| if (!pcie_port_is_host(i) || !(pcie_port[i].link_state)) |
| continue; |
| |
| while((pdev = pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, pdev))) { |
| if (pcie_port[i].root_bus_nr == pdev->bus->number) { |
| if( (rc = pcie_get_readrq(pdev)) > 512 ) { |
| printk(KERN_WARNING "PCIe%d Device rdreq size (%d) is more than supported\n", i, rc); |
| pcie_set_readrq(pdev, 512); |
| } |
| } |
| } |
| } |
| |
| |
| #ifdef CONFIG_PM |
| platform_device_register(&pcie_pwr0); |
| |
| if (device_create_file(&pcie_pwr0.dev, &dev_attr_device_reset)) |
| printk(KERN_ERR "%s: Unable to create pcie0 reset sysfs entry\n", __func__); |
| |
| if (device_create_file(&pcie_pwr0.dev, &dev_attr_serdes_pd)) |
| printk(KERN_ERR "%s: Unable to create pcie0 serdes_pd sysfs entry\n", __func__); |
| |
| if(num_pcie_port > 1) { |
| platform_device_register(&pcie_pwr1); |
| |
| if (device_create_file(&pcie_pwr1.dev, &dev_attr_device_reset)) |
| printk(KERN_ERR "%s: Unable to create pcie1 reset sysfs entry\n", __func__); |
| |
| if (device_create_file(&pcie_pwr1.dev, &dev_attr_serdes_pd)) |
| printk(KERN_ERR "%s: Unable to create pcie1 serdes_pd sysfs entry\n", __func__); |
| } |
| |
| platform_driver_register(&comcerto_pcie_driver); |
| #endif |
| |
| return 0; |
| } |
| subsys_initcall(comcerto_pcie_init); |
| |
| |