mirror of
https://gitlab.rtems.org/rtems/rtos/rtems.git
synced 2026-09-20 18:28:50 +08:00
558 lines
15 KiB
C
558 lines
15 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
|
|
/*
|
|
* AMBA Plug & Play routines
|
|
*
|
|
* COPYRIGHT (c) 2011.
|
|
* Aeroflex Gaisler.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <grlib/ambapp.h>
|
|
#include <bsp.h>
|
|
|
|
#include <grlib/grlib_impl.h>
|
|
|
|
#define AMBA_CONF_AREA 0xff000
|
|
#define AMBA_AHB_SLAVE_CONF_AREA ( 1 << 11 )
|
|
#define AMBA_APB_SLAVES 16
|
|
|
|
/* Allocate one AMBA device */
|
|
static struct ambapp_dev *ambapp_alloc_dev_struct(
|
|
const struct ambapp_context *ctx,
|
|
int dev_type
|
|
)
|
|
{
|
|
struct ambapp_dev *dev;
|
|
size_t size = sizeof( *dev );
|
|
|
|
if ( dev_type == DEV_APB_SLV ) {
|
|
size += sizeof( struct ambapp_apb_info );
|
|
} else {
|
|
size += sizeof( struct ambapp_ahb_info ); /* AHB */
|
|
}
|
|
dev = ( *ctx->alloc )( size );
|
|
if ( dev != NULL ) {
|
|
dev = memset( dev, 0, size );
|
|
dev->dev_type = dev_type;
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static void ambapp_ahb_dev_init(
|
|
unsigned int ioarea,
|
|
struct ambapp_mmap *mmaps,
|
|
struct ambapp_pnp_ahb *ahb,
|
|
struct ambapp_dev *dev,
|
|
int ahbidx
|
|
)
|
|
{
|
|
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_TO_AHB( dev );
|
|
ahb_info->common.ver = ambapp_pnp_ver( ahb->id );
|
|
ahb_info->common.irq = ambapp_pnp_irq( ahb->id );
|
|
ahb_info->common.ahbidx = ahbidx;
|
|
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 ];
|
|
|
|
/* 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;
|
|
ahb_info->type[ bar ] = ambapp_pnp_mbar_type( mbar );
|
|
}
|
|
}
|
|
|
|
static void ambapp_apb_dev_init(
|
|
unsigned int base,
|
|
struct ambapp_mmap *mmaps,
|
|
struct ambapp_pnp_apb *apb,
|
|
struct ambapp_dev *dev,
|
|
int ahbidx
|
|
)
|
|
{
|
|
(void) mmaps;
|
|
|
|
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_TO_APB( dev );
|
|
apb_info->common.ver = ambapp_pnp_ver( apb->id );
|
|
apb_info->common.irq = ambapp_pnp_irq( apb->id );
|
|
apb_info->common.ahbidx = ahbidx;
|
|
apb_info->start = ambapp_pnp_apb_start( apb->iobar, base );
|
|
apb_info->mask = ambapp_pnp_apb_mask( apb->iobar );
|
|
}
|
|
|
|
static int ambapp_add_ahbbus( struct ambapp_bus *abus, unsigned int ioarea )
|
|
{
|
|
int i;
|
|
for ( i = 0; i < AHB_BUS_MAX; i++ ) {
|
|
if ( abus->ahbs[ i ].ioarea == 0 ) {
|
|
abus->ahbs[ i ].ioarea = ioarea;
|
|
return i;
|
|
} else if ( abus->ahbs[ i ].ioarea == ioarea ) {
|
|
/* Bus already added */
|
|
return -1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Internal AMBA Scanning Function */
|
|
static int ambapp_scan2(
|
|
struct ambapp_bus *abus,
|
|
unsigned int ioarea,
|
|
const struct ambapp_context *ctx,
|
|
struct ambapp_dev *parent,
|
|
struct ambapp_dev **root
|
|
)
|
|
{
|
|
struct ambapp_pnp_ahb *ahb, ahb_buf;
|
|
struct ambapp_pnp_apb *apb, apb_buf;
|
|
struct ambapp_dev *dev, *prev, *prevapb, *apbdev;
|
|
struct ambapp_ahb_info *ahb_info;
|
|
int maxloops = 64;
|
|
unsigned int apbbase, bridge_adr;
|
|
int i, j, ahbidx;
|
|
|
|
*root = NULL;
|
|
|
|
if ( parent ) {
|
|
/* scan first bus for 64 devices, rest for 16 devices */
|
|
maxloops = 16;
|
|
}
|
|
|
|
ahbidx = ambapp_add_ahbbus( abus, ioarea );
|
|
if ( ahbidx < 0 ) {
|
|
/* Bus already scanned, stop */
|
|
return 0;
|
|
}
|
|
|
|
prev = parent;
|
|
|
|
/* AHB MASTERS */
|
|
ahb = (struct ambapp_pnp_ahb *) (uintptr_t) ( ioarea | AMBA_CONF_AREA );
|
|
for ( i = 0; i < maxloops; i++, ahb++ ) {
|
|
( *ctx->copy_from_device )(
|
|
&ahb_buf,
|
|
ahb,
|
|
sizeof( struct ambapp_pnp_ahb ),
|
|
abus
|
|
);
|
|
if ( ahb_buf.id == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
/* An AHB device present here */
|
|
dev = ambapp_alloc_dev_struct( ctx, DEV_AHB_MST );
|
|
if ( !dev ) {
|
|
return -1;
|
|
}
|
|
|
|
ambapp_ahb_dev_init( ioarea, abus->mmaps, &ahb_buf, dev, ahbidx );
|
|
|
|
if ( *root == NULL ) {
|
|
*root = dev;
|
|
}
|
|
|
|
if ( prev != parent ) {
|
|
prev->next = dev;
|
|
}
|
|
dev->prev = prev;
|
|
prev = dev;
|
|
}
|
|
|
|
/* AHB SLAVES */
|
|
ahb = (struct ambapp_pnp_ahb *) (uintptr_t) ( ioarea | AMBA_CONF_AREA |
|
|
AMBA_AHB_SLAVE_CONF_AREA );
|
|
for ( i = 0; i < maxloops; i++, ahb++ ) {
|
|
( *ctx->copy_from_device )(
|
|
&ahb_buf,
|
|
ahb,
|
|
sizeof( struct ambapp_pnp_ahb ),
|
|
abus
|
|
);
|
|
if ( ahb_buf.id == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
/* An AHB device present here */
|
|
dev = ambapp_alloc_dev_struct( ctx, DEV_AHB_SLV );
|
|
if ( !dev ) {
|
|
return -1;
|
|
}
|
|
|
|
ambapp_ahb_dev_init( ioarea, abus->mmaps, &ahb_buf, dev, ahbidx );
|
|
|
|
if ( *root == NULL ) {
|
|
*root = dev;
|
|
}
|
|
|
|
if ( prev != parent ) {
|
|
prev->next = dev;
|
|
}
|
|
dev->prev = prev;
|
|
prev = dev;
|
|
|
|
ahb_info = DEV_TO_AHB( dev );
|
|
|
|
/* Is it a AHB/AHB Bridge ? */
|
|
if (
|
|
( ( dev->device == GAISLER_AHB2AHB ) &&
|
|
( dev->vendor == VENDOR_GAISLER ) && ( ahb_info->common.ver > 0 ) ) ||
|
|
( ( dev->device == GAISLER_L2CACHE ) &&
|
|
( dev->vendor == VENDOR_GAISLER ) ) ||
|
|
( ( dev->device == GAISLER_GRIOMMU ) &&
|
|
( dev->vendor == VENDOR_GAISLER ) )
|
|
) {
|
|
/* AHB/AHB Bridge Found, recurse down the
|
|
* Bridge
|
|
*/
|
|
if ( ahb_info->custom[ 1 ] != 0 ) {
|
|
bridge_adr = ambapp_addr_from( abus->mmaps, ahb_info->custom[ 1 ] );
|
|
/* Scan next bus if not already scanned */
|
|
if ( ambapp_scan2( abus, bridge_adr, ctx, dev, &dev->children ) ) {
|
|
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;
|
|
apbbase = ahb_info->start[ 0 ];
|
|
|
|
/* APB SLAVES */
|
|
apb = (struct ambapp_pnp_apb *) (uintptr_t) ( apbbase | AMBA_CONF_AREA );
|
|
for ( j = 0; j < AMBA_APB_SLAVES; j++, apb++ ) {
|
|
( *ctx->copy_from_device )( &apb_buf, apb, sizeof( *apb ), abus );
|
|
if ( apb_buf.id == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
apbdev = ambapp_alloc_dev_struct( ctx, DEV_APB_SLV );
|
|
if ( !apbdev ) {
|
|
return -1;
|
|
}
|
|
|
|
ambapp_apb_dev_init( apbbase, abus->mmaps, &apb_buf, apbdev, ahbidx );
|
|
|
|
if ( prevapb != dev ) {
|
|
prevapb->next = apbdev;
|
|
} else {
|
|
dev->children = apbdev;
|
|
}
|
|
apbdev->prev = prevapb;
|
|
prevapb = apbdev;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remember first AHB MST/SLV device on bus and Parent Bridge */
|
|
abus->ahbs[ ahbidx ].dev = *root;
|
|
abus->ahbs[ ahbidx ].bridge = parent;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *ambapp_memcpy(
|
|
void *dest,
|
|
const void *src,
|
|
int n,
|
|
struct ambapp_bus *abus RTEMS_UNUSED
|
|
)
|
|
{
|
|
return memcpy( dest, src, n );
|
|
}
|
|
|
|
static const struct ambapp_context default_ctx = {
|
|
.copy_from_device = ambapp_memcpy,
|
|
.alloc = rtems_malloc
|
|
};
|
|
|
|
/* Build AMBA Plug & Play device graph */
|
|
int ambapp_scan(
|
|
struct ambapp_bus *abus,
|
|
unsigned int ioarea,
|
|
const struct ambapp_context *ctx,
|
|
struct ambapp_mmap *mmaps
|
|
)
|
|
{
|
|
memset( abus, 0, sizeof( *abus ) );
|
|
abus->mmaps = mmaps;
|
|
|
|
if ( ctx == NULL ) {
|
|
ctx = &default_ctx;
|
|
}
|
|
|
|
return ambapp_scan2( abus, ioarea, ctx, NULL, &abus->root );
|
|
}
|
|
|
|
/* Match search options againt device */
|
|
static int ambapp_dev_match_options(
|
|
struct ambapp_dev *dev,
|
|
unsigned int options,
|
|
int vendor,
|
|
int device
|
|
)
|
|
{
|
|
if (
|
|
( ( ( options & ( OPTIONS_ALL_DEVS ) ) == OPTIONS_ALL_DEVS ) || /* 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 ) ) && /* VENDOR/DEV ID */
|
|
( ( device == -1 ) || ( device == dev->device ) ) &&
|
|
( ( ( options & OPTIONS_ALL ) == OPTIONS_ALL ) || /* 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 *dev,
|
|
unsigned int options,
|
|
int vendor,
|
|
int device,
|
|
ambapp_func_t func,
|
|
void *arg
|
|
)
|
|
{
|
|
int index, ret;
|
|
struct ambapp_dev *apbslv;
|
|
|
|
ret = 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 ) {
|
|
ret = func( apbslv, index, arg );
|
|
if ( ret != 0 ) {
|
|
break; /* Signalled stopped */
|
|
}
|
|
}
|
|
index++;
|
|
apbslv = apbslv->next;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Traverse the prescanned device information */
|
|
static int ambapp_for_each_dev(
|
|
struct ambapp_dev *root,
|
|
unsigned int options,
|
|
int vendor,
|
|
int device,
|
|
ambapp_func_t func,
|
|
void *arg
|
|
)
|
|
{
|
|
struct ambapp_dev *dev;
|
|
int ahb_slave = 0;
|
|
int index, ret;
|
|
|
|
/* 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 */
|
|
ret = func( dev, index, arg );
|
|
if ( ret != 0 ) {
|
|
return ret; /* Signalled stopped */
|
|
}
|
|
}
|
|
|
|
if (
|
|
( options & OPTIONS_DEPTH_FIRST ) && ( options & OPTIONS_APB_SLVS )
|
|
) {
|
|
/* Check is APB bridge, and process all APB
|
|
* Slaves in that case
|
|
*/
|
|
ret = ambapp_for_each_apb( dev, options, vendor, device, func, arg );
|
|
if ( ret != 0 ) {
|
|
return ret; /* Signalled stopped */
|
|
}
|
|
}
|
|
|
|
if ( options & OPTIONS_DEPTH_FIRST ) {
|
|
if ( dev->children && ( dev->children->dev_type != DEV_APB_SLV ) ) {
|
|
/* Found AHB Bridge, recurse */
|
|
ret = ambapp_for_each_dev(
|
|
dev->children,
|
|
options,
|
|
vendor,
|
|
device,
|
|
func,
|
|
arg
|
|
);
|
|
if ( ret != 0 ) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
*/
|
|
ret = ambapp_for_each_apb( dev, options, vendor, device, func, arg );
|
|
if ( ret != 0 ) {
|
|
return ret; /* 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 */
|
|
ret = ambapp_for_each_dev(
|
|
dev->children,
|
|
options,
|
|
vendor,
|
|
device,
|
|
func,
|
|
arg
|
|
);
|
|
if ( ret != 0 ) {
|
|
return ret;
|
|
}
|
|
}
|
|
dev = dev->next;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ambapp_for_each(
|
|
struct ambapp_bus *abus,
|
|
unsigned int options,
|
|
int vendor,
|
|
int device,
|
|
ambapp_func_t func,
|
|
void *arg
|
|
)
|
|
{
|
|
return ambapp_for_each_dev( abus->root, options, vendor, device, func, arg );
|
|
}
|