/***************************************************************************************\

File Name:      mtx_drv.c

Description:    Main file of MTX kernel module. 
                Implements module initialization and cleanup calls.

References:     None.

Author:         Karl Lessard    <klessard@matrox.com>

    Copyright (c) 2001, Matrox Graphics Inc.
    All Rights Reserved.

\***************************************************************************************/

#include "mtx_drv.h"

/* Supported boards */
static struct pci_device_id 
mtx_supported_cards[] = {
   
    MTX_PCI_DEVICE_ID( PCI_DEVICE_ID_PSERIES_FAMILY_A, PCI_ANY_ID ),
    MTX_PCI_DEVICE_ID( PCI_DEVICE_ID_PSERIES_FAMILY_B, PCI_ANY_ID ),
    MTX_PCI_DEVICE_ID( PCI_DEVICE_ID_PSERIES_FAMILY_C, PCI_ANY_ID ),
    MTX_PCI_DEVICE_ID( PCI_DEVICE_ID_PSERIES_FAMILY_D, PCI_ANY_ID ),
    { 0, 0, 0, 0, 0, 0, 0 }
};

/* Driver structure used by PCI driver interface */
static struct pci_driver 
mtx_pci_driver = {
    
    name:     (char*)MTX_DRIVER_NAME,       
    id_table: mtx_supported_cards, 
    probe:    mtx_dev_probe,        
    remove:   mtx_dev_remove,
};

/* MTX file operations */
static struct file_operations 
mtx_fops = {
    owner:      THIS_MODULE,
    open:       mtx_open,
    release:    mtx_release,
    ioctl:      mtx_ioctl,
    mmap:       mtx_mmap,
};

/* IOCTLs for MTX driver */
mtx_ioctl_t mtx_ioctls[MTX_IOCTL_MAXNR];

/* MTX device major number */
static int mtx_major_nbr = MTX_MAJOR_NUMBER;

/* Module description */
MODULE_AUTHOR("Matrox Graphics Inc.");
MODULE_DESCRIPTION("Generic linux driver for Matrox graphics devices");
MODULE_SUPPORTED_DEVICE("Parhelia 512");

#ifdef MODULE_LICENSE
MODULE_LICENSE("Copyright (c) 2002, 2004, Matrox Graphics Inc.");
#endif


/* Get Module parameters */
MTX_PUBLIC_MODULE_PARM_DECL(localbm, 0x0, "1 = Bus mastering from on-board memory; 0 = Bus mastering is using fast system memory (PCI, AGP) (default)");
MTX_PUBLIC_MODULE_PARM_DECL(agp, 0x1, "    1 = Enable AGP transfers (default); 0 = Use PCI transfers");
MTX_PUBLIC_MODULE_PARM_DECL(agprate, 0x1, "Set the agprate to either 1x (default), 2x or 4x");
MTX_MODULE_PARM_DECL(fw, 0x0);             /* Fastwrite available */
MTX_MODULE_PARM_DECL(enhmemacc, 0x1);      /* Enhmemacc register */

LutParm* g_apoModParms[ MTX_MODPARM_LUT_COUNT ];

/* Lookup table to retrieve a module parameter from a string */
static LutParm g_aoMtxModGenParms[ ] = { MTX_MODPARM( localbm ), 
                                         MTX_MODPARM( agp ), 
                                         MTX_MODPARM( agprate ), 
                                         MTX_MODPARM( fw ), 
                                         MTX_MODPARM( enhmemacc ),
                                         "", NULL };

/* General system information */
u32 sys_flags = 0;
        
struct pci_dev *pci_host_dev = NULL, *pci_isa_dev = NULL;


/***************************************************************************************\

Function:       ModuleGetNbSyms

Description:    Utility function to return the number of exported symbols in a module.
                
Parameters:     pmod        Pointer to a module.
                
Return Value:   int         number of exported symbols in the module

Comments:       

\***************************************************************************************/
INLINE int ModuleGetNbSyms(struct module* pmod) 
{
#ifdef KERNEL_2_6
    return pmod->num_syms;
#else
    return pmod->nsyms;
#endif
}

/***************************************************************************************\

Function:       GetLutParmEntry

Description:    Utility function to return an entry in the Lut
                
Parameters:     iParmLut    Look up table index
                iParmIndex  Parameter index 

Return Value:   Lutparm*    Pointer to a Lut entry

Comments:       

\***************************************************************************************/
INLINE LutParm* GetLutParmEntry( UINT32 iParmLut, UINT32 iParmIndex )
{
    return g_apoModParms[iParmLut] + iParmIndex;
}

/***************************************************************************************\

Function:       MtxParm_InitLut

Description:    Initializes the parameters look up table.
                
Parameters:     iLutIndex       Look up table index.
                poLutParm       Address of parameter look up table.

Return Value:   INT32           Error code

Comments:       

\***************************************************************************************/
STACK_LINKAGE INT32 MtxParm_InitLut( UINT32 iLutIndex, LutParm* poLutParm )
{
    if ( iLutIndex > MTX_MODPARM_LUT_COUNT )
    {
        return -1;
    }
    
    g_apoModParms[iLutIndex] = poLutParm;
    
    return 0;
}

/***************************************************************************************
 * Function:       mtx_do_tests
 *
 * Description:    Check general configuration of the system and select proper 
 *                 behavior for our driver functionnalities
 *
 * Parameters:     None.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
static void mtx_do_tests(void)
{
    u8 byte;

    if (!(sys_flags & AGPINFO_FLAG_DONE))
    {
        sys_flags = 0;

        /* Detect bridges chipsets */
        pci_host_dev = pci_find_slot(0, 0);
        if (pci_host_dev != NULL) 
        {
            sys_flags |= AGPINFO_FLAG_FOUND_CHIPSET;
        }
        else 
        {
            sys_flags |= AGPINFO_FLAG_PCIMEMORY | AGPINFO_FLAG_PCIXFER_CAP;
            sys_flags |= AGPINFO_FLAG_PCIXFER | AGPINFO_FLAG_PCIXFER_READ | AGPINFO_FLAG_PCIXFER_WRITE;
        }

        pci_isa_dev = pci_find_slot(0, PCI_DEVFN(4, 0));
        
        if (pci_isa_dev != NULL) 
        {
            sys_flags |= AGPINFO_FLAG_FOUND_CHIPSET_ISA;
        }

        if (agp_drv != NULL) 
        {
            sys_flags |= AGPINFO_FLAG_FOUND_CHIPSET_AGP;
        }

        if (!pci_host_dev)
            return;

        /* Set hybrid mode */
        sys_flags |= AGPINFO_FLAG_HYBRID_CAP;

        if (pci_host_dev->vendor == PCI_VENDOR_ID_SERVERWORKS) 
        {
            sys_flags &= ~AGPINFO_FLAG_HYBRID_CAP;
        }

        /* Set PCI transfers availabitity */
        sys_flags |= AGPINFO_FLAG_PCIMEMORY | AGPINFO_FLAG_PCIXFER_CAP;

        if ((pci_host_dev->device == PCI_DEVICE_ID_AL_M1531) ||
            (pci_host_dev->device == PCI_DEVICE_ID_INTEL_82437)) 
        {
            sys_flags &= ~AGPINFO_FLAG_PCIXFER_CAP;
            
        } 
        else if ( ! MTX_MODULE_PARM( localbm ) ) 
        {
            sys_flags |= AGPINFO_FLAG_PCIXFER | AGPINFO_FLAG_PCIXFER_READ | AGPINFO_FLAG_PCIXFER_WRITE;

            /* Execute patches for some chipset before doing PCI busmastering */
            switch (pci_host_dev->device)
            {
                case PCI_DEVICE_ID_INTEL_82443LX_0:
                case 0x7582: /* 82443LX_2 */

                    if (pci_read_config_byte(pci_host_dev, 0xB1L, &byte)) {

                        MTX_INFO("Applying 0xB1 register patch for 440LX chipsets\n");
                        pci_write_config_byte(pci_host_dev, 0xB1L, (byte | 0x20L));
                    }

                    sys_flags &= ~(AGPINFO_FLAG_PCIXFER_READ | AGPINFO_FLAG_HYBRID_CAP);
                    break;

                case PCI_DEVICE_ID_SI_5591:

                    MTX_INFO("Applying 0xA4 register patch for SIS chipsets\n");
                    pci_write_config_byte(pci_host_dev, 0xA4L, 0x09L);
                    break;

                case PCI_DEVICE_ID_AL_M1541:

                    if (pci_read_config_byte(pci_host_dev, 0x43L, &byte)) {

                        MTX_INFO("Applying 0x43 register patch for ALI chipsets\n");
                        pci_write_config_byte(pci_host_dev, 0x43L, (byte | 0x80L));
                    }
                    break;

                case 0x3575 /* INTEL_I830 */:
                case 0x1A21 /* INTEL_I840_21 */:
                case 0x1A23 /* INTEL_I840_23 */:

                    sys_flags |= AGPINFO_FLAG_PCIALIGN64;
                    break;
            }
        }

        /* Set AGP transfers availabitily */
        if (sys_flags &  AGPINFO_FLAG_PCIXFER) {

            sys_flags |= AGPINFO_FLAG_AGPXFER_CAP | AGPINFO_FLAG_AGPMEMORY;
            
            if (MTX_MODULE_PARM(agp)) {
                
                sys_flags |= AGPINFO_FLAG_AGPXFER;
            }

            if (MTX_MODULE_PARM(fw)) {
                
                sys_flags |= AGPINFO_FLAG_FASTWRITE;
            }

            /* Execute patches for some chipset before doing AGP busmastering */
            switch (pci_host_dev->vendor)
            {
                case PCI_VENDOR_ID_SERVERWORKS:
                    sys_flags |= AGPINFO_FLAG_AGPDELAY;
                    break;

                case PCI_VENDOR_ID_VIA:

                    sys_flags |= AGPINFO_FLAG_AGPSERIALIZE;

                    switch (pci_host_dev->device) 
                    {
#ifdef KERNEL_2_6
                        case PCI_DEVICE_ID_VIA_82C691_0: /* 694X */
#else
                        case PCI_DEVICE_ID_VIA_82C691: /* 694X */
#endif                        
                        case PCI_DEVICE_ID_VIA_8363_0: /* KT133 */
                        case PCI_DEVICE_ID_VIA_8371_0: /* KX133 */
                        case PCI_DEVICE_ID_VIA_8605_0: /* S3 */
                            sys_flags |= AGPINFO_FLAG_1XAFTERRESET;
                            break;
                    }
                    break;

                case PCI_VENDOR_ID_SI:
                    sys_flags |= AGPINFO_FLAG_AGPSERIALIZE;
                    break;
            }
        }

        sys_flags |= AGPINFO_FLAG_DONE;
    }
}

/***************************************************************************************
 * Function:       mtx_init 
 *
 * Description:    Called via init_module at module load time.    
 *
 * Parameters:     None.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
static int __init mtx_init(void)
{
    int result;

    MTX_INFO("MTX driver v%u.%u.%u\n",
              MTX_VERSION_MAJOR, MTX_VERSION_MINOR, MTX_VERSION_PATCH);

    /* Registering device driver */
    result = register_chrdev(mtx_major_nbr, MTX_DRIVER_NAME, &mtx_fops);
    
    if (result < 0) {
        MTX_ERROR("Failed to register device driver using %u as major number.\n", 
                   MTX_MAJOR_NUMBER);
        return result;
    }
    
    MtxParm_InitLut( MTX_GEN_MODPARM_LUT, g_aoMtxModGenParms );

    if (mtx_major_nbr == 0) {

        /* Major number allocated dynamically */
        mtx_major_nbr = result;
    }

    /* Check if we can support AGP boards */
    if (MTX_MODULE_PARM(agp) && (mtx_agp_init() < 0)) { 

        MTX_WARNING("Fail to retrieve AGP GART driver. AGP transfers disabled\n");
        MTX_MODULE_PARM(agp) = 0;
    }

    /* Check some system settings */
    mtx_do_tests();

    /* Reset and init ioctl table */
    memset(&mtx_ioctls, 0, sizeof(mtx_ioctl_t) * MTX_IOCTL_MAXNR);

    MTX_ADD_IOCTL(MTX_IOCTL_GET_MEMORY_INFO,    mtx_ioctl_get_memory_info, 0, 0, 0);
    MTX_ADD_IOCTL(MTX_IOCTL_GET_BUFFER,         mtx_ioctl_get_buffer,      0, 1, 0);
    MTX_ADD_IOCTL(MTX_IOCTL_RELEASE_BUFFER,     mtx_ioctl_release_buffer,  0, 1, 0);
    MTX_ADD_IOCTL(MTX_IOCTL_DRIVER_VALUE_REQUEST, mtx_ioctl_driver_value_request, 0, 1, 0);
  #ifdef DEBUG
    MTX_ADD_IOCTL(MTX_IOCTL_HARD_RESET,         mtx_ioctl_hard_reset,      0, 1, 1); /* for debug */
  #endif

    /* init all core drivers */
    mtx_parhl_driver.init();

    /* Registering PCI driver */
    return pci_module_init(&mtx_pci_driver);
}

/***************************************************************************************
 * Function:       mtx_cleanup
 *
 * Description:    Called via cleanup_module at module unload time.
 *
 * Parameters:     None.
 *
 * Return Value:   None.
 *
 * Comments:
 *
 */
static void __exit mtx_cleanup(void)
{
    int result;

    /* Unregistering PCI driver */
    pci_unregister_driver(&mtx_pci_driver);
    
    /* cleanup all core drivers */
    mtx_parhl_driver.cleanup();

    /* Cleanup AGP driver */
    mtx_agp_cleanup();
    
    /* Unregistering device driver */
    result = unregister_chrdev(mtx_major_nbr, MTX_DRIVER_NAME);

    if (result < 0) {
        MTX_ERROR("Failed to unregister device driver using %u as major number.\n", 
                  MTX_MAJOR_NUMBER);
        return;
    }
    
    /* Dump memory statistics. It must be all zeros */
    mtx_mem_stats_dump_list(0);
}

/* Declaration of init and cleanup functions */
module_init(mtx_init);
module_exit(mtx_cleanup);

/***************************************************************************************
 * Function:       mtx_find_symbol
 *
 * Description:    Try to find a symbol in the module and returns its value.
 *
 * Parameters:     symbol       Symbol name
 *
 * Return Value:   Pointer to symbol value, NULL if failed.
 *
 * Comments:
 *
 */

#ifdef KERNEL_2_4
void* mtx_find_symbol(const char* symbol)
{
    struct module*      pmod = THIS_MODULE;
    const ModuleSymbol* psym;
    
    if ( ! symbol )
    {
        return NULL;
    }

    if ( MOD_CAN_QUERY(pmod) )
    {
        int i;
        for ( i = 0, psym = pmod->syms; i < ModuleGetNbSyms(pmod); i++, psym++)
        {
            if (!strcmp(psym->name, symbol))
            {
                /* We have a match! */
                return (void*)(psym->value);
            }
        }
    }

    /* No match found */
    return NULL;
}

#else

/***************************************************************************************\

Function:       mtx_find_symbol

Description:    Returns the value of a symbol identified by its name.
                
Parameters:     pszSymbol           Symbol name 
                
Return Value:   VOID*               Opaque pointer

Comments:       This is a temporary implementation.  It still does not support other 
                parameter type than "int" although the return value is opaque.  
                Maybe we should use the get/set model of struct kernel_param.

\***************************************************************************************/
STACK_LINKAGE void* mtx_find_symbol(const char* pszSymbol)
{   
    int iParmLut = 0;

    if ( ! pszSymbol )
    {
        return NULL;
    }

    for ( iParmLut = 0; iParmLut < MTX_MODPARM_LUT_COUNT; iParmLut++ )
    {
        if ( g_apoModParms[iParmLut] != NULL )
        {
            int iParmIndex = 0;
            while ( GetLutParmEntry(iParmLut, iParmIndex)->pvObject != NULL )
            {
                if ( strcmp( GetLutParmEntry(iParmLut, iParmIndex)->pszName, pszSymbol ) == 0 )
                {
                    /* We have a match! */
                    return (void*) ( GetLutParmEntry(iParmLut, iParmIndex)->pvObject );
                }
                
                iParmIndex++;
            }
        }
    }
        
    /* No match found */
    return NULL;
}
#endif /* #ifdef KERNEL_2_4 */
