summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libcpu/powerpc/mpc55xx/edma/edma.c
blob: cedcb60501f0ff6fab6b304755c764f40bb0380a (plain) (tree)
1
2
3
4
5
6
7
8
9








                                               
                                                                      
  








                                                          





                            
                   

                   
                    
 

                                 

                                   




                                   
 
                                   
 
                                    
 
                                                           
 
                                                            
 
                                               
 
                                                                         
 
                                                                      
 
                                                                       
 
                                                                             
 


                                                                               
 
                                                           
 
                                                    
 








                                                                    

 
                                                                  
 






                                         

 
                                                                         
 






                                        

 
                                                                           
 






                                        

 
                                             
 






                                                                     

 
                                                   
 













































                                                                        

 
                                                                         
 
                                                                     
 
                                         
 





                                      
 


                                                                        
 
                                         
 





                                      
 








































                                                                              
 







































































                                                                
 
/**
 * @file
 *
 * @ingroup mpc55xx
 *
 * @brief Enhanced Direct Memory Access (eDMA).
 */

/*
 * Copyright (c) 2008-2011 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Obere Lagerstr. 30
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.com/license/LICENSE.
 */

#include <mpc55xx/regs.h>
#include <mpc55xx/edma.h>
#include <mpc55xx/mpc55xx.h>

#include <assert.h>
#include <string.h>

#include <bsp/irq.h>

#if MPC55XX_CHIP_TYPE / 10 == 551
  #define EDMA_CHANNEL_COUNT 16U
#elif MPC55XX_CHIP_TYPE / 10 == 564
  #define EDMA_CHANNEL_COUNT 16U
#elif MPC55XX_CHIP_TYPE / 10 == 567
  #define EDMA_CHANNEL_COUNT 96U
#else
  #define EDMA_CHANNEL_COUNT 64U
#endif

#define EDMA_CHANNELS_PER_GROUP 32U

#define EDMA_CHANNELS_PER_MODULE 64U

#define EDMA_GROUP_COUNT ((EDMA_CHANNEL_COUNT + 31U) / 32U)

#define EDMA_MODULE_COUNT ((EDMA_CHANNEL_COUNT + 63U) / 64U)

#define EDMA_INVALID_CHANNEL EDMA_CHANNEL_COUNT

#define EDMA_IS_CHANNEL_INVALID(i) ((unsigned) (i) >= EDMA_CHANNEL_COUNT)

#define EDMA_IS_CHANNEL_VALID(i) ((unsigned) (i) < EDMA_CHANNEL_COUNT)

#define EDMA_GROUP_INDEX(channel) ((channel) / EDMA_CHANNELS_PER_GROUP)

#define EDMA_GROUP_BIT(channel) (1U << ((channel) % EDMA_CHANNELS_PER_GROUP))

#define EDMA_MODULE_INDEX(channel) ((channel) / EDMA_CHANNELS_PER_MODULE)

#define EDMA_MODULE_BIT(channel) (1U << ((channel) % EDMA_CHANNELS_PER_MODULE))

static uint32_t edma_channel_occupation [EDMA_GROUP_COUNT];

static RTEMS_CHAIN_DEFINE_EMPTY(edma_channel_chain);

volatile struct EDMA_tag *edma_get_regs_by_channel(unsigned channel)
{
#if EDMA_MODULE_COUNT == 1
  return &EDMA;
#elif EDMA_MODULE_COUNT == 2
  return channel < EDMA_CHANNELS_PER_MODULE ? &EDMA_A : &EDMA_B;
#else
  #error "unsupported module count"
#endif
}

volatile struct EDMA_tag *edma_get_regs_by_module(unsigned module)
{
#if EDMA_MODULE_COUNT == 1
  return &EDMA;
#elif EDMA_MODULE_COUNT == 2
  return module == 0 ? &EDMA_A : &EDMA_B;
#else
  #error "unsupported module count"
#endif
}

static uint32_t edma_bit_array_set(unsigned channel, uint32_t *bit_array)
{
  unsigned array = channel / 32;
  uint32_t bit = 1U << (channel % 32);
  uint32_t previous = bit_array [array];

  bit_array [array] = previous | bit;

  return previous;
}

static uint32_t edma_bit_array_clear(unsigned channel, uint32_t *bit_array)
{
  unsigned array = channel / 32;
  uint32_t bit = 1U << (channel % 32);
  uint32_t previous = bit_array [array];

  bit_array [array] = previous & ~bit;

  return previous;
}

static void edma_interrupt_handler(void *arg)
{
  mpc55xx_edma_channel_entry *e = arg;
  unsigned channel = e->channel;
  volatile struct EDMA_tag *edma = edma_get_regs_by_channel(channel);

  edma->CIRQR.R = (uint8_t) channel;

  e->done(e, 0);
}

static void edma_interrupt_error_handler(void *arg)
{
  rtems_chain_control *chain = &edma_channel_chain;
  rtems_chain_node *node = rtems_chain_first(chain);
  uint32_t error_channels [] = {
#if EDMA_GROUP_COUNT >= 1
    EDMA.ERL.R
#endif
#if EDMA_GROUP_COUNT >= 2
    , EDMA.ERH.R
#endif
#if EDMA_GROUP_COUNT >= 3
    , EDMA_B.ERL.R
#endif
  };
  uint32_t error_status [] = {
#if EDMA_GROUP_COUNT >= 1
    EDMA.ESR.R
#endif
#if EDMA_GROUP_COUNT >= 3
    , EDMA_B.ESR.R
#endif
  };

#if EDMA_GROUP_COUNT >= 1
  EDMA.ERL.R = error_channels [0];
#endif
#if EDMA_GROUP_COUNT >= 2
  EDMA.ERH.R = error_channels [1];
#endif
#if EDMA_GROUP_COUNT >= 3
  EDMA_B.ERL.R = error_channels [2];
#endif

  while (!rtems_chain_is_tail(chain, node)) {
    mpc55xx_edma_channel_entry *e = (mpc55xx_edma_channel_entry *) node;
    unsigned channel = e->channel;
    unsigned group_index = EDMA_GROUP_INDEX(channel);
    unsigned group_bit = EDMA_GROUP_BIT(channel);

    if ((error_channels [group_index] & group_bit) != 0) {
      unsigned module_index = EDMA_MODULE_INDEX(channel);

      e->done(e, error_status [module_index]);
    }

    node = rtems_chain_next(node);
  }
}

void mpc55xx_edma_enable_hardware_requests(unsigned channel, bool enable)
{
  volatile struct EDMA_tag *edma = edma_get_regs_by_channel(channel);

  assert(EDMA_IS_CHANNEL_VALID(channel));

  if (enable) {
    edma->SERQR.R = (uint8_t) channel;
  } else {
    edma->CERQR.R = (uint8_t) channel;
  }
}

void mpc55xx_edma_enable_error_interrupts(unsigned channel, bool enable)
{
  volatile struct EDMA_tag *edma = edma_get_regs_by_channel(channel);

  assert(EDMA_IS_CHANNEL_VALID(channel));

  if (enable) {
    edma->SEEIR.R = (uint8_t) channel;
  } else {
    edma->CEEIR.R = (uint8_t) channel;
  }
}

void mpc55xx_edma_init(void)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;
  unsigned channel_remaining = EDMA_CHANNEL_COUNT;
  unsigned module = 0;
  unsigned group = 0;

  for (module = 0; module < EDMA_MODULE_COUNT; ++module) {
    volatile struct EDMA_tag *edma = edma_get_regs_by_module(module);
    unsigned channel_count = channel_remaining < EDMA_CHANNELS_PER_MODULE ?
      channel_remaining : EDMA_CHANNELS_PER_MODULE;
    unsigned channel = 0;

    channel_remaining -= channel_count;

    /* Arbitration mode: group round robin, channel fixed */
    edma->CR.B.ERGA = 1;
    edma->CR.B.ERCA = 0;
    for (channel = 0; channel < channel_count; ++channel) {
      edma->CPR [channel].R = 0x80U | (channel & 0xfU);
    }

    /* Clear TCDs */
    memset((void *) &edma->TCD [0], 0, channel_count * sizeof(edma->TCD [0]));
  }

  for (group = 0; group < EDMA_GROUP_COUNT; ++group) {
    sc = mpc55xx_interrupt_handler_install(
      MPC55XX_IRQ_EDMA_ERROR(group),
      "eDMA Error",
      RTEMS_INTERRUPT_UNIQUE,
      MPC55XX_INTC_DEFAULT_PRIORITY,
      edma_interrupt_error_handler,
      NULL
    );
    if (sc != RTEMS_SUCCESSFUL) {
      /* FIXME */
      rtems_fatal_error_occurred(0xdeadbeef);
    }
  }
}

rtems_status_code mpc55xx_edma_obtain_channel(
  mpc55xx_edma_channel_entry *e,
  unsigned irq_priority
)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;
  rtems_interrupt_level level;
  unsigned channel = e->channel;
  uint32_t channel_occupation = 0;

  if (EDMA_IS_CHANNEL_INVALID(channel)) {
    return RTEMS_INVALID_ID;
  }

  rtems_interrupt_disable(level);
  channel_occupation = edma_bit_array_set(
    channel,
    &edma_channel_occupation [0]
  );
  rtems_interrupt_enable(level);

  if ((channel_occupation & EDMA_GROUP_BIT(channel))) {
    return RTEMS_RESOURCE_IN_USE;
  }

  sc = mpc55xx_interrupt_handler_install(
    MPC55XX_IRQ_EDMA(channel),
    "eDMA Channel",
    RTEMS_INTERRUPT_SHARED,
    irq_priority,
    edma_interrupt_handler,
    e
  );
  if (sc != RTEMS_SUCCESSFUL) {
    rtems_interrupt_disable(level);
    edma_bit_array_clear(channel, &edma_channel_occupation [0]);
    rtems_interrupt_enable(level);

    return RTEMS_IO_ERROR;
  }

  rtems_chain_prepend(&edma_channel_chain, &e->node);
  mpc55xx_edma_enable_error_interrupts(channel, true);

  return RTEMS_SUCCESSFUL;
}

void mpc55xx_edma_release_channel(mpc55xx_edma_channel_entry *e)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;
  rtems_interrupt_level level;
  unsigned channel = e->channel;

  rtems_interrupt_disable(level);
  edma_bit_array_clear(channel, &edma_channel_occupation [0]);
  rtems_interrupt_enable(level);

  mpc55xx_edma_enable_hardware_requests(channel, false);
  mpc55xx_edma_enable_error_interrupts(channel, false);
  rtems_chain_extract(&e->node);

  sc = rtems_interrupt_handler_remove(
    MPC55XX_IRQ_EDMA(e->channel),
    edma_interrupt_handler,
    e
  );
  if (sc != RTEMS_SUCCESSFUL) {
    /* FIXME */
    rtems_fatal_error_occurred(0xdeadbeef);
  }

  e->done(e, 0);
}