/*******************************************************************************
 * Agere Systems Inc.
 * Wireless device driver for Linux (wlags49).
 *
 * Copyright (c) 1998-2003 Agere Systems Inc. 
 * All rights reserved.
 *   http://www.agere.com
 *
 * Initially developed by TriplePoint, Inc.
 *   http://www.triplepoint.com
 *
 *------------------------------------------------------------------------------
 *
 *   This file contains processing and initialization specific to Card Services
 *   devices (PCMCIA, CF).
 *
 *------------------------------------------------------------------------------
 *
 * SOFTWARE LICENSE
 *
 * This software is provided subject to the following terms and conditions,
 * which you should read carefully before using the software.  Using this
 * software indicates your acceptance of these terms and conditions.  If you do
 * not agree with these terms and conditions, do not use the software.
 *
 * Copyright © 2003 Agere Systems Inc.
 * All rights reserved.
 *
 * Redistribution and use in source or binary forms, with or without
 * modifications, are permitted provided that the following conditions are met:
 *
 * . Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following Disclaimer as comments in the code as
 *    well as in the documentation and/or other materials provided with the
 *    distribution.
 * 
 * . 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.
 * 
 * . Neither the name of Agere Systems Inc. nor the names of the contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * Disclaimer
 *
 * THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, INFRINGEMENT AND THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  ANY
 * USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE IS SOLELY AT THE USERS OWN
 * RISK. IN NO EVENT SHALL AGERE SYSTEMS INC. 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, INCLUDING, BUT NOT LIMITED TO, 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.
 *
 ******************************************************************************/




/*******************************************************************************
 * VERSION CONTROL INFORMATION
 *******************************************************************************
 *
 * $Author: vjs $
 * $Date: 2003/09/05 15:38:49 $
 * $Revision: 1.17 $
 * $Source: /cvsroot/wifi/wl_lkm/wireless/wl_cs.c,v $
 *
 ******************************************************************************/




/*******************************************************************************
 *  include files
 ******************************************************************************/
#include <wireless/wl_version.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/bitops.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/ioport.h>

#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ciscode.h>
#include <pcmcia/ds.h>
#include <hcf/debug.h>

#include <hcf.h>
#include <dhf.h>
#include <hcfdef.h>

#include <wireless/wl_if.h>
#include <wireless/wl_internal.h>
#include <wireless/wl_util.h>
#include <wireless/wl_main.h>
#include <wireless/wl_netdev.h>
#include <wireless/wl_cs.h>




/*******************************************************************************
 *  macro definitions
 ******************************************************************************/
#define CS_CHECK(fn, args...) \
    while ((last_ret = CardServices(last_fn = (fn), args)) != 0) goto cs_failed




/*******************************************************************************
 *  global definitions
 ******************************************************************************/
#if DBG
extern dbg_info_t *DbgInfo;
#endif  /* DBG */

static dev_info_t  dev_info = MODULE_NAME;
static dev_link_t *dev_list = NULL;




/*******************************************************************************
 *	wl_adapter_attach()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Creates an instance of the driver, allocating local data structures for
 *  one device. The device is registered with Card Services.
 *
 *  PARAMETERS:
 *
 *      none
 *
 *  RETURNS:
 *
 *      pointer to an allocated dev_link_t structure
 *      NULL on failure
 *
 ******************************************************************************/
dev_link_t *wl_adapter_attach( void )
{
    int                 i;
    int                 ret;
    client_reg_t        clientReg;
    dev_link_t          *link;
    struct net_device   *dev;
    p_s8                *irq_list = NULL;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_attach" );
    DBG_ENTER( DbgInfo );


    /* Create new ethernet device */
    link = kmalloc( sizeof( struct dev_link_t ), GFP_KERNEL);

    if( link == NULL )
    {
        DBG_ERROR( DbgInfo, "kmalloc failed, link is NULL\n" );
        DBG_LEAVE( DbgInfo );
        return link;
    }

    memset( link, 0, sizeof( struct dev_link_t ));

    link->release.function  = &wl_adapter_release;
    link->release.data      = (u_long)link;
    link->io.NumPorts1      = HCF_NUM_IO_PORTS;
    link->io.Attributes1    = IO_DATA_PATH_WIDTH_16;
    link->io.IOAddrLines    = 6;
    link->irq.Attributes    = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT;
    link->irq.IRQInfo1      = IRQ_INFO2_VALID | IRQ_LEVEL_ID;

    
    irq_list = wl_get_irq_list( );
    
    if( irq_list == NULL )
    {
        DBG_ERROR( DbgInfo, "irq_list is NULL\n" );
        goto error;
    }

    if( irq_list[0] == -1 )
    {
        link->irq.IRQInfo2 = wl_get_irq_mask( );
    }
    else
    {   
        for( i = 0; i < NELEM( irq_list ); i++ )
        {
            link->irq.IRQInfo2 |= 1 << irq_list[i];
        }
    }


    link->irq.Handler       = &wl_isr;
    link->conf.Attributes   = CONF_ENABLE_IRQ;
    link->conf.Vcc          = 50;
    link->conf.IntType      = INT_MEMORY_AND_IO;
    link->conf.ConfigIndex  = 5;
    link->conf.Present      = PRESENT_OPTION;


    dev = wl_device_alloc( );

    if( dev == NULL )
    {
        DBG_ERROR( DbgInfo, "wl_device_alloc returned NULL\n" );
        goto error;
    }

    link->priv = link->irq.Instance = dev;

    /* Add the new instance to our list of active devices */
    link->next = dev_list;
    dev_list = link;


    /* Register with Card Services */
    clientReg.dev_info              = &dev_info;
    clientReg.Attributes            = INFO_IO_CLIENT | INFO_CARD_SHARE;
    clientReg.EventMask             = 
#if DBG
                                      CS_EVENT_REGISTRATION_COMPLETE |
#endif /* DBG */                          
                                      CS_EVENT_CARD_INSERTION | 
                                      CS_EVENT_CARD_REMOVAL |
                                      CS_EVENT_RESET_PHYSICAL | 
                                      CS_EVENT_CARD_RESET |
                                      CS_EVENT_PM_SUSPEND | 
                                      CS_EVENT_PM_RESUME;
        
    clientReg.event_handler         = &wl_adapter_event;
    clientReg.Version               = 0x0210;

    clientReg.event_callback_args.client_data = link;


    ret = CardServices( RegisterClient, &link->handle, &clientReg );

    if( ret != CS_SUCCESS )
    {
        DBG_ERROR( DbgInfo, "CardServices RegisterClient failed!\n" );
        cs_error( link->handle, RegisterClient, ret );
        goto error;
    }

    DBG_LEAVE( DbgInfo );
    return link;

error:
    wl_adapter_detach( link );

    link = NULL;

    DBG_LEAVE( DbgInfo );
    return link;
}
/*============================================================================*/





/*******************************************************************************
 *	wl_adapter_detach()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      This deletes a driver "instance". The device is de-registered with Card 
 *  Services. If it has been released, then the net device is unregistered, and 
 *  all local data structures are freed. Otherwise, the structures will be  
 *  freed when the device is released.
 *
 *  PARAMETERS:
 *
 *      link    - pointer to the dev_link_t structure representing the device to
 *                detach
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_adapter_detach( dev_link_t *link )
{
    struct net_device   *dev;
    dev_link_t          **linkp;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_detach" );
    DBG_ENTER( DbgInfo );
    DBG_PARAM( DbgInfo, "link", "0x%p", link );


    dev = ( struct net_device *)link->priv;

    /* Locate device structure */
    for ( linkp = &dev_list; *linkp; linkp = &( *linkp )->next )
    {
        if( *linkp == link )
            break;
    }

    if( *linkp == NULL )
    {
        DBG_LEAVE( DbgInfo );
        return;
    }

    if( link->state & DEV_RELEASE_PENDING )
    {
        del_timer( &link->release );
        link->state &= ~DEV_RELEASE_PENDING;
    }

    if( link->state & DEV_CONFIG )
    {
        wl_adapter_release( (u_long)link );
        if( link->state & DEV_STALE_CONFIG )
        {
            link->state |= DEV_STALE_LINK;

            DBG_LEAVE( DbgInfo );
            return;
        }
    }

    if( link->handle )
    {
        CardServices( DeregisterClient, link->handle );
    }

    /* Unlink device structure, free bits */
    *linkp = link->next;

    if( dev != NULL )
    {
        wl_device_dealloc( dev );
    }

    if( link->dev )
    {
        link->dev = NULL;
    }

    kfree( link );

    DBG_LEAVE( DbgInfo );
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_release()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      After a card is removed, this routine will release the PCMCIA 
 *  configuration. If the device is still open, this will be postponed until it
 *  is closed.
 *
 *  PARAMETERS:
 *
 *      arg - a u_long representing a pointer to a dev_link_t structure for the
 *            device to be released.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_adapter_release( u_long arg )
{
    dev_link_t          *link = (dev_link_t *)arg;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_release" );
    DBG_ENTER( DbgInfo );
    DBG_PARAM( DbgInfo, "arg", "0x%08lx", arg );


    if( link->open )
    {
        DBG_TRACE( DbgInfo, MODULE_NAME " release postponed, '%s' still open\n",
                   link->dev->dev_name );

        link->state |= DEV_STALE_CONFIG;

        DBG_LEAVE( DbgInfo );
        return;
    }

    /* This call will make sure wl_remove() is called (and subsequently,
       unregister_netdev) */
    wl_release( link->priv );

    CardServices( ReleaseConfiguration, link->handle );
    CardServices( ReleaseIO, link->handle, &( link->io ));
    CardServices( ReleaseIRQ, link->handle, &( link->irq ));

    link->state &= ~DEV_CONFIG;

    if( link->state & DEV_STALE_LINK )
    {
        wl_adapter_detach( link );
    }

    DBG_LEAVE( DbgInfo );
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_event()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The card status event handler. Mostly, this schedules other stuff to run
 *  after an event is received. A CARD_REMOVAL event also sets some flags to 
 *  discourage the net drivers from trying to talk to the card.
 *
 *  PARAMETERS:
 *
 *      event       - the event recevied from Card Services
 *      priority    - the importance of the event
 *      args        - additional callback information
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int wl_adapter_event( event_t event, int priority, event_callback_args_t *args )
{
    dev_link_t        *link = args->client_data;
    struct net_device *dev = link->priv;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_event" )
    DBG_ENTER( DbgInfo );
    DBG_PARAM( DbgInfo, "event", "%s", DbgEvent( event ));
    DBG_PARAM( DbgInfo, "priority", "%d", priority );
    DBG_PARAM( DbgInfo, "args", "0x%p", args );


    switch( event )
    {
    case CS_EVENT_CARD_REMOVAL:

        link->state &= ~DEV_PRESENT;

        if( link->state & DEV_CONFIG )
        {
            netif_device_detach( dev );

            /* Perform device-specific removal */
            wl_remove( dev );

            link->release.expires = RUN_AT( HZ/20 );
            add_timer( &( link->release ));
        }

        break;


    case CS_EVENT_CARD_INSERTION:

        link->state |= ( DEV_PRESENT | DEV_CONFIG_PENDING );
        wl_adapter_insert( link );

        break;


    case CS_EVENT_PM_SUSPEND:

        link->state |= DEV_SUSPEND;
        /* Fall through... */


    case CS_EVENT_RESET_PHYSICAL:

        if( link->state & DEV_CONFIG )
        {
            if( link->open )
            {
                netif_device_detach( dev );
                if( event == CS_EVENT_PM_SUSPEND )
                {
                    wl_suspend( dev );
                }
            }

            CardServices( ReleaseConfiguration, link->handle );
        }

        break;


    case CS_EVENT_PM_RESUME:

        link->state &= ~DEV_SUSPEND;
        /* Fall through... */


    case CS_EVENT_CARD_RESET:
        
        if( link->state & DEV_CONFIG )
        {
            CardServices( RequestConfiguration, link->handle, &( link->conf ));
            
            if( event == CS_EVENT_PM_RESUME )
            {
                wl_resume( dev );
            }
            else
            {
                wl_reset( dev );
            }

            netif_device_attach( dev );
        }

        break;
    }

    DBG_LEAVE( DbgInfo );
    return 0;
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_insert()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      wl_adapter_insert() is scheduled to run after a CARD_INSERTION event is 
 *  received, to configure the PCMCIA socket, and to make the ethernet device 
 *  available to the system.
 *
 *  PARAMETERS:
 *
 *      link    - pointer to the dev_link_t structure representing the device to
 *                insert
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_adapter_insert( dev_link_t *link )
{
    client_handle_t         handle;
    struct net_device       *dev;
    tuple_t                 tuple;
    cisparse_t              parse;
    u_char                  buf[64];
    int                     last_fn, last_ret;
    config_info_t           conf;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_insert" );
    DBG_ENTER( DbgInfo );
    DBG_PARAM( DbgInfo, "link", "0x%p", link );


    handle  = link->handle;
    dev     = link->priv;


    tuple.Attributes    = 0;
    tuple.TupleData     = buf;
    tuple.TupleDataMax  = sizeof( buf );
    tuple.TupleOffset   = 0;
    tuple.DesiredTuple  = CISTPL_CONFIG;


    CS_CHECK( GetFirstTuple, handle, &tuple );
    CS_CHECK( GetTupleData, handle, &tuple );
    CS_CHECK( ParseTuple, handle, &tuple, &parse );


    link->conf.ConfigBase   = parse.config.base;
    link->conf.Present      = parse.config.rmask[0];
    link->state             |= DEV_CONFIG;


    /* Look up the current Vcc */ 
    CS_CHECK( GetConfigurationInfo, handle, &conf );
    link->conf.Vcc = conf.Vcc;


    CS_CHECK( RequestIO, link->handle, &link->io );
    CS_CHECK( RequestIRQ, link->handle, &link->irq );
    CS_CHECK( RequestConfiguration, link->handle, &link->conf );


    dev->irq        = link->irq.AssignedIRQ;
    dev->base_addr  = link->io.BasePort1;


    /* Initialize hardware */
    if( !wl_insert( dev ))
    {
        goto failed;
    }


    link->state &= ~DEV_CONFIG_PENDING;

    link->dev = &( (struct wl_private *)dev->priv )->node;
    strcpy(( (struct wl_private *)dev->priv )->node.dev_name, dev->name);


    DBG_LEAVE( DbgInfo );
    return;


cs_failed:
    cs_error( link->handle, last_fn, last_ret );


failed:
    wl_adapter_release( (u_long)link );

    DBG_LEAVE(DbgInfo);
    return;
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_open()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Open the device.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to a net_device structure representing the network
 *            device to open.
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int wl_adapter_open( struct net_device *dev )
{
    dev_link_t  *link;
    int         result = 0;
    int         hcfStatus = HCF_SUCCESS;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_open" );
    DBG_ENTER( DbgInfo );
    DBG_PARAM( DbgInfo, "dev", "%s (0x%p)", dev->name, dev );


    for( link = dev_list; link; link = link->next )
    {
        if( link->priv == dev )
            break;
    }

    if( !DEV_OK( link ))
    {
        DBG_LEAVE( DbgInfo );
        return -ENODEV;
    }

    link->open++;

    hcfStatus = wl_open( dev );

    if( hcfStatus != HCF_SUCCESS )
    {
        link->open--;
        result = -ENODEV;
    }

    DBG_LEAVE( DbgInfo );
    return result;
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_close()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Close the device.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to a net_device structure representing the network
 *            device to close.
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int wl_adapter_close( struct net_device *dev )
{
    dev_link_t             *link;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_close" );
    DBG_ENTER( DbgInfo );
    DBG_PARAM( DbgInfo, "dev", "%s (0x%p)", dev->name, dev );


    for( link = dev_list; link; link = link->next )
    {
        if( link->priv == dev ) break;
    }

    if( link == NULL )
    {
        DBG_LEAVE( DbgInfo );
        return -ENODEV;
    }

    DBG_TRACE( DbgInfo, "%s: Shutting down adapter.\n", dev->name );
    wl_close( dev );

    link->open--;

    if( link->state & DEV_STALE_CONFIG )
    {
        link->release.expires   = RUN_AT( HZ/20 );
        link->state             |= DEV_RELEASE_PENDING;
        
        add_timer( &link->release );
    }

    DBG_LEAVE( DbgInfo );
    return 0;
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_init_module()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Called by init_module() to perform PCMCIA driver initialization.
 *
 *  PARAMETERS:
 *
 *      N/A
 *
 *  RETURNS:
 *
 *      0 on success
 *      -1 on error
 *
 ******************************************************************************/
int wl_adapter_init_module( void )
{
    servinfo_t  serv;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_init_module" );
    DBG_ENTER( DbgInfo );
    DBG_TRACE( DbgInfo, "wl_adapter_init_module() -- PCMCIA\n" );


    CardServices( GetCardServicesInfo, &serv );

    if( serv.Revision != CS_RELEASE_CODE )
    {
        printk( KERN_WARNING
          "  The version of pcmcia-cs (%d.%d.%d) this driver is configured for\n"
          "  does not match the running Card Services version (%s).\n",
          serv.Revision >> 12, ( serv.Revision & 0x0F00 ) >> 8, 
          serv.Revision & 0xFF, CS_RELEASE );

        DBG_LEAVE( DbgInfo );
        return -1;
    }

    register_pcmcia_driver( &dev_info, &wl_adapter_attach, &wl_adapter_detach );

    DBG_LEAVE( DbgInfo );
    return 0;
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_cleanup_module()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Called by cleanup_module to perform driver uninitialization.
 *
 *  PARAMETERS:
 *
 *      N/A
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_adapter_cleanup_module( void )
{
    DBG_FUNC( "wl_adapter_cleanup_module" );
    DBG_ENTER( DbgInfo );
    DBG_TRACE( DbgInfo, "wl_adapter_cleanup_module() -- PCMCIA\n" );


    unregister_pcmcia_driver( &dev_info );

    while( dev_list != NULL )
    {
        wl_adapter_detach( dev_list );
    }

    DBG_LEAVE( DbgInfo );
    return;
}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_is_open()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Check with Card Services to determine if this device is open.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the net_device structure whose open status will be
 *            checked
 *
 *  RETURNS:
 *
 *      nonzero if device is open
 *      0 otherwise
 *
 ******************************************************************************/
int wl_adapter_is_open( struct net_device *dev )
{
    dev_link_t *link;

    for( link = dev_list; link; link = link->next )
    {
        if( link->priv == dev )
            break;
    }

    if( !DEV_OK( link ))
    {
        return 0;
    }

    return( link->open );
}
/*============================================================================*/




/*******************************************************************************
 *	cs_error()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Report an error with Card Services
 *
 *  PARAMETERS:
 *
 *      handle  - the handle stored in the dev_link_t structure for the device.
 *      func    - the card services function, attempted with a call to
 *                CardServices(), which failed.
 *      ret     - the return value from the failed CardServices() call.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void cs_error( client_handle_t handle, int func, int ret )
{
    error_info_t err = { func, ret };

    CardServices( ReportError, handle, &err );

    return;
}
/*============================================================================*/




#if DBG

/*******************************************************************************
 *	DbgEvent()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Converts the card serivces events to text for debugging.
 *
 *  PARAMETERS:
 *
 *      mask    - a integer representing the error(s) being reported by Card 
 *                Services.
 *
 *  RETURNS:
 *
 *      a pointer to a string describing the error(s)
 *
 ******************************************************************************/
const char *DbgEvent( int mask )
{
    static char DbgBuffer[256];
    char *pBuf;
    /*------------------------------------------------------------------------*/


    pBuf    = DbgBuffer;
    *pBuf   = '\0';


    if( mask & CS_EVENT_WRITE_PROTECT )
        strcat( pBuf, "WRITE_PROTECT " );

    if(mask & CS_EVENT_CARD_LOCK)
        strcat( pBuf, "CARD_LOCK " );

    if(mask & CS_EVENT_CARD_INSERTION)
        strcat( pBuf, "CARD_INSERTION " );

    if(mask & CS_EVENT_CARD_REMOVAL)
        strcat( pBuf, "CARD_REMOVAL " );

    if(mask & CS_EVENT_BATTERY_DEAD)
        strcat( pBuf, "BATTERY_DEAD " );

    if(mask & CS_EVENT_BATTERY_LOW)
        strcat( pBuf, "BATTERY_LOW " );

    if(mask & CS_EVENT_READY_CHANGE)
        strcat( pBuf, "READY_CHANGE " );

    if(mask & CS_EVENT_CARD_DETECT)
        strcat( pBuf, "CARD_DETECT " );

    if(mask & CS_EVENT_RESET_REQUEST)
        strcat( pBuf, "RESET_REQUEST " );

    if(mask & CS_EVENT_RESET_PHYSICAL)
        strcat( pBuf, "RESET_PHYSICAL " );

    if(mask & CS_EVENT_CARD_RESET)
        strcat( pBuf, "CARD_RESET " );

    if(mask & CS_EVENT_REGISTRATION_COMPLETE)
        strcat( pBuf, "REGISTRATION_COMPLETE " );

    if(mask & CS_EVENT_RESET_COMPLETE)
        strcat( pBuf, "RESET_COMPLETE " );

    if(mask & CS_EVENT_PM_SUSPEND)
        strcat( pBuf, "PM_SUSPEND " );

    if(mask & CS_EVENT_PM_RESUME)
        strcat( pBuf, "PM_RESUME " );

    if(mask & CS_EVENT_INSERTION_REQUEST)
        strcat( pBuf, "INSERTION_REQUEST " );

    if(mask & CS_EVENT_EJECTION_REQUEST)
        strcat( pBuf, "EJECTION_REQUEST " );

    if(mask & CS_EVENT_MTD_REQUEST)
        strcat( pBuf, "MTD_REQUEST " );

    if(mask & CS_EVENT_ERASE_COMPLETE)
        strcat( pBuf, "ERASE_COMPLETE " );

    if(mask & CS_EVENT_REQUEST_ATTENTION)
        strcat( pBuf, "REQUEST_ATTENTION " );

    if(mask & CS_EVENT_CB_DETECT)
        strcat( pBuf, "CB_DETECT " );

    if(mask & CS_EVENT_3VCARD)
        strcat( pBuf, "3VCARD " );

    if(mask & CS_EVENT_XVCARD)
        strcat( pBuf, "XVCARD " );


    if( *pBuf )
    {
        pBuf[strlen(pBuf) - 1] = '\0';
    }
    else
    {
        if( mask != 0x0 )
        {
            sprintf( pBuf, "<<0x%08x>>", mask );
        }
    }

    return pBuf;
}
/*============================================================================*/

#endif  /* DBG */
