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

File Name:      mtx_dev.c

Description:    MTX devices manipulation. Supported devices detected on the PCI buses
                are stored in a global list of mtx_dev.

References:     None.

Author:         Karl Lessard    <klessard@matrox.com>

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

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

#define __NO_VERSION__
#include "mtx_drv.h"


/******************************************
 *
 * Global variables
 *
 *****************************************/

static LIST_HEAD(mtx_devices);

#define PciMap_Is64BitBAR( b )  ( ( b & PCI_BASE_ADDRESS_MEM_TYPE_MASK ) == PCI_BASE_ADDRESS_MEM_TYPE_64 )

#if DEBUG
/***************************************************************************************
 * Function:       mtx_dev_dump_regions
 *
 * Description:    Dump list of allocated regions for this device
 *
 * Parameters:     dev           MTX device to init
 *  
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
static void
mtx_dev_dump_regions(mtx_device_t *dev)
{
    unsigned int total_dumped = 0;
    struct list_head *pos;
    mtx_region_t *region;
    
    MTX_PRINT("Dump device regions\n");
        
    list_for_each(pos, &dev->memory_regions)
    {
        region = list_entry(pos, mtx_region_t, list_node);
       
        if (region) {

            /* Dump region to screen */
            MTX_PRINT("  Regions: base 0x%08lx, kaddr %p, size %lu, type %u\n",
                       region->base, region->kaddr, region->size, region->type);

            total_dumped++;
        }
    }

    MTX_PRINT("%u regions dumped\n", total_dumped);
}
#endif

/***************************************************************************************
 * Function:       mtx_dev_do_tests
 *
 * Description:    Do different test on the device and keep results in test flags. This
 *                 may change driver behavior depending on specific hardware.
 *
 * Parameters:     dev          MTX device
 *
 * Return Value:   None.
 *
 * Comments:
 */
static void mtx_dev_do_tests(mtx_device_t *dev)
{
    unsigned long flags = sys_flags;
    u32 dword = 0;

    /* Detect PCI or AGP boards */
    if (dev->agp_cap_offset != -1) { 
        
        flags |= AGPINFO_FLAG_AGPBOARD;
        
    } else {
        
        flags |= AGPINFO_FLAG_PCIBOARD;
        flags &= ~(AGPINFO_FLAG_AGPXFER | AGPINFO_FLAG_AGPXFER_CAP | AGPINFO_FLAG_AGPMEMORY);
    }

    /* Check busmastering capabilities */
    if (flags & AGPINFO_FLAG_PCIXFER_CAP) {

        pci_read_config_dword(dev->pci_dev, PCI_COMMAND, &dword);

        if ((dword & PCI_COMMAND_MASTER) == 0) {
        
            /* Try to force busmastering capabilities */
            pci_write_config_dword(dev->pci_dev, PCI_COMMAND, dword | PCI_COMMAND_MASTER);

            pci_read_config_dword(dev->pci_dev, PCI_COMMAND, &dword);

            if ((dword & PCI_COMMAND_MASTER) == 0)
            {
                /* Busmastering is disabled and is not (or cannot be) forced, 
                 * reset our flags accordingly */
                flags &= ~(AGPINFO_FLAG_PCIXFER_CAP | 
                           AGPINFO_FLAG_PCIXFER |
                           AGPINFO_FLAG_PCIXFER_READ | AGPINFO_FLAG_PCIXFER_WRITE |
                           AGPINFO_FLAG_AGPXFER_CAP | 
                           AGPINFO_FLAG_AGPXFER);
            }
        }
    }

    if (flags & AGPINFO_FLAG_PCIXFER && (pci_host_dev != NULL)) {

        /* Patch enhmemacc for some chipsets or if requested */
        if ((pci_host_dev->device == 0x1A30 /* INTEL_I845 */) ||
            (pci_host_dev->device == 0x2530 /* INTEL_I850 */) ||
            (pci_host_dev->device == 0x3575 /* INTEL_I830 */) ||
            (pci_host_dev->device == 0x2531 /* INTEL_I860 */) ||
            (MTX_MODULE_PARM(enhmemacc) == 0)) {

            pci_read_config_dword(dev->pci_dev, MTX_PCI_OPTION, &dword);
            dword &= ~(MTX_PCI_OPTION_ENHMEMACC_MASK);
            pci_write_config_dword(dev->pci_dev, MTX_PCI_OPTION, dword);
        }
    }

    /* Check if we can do fastwrite on this board */
    if (flags & AGPINFO_FLAG_FASTWRITE) {

        pci_read_config_dword(dev->pci_dev, dev->agp_cap_offset + PCI_AGP_COMMAND, 
                              &dword);
        
        if (!(dword & (PCI_AGP_COMMAND_RATE2 | PCI_AGP_COMMAND_RATE4))) {

            dword &= ~(PCI_AGP_COMMAND_FW);
            pci_write_config_dword(dev->pci_dev, dev->agp_cap_offset + PCI_AGP_COMMAND,
                                   dword);
        }

        if (!(dword & PCI_AGP_COMMAND_FW)) {

            flags &= ~AGPINFO_FLAG_FASTWRITE;
        }
    }

    /* Patch request depth */
    if (flags & AGPINFO_FLAG_AGPXFER) {

        int max_request_depth = 0xFFL;

        if (dev->pci_dev->vendor == PCI_VENDOR_ID_SERVERWORKS)
            max_request_depth = 0x00L;
        
        pci_read_config_dword(dev->pci_dev, dev->agp_cap_offset + PCI_AGP_COMMAND, 
                              &dword);

        if (((dword & PCI_AGP_COMMAND_RQ_MASK) >> 24L) > max_request_depth) {
            
            dword &= ~PCI_AGP_COMMAND_RQ_MASK;
            dword |= ((max_request_depth << 24L) & PCI_AGP_COMMAND_RQ_MASK);

            pci_write_config_dword(dev->pci_dev, dev->agp_cap_offset + PCI_AGP_COMMAND,
                                   dword);
        }
    }

    if (flags & AGPINFO_FLAG_AGPXFER) {

        /* Test AGP services */
        if (mtx_agp_test_device(dev) != 0) {

            flags &= ~(AGPINFO_FLAG_AGPXFER | AGPINFO_FLAG_AGPMEMORY);
        }
    }

    dev->bm_flags = flags;

 #if 1
    MTX_INFO("Busmastering flags:\n");
    MTX_INFO("  Board type detected: %s\n", (flags & AGPINFO_FLAG_AGPBOARD) ? "AGP" :
                                            ((flags & AGPINFO_FLAG_PCIBOARD) ? "PCI" : 
                                             "none"));
    
    if (flags & AGPINFO_FLAG_FOUND_CHIPSET) MTX_INFO("  Chipset 0x%04x:0x%04x was detected\n", 
                                                     dev->pci_dev->device, dev->pci_dev->vendor);
    else MTX_INFO("  No chipset detected!\n");
    if (flags & AGPINFO_FLAG_FOUND_CHIPSET_ISA) MTX_INFO("  ISA chipset was detected\n");
    if (flags & AGPINFO_FLAG_FOUND_CHIPSET_AGP) MTX_INFO("  AGP chipset was detected\n");
    
    if (flags & AGPINFO_FLAG_PCIALIGN64)    MTX_INFO("  PCI addresses are aligned on 64 bits\n");
    if (flags & AGPINFO_FLAG_FASTWRITE)     MTX_INFO("  FastWrite transfers are used\n");
    if (flags & AGPINFO_FLAG_PCIXFER) {
        
        MTX_INFO("  PCI transfers available for %s %s\n",
                 (flags & AGPINFO_FLAG_PCIXFER_READ) ? "read" : "",
                 (flags & AGPINFO_FLAG_PCIXFER_WRITE) ? "write" : "");
    }
    
    if (flags & AGPINFO_FLAG_AGPXFER) {
        
        MTX_INFO("  AGP transfers available\n");
        
        if (flags & AGPINFO_FLAG_AGPDELAY)      MTX_INFO("  AGP delaying is used\n");
        if (flags & AGPINFO_FLAG_AGPSERIALIZE)  MTX_INFO("  AGP serialize is used\n");
        if (flags & AGPINFO_FLAG_AGPREADONLY)   MTX_INFO("  AGP tranfers are read only\n");
        /* if (flags & AGPINFO_FLAG_1XAFTERRESET)  MTX_INFO("  1X is forced after reset\n"); */
    }

    if (flags & AGPINFO_FLAG_HYBRID_ENABLE) MTX_INFO("  Hybrid mode is enabled\n");
 #endif
}

/***************************************************************************************
 * Function:       mtx_dev_probe
 *
 * Description:    Add an MTX device from a PCI device previously detected and
 *                 supported by this driver.
 *
 * Parameters:     pci_dev          Supported device probed by the PCI driver.
 *                 pci_id           PCI id for this device. 
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       This method is called via the (*probe) ptr in pci_driver 
 *                 structure, and is called when the PCI driver detected a 
 *                 new device supported by our hardware.
 */
int 
mtx_dev_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
    mtx_device_t *dev;
    
    if ( pci_enable_device( pci_dev ) )
        return -ENODEV;

    /* Allocate a new device structure */
    dev = (mtx_device_t *) MTXALLOC(sizeof(mtx_device_t));
    if (!dev)
        return -ENOMEM;

    /* Initialize some device data */
    dev->pci_dev  = pci_dev;
    dev->pci_id   = pci_id;
    spin_lock_init(&dev->count_lock);
    
    /* Add reference to this device in global device list */
    list_add_tail(&dev->list_node, &mtx_devices);
  
    /* Print some useful information */
    MTX_INFO("0x%04x(sub:0x%04x) board found at %02x:%02x.%1x\n", 
              dev->pci_id->device, 
              dev->pci_id->subdevice, 
              dev->pci_dev->bus->number, 
              PCI_SLOT(dev->pci_dev->devfn), 
              PCI_FUNC(dev->pci_dev->devfn));

    /* If this device is connected to AGP port, acquire control of AGP backend */
    if ((dev->agp_cap_offset = pci_find_capability(pci_dev, PCI_CAP_ID_AGP)) != 0) {

        if (mtx_agp_acquire(dev) < 0) {
            
            MTX_WARNING("Fail to acquire control of AGP driver. "
                        "AGP tranfers are therefore disabled\n");
        }
        
    } else {

        /* invalidate cap offset */
        dev->agp_cap_offset = -1;
    }

    return 0;
}

/***************************************************************************************
 * Function:       mtx_dev_remove
 *
 * Description:    Cleanup/Remove a device previously initialized.
 *
 * Parameters:     None.
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:
 */
void 
mtx_dev_remove(struct pci_dev *pci_dev)
{
    mtx_device_t *dev;

    /* Get MTX device from PCI bus id */
    dev = mtx_dev_find(pci_dev->bus->number, PCI_SLOT(pci_dev->devfn));
    if (!dev) return;
    
    MTX_DEBUG("removing 0x%04x at %02x:%02x.%x\n", 
               dev->pci_id->device, 
               dev->pci_dev->bus->number, 
               PCI_SLOT(dev->pci_dev->devfn), 
               PCI_FUNC(dev->pci_dev->devfn));
        
    /* Release AGP backend if we are connected to AGP port */
    mtx_agp_release(dev);
    
    /* Remove from device list */
    list_del(&dev->list_node);
    
    /* Free device memory */
    MTXFREE(dev, sizeof(*dev));
}

/***************************************************************************************
 * Function:       mtx_dev_find
 *
 * Description:    Find a MTX device from its PCI ID.
 *
 * Parameters:     pci_bus          PCI bus number
 *                 pci_slot         PCI slot number
 *
 * Return Value:   Pointer to an MTX device, NULL if there's no matching device
 *
 * Comments:       None.
 */
mtx_device_t* 
mtx_dev_find(unsigned int pci_bus, unsigned int pci_slot)
{
    mtx_device_t* dev;
    struct list_head *pos;

    /* Check for a matching device in the device table */ 
    list_for_each(pos, &mtx_devices)
    {
        dev = list_entry(pos, mtx_device_t, list_node);
        
        if (dev->pci_dev->bus->number == pci_bus && 
            PCI_SLOT(dev->pci_dev->devfn) == pci_slot)
        {
            /* We've found a matching device, return it! */
            return dev;
        }
    }

    /* No matches found */
    return NULL;
}

/***************************************************************************************
 * Function:       mtx_dev_init
 *
 * Description:    Init a MTX device properly to be used by the driver. PCI memory
 *                 mapped regions are initialized in this method.
 *
 * Parameters:     dev            MTX device to init
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
int
mtx_dev_init(mtx_device_t *dev)
{
    DWORD   dwFrameBufferBAR;

    /* Check which board we are driving and attach its core driver */
    switch (dev->pci_dev->device)
    {
        case PCI_DEVICE_ID_PSERIES_FAMILY_A:
        case PCI_DEVICE_ID_PSERIES_FAMILY_B:
        case PCI_DEVICE_ID_PSERIES_FAMILY_C:
        case PCI_DEVICE_ID_PSERIES_FAMILY_D:
            dev->core = &mtx_parhl_driver;
            break;

        default:
            /* No core driver for this device */
            return -EINVAL;
    }

    /* Init our private data */
    sema_init(&dev->semaphore, 1);

    INIT_LIST_HEAD(&dev->memory_regions);
    
    /* Init PCI registers region */
    ClientPciConfigReadDword( dev->pci_dev, PCI_BASE_ADDRESS_0, &dwFrameBufferBAR );
    if ( ! PciMap_Is64BitBAR( dwFrameBufferBAR ) )
    {
        READ_PCI_REGION(dev, dev->registers, 1);
    }
    else
    {
        READ_PCI_REGION(dev, dev->registers, 2);
    }
    
    dev->registers.kaddr     = ClientIoRemap(dev->registers.base, dev->registers.size);
    if (dev->registers.kaddr == NULL) {
        MTX_ERROR("Core driver failed to map register region 0x%08lx of size %ld\n", 
	           dev->registers.base, dev->registers.size);
	    return -ENOMEM;
    }
    dev->registers.flags     = MTX_REGION_ALLOCATED;
    dev->registers.scope     = MTX_MEMSCOPE_AUTH;
    dev->registers.type      = MTX_MEMTYPE_IO;
    dev->registers.mtrr      = -1;
    dev->registers.pid    = -1;
    
    ADD_REGION_TO_LIST(dev, &dev->registers);

    MTX_INFO("Registers at 0x%08lx, size: %luK, flags: %lu, knl_addr: 0x%08x\n", 
             dev->registers.base, dev->registers.size/1024, 
             dev->registers.io_flags, (u32)dev->registers.kaddr);
    
    /* Init PCI framebuffer region */
    READ_PCI_REGION(dev, dev->framebuffer, 0);
   
    /* Do not map framebuffer */ 
    dev->framebuffer.kaddr      = NULL;
    dev->framebuffer.flags      = MTX_REGION_ALLOCATED;
    dev->framebuffer.scope      = MTX_MEMSCOPE_AUTH;
    dev->framebuffer.type       = MTX_MEMTYPE_IO;
    dev->framebuffer.mtrr       = -1;
    dev->framebuffer.pid     = -1;
        
  #if defined(__i386__)
    /* try to set write-combining transfer type to framebuffer */
    dev->framebuffer.mtrr = 
        mtrr_add(dev->framebuffer.base, dev->framebuffer.size, MTRR_TYPE_WRCOMB, 1);
  #endif
    
    ADD_REGION_TO_LIST(dev, &dev->framebuffer);
    
    MTX_INFO("Framebuffer at 0x%08lx, size: %luM, flags: %lu, knl_addr: 0x%08x, "
             "write-combining: %s\n", dev->framebuffer.base, dev->framebuffer.size/1024/1024, 
              dev->framebuffer.io_flags, (u32)dev->framebuffer.kaddr,
              ((dev->framebuffer.mtrr < 0) ? "NO" : "YES"));
    
    /* If this device is the AGP one, enable AGP point-to-point connection */
    if (mtx_agp_enable(dev) == 0) {

        MTX_INFO("AGP aperture at 0x%08lx, size: %luK, rate: %dX, write-combining: %s\n", 
                  agp_drv->aperture_base, agp_drv->aperture_size / 1024L, agp_drv->rate,
                  ((agp_drv->mtrr < 0) ? "NO" : "YES"));
    }

    /* Test device capabilities */
    mtx_dev_do_tests(dev);

    /* Allocate bitmap for context id's */
    dev->context_bitmap = MTXALLOC(MAX_CONTEXTS_PER_DEVICE/8);
    if (!dev->context_bitmap)
        return -ENOMEM;
    
    /* Finally, enable device from core driver */
    dev->core_data = dev->core->enable(dev, dev->bm_flags);

    if (dev->core_data == (MTXHANDLE)NULL) {
        MTX_ERROR("Core driver fail to initialize device 0x%04x\n", dev->pci_dev->device);
        return -EINVAL;
    }

    /* Request our interrupt line (should be done after core initialization) */
/*TODO remove this when mtx.o opensource will exist*/
#ifndef OPENPARHL
    mtx_irq_init(dev);
#endif

    return 0;
}

/***************************************************************************************
 * Function:       mtx_dev_cleanup
 *
 * Description:    Cleanup data MTX device created during initialization.
 *
 * Parameters:     dev           MTX device to init
 *  
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
int
mtx_dev_cleanup(mtx_device_t *dev)
{
    struct list_head *pos, *tmppos;
    mtx_region_t *region;

    if (dev->core) {

        /* First, free interrupt line */
        mtx_irq_cleanup(dev);

        /* Disable device in core driver */
        if (dev->core_data) 
            dev->core->disable(dev->core_data);

        /* Delete context id bitmap */
        if (dev->context_bitmap)
            MTXFREE(dev->context_bitmap, (MAX_CONTEXTS_PER_DEVICE/8));

#if 0 //DEBUG
        /* Dump memory regions list (should be empty) */
        mtx_dev_dump_regions(dev);
#endif

        /* Free any remaining memory regions */
        list_for_each_safe(pos, tmppos, &dev->memory_regions)
        {
            region = list_entry(pos, mtx_region_t, list_node);

            if (region) {

                /* Free memory attached to this region */
                MtxRegion_Free(dev, region);
            }
        }

        /* Empty region list */
        INIT_LIST_HEAD(&dev->memory_regions);

        /* If this device is the AGP one, disable AGP point-to-point connection */
        mtx_agp_disable(dev);
    }  

    return 0;
}
