/* GRLIB PCIF PCI HOST driver. * * COPYRIGHT (c) 2008. * Cobham Gaisler AB. * * Configures the PCIF core and initialize, * - the PCI Library (pci.c) * - the general part of the PCI Bus driver (pci_bus.c) * * System interrupt assigned to PCI interrupt (INTA#..INTD#) is by * default taken from Plug and Play, but may be overridden by the * driver resources INTA#..INTD#. * * The license and distribution terms for this file may be * found in found in the file LICENSE in this distribution or at * http://www.rtems.com/license/LICENSE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Configuration options */ #define SYSTEM_MAINMEM_START 0x40000000 /* Interrupt assignment. Set to other value than 0xff in order to * override defaults and plug&play information */ #ifndef PCIF_INTA_SYSIRQ #define PCIF_INTA_SYSIRQ 0xff #endif #ifndef PCIF_INTB_SYSIRQ #define PCIF_INTB_SYSIRQ 0xff #endif #ifndef PCIF_INTC_SYSIRQ #define PCIF_INTC_SYSIRQ 0xff #endif #ifndef PCIF_INTD_SYSIRQ #define PCIF_INTD_SYSIRQ 0xff #endif /*#define DEBUG 1 */ #ifdef DEBUG #define DBG(x...) printk(x) #else #define DBG(x...) #endif /* * Bit encode for PCI_CONFIG_HEADER_TYPE register */ struct pcif_regs { volatile unsigned int bars[4]; /* 0x00-0x10 */ volatile unsigned int bus; /* 0x10 */ volatile unsigned int map_io; /* 0x14 */ volatile unsigned int status; /* 0x18 */ volatile unsigned int intr; /* 0x1c */ int unused[(0x40-0x20)/4]; /* 0x20-0x40 */ volatile unsigned int maps[(0x80-0x40)/4]; /* 0x40-0x80*/ }; /* Used internally for accessing the PCI bridge's configuration space itself */ #define HOST_TGT PCI_DEV(0xff, 0, 0) struct pcif_priv *pcifpriv = NULL; static int pcif_minor = 0; /* PCI Interrupt assignment. Connects an PCI interrupt pin (INTA#..INTD#) * to a system interrupt number. */ unsigned char pcif_pci_irq_table[4] = { /* INTA# */ PCIF_INTA_SYSIRQ, /* INTB# */ PCIF_INTB_SYSIRQ, /* INTC# */ PCIF_INTC_SYSIRQ, /* INTD# */ PCIF_INTD_SYSIRQ }; /* Driver private data struture */ struct pcif_priv { struct drvmgr_dev *dev; struct pcif_regs *regs; int irq; int minor; int irq_mask; unsigned int pci_area; unsigned int pci_area_end; unsigned int pci_io; unsigned int pci_conf; unsigned int pci_conf_end; uint32_t devVend; /* Host PCI Vendor/Device ID */ uint32_t bar1_size; struct drvmgr_map_entry maps_up[2]; struct drvmgr_map_entry maps_down[2]; struct pcibus_config config; }; int pcif_init1(struct drvmgr_dev *dev); int pcif_init3(struct drvmgr_dev *dev); /* PCIF DRIVER */ struct drvmgr_drv_ops pcif_ops = { .init = {pcif_init1, NULL, pcif_init3, NULL}, .remove = NULL, .info = NULL }; struct amba_dev_id pcif_ids[] = { {VENDOR_GAISLER, GAISLER_PCIF}, {0, 0} /* Mark end of table */ }; struct amba_drv_info pcif_info = { { DRVMGR_OBJ_DRV, /* Driver */ NULL, /* Next driver */ NULL, /* Device list */ DRIVER_AMBAPP_GAISLER_PCIF_ID, /* Driver ID */ "PCIF_DRV", /* Driver Name */ DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */ &pcif_ops, NULL, /* Funcs */ 0, /* No devices yet */ sizeof(struct pcif_priv), /* Let drvmgr alloc private */ }, &pcif_ids[0] }; void pcif_register_drv(void) { DBG("Registering PCIF driver\n"); drvmgr_drv_register(&pcif_info.general); } int pcif_cfg_r32(pci_dev_t dev, int ofs, uint32_t *val) { struct pcif_priv *priv = pcifpriv; volatile uint32_t *pci_conf; uint32_t devfn; int retval; int bus = PCI_DEV_BUS(dev); if (ofs & 3) return PCISTS_EINVAL; if (PCI_DEV_SLOT(dev) > 15) { *val = 0xffffffff; return PCISTS_OK; } /* PCIF can access "non-standard" devices on bus0 (on AD11.AD16), * but we skip them. */ if (dev == HOST_TGT) bus = devfn = 0; if (bus == 0) devfn = PCI_DEV_DEVFUNC(dev) + PCI_DEV(0, 6, 0); else devfn = PCI_DEV_DEVFUNC(dev); /* Select bus */ priv->regs->bus = bus << 16; pci_conf = (volatile uint32_t *)(priv->pci_conf | (devfn << 8) | ofs); *val = *pci_conf; if (priv->regs->status & 0x30000000) { *val = 0xffffffff; retval = PCISTS_MSTABRT; } else retval = PCISTS_OK; DBG("pci_read: [%x:%x:%x] reg: 0x%x => addr: 0x%x, val: 0x%x\n", PCI_DEV_EXPAND(dev), ofs, pci_conf, *val); return retval; } int pcif_cfg_r16(pci_dev_t dev, int ofs, uint16_t *val) { uint32_t v; int retval; if (ofs & 1) return PCISTS_EINVAL; retval = pcif_cfg_r32(dev, ofs & ~0x3, &v); *val = 0xffff & (v >> (8*(ofs & 0x3))); return retval; } int pcif_cfg_r8(pci_dev_t dev, int ofs, uint8_t *val) { uint32_t v; int retval; retval = pcif_cfg_r32(dev, ofs & ~0x3, &v); *val = 0xff & (v >> (8*(ofs & 3))); return retval; } int pcif_cfg_w32(pci_dev_t dev, int ofs, uint32_t val) { struct pcif_priv *priv = pcifpriv; volatile uint32_t *pci_conf; uint32_t devfn; int bus = PCI_DEV_BUS(dev); if (ofs & ~0xfc) return PCISTS_EINVAL; if (PCI_DEV_SLOT(dev) > 15) return PCISTS_MSTABRT; /* PCIF can access "non-standard" devices on bus0 (on AD11.AD16), * but we skip them. */ if (dev == HOST_TGT) bus = devfn = 0; if (bus == 0) devfn = PCI_DEV_DEVFUNC(dev) + PCI_DEV(0, 6, 0); else devfn = PCI_DEV_DEVFUNC(dev); /* Select bus */ priv->regs->bus = bus << 16; pci_conf = (volatile uint32_t *)(priv->pci_conf | (devfn << 8) | ofs); *pci_conf = val; DBG("pci_write - [%x:%x:%x] reg: 0x%x => addr: 0x%x, val: 0x%x\n", PCI_DEV_EXPAND(dev), ofs, pci_conf, value); return PCISTS_OK; } int pcif_cfg_w16(pci_dev_t dev, int ofs, uint16_t val) { uint32_t v; int retval; if (ofs & 1) return PCISTS_EINVAL; retval = pcif_cfg_r32(dev, ofs & ~0x3, &v); if (retval != PCISTS_OK) return retval; v = (v & ~(0xffff << (8*(ofs&3)))) | ((0xffff&val) << (8*(ofs&3))); return pcif_cfg_w32(dev, ofs & ~0x3, v); } int pcif_cfg_w8(pci_dev_t dev, int ofs, uint8_t val) { uint32_t v; int retval; retval = pcif_cfg_r32(dev, ofs & ~0x3, &v); if (retval != PCISTS_OK) return retval; v = (v & ~(0xff << (8*(ofs&3)))) | ((0xff&val) << (8*(ofs&3))); return pcif_cfg_w32(dev, ofs & ~0x3, v); } /* Return the assigned system IRQ number that corresponds to the PCI * "Interrupt Pin" information from configuration space. * * The IRQ information is stored in the pcif_pci_irq_table configurable * by the user. * * Returns the "system IRQ" for the PCI INTA#..INTD# pin in irq_pin. Returns * 0xff if not assigned. */ uint8_t pcif_bus0_irq_map(pci_dev_t dev, int irq_pin) { uint8_t sysIrqNr = 0; /* not assigned */ int irq_group; if ( (irq_pin >= 1) && (irq_pin <= 4) ) { /* Use default IRQ decoding on PCI BUS0 according slot numbering */ irq_group = PCI_DEV_SLOT(dev) & 0x3; irq_pin = ((irq_pin - 1) + irq_group) & 0x3; /* Valid PCI "Interrupt Pin" number */ sysIrqNr = pcif_pci_irq_table[irq_pin]; } return sysIrqNr; } int pcif_translate(uint32_t *address, int type, int dir) { /* No address translation implmented at this point */ return 0; } extern struct pci_memreg_ops pci_memreg_sparc_be_ops; /* PCIF Big-Endian PCI access routines */ struct pci_access_drv pcif_access_drv = { .cfg = { pcif_cfg_r8, pcif_cfg_r16, pcif_cfg_r32, pcif_cfg_w8, pcif_cfg_w16, pcif_cfg_w32, }, .io = /* PCIF only supports Big-endian */ { _ld8, _ld_be16, _ld_be32, _st8, _st_be16, _st_be32, }, .memreg = &pci_memreg_sparc_be_ops, .translate = pcif_translate, }; /* Initializes the PCIF core hardware * */ int pcif_hw_init(struct pcif_priv *priv) { struct pcif_regs *regs; uint32_t data, size; int mst; pci_dev_t host = HOST_TGT; regs = priv->regs; /* Mask PCI interrupts */ regs->intr = 0; /* Get the PCIF Host PCI ID */ pcif_cfg_r32(host, PCI_VENDOR_ID, &priv->devVend); /* set 1:1 mapping between AHB -> PCI memory space, for all Master cores */ for ( mst=0; mst<16; mst++) { regs->maps[mst] = priv->pci_area; /* Check if this register is implemented */ if ( regs->maps[mst] != priv->pci_area ) break; } /* and map system RAM at pci address SYSTEM_MAINMEM_START. This way * PCI targets can do DMA directly into CPU main memory. */ regs->bars[0] = SYSTEM_MAINMEM_START; regs->bars[1] = 0; regs->bars[2] = 0; regs->bars[3] = 0; /* determine size of target BAR1 */ pcif_cfg_w32(host, PCI_BASE_ADDRESS_1, 0xffffffff); pcif_cfg_r32(host, PCI_BASE_ADDRESS_1, &size); priv->bar1_size = (~(size & ~0xf)) + 1; pcif_cfg_w32(host, PCI_BASE_ADDRESS_0, 0); pcif_cfg_w32(host, PCI_BASE_ADDRESS_1, SYSTEM_MAINMEM_START); pcif_cfg_w32(host, PCI_BASE_ADDRESS_2, 0); pcif_cfg_w32(host, PCI_BASE_ADDRESS_3, 0); pcif_cfg_w32(host, PCI_BASE_ADDRESS_4, 0); pcif_cfg_w32(host, PCI_BASE_ADDRESS_5, 0); /* set as bus master and enable pci memory responses */ pcif_cfg_r32(host, PCI_COMMAND, &data); data |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); pcif_cfg_w32(host, PCI_COMMAND, data); /* Successful */ return 0; } /* Initializes the PCIF core and driver, must be called before calling init_pci() * * Return values * 0 Successful initalization * -1 Error during initialization, for example "PCI core not found". * -2 Error PCI controller not HOST (targets not supported) * -3 Error due to PCIF hardware initialization * -4 Error registering driver to PCI layer */ int pcif_init(struct pcif_priv *priv) { struct ambapp_apb_info *apb; struct ambapp_ahb_info *ahb; int pin; union drvmgr_key_value *value; char keyname[6]; struct amba_dev_info *ainfo = priv->dev->businfo; /* Find PCI core from Plug&Play information */ apb = ainfo->info.apb_slv; ahb = ainfo->info.ahb_slv; /* Found PCI core, init private structure */ priv->irq = apb->irq; priv->regs = (struct pcif_regs *)apb->start; /* Calculate the PCI windows * AMBA->PCI Window: AHB SLAVE AREA0 * AMBA->PCI I/O cycles Window: AHB SLAVE AREA1 Lower half * AMBA->PCI Configuration cycles Window: AHB SLAVE AREA1 Upper half */ priv->pci_area = ahb->start[0]; priv->pci_area_end = ahb->start[0] + ahb->mask[0]; priv->pci_io = ahb->start[1]; priv->pci_conf = ahb->start[1] + (ahb->mask[1] >> 1); priv->pci_conf_end = ahb->start[1] + ahb->mask[1]; /* On systems where PCI I/O area and configuration area is apart of the "PCI Window" * the PCI Window stops at the start of the PCI I/O area */ if ( (priv->pci_io > priv->pci_area) && (priv->pci_io < (priv->pci_area_end-1)) ) { priv->pci_area_end = priv->pci_io; } /* Init PCI interrupt assignment table to all use the interrupt routed through * the PCIF core. */ strcpy(keyname, "INTX#"); for (pin=1; pin<5; pin++) { if ( pcif_pci_irq_table[pin-1] == 0xff ) { pcif_pci_irq_table[pin-1] = priv->irq; /* User may override Plug & Play IRQ */ keyname[3] = 'A' + (pin-1); value = drvmgr_dev_key_get(priv->dev, keyname, KEY_TYPE_INT); if ( value ) pcif_pci_irq_table[pin-1] = value->i; } } priv->irq_mask = 0xf; value = drvmgr_dev_key_get(priv->dev, "", KEY_TYPE_INT); if ( value ) priv->irq_mask = value->i & 0xf; /* This driver only support HOST systems, we check for HOST */ if ( priv->regs->status & 0x00000001 ) { /* Target not supported */ return -2; } /* Init the PCI Core */ if ( pcif_hw_init(priv) ) { return -3; } /* Down streams translation table */ priv->maps_down[0].name = "AMBA -> PCI MEM Window"; priv->maps_down[0].size = priv->pci_area_end - priv->pci_area; priv->maps_down[0].from_adr = (void *)priv->pci_area; priv->maps_down[0].to_adr = (void *)priv->pci_area; /* End table */ priv->maps_down[1].size = 0; /* Up streams translation table */ priv->maps_up[0].name = "Target BAR1 -> AMBA"; priv->maps_up[0].size = priv->bar1_size; priv->maps_up[0].from_adr = (void *)SYSTEM_MAINMEM_START; priv->maps_up[0].to_adr = (void *)SYSTEM_MAINMEM_START; /* End table */ priv->maps_up[1].size = 0; return 0; } /* Called when a core is found with the AMBA device and vendor ID * given in pcif_ids[]. */ int pcif_init1(struct drvmgr_dev *dev) { struct pcif_priv *priv; struct pci_auto_setup pcif_auto_cfg; DBG("PCIF[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name); if ( pcif_minor != 0 ) { printf("Driver only supports one PCI core\n"); return DRVMGR_FAIL; } priv = dev->priv; if ( !priv ) return DRVMGR_NOMEM; dev->priv = priv; priv->dev = dev; priv->minor = pcif_minor++; pcifpriv = priv; if ( pcif_init(priv) ) { printf("Failed to initialize PCIF driver\n"); free(priv); dev->priv = NULL; return DRVMGR_FAIL; } /* Host is always Big-Endian */ pci_endian = PCI_BIG_ENDIAN; /* Register the PCI core at the PCI layer */ if (pci_access_drv_register(&pcif_access_drv)) { /* Access routines registration failed */ return DRVMGR_FAIL; } /* Prepare memory MAP */ pcif_auto_cfg.options = 0; pcif_auto_cfg.mem_start = 0; pcif_auto_cfg.mem_size = 0; pcif_auto_cfg.memio_start = priv->pci_area; pcif_auto_cfg.memio_size = priv->pci_area_end - priv->pci_area; pcif_auto_cfg.io_start = priv->pci_io; pcif_auto_cfg.io_size = priv->pci_conf - priv->pci_io; pcif_auto_cfg.irq_map = pcif_bus0_irq_map; pcif_auto_cfg.irq_route = NULL; /* use standard routing */ pci_config_register(&pcif_auto_cfg); if (pci_config_init()) { /* PCI configuration failed */ return DRVMGR_FAIL; } priv->config.maps_down = &priv->maps_down[0]; priv->config.maps_up = &priv->maps_up[0]; return pcibus_register(dev, &priv->config); } int pcif_init3(struct drvmgr_dev *dev) { struct pcif_priv *priv = dev->priv; /* Unmask all interrupts, on some sytems this * might be problematic because all PCI IRQs are * not connected on the PCB or used for something * else. The irqMask driver resource can be used to * control which PCI IRQs are used to generate the * PCI system IRQ, example: * * 0xf - enable all (DEFAULT) * 0x8 - enable one PCI irq * * Before unmasking PCI IRQ, all PCI boards must * have been initialized and IRQ turned off to avoid * system hang. */ priv->regs->intr = priv->irq_mask; return DRVMGR_OK; }