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

File Name:      mtx_agp.c

Description:    AGP driver backend control

References:     None.

Author:         Karl Lessard    <klessard@matrox.com>

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

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

#define __NO_VERSION__
#include "mtx_drv.h"

mtx_agp_driver_t *agp_drv = NULL;

#ifdef AGPBACKEND
/* AGP backend support */
const drm_agp_t *agp_backend = NULL;
#endif

void mtx_agp_chipset_info_print( AGP_KernInfo* poAGPInfo );

/***************************************************************************************
 * Function:       mtx_agp_init
 *
 * Description:    Init our driver properly to use the AGP driver available from
 *                 the kernel.
 *
 * Parameters:     None.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
int mtx_agp_init(void)
{
    /* Alloc a MTX agp driver structure */
    agp_drv = (mtx_agp_driver_t*) MTXALLOC(sizeof(mtx_agp_driver_t));

    if ( ! agp_drv )
    {
        return -ENOMEM;
    }

    MTX_INFO("Allocated a MTX agp driver strucure\n");
    
    agp_drv->mtrr = -1;

#ifdef AGPBACKEND
    /* Connect to AGP driver */
    agp_backend = (drm_agp_t*)inter_module_get_request("drm_agp","agpgart");

    if ( ! agp_backend )
    {
        MTX_ERROR("Failed to retrieve agpgart symbol table\n");
        goto cleanup;
    }
#endif

    /* Copy AGP information */
    MTX_AGPGART_COPY_INFO( agp_backend, &agp_drv->kern_info);
    
    agp_drv->aperture_base = agp_drv->kern_info.aper_base;
    agp_drv->aperture_size = agp_drv->kern_info.aper_size * 1024L * 1024L;

    if ( agp_drv->kern_info.chipset == NOT_SUPPORTED ) 
    {
        MTX_ERROR("AGP driver is unsupported"); 
        goto cleanup;
    }
    
    mtx_agp_chipset_info_print( &agp_drv->kern_info );

    return 0;

cleanup:

    mtx_agp_cleanup();
    return -ENODEV;
}

/***************************************************************************************
 * Function:       mtx_agp_cleanup
 *
 * Description:    Cleanup our AGP data.
 *
 * Parameters:     None.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
void
mtx_agp_cleanup(void)
{
    if (!agp_drv) return;
    
#ifdef AGPBACKEND
    if (agp_backend != NULL)
    {
        /* release drm_agp interface */
        inter_module_put("drm_agp");
        agp_backend = NULL;
    }
#endif

    /* free agp_drv pointer */
    MTXFREE(agp_drv, sizeof(mtx_agp_driver_t));
    agp_drv = NULL;
}

/***************************************************************************************
 * Function:       mtx_agp_acquire
 *
 * Description:    Acquire control of AGP driver.
 *
 * Parameters:     dev          MTX device plugged to AGP port
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
int 
mtx_agp_acquire(mtx_device_t *dev) {

    int ret;

    if (!agp_drv || (agp_drv->dev != NULL)) 
        return -EINVAL;

    /* acquire control of AGP backend */
    if ((ret = MTX_AGPGART_BACKEND_ACQUIRE(agp_backend)) < 0)
        return ret;

    agp_drv->dev = dev;

    return 0;
};

/***************************************************************************************
 * Function:       mtx_agp_release
 *
 * Description:    Release control of AGP driver.
 *
 * Parameters:     dev          MTX device plugged to AGP port
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
void 
mtx_agp_release(mtx_device_t *dev) {

    if (!agp_drv || (!agp_drv->dev) || (dev != agp_drv->dev))
        return;

    /* release control of AGP backend */
    MTX_AGPGART_BACKEND_RELEASE(agp_backend);

    agp_drv->dev = NULL;
};

/***************************************************************************************
 * Function:       mtx_agp_enable
 *
 * Description:    Start communication with the AGP GART controller.
 *
 * Parameters:     dev          MTX device plugged to AGP port
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
int 
mtx_agp_enable(mtx_device_t *dev) {

    u32 agp_status, agp_command;

    if (!agp_drv || !(agp_drv->dev) || (agp_drv->dev != dev) || (agp_drv->enabled)) 
        return -EINVAL;

    pci_read_config_dword(dev->pci_dev, dev->agp_cap_offset + PCI_AGP_STATUS, &agp_status);
    pci_read_config_dword(dev->pci_dev, dev->agp_cap_offset + PCI_AGP_COMMAND, &agp_command);

    switch (MTX_MODULE_PARM(agprate)) {

       default:
        case 4:
            if (agp_status & PCI_AGP_STATUS_RATE4) {

                agp_drv->rate = 4;
                break;
                
            } /* else fallthrough */

        case 2:
            if (agp_status & PCI_AGP_STATUS_RATE2) {

                agp_drv->rate = 2;
                break;

            } /* else fallthrough */
            
        case 1: /* 1X */
            if (agp_status & PCI_AGP_STATUS_RATE1) {

                agp_drv->rate = 1;
                break;
                
            } /* else */

            MTX_ERROR("Cannot set agp_drv->rate for AGP transfers "
                      "(requested agp_drv->rate: %dX, agp_status: 0x%08x\n)",
                      MTX_MODULE_PARM(agprate), agp_status);
            
            return -EINVAL;
    }

    /* build our AGP mode */
    agp_command = ((agp_status & PCI_AGP_STATUS_RQ_MASK) & 0x1f000000) |
                  (agp_status & PCI_AGP_STATUS_SBA) |
                  PCI_AGP_COMMAND_AGP |
                  (agp_status & PCI_AGP_STATUS_FW) |
                  agp_drv->rate;

    /* enable AGP controller */
    MTX_AGPGART_BACKEND_ENABLE(agp_backend,agp_command);

    agp_drv->enabled = 1;

    mtx_agp_apply_rate_patches();
                            
  #if defined(__i386__)
    /* try to set write-combining transfer type to full AGP aperture */
    agp_drv->mtrr = mtrr_add(agp_drv->aperture_base, agp_drv->aperture_size, MTRR_TYPE_WRCOMB, 1);
  #endif
        
    return 0;
};

/***************************************************************************************
 * Function:       mtx_agp_disable
 *
 * Description:    Stop communication with the AGP GART controller.
 *
 * Parameters:     dev          MTX device plugged to AGP port
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 *
 */
void 
mtx_agp_disable(mtx_device_t *dev) {

    if (!agp_drv || !(agp_drv->dev) || (dev != agp_drv->dev) || !(agp_drv->enabled))
        return;

    agp_drv->enabled = 0;
    
    if (agp_drv->mtrr >= 0) {
      #if defined(__i386__)
        /* release memory range */
        mtrr_del(agp_drv->mtrr, agp_drv->aperture_base, agp_drv->aperture_size);
      #endif
        agp_drv->mtrr = -1;
    }
}

/***************************************************************************************
 * Function:       mtx_agp_test_device
 *
 * Description:    Test if AGP transfers work fine for this device
 *
 * Parameters:     dev          MTX device
 *
 * Return Value:   0 if succeed, -1 otherwise.
 *
 * Comments:
 *
 */
int mtx_agp_test_device(mtx_device_t *dev)
{
    int disable_after = 0;
    
    if (!agp_drv)
        return -1;
    
    if (agp_drv->dev && (dev != agp_drv->dev))
        return -1;

    if (!(agp_drv->enabled)) {

        // Enable device for tests, we'll disable it later
        mtx_agp_enable(dev);
        disable_after = 1;
    }

    // no tests for now...

    if (disable_after)
        mtx_agp_disable(dev);

    return 0;
}


/***************************************************************************************
 * Function:       mtx_agp_apply_rate_patches
 *
 * Description:    Apply some patches to the chipset depending on rate selected.
 *
 * Parameters:     None.
 *
 * Return Value:   None.
 *
 * Comments:
 *
 */
void mtx_agp_apply_rate_patches(void)
{
    u8 byte;
    pci_read_config_byte(pci_host_dev, 0xB0L, &byte);
    
    /* Apply patches that depends on chipset */
    if (pci_host_dev->vendor == PCI_VENDOR_ID_VIA) {
        
        if ((pci_host_dev->device == PCI_DEVICE_ID_VIA_8605_0) ||
            (pci_host_dev->device == PCI_DEVICE_ID_VIA_8371_0)) {

            switch (agp_drv->rate) {

                case 2:
                    MTX_INFO("Applying 0xB0 register patch for VIA chipsets in 2X\n");
                    pci_write_config_byte(pci_host_dev, 0xB0L, (byte | 0x80L));
                    break;

                case 4:
                    MTX_INFO("Applying 0xB0 register patch for VIA chipsets in 4X\n");
                    pci_write_config_byte(pci_host_dev, 0xB0L, (byte & (~0x80L)));
                    break;
            }
        }
        
    } else if (pci_host_dev->vendor == PCI_VENDOR_ID_INTEL) {

        if (agp_drv->rate == 2)
        {
            switch (pci_host_dev->device) {

                case 0x2500: case 0x2501:
                case 0x2502: case 0x2503:
                case 0x2504: case 0x250B: 
                case 0x250F: /* I820_0x */
                case 0x1130: /* I815 */
                case 0x2530: /* I850 */

                    MTX_INFO("Applying 4XOverride patch for some Intel chipsets at AGP2X\n");
                    pci_write_config_byte(pci_host_dev, 0xB0L, (byte & (0x01L)));
                    break;
            }
        }
    }

    /* Make sure that fastwrite is disabled at 1X */
    if (agp_drv->rate == 1) {

        sys_flags &= ~AGPINFO_FLAG_FASTWRITE;
    }
}

void mtx_agp_chipset_info_print( AGP_KernInfo* poAGPInfo )
{
#ifdef KERNEL_2_4
    const char *chipset_name;

    switch( poAGPInfo->chipset ) 
    {
        case INTEL_GENERIC:     chipset_name = "Intel";             break;
        case INTEL_LX:          chipset_name = "Intel 440LX";       break;
        case INTEL_BX:          chipset_name = "Intel 440BX";       break;
        case INTEL_GX:          chipset_name = "Intel 440GX";       break;
        case INTEL_I810:        chipset_name = "Intel i810";        break;
        case INTEL_I815:        chipset_name = "Intel i815";        break;
#if LINUX_VERSION_CODE >= 0x020415
        case INTEL_I820:        chipset_name = "Intel i820";        break;
#endif
#if LINUX_VERSION_CODE >= 0x020418
        case INTEL_I830_M:      chipset_name = "Intel i830 M";      break;       
#endif
        case INTEL_I840:        chipset_name = "Intel i840";        break;
#if LINUX_VERSION_CODE >= 0x020415
        case INTEL_I845:        chipset_name = "Intel i845";        break;
#endif
#if LINUX_VERSION_CODE >= 0x020419
        case INTEL_I845_G:      chipset_name = "Intel i845 G";      break;       
#endif
        case INTEL_I850:        chipset_name = "Intel i850";        break;
#if LINUX_VERSION_CODE >= 0x020418
        case INTEL_I860:        chipset_name = "Intel i860";        break;
#endif
        case VIA_GENERIC:       chipset_name = "VIA";               break;
        case VIA_VP3:           chipset_name = "VIA VP3";           break;
        case VIA_MVP3:		    chipset_name = "VIA MVP3";          break;
        case VIA_MVP4:		    chipset_name = "VIA MVP4";          break;
        case VIA_APOLLO_KX133:	chipset_name = "VIA Apollo KX133";  break;
        case VIA_APOLLO_KT133:	chipset_name = "VIA Apollo KT133";  break;
        case VIA_APOLLO_PRO: 	chipset_name = "VIA Apollo Pro";    break;
                                
        case SIS_GENERIC:	    chipset_name = "SiS";               break;
                            
        case AMD_GENERIC:	    chipset_name = "AMD";               break;
        case AMD_IRONGATE:	    chipset_name = "AMD Irongate";      break;
#if LINUX_VERSION_CODE >= 0x020418
        case AMD_761:	        chipset_name = "AMD 761";           break;
        case AMD_762:	        chipset_name = "AMD 762";           break;
#endif
#if LINUX_VERSION_CODE >= 0x020419
        case AMD_8151:	        chipset_name = "AMD 8151";          break;
#endif
        case ALI_GENERIC:	    chipset_name = "ALi";               break;
        case ALI_M1541:     	chipset_name = "ALi M1541";         break;
#if LINUX_VERSION_CODE >= 0x020402
        case ALI_M1621: 	    chipset_name = "ALi M1621";         break;
        case ALI_M1631: 	    chipset_name = "ALi M1631";         break;
        case ALI_M1632: 	    chipset_name = "ALi M1632";         break;
        case ALI_M1641: 	    chipset_name = "ALi M1641";         break;
        case ALI_M1647: 	    chipset_name = "ALi M1647";         break;
        case ALI_M1651: 	    chipset_name = "ALi M1651";         break;
#endif
#if LINUX_VERSION_CODE >= 0x020419
        case ALI_M1671: 	    chipset_name = "ALi M1671";         break;
#endif

#if LINUX_VERSION_CODE >= 0x020418
        case HP_ZX1: 	        chipset_name = "HP ZX1";            break;
#endif

#if LINUX_VERSION_CODE >= 0x020406
        case SVWRKS_HE:         chipset_name = "Serverworks HE";    break;
        case SVWRKS_LE:         chipset_name = "Serverworks LE";    break;
        case SVWRKS_GENERIC:    chipset_name = "Serverworks Generic";   break;
#endif                          
        default:		        chipset_name = "Unknown";           break;
    }
    
    MTX_INFO("AGP driver v%u.%u on %s\n", 
             agp_drv->kern_info.version.major, 
             agp_drv->kern_info.version.minor,
             chipset_name);
#endif

}
