/* * Copyright (c) 2011 Aeroflex Gaisler * * BSD license: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #undef AMBA_TYPE_AHBIO_ADDR #include #include #define AMBA_CONF_AREA 0xff000 #define AMBA_AHB_SLAVE_CONF_AREA (1 << 11) #define AMBA_APB_SLAVES 16 #define DPRINTF(p) printf p /* Allocate */ struct ambapp_dev_hdr * ambapp_alloc_dev_struct (int dev_type) { int size = sizeof (struct ambapp_dev_hdr); struct ambapp_dev_hdr *dev; if (dev_type == DEV_APB_SLV) { size += sizeof (struct ambapp_apb_info); } else { /* AHB */ size += sizeof (struct ambapp_ahb_info); } dev = malloc (size); if (dev == NULL) return NULL; memset (dev, 0, size); dev->devinfo = (void *) (dev + 1); dev->dev_type = dev_type; return dev; } unsigned int ambapp_addr_from (struct ambapp_mmap *mmaps, unsigned int address) { /* no translation? */ if (!mmaps) return address; while (mmaps->size) { if ((address >= mmaps->remote_adr) && (address <= (mmaps->remote_adr + (mmaps->size - 1)))) { return (address - mmaps->remote_adr) + mmaps->local_adr; } mmaps++; } return 1; } void ambapp_ahb_dev_init (unsigned int ioarea, struct ambapp_mmap *mmaps, struct ambapp_pnp_ahb *ahb, struct ambapp_dev_hdr *dev) { int bar; struct ambapp_ahb_info *ahb_info; unsigned int addr, mask, mbar; /* Setup device struct */ dev->vendor = ambapp_pnp_vendor (ahb->id); dev->device = ambapp_pnp_device (ahb->id); ahb_info = dev->devinfo; ahb_info->ver = ambapp_pnp_ver (ahb->id); ahb_info->irq = ambapp_pnp_irq (ahb->id); ahb_info->custom[0] = (unsigned int) ahb->custom[0]; ahb_info->custom[1] = (unsigned int) ahb->custom[1]; ahb_info->custom[2] = (unsigned int) ahb->custom[2]; DPRINTF (("+AHB device %d:%d\n", dev->device, dev->vendor)); /* Memory BARs */ for (bar = 0; bar < 4; bar++) { mbar = ahb->mbar[bar]; if (mbar == 0) { addr = 0; mask = 0; } else { addr = ambapp_pnp_start (mbar); if (ambapp_pnp_mbar_type (mbar) == AMBA_TYPE_AHBIO) { /* AHB I/O area is releative IO_AREA */ addr = AMBA_TYPE_AHBIO_ADDR (addr, ioarea); mask = (((unsigned int) (ambapp_pnp_mbar_mask ((~mbar)) << 8) | 0xff)) + 1; } else { /* AHB memory area, absolute address */ addr = ambapp_addr_from (mmaps, addr); mask = (~((unsigned int) (ambapp_pnp_mbar_mask (mbar) << 20))) + 1; } } ahb_info->start[bar] = addr; ahb_info->mask[bar] = mask; } } void ambapp_apb_dev_init (unsigned int base, struct ambapp_mmap *mmaps, struct ambapp_pnp_apb *apb, struct ambapp_dev_hdr *dev) { struct ambapp_apb_info *apb_info; /* Setup device struct */ dev->vendor = ambapp_pnp_vendor (apb->id); dev->device = ambapp_pnp_device (apb->id); apb_info = dev->devinfo; apb_info->ver = ambapp_pnp_ver (apb->id); apb_info->irq = ambapp_pnp_irq (apb->id); apb_info->start = ambapp_pnp_apb_start (apb->iobar, base); apb_info->mask = ambapp_pnp_apb_mask (apb->iobar); DPRINTF (("+APB device %d:%d\n", dev->device, dev->vendor)); } #define MAX_NUM_BUSES 16 void ambapp_add_scanned_bus (unsigned int *ioareas, unsigned int ioarea) { int i; for (i = 0; i < MAX_NUM_BUSES; i++) { if (ioareas[i] == 0) { ioareas[i] = ioarea; return; } } } int ambapp_has_been_scanned (unsigned int *ioareas, unsigned int ioarea) { int i; if (!ioareas) return 0; for (i = 0; i < MAX_NUM_BUSES; i++) { if (ioareas[i] == 0) { break; } else if (ioareas[i] == ioarea) { return 1; } } return 0; } int ambapp_scan (unsigned int ioarea, struct ambapp_dev_hdr *parent, struct ambapp_mmap *mmaps, void *(*memfunc) (void *dest, const void *src, int n), struct ambapp_dev_hdr **root, void *internal) { struct ambapp_pnp_ahb *ahb, ahb_buf; struct ambapp_pnp_apb *apb, apb_buf; struct ambapp_dev_hdr *dev, *prev, *prevapb, *apbdev; struct ambapp_ahb_info *ahb_info; int maxloops = 64; unsigned int apbbase, bridge_address; int i, j; DPRINTF (("Scan at 0x%08x\n", ioarea)); /* Default to memcpy() */ if (!memfunc) memfunc = (void *(*)(void *dest, const void *src, int n)) memcpy; *root = NULL; if (parent) { /* scan first bus for 64 devices, rest for 16 devices */ maxloops = 16; } else { DPRINTF (("+(malloc:")); internal = malloc (sizeof (unsigned int) * MAX_NUM_BUSES); DPRINTF (("0x%x)\n", internal)); if (!internal) return -1; memset (internal, 0, sizeof (unsigned int) * MAX_NUM_BUSES); ambapp_add_scanned_bus (internal, ioarea); } prev = parent; /* AHB MASTERS */ ahb = (struct ambapp_pnp_ahb *) (ioarea | AMBA_CONF_AREA); for (i = 0; i < maxloops; i++) { memfunc (&ahb_buf, ahb, sizeof (struct ambapp_pnp_ahb)); if (ahb_buf.id != 0) { /* A AHB device present here */ dev = ambapp_alloc_dev_struct (DEV_AHB_MST); if (!dev) return -1; ambapp_ahb_dev_init (ioarea, mmaps, &ahb_buf, dev); if (*root == NULL) *root = dev; if (prev != parent) prev->next = dev; dev->prev = prev; prev = dev; } ahb++; } /* AHB SLAVES */ ahb = (struct ambapp_pnp_ahb *) (ioarea | AMBA_CONF_AREA | AMBA_AHB_SLAVE_CONF_AREA); for (i = 0; i < maxloops; i++) { memfunc (&ahb_buf, ahb, sizeof (struct ambapp_pnp_ahb)); if (ahb_buf.id != 0) { /* A AHB device present here */ dev = ambapp_alloc_dev_struct (DEV_AHB_SLV); if (!dev) return -1; ambapp_ahb_dev_init (ioarea, mmaps, &ahb_buf, dev); if (prev != parent) prev->next = dev; dev->prev = prev; prev = dev; /* Is it a AHB/AHB Bridge ? */ if ((dev->device == GAISLER_AHB2AHB) && (dev->vendor == VENDOR_GAISLER)) { /* AHB/AHB Bridge Found, recurse down the Bridge */ ahb_info = dev->devinfo; if (ahb_info->ver) { bridge_address = ambapp_addr_from (mmaps, ahb_info->custom[1]); DPRINTF (("+(AHBAHB:0x%x)\n", bridge_address)); /* Makes sure bus only scanned once */ if (ambapp_has_been_scanned (internal, bridge_address) == NULL) { ambapp_add_scanned_bus (internal, bridge_address); if (ambapp_scan (bridge_address, dev, mmaps, memfunc, &dev->children, internal)) return -1; } } } else if ((dev->device == GAISLER_APBMST) && (dev->vendor == VENDOR_GAISLER)) { /* AHB/APB Bridge Found, add the APB devices to this AHB Slave's children */ prevapb = dev; ahb_info = dev->devinfo; apbbase = ahb_info->start[0]; apb = (struct ambapp_pnp_apb *) (apbbase | AMBA_CONF_AREA); for (j = 0; j < AMBA_APB_SLAVES; j++) { memfunc (&apb_buf, apb, sizeof (struct ambapp_pnp_apb)); if (apb_buf.id) { apbdev = ambapp_alloc_dev_struct (DEV_APB_SLV); if (!dev) return -1; ambapp_apb_dev_init (apbbase, mmaps, &apb_buf, apbdev); if (prevapb != dev) prevapb->next = apbdev; else dev->children = apbdev; apbdev->prev = prevapb; prevapb = apbdev; } apb++; } } } ahb++; } if (parent == NULL) { free (internal); } return 0; } /* Match search options againt device */ int ambapp_dev_match_options (struct ambapp_dev_hdr *dev, unsigned int options, int vendor, int device) { if ((((options & (OPTIONS_ALL_DEVS)) == OPTIONS_ALL_DEVS) || /* Match TYPE */ ((options & OPTIONS_AHB_MSTS) && (dev->dev_type == DEV_AHB_MST)) || ((options & OPTIONS_AHB_SLVS) && (dev->dev_type == DEV_AHB_SLV)) || ((options & OPTIONS_APB_SLVS) && (dev->dev_type == DEV_APB_SLV))) && ((vendor == -1) || (vendor == dev->vendor)) && /* Match ID */ ((device == -1) || (device == dev->device)) && (((options & OPTIONS_ALL) == OPTIONS_ALL) || /* Match Allocated State */ ((options & OPTIONS_FREE) && DEV_IS_FREE (dev)) || ((options & OPTIONS_ALLOCATED) && DEV_IS_ALLOCATED (dev)))) { return 1; } return 0; } /* If device is an APB bridge all devices on the APB bridge is processed */ static int ambapp_for_each_apb (struct ambapp_dev_hdr *dev, unsigned int options, int vendor, int device, int maxdepth, ambapp_func_t func, void *arg) { int index; struct ambapp_dev_hdr *apbslv; if (maxdepth < 0) return 0; if (dev->children && (dev->children->dev_type == DEV_APB_SLV)) { /* Found a APB Bridge */ index = 0; apbslv = dev->children; while (apbslv) { if (ambapp_dev_match_options (apbslv, options, vendor, device) == 1) { if (func (apbslv, index, maxdepth, arg) == 1) return 1; /* Signalled stopped */ } index++; apbslv = apbslv->next; } } return 0; } /* Traverse the prescanned device information */ int ambapp_for_each (struct ambapp_dev_hdr *root, unsigned int options, int vendor, int device, int maxdepth, ambapp_func_t func, void *arg) { struct ambapp_dev_hdr *dev; int ahb_slave = 0; int index; if (maxdepth < 0) return 0; /* Start at device 'root' and process downwards. * * Breadth first search, search order * 1. AHB MSTS * 2. AHB SLVS * 3. APB SLVS on primary bus * 4. AHB/AHB secondary... -> step to 1. */ /* AHB MST / AHB SLV */ if (options & (OPTIONS_AHB_MSTS | OPTIONS_AHB_SLVS | OPTIONS_DEPTH_FIRST)) { index = 0; dev = root; while (dev) { if ((dev->dev_type == DEV_AHB_SLV) && !ahb_slave) { /* First AHB Slave */ ahb_slave = 1; index = 0; } /* Conditions must be fullfilled for function to be called */ if (ambapp_dev_match_options (dev, options, vendor, device) == 1) { /* Correct device and vendor ID */ if (func (dev, index, maxdepth, arg) == 1) return 1; /* Signalled stopped */ } if ((options & OPTIONS_DEPTH_FIRST) && (options & OPTIONS_APB_SLVS)) { /* Check is APB bridge, and process all APB Slaves in that case */ if (ambapp_for_each_apb (dev, options, vendor, device, (maxdepth - 1), func, arg) == 1) return 1; /* Signalled stopped */ } if (options & OPTIONS_DEPTH_FIRST) { if (dev->children && (dev->children->dev_type != DEV_APB_SLV)) { /* Found AHB Bridge, recurse */ if (ambapp_for_each (dev->children, options, vendor, device, (maxdepth - 1), func, arg) == 1) return 1; } } index++; dev = dev->next; } } /* Find APB Bridges */ if ((options & OPTIONS_APB_SLVS) && !(options & OPTIONS_DEPTH_FIRST)) { dev = root; while (dev) { /* Check is APB bridge, and process all APB Slaves in that case */ if (ambapp_for_each_apb (dev, options, vendor, device, (maxdepth - 1), func, arg) == 1) return 1; /* Signalled stopped */ dev = dev->next; } } /* Find AHB Bridges */ if (!(options & OPTIONS_DEPTH_FIRST)) { dev = root; while (dev) { if (dev->children && (dev->children->dev_type != DEV_APB_SLV)) { /* Found AHB Bridge, recurse */ if (ambapp_for_each (dev->children, options, vendor, device, (maxdepth - 1), func, arg) == 1) return 1; } dev = dev->next; } } return 0; } int ambapp_alloc_dev (struct ambapp_dev_hdr *dev, void *owner) { if (dev->owner) return -1; dev->owner = owner; return 0; } void ambapp_free_dev (struct ambapp_dev_hdr *dev) { dev->owner = NULL; } struct ambapp_dev_find_match_arg { int index; int count; int type; void *dev; }; /* AMBA PP find routines */ int ambapp_dev_find_match (struct ambapp_dev_hdr *dev, int index, int maxdepth, void *arg) { struct ambapp_dev_find_match_arg *p = arg; if (p->index == 0) { /* Found controller, stop */ if (p->type == DEV_APB_SLV) { *(struct ambapp_apb_info *) p->dev = *(struct ambapp_apb_info *) dev->devinfo; p->dev = ((struct ambapp_apb_info *) p->dev) + 1; } else { *(struct ambapp_ahb_info *) p->dev = *(struct ambapp_ahb_info *) dev->devinfo; p->dev = ((struct ambapp_ahb_info *) p->dev) + 1; } p->count--; if (p->count < 1) return 1; } else { p->index--; } return 0; } int ambapp_find_apbslvs_next (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_apb_info *dev, int index, int maxno) { struct ambapp_dev_find_match_arg arg; arg.index = index; arg.count = maxno; arg.type = DEV_APB_SLV; /* APB */ arg.dev = dev; ambapp_for_each (root, (OPTIONS_ALL | OPTIONS_APB_SLVS), vendor, device, 10, ambapp_dev_find_match, &arg); return maxno - arg.count; } int ambapp_find_apbslv (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_apb_info *dev) { return ambapp_find_apbslvs_next (root, vendor, device, dev, 0, 1); } int ambapp_find_apbslv_next (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_apb_info *dev, int index) { return ambapp_find_apbslvs_next (root, vendor, device, dev, index, 1); } int ambapp_find_apbslvs (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_apb_info *dev, int maxno) { return ambapp_find_apbslvs_next (root, vendor, device, dev, 0, maxno); } int ambapp_find_ahbslvs_next (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_ahb_info *dev, int index, int maxno) { struct ambapp_dev_find_match_arg arg; arg.index = index; arg.count = maxno; arg.type = DEV_AHB_SLV; /* AHB SLV */ arg.dev = dev; ambapp_for_each (root, (OPTIONS_ALL | OPTIONS_AHB_SLVS), vendor, device, 10, ambapp_dev_find_match, &arg); return maxno - arg.count; } int ambapp_find_ahbslv_next (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_ahb_info *dev, int index) { return ambapp_find_ahbslvs_next (root, vendor, device, dev, index, 1); } int ambapp_find_ahbslv (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_ahb_info *dev) { return ambapp_find_ahbslvs_next (root, vendor, device, dev, 0, 1); } int ambapp_find_ahbslvs (struct ambapp_dev_hdr *root, int vendor, int device, struct ambapp_ahb_info *dev, int maxno) { return ambapp_find_ahbslvs_next (root, vendor, device, dev, 0, maxno); } struct ambapp_dev_hdr * ambapp_find_parent (struct ambapp_dev_hdr *dev) { while (dev->prev) { if (dev == dev->prev->children) { return dev->prev; } dev = dev->prev; } return NULL; } struct ambapp_dev_hdr *ambapp_root = NULL; extern unsigned int console; extern unsigned int rtc; void pnpinit (void) { struct ambapp_apb_info dev; int n; ambapp_scan (LEON3_IO_AREA, NULL, NULL, NULL, &ambapp_root, NULL); if ((n = ambapp_find_apbslv (ambapp_root, VENDOR_GAISLER, GAISLER_APBUART, &dev)) == 1) { console = dev.start; DPRINTF (("Found abuart at 0x%x\n", console)); } if ((n = ambapp_find_apbslv (ambapp_root, VENDOR_GAISLER, GAISLER_GPTIMER, &dev)) == 1) { rtc = dev.start + 0x10; DPRINTF (("Found rtc at 0x%x\n", rtc)); } }