| /* |
| * 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 <asm-leon/amba.h> |
| #undef AMBA_TYPE_AHBIO_ADDR |
| #include <asm-leon/lambapp.h> |
| #include <string.h> |
| |
| #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)); |
| } |
| } |