/*******************************************************************************
 * 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 PCI/miniPCI
 *   devices.
 *
 *------------------------------------------------------------------------------
 *
 * 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_pci.c,v $
 *
 ******************************************************************************/




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

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/init.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/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>

#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/ioport.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_pci.h>




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


/* define the PCI device Table Cardname and id tables */
enum hermes_pci_versions {
	CH_Agere_Systems_Mini_PCI_V1 = 0,
};


static struct pci_device_id wl_pci_tbl[] __devinitdata = {
	{ WL_LKM_PCI_VENDOR_ID, WL_LKM_PCI_DEVICE_ID_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Agere_Systems_Mini_PCI_V1 },
    { WL_LKM_PCI_VENDOR_ID, WL_LKM_PCI_DEVICE_ID_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Agere_Systems_Mini_PCI_V1 },
	{ }			/* Terminating entry */
};

MODULE_DEVICE_TABLE(pci, wl_pci_tbl);




/*******************************************************************************
 * function prototypes
 ******************************************************************************/
int __devinit wl_pci_probe( struct pci_dev *pdev,
                                const struct pci_device_id *ent );
void __devexit wl_pci_remove(struct pci_dev *pdev);
int wl_pci_setup( struct pci_dev *pdev );




/*******************************************************************************
 * PCI module function registration
 ******************************************************************************/
static struct pci_driver wl_driver =
{
	name:		MODULE_NAME,
    id_table:	wl_pci_tbl,
	probe:		wl_pci_probe,
	remove:		__devexit_p(wl_pci_remove),
    suspend:    NULL,
    resume:     NULL,
};




/*******************************************************************************
 *	wl_adapter_init_module()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Called by init_module() to perform PCI-specific driver initialization.
 *
 *  PARAMETERS:
 *
 *      N/A
 *
 *  RETURNS:
 *
 *      0
 *
 ******************************************************************************/
int wl_adapter_init_module( void )
{
    int result;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_init_module()" );
    DBG_ENTER( DbgInfo );


    result = pci_register_driver( &wl_driver );
    
    DBG_LEAVE( DbgInfo );

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




/*******************************************************************************
 *	wl_adapter_cleanup_module()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Called by init_module() to perform PCI-specific driver cleanup.
 *
 *  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() -- PCI\n" );

    pci_unregister_driver( &wl_driver );

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




/*******************************************************************************
 *	wl_adapter_insert()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Called by wl_pci_probe() to continue the process of device insertion. 
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      TRUE or FALSE
 *
 ******************************************************************************/
int wl_adapter_insert( struct net_device *dev )
{
    struct wl_private *lp = NULL;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_insert" );
    DBG_ENTER( DbgInfo );


    DBG_TRACE( DbgInfo, "wl_adapter_insert() -- PCI\n" );

    if( dev == NULL )
    {
        DBG_ERROR( DbgInfo, "net_device pointer is NULL!!!\n" );
        goto failed;
    }

    lp = (struct wl_private *)dev->priv;

    if( lp == NULL )
    {
        DBG_ERROR( DbgInfo, "wl_private pointer is NULL!!!\n" );
        goto failed;
    }

    /* Perform remaining device initialization */
    if( !wl_insert( dev ))
    {
        DBG_TRACE( DbgInfo, "wl_insert() FAILED\n" );
        goto failed;
    }


    DBG_LEAVE( DbgInfo );
    return TRUE;

failed:
    DBG_LEAVE( DbgInfo );
    return FALSE;

}
/*============================================================================*/




/*******************************************************************************
 *	wl_adapter_open()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Open the device.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      an HCF status code
 *
 ******************************************************************************/
int wl_adapter_open( struct net_device *dev )
{
    int         result = 0;
    int         hcfStatus = HCF_SUCCESS;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_adapter_open" );
    DBG_ENTER( DbgInfo );


    DBG_TRACE( DbgInfo, "wl_adapter_open() -- PCI\n" );
    
    hcfStatus = wl_open( dev );

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

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




/*******************************************************************************
 *	wl_adapter_close()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Close the device
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      0
 *
 ******************************************************************************/
int wl_adapter_close( struct net_device *dev )
{
    DBG_FUNC( "wl_adapter_close" );
    DBG_ENTER( DbgInfo );


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

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




/*******************************************************************************
 *	wl_adapter_is_open()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Check whether this device is open. Returns 
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      nonzero if device is open.
 *
 ******************************************************************************/
int wl_adapter_is_open( struct net_device *dev )
{
    /* This function is used in PCMCIA to check the status of the 'open' field
       in the dev_link_t structure associated with a network device. There
       doesn't seem to be an analog to this for PCI, and checking the status 
       contained in the net_device structure doesn't have the same effect. 
       For now, return TRUE, but find out if this is necessary for PCI. */

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




/*******************************************************************************
 *	wl_pci_probe()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Registered in the pci_driver structure, this function is called when the
 *  PCI subsystem finds a new PCI device which matches the infomation contained
 *  in the pci_device_id table.
 *
 *  PARAMETERS:
 *
 *      pdev    - a pointer to the device's pci_dev structure
 *      ent     - this device's entry in the pci_device_id table
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int __devinit wl_pci_probe( struct pci_dev *pdev,
                                const struct pci_device_id *ent )
{
    int result;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_pci_probe" );
    DBG_ENTER( DbgInfo );


    result = wl_pci_setup( pdev );
    
    DBG_LEAVE( DbgInfo );

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




/*******************************************************************************
 *	wl_pci_remove()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Registered in the pci_driver structure, this function is called when the
 *  PCI subsystem detects that a PCI device which matches the infomation 
 *  contained in the pci_device_id table has been removed.
 *
 *  PARAMETERS:
 *
 *      pdev - a pointer to the device's pci_dev structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void __devexit wl_pci_remove(struct pci_dev *pdev)
{
    struct net_device       *dev = NULL;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_pci_remove" );
    DBG_ENTER( DbgInfo );


    /* Make sure the pci_dev pointer passed in is valid */
    if( pdev == NULL )
    {
        DBG_ERROR( DbgInfo, "PCI subsys passed in an invalid pci_dev pointer\n" );
        return;
    }

    dev = (struct net_device *)pci_get_drvdata( pdev );
    if( dev == NULL )
    {
        DBG_ERROR( DbgInfo, "Could not retrieve net_device structure\n" );
        return;
    }

    /* Perform device cleanup */
    wl_remove( dev );
    free_irq( dev->irq, dev );
    wl_device_dealloc( dev );

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




/*******************************************************************************
 *	wl_pci_setup()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Called by wl_pci_probe() to begin a device's initialization process.
 *
 *  PARAMETERS:
 *
 *      pdev - a pointer to the device's pci_dev structure
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int wl_pci_setup( struct pci_dev *pdev )
{
    int                 result = 0;
    struct net_device   *dev = NULL;
    /*------------------------------------------------------------------------*/


    DBG_FUNC( "wl_pci_setup" );
    DBG_ENTER( DbgInfo );


    /* Make sure the pci_dev pointer passed in is valid */
    if( pdev == NULL )
    {
        DBG_ERROR( DbgInfo, "PCI subsys passed in an invalid pci_dev pointer\n" );
        return -ENODEV;
    }

    result = pci_enable_device( pdev );
    if( result != 0 )
    {
        DBG_ERROR( DbgInfo, "pci_enable_device() failed\n" );
        DBG_LEAVE( DbgInfo );
        return result;
    }

    /* We found our device! Let's register it with the system */
    DBG_TRACE( DbgInfo, "Found our device, now registering\n" );
    dev = wl_device_alloc( );
    if( dev == NULL )
    {
        DBG_ERROR( DbgInfo, "Could not register device!!!\n" );
        DBG_LEAVE( DbgInfo );
        return -ENOMEM;
    }

    /* Make sure that space was allocated for our private adapter struct */
    if( dev->priv == NULL )
    {
        DBG_ERROR( DbgInfo, "Private adapter struct was not allocated!!!\n" );
        DBG_LEAVE( DbgInfo );
        return -ENOMEM;
    }

    /* Register our private adapter structure with PCI */
    pci_set_drvdata( pdev, dev );

    /* Fill out bus specific information in the net_device struct */
    dev->irq = pdev->irq;
    SET_MODULE_OWNER( dev );
    
    DBG_TRACE( DbgInfo, "Device Base Address: %#03lx\n", pdev->resource[0].start );
	dev->base_addr = pdev->resource[0].start;

    /* Initialize our device here */
    if( !wl_adapter_insert( dev ))
    {
        DBG_ERROR( DbgInfo, "wl_adapter_insert() FAILED!!!\n" );
        wl_device_dealloc( dev );
        DBG_LEAVE( DbgInfo );
        return -EINVAL;
    }

    //register_netdev( dev );
    
    /* Register our ISR */
    DBG_TRACE( DbgInfo, "Registering ISR...\n" );
    
    result = request_irq(dev->irq, wl_isr, SA_SHIRQ, MODULE_NAME, dev);
    if( result )
	{
        DBG_WARNING( DbgInfo, "Could not register ISR!!!\n" );
        DBG_LEAVE( DbgInfo );
        return result;
	}
    

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


