Files
rtems/bsps/shared/grlib/amba/ambapp.c
T

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 );
}