diff options
Diffstat (limited to 'bsps/sparc/shared/pci/pcif.c')
-rw-r--r-- | bsps/sparc/shared/pci/pcif.c | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/bsps/sparc/shared/pci/pcif.c b/bsps/sparc/shared/pci/pcif.c new file mode 100644 index 0000000000..c0b8fc9629 --- /dev/null +++ b/bsps/sparc/shared/pci/pcif.c @@ -0,0 +1,586 @@ +/* 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.org/license/LICENSE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libcpu/byteorder.h> +#include <libcpu/access.h> +#include <rtems/bspIo.h> +#include <pci.h> +#include <pci/cfg.h> + +#include <drvmgr/drvmgr.h> +#include <drvmgr/ambapp_bus.h> +#include <ambapp.h> +#include <drvmgr/pci_bus.h> +#include <bsp/pcif.h> + + +/* 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); +} + +static 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; + else 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; +} + +static 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; +} + +static 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; +} + +static 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; + else 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; +} + +static 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); +} + +static 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. + */ +static 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; +} + +static 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 + * + */ +static 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, PCIR_VENDOR, &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, PCIR_BAR(1), 0xffffffff); + pcif_cfg_r32(host, PCIR_BAR(1), &size); + priv->bar1_size = (~(size & ~0xf)) + 1; + + pcif_cfg_w32(host, PCIR_BAR(0), 0); + pcif_cfg_w32(host, PCIR_BAR(1), SYSTEM_MAINMEM_START); + pcif_cfg_w32(host, PCIR_BAR(2), 0); + pcif_cfg_w32(host, PCIR_BAR(3), 0); + pcif_cfg_w32(host, PCIR_BAR(4), 0); + pcif_cfg_w32(host, PCIR_BAR(5), 0); + + /* set as bus master and enable pci memory responses */ + pcif_cfg_r32(host, PCIR_COMMAND, &data); + data |= (PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); + pcif_cfg_w32(host, PCIR_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 + */ +static 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, DRVMGR_KT_INT); + if ( value ) + pcif_pci_irq_table[pin-1] = value->i; + } + } + + priv->irq_mask = 0xf; + value = drvmgr_dev_key_get(priv->dev, "", DRVMGR_KT_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; +} |