/*
#     ___  _ _      ___
#    |    | | |    |
# ___|    |   | ___|    PS2DEV Open Source Project.
#----------------------------------------------------------
# (c) 2006/7 Eugene Plotnikov <e-plotnikov@operamail.com>
# Licenced under Academic Free License version 2.0
# Review ps2sdk README & LICENSE files for further details.
#
*/
#ifndef __SMS_DMA_H
#define __SMS_DMA_H

#include <kernel.h>

typedef struct DMAChannel {

 volatile unsigned int m_CHCR __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_MADR __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_QWC  __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_TADR __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_ASR0 __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_ASR1 __attribute__(   (  aligned( 16 )  )   );

} DMAChannel;

typedef struct DMACRegs {

 volatile unsigned int m_CTRL  __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_STAT  __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_PCR   __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_SQWC  __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_RBSR  __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_RBOR  __attribute__(   (  aligned( 16 )  )   );
 volatile unsigned int m_STADR __attribute__(   (  aligned( 16 )  )   );

} DMACRegs;

#define DMAC (  ( volatile DMACRegs* )0x1000E000  )

#define DMATAG_ID_REFE 0x0
#define DMATAG_ID_CNT  0x1
#define DMATAG_ID_NEXT 0x2
#define DMATAG_ID_REF  0x3
#define DMATAG_ID_REFS 0x4
#define DMATAG_ID_CALL 0x5
#define DMATAG_ID_RET  0x6
#define DMATAG_ID_END  0x7

typedef union DMATag {

 struct {

  unsigned long QWC  : 16 __attribute__ (  ( packed )  );
  unsigned long m_Pad: 10 __attribute__ (  ( packed )  );
  unsigned long PCE  :  2 __attribute__ (  ( packed )  );
  unsigned long ID   :  3 __attribute__ (  ( packed )  );
  unsigned long IRQ  :  1 __attribute__ (  ( packed )  );
  unsigned long ADDR : 31 __attribute__ (  ( packed )  );
  unsigned long SPR  :  1 __attribute__ (  ( packed )  );

 } __attribute__ (  ( packed )  );

 unsigned long m_Value __attribute__ (  ( packed )  );

} DMATag __attribute__(   (  aligned( 16 )  )   );

#define DMA_TAG( QWC, PCE, ID, IRQ, ADDR, SPR ) (                                                \
 (  ( unsigned long )(                  QWC   ) <<  0  ) | (  ( unsigned long )( PCE ) << 26  ) | \
 (  ( unsigned long )(                  ID    ) << 28  ) | (  ( unsigned long )( IRQ ) << 31  ) | \
 (  ( unsigned long )(  ( unsigned int )ADDR  ) << 32  ) | (  ( unsigned long )( SPR ) << 63  )   \
)

#define DMA_SET_CTRL( A, B, C, D, E, F )                                                        \
 ( unsigned int )( A ) << 0 | ( unsigned int )( B ) << 1 | \
 ( unsigned int )( C ) << 2 | ( unsigned int )( D ) << 4 | \
 ( unsigned int )( E ) << 6 | ( unsigned int )( F ) << 8

#define D_CTRL_RELE_OFF 0
#define D_CTRL_RELE_ON  1

#define D_CTRL_MFD_OFF 0
#define D_CTRL_MFD_RES 1
#define D_CTRL_MFD_VIF 2
#define D_CTRL_MFD_GIF 3

#define D_CTRL_STS_UNSPEC 0
#define D_CTRL_STS_SIF    1
#define D_CTRL_STS_SPR    2
#define D_CTRL_STS_IPU    3

#define D_CTRL_STD_OFF 0
#define D_CTRL_STD_VIF 1
#define D_CTRL_STD_GIF 2
#define D_CTRL_STD_SIF 3

#define D_CTRL_RCYC_8   0
#define D_CTRL_RCYC_16  1
#define D_CTRL_RCYC_32  2
#define D_CTRL_RCYC_64  3
#define D_CTRL_RCYC_128 4
#define D_CTRL_RCYC_256 5

#define D_PCR_CPC_VIF0         0x00000001
#define D_PCR_CPC_VIF1         0x00000002
#define D_PCR_CPC_GIF          0x00000004
#define D_PCR_CPC_FROM_IPU     0x00000008
#define D_PCR_CPC_TO_IPU       0x00000010
#define D_PCR_CPC_FROM_SIF0    0x00000020
#define D_PCR_CPC_TO_SIF1      0x00000040
#define D_PCR_CPC_SIF2         0x00000080
#define D_PCR_CPC_FROM_SPR     0x00000100
#define D_PCR_CPC_TO_SPR       0x00000200

#define D_PCR_CDE_VIF0         0x80010000
#define D_PCR_CDE_VIF1         0x80020000
#define D_PCR_CDE_GIF          0x80040000
#define D_PCR_CDE_FROM_IPU     0x80080000
#define D_PCR_CDE_TO_IPU       0x80100000
#define D_PCR_CDE_FROM_SIF0    0x80200000
#define D_PCR_CDE_TO_SIF1      0x80400000
#define D_PCR_CDE_SIF2         0x80800000
#define D_PCR_CDE_FROM_SPR     0x81000000
#define D_PCR_CDE_TO_SPR       0x82000000

#define DMAC_VIF0      (  ( volatile DMAChannel* )0x10008000  )
#define DMAC_VIF1      (  ( volatile DMAChannel* )0x10009000  )
#define DMAC_GIF       (  ( volatile DMAChannel* )0x1000A000  )
#define DMAC_FROM_IPU  (  ( volatile DMAChannel* )0x1000B000  )
#define DMAC_TO_IPU    (  ( volatile DMAChannel* )0x1000B400  )
#define DMAC_FROM_SIF0 (  ( volatile DMAChannel* )0x1000C000  )
#define DMAC_TO_SIF1   (  ( volatile DMAChannel* )0x1000C400  )
#define DMAC_SIF2      (  ( volatile DMAChannel* )0x1000C800  )
#define DMAC_FROM_SPR  (  ( volatile DMAChannel* )0x1000D000  )
#define DMAC_TO_SPR    (  ( volatile DMAChannel* )0x1000D400  )

#define DMAC_I_VIF0      0
#define DMAC_I_VIF1      1
#define DMAC_I_GIF       2
#define DMAC_I_FROM_IPU  3
#define DMAC_I_TO_IPU    4
#define DMAC_I_FROM_SIF0 5
#define DMAC_I_TO_SIF1   6
#define DMAC_I_SIF2      7
#define DMAC_I_FROM_SPR  8
#define DMAC_I_TO_SPR    9

#ifdef __cplusplus
extern "C" {
#endif  /* __cplusplus */

static void inline DMA_Wait ( volatile DMAChannel* apChan ) {
 while ( apChan -> m_CHCR & 0x00000100 );
}  /* end DMA_Wait */

static void inline DMA_Send ( volatile DMAChannel* apChan, void* apAddr, unsigned int aQWC ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "1:\n\t"
  "lw   $at, 0(%0)\n\t"
  "nop\n\t"
  "nop\n\t"
  "andi $at, $at, 0x0100\n\t"
  "nop\n\t"
  "bne  $at, $zero, 1b\n\t"
  "li   $at, 0x0101\n\t"
  "sw   %1, 16(%0)\n\t"
  "sw   %2, 32(%0)\n\t"
  "sw   $at, 0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apAddr ), "r"( aQWC ) : "at"
 );
}  /* end DMA_Send */

static void inline DMA_SendA ( volatile DMAChannel* apChan, void* apAddr, unsigned int aQWC ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "li   $at, 0x0101\n\t"
  "sw   %1, 16(%0)\n\t"
  "sw   %2, 32(%0)\n\t"
  "sw   $at, 0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apAddr ), "r"( aQWC ) : "at"
 );
}  /* end DMA_SendA */

static void inline DMA_SendChain ( volatile DMAChannel* apChan, void* apAddr ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "1:\n\t"
  "lw   $at, 0(%0)\n\t"
  "nop\n\t"
  "nop\n\t"
  "andi $at, $at, 0x0100\n\t"
  "nop\n\t"
  "bne  $at, $zero, 1b\n\t"
  "li   $at, 0x0105\n\t"
  "sw   $zero, 32(%0)\n\t"
  "sw   %1,    48(%0)\n\t"
  "sw   $at,    0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apAddr ) : "at"
 );
}  /* end DMA_SendChain */

static void inline DMA_SendChainA ( volatile DMAChannel* apChan, void* apAddr ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "li   $at, 0x0105\n\t"
  "sw   $zero, 32(%0)\n\t"
  "sw   %1,    48(%0)\n\t"
  "sw   $at,    0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apAddr ) : "at"
 );
}  /* end DMA_SendChainA */

static void inline DMA_SendChainT ( volatile DMAChannel* apChan, void* apAddr ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "1:\n\t"
  "lw   $at, 0(%0)\n\t"
  "nop\n\t"
  "nop\n\t"
  "andi $at, $at, 0x0100\n\t"
  "nop\n\t"
  "bne  $at, $zero, 1b\n\t"
  "li   $at, 0x0145\n\t"
  "sw   $zero, 32(%0)\n\t"
  "sw   %1,    48(%0)\n\t"
  "sw   $at,    0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apAddr ) : "at"
 );
}  /* end DMA_SendChainT */

static void inline DMA_RecvA ( volatile DMAChannel* apChan, void* apAddr, unsigned int aQWC ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "li   $at, 0x0100\n\t"
  "sw   %1, 16(%0)\n\t"
  "sw   %2, 32(%0)\n\t"
  "sw   $at, 0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apAddr ), "r"( aQWC ) : "at"
 );
}  /* end DMA_RecvA */

static void inline DMA_RecvS ( volatile DMAChannel* apChan, void* apDst, void* apSrc, unsigned int aQWC ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "li   $at, 0x0101\n\t"
  "sw   %2,  16(%0)\n\t"
  "sw   %3,  32(%0)\n\t"
  "sw   %1, 128(%0)\n\t"
  "sw   $at,  0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apDst ), "r"( apSrc ), "r"( aQWC ) : "at"
 );

}  /* end DMA_RecvS */

static void inline DMA_SendS ( volatile DMAChannel* apChan, void* apDst, void* apSrc, unsigned int aQWC ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "1:\n\t"
  "lw   $at, 0(%0)\n\t"
  "nop\n\t"
  "nop\n\t"
  "andi $at, $at, 0x0100\n\t"
  "nop\n\t"
  "bne  $at, $zero, 1b\n\t"
  "li   $at, 0x0100\n\t"
  "sw   %1,  16(%0)\n\t"
  "sw   %3,  32(%0)\n\t"
  "sw   %2, 128(%0)\n\t"
  "sw   $at,  0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apDst ), "r"( apSrc ), "r"( aQWC ) : "at"
 );
}  /* end DMA_SendS */

static void inline DMA_SendSA ( volatile DMAChannel* apChan, void* apDst, void* apSrc, unsigned int aQWC ) {
 __asm__ __volatile__(
  ".set noreorder\n\t"
  ".set noat\n\t"
  "addiu    $at, $zero, 0x0100\n\t"
  "sw       %1,  16(%0)\n\t"
  "sw       %3,  32(%0)\n\t"
  "sw       %2, 128(%0)\n\t"
  "sw       $at,  0(%0)\n\t"
  ".set at\n\t"
  ".set reorder\n\t"
  ::"r"( apChan ), "r"( apDst ), "r"( apSrc ), "r"( aQWC ) : "at"
 );
}  /* end DMA_SendSA */

void DMA_Stop ( volatile DMAChannel* );

#ifdef __cplusplus
}
#endif  /* __cplusplus */
#endif  /* __SMS_DMA_H */
