/**
 * @addtogroup pilote_arm_interruption
 * @{
 */

/**
 * @file interruption.c
 *
 * @brief Impl�mentation du pilote du contr�leur d'interruptions du microcontr�leur OKI ML675001
 *
 * @todo Ajouter les autres gestionnaires d'exceptions
 *
 * $Author$
 * $Date$
 *
 * @version 2.0
 * $Revision$
 */

#include "interruption.h"
#include "interruption_parametres_possibles.h"
#include "interruption_configuration_par_defaut.h"
#include "interruption_registres.h"

#define NULL ((void *)0)

/**
 * @brief Tableau des fonctions de gestion d'interruptions (IRQ)
 *
 * Chaque �l�ment de ce tableau correspond � une source d'interruption support�e
 */
static Interruption_Gestionnaire Interruption_gestionnaires_irq [ INTERRUPTION_NOMBRE_SOURCES ]; 

/**
 * @brief Tableau de pointeurs vers les donn�es utilisateur � passer aux gestionnaires d'interruptions (IRQ)
 */
static void *Interruption_arguments_irq [ INTERRUPTION_NOMBRE_SOURCES ];

/** @brief Pointeur vers la fonction de gestion des interruptions rapides (FIQ) */
static Interruption_Gestionnaire Interruption_gestionnaire_fiq;

/** @brief Pointeur vers les donn�es utilisateur � passer au gestionnaire d'interruptiosn rapides (FIQ) */
static void * Interruption_arguments_fiq;

/** @brief Tableau des enregistrements de gestionnaires d'appels syst�mes (SWI) */
static struct
{
   /** @brief Num�ro d'appel syst�me */
   UInt32 numero_service;

   /** @brief Pointeur vers la fonction de traitement de la demande de service */
   Interruption_GestionnaireAppelSysteme gestionnaire;

} Interruption_gestionnaires_swi [ INTERRUPTION_NOMBRE_SERVICES_SYSTEME ];

/** @brief Nombre de services syst�mes enregistres */
static UInt32 Interruption_nombre_services;

/**
 * @name Gestionnaires d'interruptions
 * @{
 */

/**
 * @brief Gestionnaire d'interruption par d�faut pour les sources support�es
 */
static void Interruption_gestionnaire_par_defaut ();

/** @} */

/**
 * @name Fonctions d'initialisation 
 * @{
 */

void Interruption_initialiser ()
{
   int s;
   Interruption_desactiver_irq ();
   Interruption_desactiver_fiq ();

   /* Mise en place d'un gestionnaire par d�faut pour les diff�rentes sources d'interruptions
    * et d�sactivation de toutes les sources */

   for(s=0; s<INTERRUPTION_NOMBRE_SOURCES; s++) {
      Interruption_set_gestionnaire_irq (s, Interruption_gestionnaire_par_defaut, NULL);
      Interruption_set_priorite(s, 0);
   }
   Interruption_set_gestionnaire_fiq (Interruption_gestionnaire_par_defaut, NULL);

   Interruption_nombre_services = 0;

   /* D�tection sur niveau bas par d�faut pour les interruptions externes */
   Interruption_set_detection (INTERRUPTION_EXTERNE_0, INTERRUPTION_DETECTION_NIVEAU_BAS);
   Interruption_set_detection (INTERRUPTION_EXTERNE_1, INTERRUPTION_DETECTION_NIVEAU_BAS);
   Interruption_set_detection (INTERRUPTION_EXTERNE_2, INTERRUPTION_DETECTION_NIVEAU_BAS);
   Interruption_set_detection (INTERRUPTION_EXTERNE_3, INTERRUPTION_DETECTION_NIVEAU_BAS);
}
 
/** @} */ 

/**
 * @name Fonctions de configuration
 * @{
 */

void Interruption_set_priorite (int source, int niveau)
{
   Interruption1 *Interruption_banque1 = (Interruption1*)INTERRUPTION_ADRESSE_BASE_1;
   Interruption2 *Interruption_banque2 = (Interruption2*)INTERRUPTION_ADRESSE_BASE_2;

	switch(source)
	{
      case INTERRUPTION_TIMER_SYSTEME:                
         Interruption_banque1->ilc0.ilr0 = niveau;
         break;

      case INTERRUPTION_WATCHDOG:                     
      case INTERRUPTION_INTERVALLE_WATCHDOG:          
         Interruption_banque1->ilc0.ilr1 = niveau;
         break;

      case INTERRUPTION_GPIO_A:                       
      case INTERRUPTION_GPIO_B:                       
         Interruption_banque1->ilc0.ilr4 = niveau;
         break;

      case INTERRUPTION_GPIO_C:                       
      case INTERRUPTION_GPIO_DE:                      
         Interruption_banque1->ilc0.ilr6 = niveau;
         break;

      case INTERRUPTION_LOGICIELLE:                   
         Interruption_banque1->ilc1.ilr8 = niveau;
         break;

      case INTERRUPTION_UART:                         
         Interruption_banque1->ilc1.ilr9 = niveau;
         break;

      case INTERRUPTION_SIO:                          
         Interruption_banque1->ilc1.ilr10 = niveau;
         break;

      case INTERRUPTION_CONVERTISSEUR_AN:             
         Interruption_banque1->ilc1.ilr11 = niveau;
         break;

      case INTERRUPTION_PWM_0:                        
         Interruption_banque1->ilc1.ilr12 = niveau;
         break;

      case INTERRUPTION_PWM_1:                        
         Interruption_banque1->ilc1.ilr13 = niveau;
         break;

      case INTERRUPTION_SSIO:                        
         Interruption_banque1->ilc1.ilr14 = niveau;
         break;

      case INTERRUPTION_I2C:                          
         Interruption_banque1->ilc1.ilr15 = niveau;
         break;

      case INTERRUPTION_TIMER_0:                      
      case INTERRUPTION_TIMER_1:                      
         Interruption_banque2->ilc.ilr16 = niveau;
         break;

      case INTERRUPTION_TIMER_2:                      
      case INTERRUPTION_TIMER_3:                      
         Interruption_banque2->ilc.ilr18 = niveau;
         break;

      case INTERRUPTION_TIMER_4:                      
      case INTERRUPTION_TIMER_5:                      
         Interruption_banque2->ilc.ilr20 = niveau;
         break;

      case INTERRUPTION_EXTERNE_0:                    
         Interruption_banque2->ilc.ilr22 = niveau;
         break;

      case INTERRUPTION_DMA_0:                        
      case INTERRUPTION_DMA_1:                        
         Interruption_banque2->ilc.ilr24 = niveau;
         break;

      case INTERRUPTION_EXTERNE_1:                    
         Interruption_banque2->ilc.ilr26 = niveau;
         break;

      case INTERRUPTION_EXTERNE_2:                    
      case INTERRUPTION_EXTERNE_3:                    
         Interruption_banque2->ilc.ilr28 = niveau;
	}
}

int Interruption_get_priorite (int source)
{
   Interruption1 *Interruption_banque1 = (Interruption1*)INTERRUPTION_ADRESSE_BASE_1;
   Interruption2 *Interruption_banque2 = (Interruption2*)INTERRUPTION_ADRESSE_BASE_2;

	switch(source)
	{
      case INTERRUPTION_TIMER_SYSTEME:                
         return Interruption_banque1->ilc0.ilr0;
         break;

      case INTERRUPTION_WATCHDOG:                     
      case INTERRUPTION_INTERVALLE_WATCHDOG:          
         return Interruption_banque1->ilc0.ilr1;
         break;

      case INTERRUPTION_GPIO_A:                       
      case INTERRUPTION_GPIO_B:                       
         return Interruption_banque1->ilc0.ilr4;
         break;

      case INTERRUPTION_GPIO_C:                       
      case INTERRUPTION_GPIO_DE:                      
         return Interruption_banque1->ilc0.ilr6;
         break;

      case INTERRUPTION_LOGICIELLE:                   
         return Interruption_banque1->ilc1.ilr8;
         break;

      case INTERRUPTION_UART:                         
         return Interruption_banque1->ilc1.ilr9;
         break;

      case INTERRUPTION_SIO:                          
         return Interruption_banque1->ilc1.ilr10;
         break;

      case INTERRUPTION_CONVERTISSEUR_AN:             
         return Interruption_banque1->ilc1.ilr11;
         break;

      case INTERRUPTION_PWM_0:                        
         return Interruption_banque1->ilc1.ilr12;
         break;

      case INTERRUPTION_PWM_1:                        
         return Interruption_banque1->ilc1.ilr13;
         break;

      case INTERRUPTION_SSIO:                         
         return Interruption_banque1->ilc1.ilr14;
         break;

      case INTERRUPTION_I2C:                          
         return Interruption_banque1->ilc1.ilr15;
         break;

      case INTERRUPTION_TIMER_0:                      
      case INTERRUPTION_TIMER_1:                      
         return Interruption_banque2->ilc.ilr16;
         break;

      case INTERRUPTION_TIMER_2:                      
      case INTERRUPTION_TIMER_3:                      
         return Interruption_banque2->ilc.ilr18;
         break;

      case INTERRUPTION_TIMER_4:                      
      case INTERRUPTION_TIMER_5:                      
         return Interruption_banque2->ilc.ilr20;
         break;

      case INTERRUPTION_EXTERNE_0:                    
         return Interruption_banque2->ilc.ilr22;
         break;

      case INTERRUPTION_DMA_0:                        
      case INTERRUPTION_DMA_1:                        
         return Interruption_banque2->ilc.ilr24;
         break;

      case INTERRUPTION_EXTERNE_1:                    
         return Interruption_banque2->ilc.ilr26;
         break;

      case INTERRUPTION_EXTERNE_2:                    
      case INTERRUPTION_EXTERNE_3:                    
         return Interruption_banque2->ilc.ilr28;
	}
}

void Interruption_set_gestionnaire_irq (int source, Interruption_Gestionnaire gestionnaire, void *arguments)
{
	Interruption_gestionnaires_irq[source] = gestionnaire;
   Interruption_arguments_irq[source] = arguments;
}

void Interruption_set_gestionnaire_fiq (Interruption_Gestionnaire gestionnaire, void *arguments)
{
	Interruption_gestionnaire_fiq = gestionnaire;
   Interruption_arguments_fiq = arguments;
}

void Interruption_set_gestionnaire_swi (UInt32 numero_service, Interruption_GestionnaireAppelSysteme gestionnaire)
{
   if(Interruption_nombre_services < INTERRUPTION_NOMBRE_SERVICES_SYSTEME) {
      Interruption_gestionnaires_swi [Interruption_nombre_services].numero_service = numero_service;
      Interruption_gestionnaires_swi [Interruption_nombre_services].gestionnaire = gestionnaire;
      ++ Interruption_nombre_services;
   }
}

void Interruption_set_detection (int source, int mode)
{
   Interruption2 *Interruption_banque2 = (Interruption2*)INTERRUPTION_ADRESSE_BASE_2;

   switch(source)
   {
      case INTERRUPTION_EXTERNE_0:                    
         Interruption_banque2->idm.idm22 = mode;
         break;

      case INTERRUPTION_EXTERNE_1:                    
         Interruption_banque2->idm.idm26 = mode;
         break;

      case INTERRUPTION_EXTERNE_2:                    
         Interruption_banque2->idm.idm28 = mode;
         break;

      case INTERRUPTION_EXTERNE_3:                    
         Interruption_banque2->idm.idm31 = mode;
   }
}

/** @} */

/**
 * @name Gestionnaires d'interruptions
 * @{
 */

/**
 * @brief Gestionnaire principal des interruptions de type IRQ
 *
 * Cette fonction est invoqu�e syst�matiquement pour toutes les demandes d'interruptions de type IRQ.
 * Elle se charge de rediriger le traitement vers le gestionnaire affect� � la source de la demande.
 */
__attribute__ ((interrupt ("IRQ")))
void Interruption_gestionnaire_principal_irq ()
{
   Interruption1 *Interruption_banque1 = (Interruption1*)INTERRUPTION_ADRESSE_BASE_1;

   /* Identification du num�ro de la source de la demande */
   int source = Interruption_banque1->irn.source;

   /* Invocation du gestionnaire d'interruption pour la source identifi�e */
   Interruption_gestionnaires_irq[source] (Interruption_arguments_irq[source]);

   Interruption_acquitter_niveau_courant ();
}

void Interruption_acquitter_niveau_courant ()
{
   Interruption1 *Interruption_banque1 = (Interruption1*)INTERRUPTION_ADRESSE_BASE_1;
   Interruption2 *Interruption_banque2 = (Interruption2*)INTERRUPTION_ADRESSE_BASE_2;

   int source = Interruption_banque1->irn.source;

   /* Acquittement de la demande pour le niveau de priorit� courant */
   Interruption_banque1->cilcl = INTERRUPTION_ACQUITTEMENT;

   /* Acquittement de la demande pour une interruption externe avec d�tection sur front */
   if(source == INTERRUPTION_EXTERNE_0 ||
      source == INTERRUPTION_EXTERNE_1 ||
      source == INTERRUPTION_EXTERNE_2 ||
      source == INTERRUPTION_EXTERNE_3)
      Interruption_banque2->ircl = source;
}

/**
 * @brief Gestionnaire principal des interruptions de type FIQ
 *
 * Cette fonction est invoqu�e syst�matiquement pour toutes les demandes d'interruptions de type FIQ.
 * Elle se charge de rediriger le traitement vers le gestionnaire affect� � la source de la demande.
 */
__attribute__ ((interrupt ("FIQ")))
void Interruption_gestionnaire_principal_fiq ()
{
   Interruption_gestionnaire_fiq (Interruption_arguments_fiq);

   /* @todo Acquitter les FIQ ?*/
}

UInt32 Interruption_gestionnaire_principal_swi (UInt32 numero_service, UInt32 *arguments)
{
   UInt32 i;
   for(i=0; i<Interruption_nombre_services; i++) {
      if(Interruption_gestionnaires_swi[i].numero_service == numero_service)
         return Interruption_gestionnaires_swi[i].gestionnaire(arguments);
   }
}

static void Interruption_gestionnaire_par_defaut ()
{
   
}

/** @} */

/**
 * @name Fonctions de gestion des �v�nements
 * @{
 */
int Interruption_get_source ()
{
  Interruption1 *Interruption_banque1 = (Interruption1*)INTERRUPTION_ADRESSE_BASE_1;
  return Interruption_banque1->irn.source;
}

void Interruption_activer_irq ()
{
   /* Ce sous-programme agit directement sur le registre d'�tat
    * en positionnant � 0 le bit IRQ (bit 7) */
   __asm__(
      "mrs r4, cpsr;"
      "bic r4, r4, #0x80;"
      "msr cpsr_c, r4"
   );
}

void Interruption_desactiver_irq ()
{
   /* Ce sous-programme agit directement sur le registre d'�tat
    * en positionnant � 1 le bit IRQ (bit 7) */
   __asm__(
      "mrs r4, cpsr;"
      "orr r4, r4, #0x80;"
      "msr cpsr_c, r4"
   );
}

void Interruption_activer_fiq ()
{
   /* Ce sous-programme agit directement sur le registre d'�tat
    * en positionnant � 0 le bit FIQ (bit 6) */
   __asm__(
      "mrs r4, cpsr;"
      "bic r4, r4, #0x40;"
      "msr cpsr_c, r4"
   );
}

void Interruption_desactiver_fiq ()
{
   /* Ce sous-programme agit directement sur le registre d'�tat
    * en positionnant � 1 le bit FIQ (bit 6) */
   __asm__(
      "mrs r4, cpsr;"
      "orr r4, r4, #0x40;"
      "msr cpsr_c, r4"
   );
}

/** @} */

/** @}*/