#include "radeon.h"
#include "radeon_macros.h"
#include "radeon_probe.h"
#include "radeon_reg.h"
#include "Xv.h"
#include "radeon_video.h"

#include "xf86.h"
#include "xf86PciInfo.h"

#include "generic_bus.h"

#define VIP_NAME      "RADEON VIP BUS"
#define VIP_TYPE      "ATI VIP BUS"

/* Status defines */
#define VIP_BUSY  0
#define VIP_IDLE  1
#define VIP_RESET 2

static Bool RADEONVIP_ioctl(GENERIC_BUS_Ptr b, long ioctl, long arg1, char *arg2)
{
    long count;
    switch(ioctl){
        case GB_IOCTL_GET_NAME:
                  count=strlen(VIP_NAME)+1;
                  if(count>arg1)return FALSE;
                  memcpy(arg2,VIP_NAME,count);
                  return TRUE;
                  
        case GB_IOCTL_GET_TYPE:
                  count=strlen(VIP_TYPE)+1;
                  if(count>arg1)return FALSE;
                  memcpy(arg2,VIP_TYPE,count);
                  return TRUE;
                  
        default: 
                  return FALSE;
    }
}

static CARD32 RADEONVIP_idle(GENERIC_BUS_Ptr b)
{
   ScrnInfoPtr pScrn = xf86Screens[b->scrnIndex];
   RADEONInfoPtr info = RADEONPTR(pScrn);
   unsigned char *RADEONMMIO = info->MMIO;

   CARD32 timeout;
   
   RADEONWaitForIdleMMIO(pScrn);
   timeout = INREG(RADEON_VIPH_TIMEOUT_STAT);
   if(timeout & RADEON_VIPH_TIMEOUT_STAT__VIPH_REG_STAT) /* lockup ?? */
   {
       RADEONWaitForFifo(pScrn, 2);
       OUTREG(RADEON_VIPH_TIMEOUT_STAT, (timeout & 0xffffff00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REG_AK);
       RADEONWaitForIdleMMIO(pScrn);
       return (INREG(RADEON_VIPH_CONTROL) & 0x2000) ? VIP_BUSY : VIP_RESET;
   }
   RADEONWaitForIdleMMIO(pScrn);
   return (INREG(RADEON_VIPH_CONTROL) & 0x2000) ? VIP_BUSY : VIP_IDLE ;
}

/* address format:
     ((device & 0x3)<<14)   | (fifo << 12) | (addr)
*/

static Bool RADEONVIP_read(GENERIC_BUS_Ptr b, CARD32 address, CARD32 count, CARD8 *buffer)
{
   ScrnInfoPtr pScrn = xf86Screens[b->scrnIndex];
   RADEONInfoPtr info = RADEONPTR(pScrn);
   unsigned char *RADEONMMIO = info->MMIO;
   CARD32 status,tmp;

   if((count!=1) && (count!=2) && (count!=4))
   {
   xf86DrvMsg(pScrn->scrnIndex,X_ERROR,"Attempt to access VIP bus with non-stadard transaction length\n");
   return FALSE;
   }
   
   RADEONWaitForFifo(pScrn, 2);
   OUTREG(RADEON_VIPH_REG_ADDR, address | 0x2000);
   write_mem_barrier();
   while(VIP_BUSY == (status = RADEONVIP_idle(b)));
   if(VIP_IDLE != status) return FALSE;
   
/*
         disable RADEON_VIPH_REGR_DIS to enable VIP cycle.
         The LSB of RADEON_VIPH_TIMEOUT_STAT are set to 0
         because 1 would have acknowledged various VIP
         interrupts unexpectedly 
*/      
   RADEONWaitForIdleMMIO(pScrn);
   OUTREG(RADEON_VIPH_TIMEOUT_STAT, INREG(RADEON_VIPH_TIMEOUT_STAT) & (0xffffff00 & ~RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS) );
   write_mem_barrier();
/*
         the value returned here is garbage.  The read merely initiates
         a register cycle
*/
    RADEONWaitForIdleMMIO(pScrn);
    INREG(RADEON_VIPH_REG_DATA);
    
    while(VIP_BUSY == (status = RADEONVIP_idle(b)));
    if(VIP_IDLE != status) return FALSE;
/*
        set RADEON_VIPH_REGR_DIS so that the read won't take too long.
*/
    RADEONWaitForIdleMMIO(pScrn);
    tmp=INREG(RADEON_VIPH_TIMEOUT_STAT);
    OUTREG(RADEON_VIPH_TIMEOUT_STAT, (tmp & 0xffffff00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);         
    write_mem_barrier();
    RADEONWaitForIdleMMIO(pScrn);
    switch(count){
        case 1:
             *buffer=(CARD8)(INREG(RADEON_VIPH_REG_DATA) & 0xff);
             break;
        case 2:
             *(CARD16 *)buffer=(CARD16) (INREG(RADEON_VIPH_REG_DATA) & 0xffff);
             break;
        case 4:
             *(CARD32 *)buffer=(CARD32) ( INREG(RADEON_VIPH_REG_DATA) & 0xffffffff);
             break;
        }
     while(VIP_BUSY == (status = RADEONVIP_idle(b)));
     if(VIP_IDLE != status) return FALSE;
 /*     
 so that reading RADEON_VIPH_REG_DATA would not trigger unnecessary vip cycles.
*/
     OUTREG(RADEON_VIPH_TIMEOUT_STAT, (INREG(RADEON_VIPH_TIMEOUT_STAT) & 0xffffff00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);
     write_mem_barrier();
     return TRUE;
}

static Bool RADEONVIP_write(GENERIC_BUS_Ptr b, CARD32 address, CARD32 count, CARD8 *buffer)
{
    ScrnInfoPtr pScrn = xf86Screens[b->scrnIndex];
    RADEONInfoPtr info = RADEONPTR(pScrn);
    unsigned char *RADEONMMIO = info->MMIO;
    
    CARD32 status;


    if((count!=4))
    {
    xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Attempt to access VIP bus with non-stadard transaction length\n");
    return FALSE;
    }
    
    RADEONWaitForFifo(pScrn, 2);
    OUTREG(RADEON_VIPH_REG_ADDR, address & (~0x2000));
    while(VIP_BUSY == (status = RADEONVIP_idle(b)));
    
    if(VIP_IDLE != status) return FALSE;
    
    RADEONWaitForFifo(pScrn, 2);
    switch(count){
        case 4:
             OUTREG(RADEON_VIPH_REG_DATA, *(CARD32 *)buffer);
             break;
        }
    write_mem_barrier();
    while(VIP_BUSY == (status = RADEONVIP_idle(b)));
    if(VIP_IDLE != status) return FALSE;
    return TRUE;
}

void RADEONVIP_reset(ScrnInfoPtr pScrn, RADEONPortPrivPtr pPriv)
{
    RADEONInfoPtr info = RADEONPTR(pScrn);
    unsigned char *RADEONMMIO = info->MMIO;


    RADEONWaitForIdleMMIO(pScrn);
    switch(info->ChipFamily){
    	case CHIP_FAMILY_RV250:
	    OUTREG(RADEON_VIPH_CONTROL, 0x003F0009); /* slowest, timeout in 16 phases */
	    OUTREG(RADEON_VIPH_TIMEOUT_STAT, (INREG(RADEON_VIPH_TIMEOUT_STAT) & 0xFFFFFF00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);
	    OUTREG(RADEON_VIPH_DV_LAT, 0x444400FF); /* set timeslice */
	    OUTREG(RADEON_VIPH_BM_CHUNK, 0x0);
	    OUTREG(RADEON_TEST_DEBUG_CNTL, INREG(RADEON_TEST_DEBUG_CNTL) & (~RADEON_TEST_DEBUG_CNTL__TEST_DEBUG_OUT_EN));
	    break;
	default:
	    OUTREG(RADEON_VIPH_CONTROL, 0x003F0004); /* slowest, timeout in 16 phases */
	    OUTREG(RADEON_VIPH_TIMEOUT_STAT, (INREG(RADEON_VIPH_TIMEOUT_STAT) & 0xFFFFFF00) | RADEON_VIPH_TIMEOUT_STAT__VIPH_REGR_DIS);
	    OUTREG(RADEON_VIPH_DV_LAT, 0x444400FF); /* set timeslice */
	    OUTREG(RADEON_VIPH_BM_CHUNK, 0x151);
	    OUTREG(RADEON_TEST_DEBUG_CNTL, INREG(RADEON_TEST_DEBUG_CNTL) & (~RADEON_TEST_DEBUG_CNTL__TEST_DEBUG_OUT_EN));
	} 
}

void RADEONVIP_init(ScrnInfoPtr pScrn, RADEONPortPrivPtr pPriv)
{
    pPriv->VIP=xcalloc(1,sizeof(GENERIC_BUS_Rec));
    pPriv->VIP->scrnIndex=pScrn->scrnIndex;
    pPriv->VIP->DriverPrivate.ptr=pPriv;
    pPriv->VIP->ioctl=RADEONVIP_ioctl;
    pPriv->VIP->read=RADEONVIP_read;
    pPriv->VIP->write=RADEONVIP_write;
    
    RADEONVIP_reset(pScrn, pPriv);
}