view src/cs/layer1/tm_cust0/l1tm_cust.c @ 303:f76436d19a7a default tip

!GPRS config: fix long-standing AT+COPS chance hanging bug There has been a long-standing bug in FreeCalypso going back years: sometimes in the AT command bring-up sequence of an ACI-only MS, the AT+COPS command would produce only a power scan followed by cessation of protocol stack activity (only L1 ADC traces), instead of the expected network search sequence. This behaviour was seen in different FC firmware versions going back to Citrine, and seemed to follow some law of chance, not reliably repeatable. This bug has been tracked down and found to be specific to !GPRS configuration, stemming from our TCS2/TCS3 hybrid and reconstruction of !GPRS support that was bitrotten in TCS3.2/LoCosto version. ACI module psa_mms.c, needed only for !GPRS, was missing in the TCS3 version and had to be pulled from TCS2 - but as it turns out, there is a new field in the MMR_REG_REQ primitive that needs to be set correctly, and that psa_mms.c module is the place where this initialization needed to be added.
author Mychaela Falconia <falcon@freecalypso.org>
date Thu, 08 Jun 2023 08:23:37 +0000
parents 4e78acac3d88
children
line wrap: on
line source

/************* Revision Controle System Header *************
 *                  GSM Layer 1 software
 * L1TM_CUST.C
 *
 *        Filename %M%
 *        Version  %I%
 *        Date     %G%
 * 
 ************* Revision Controle System Header *************/

#include "l1_confg.h"
#if TESTMODE

#include "tm_defs.h"
#include "l1_const.h"
#include "l1_types.h"

#include "l1tm_defty.h"
#include "l1tm_cust.h"

#if (AUDIO_TASK == 1)
  #include "l1audio_const.h"
  #include "l1audio_cust.h"
  #include "l1audio_defty.h"
#endif
  
#if (L1_GTT == 1)
  #include "l1gtt_const.h"
  #include "l1gtt_defty.h"
#endif
#include "l1_defty.h"
#include "l1_msgty.h"
#include "l1_tabs.h"

#include "l1tm_msgty.h"
#include "l1tm_varex.h"

#include "abb.h"

#if (RF==35)
  #include "tpudrv35.h"
  #include "l1_rf35.h"
#endif

#if (RF==12)
  #include "tpudrv12.h"
  #include "l1_rf12.h"
#endif

#if (RF==10)
  #include "tpudrv10.h"
  #include "l1_rf10.h"
#endif

#if (RF==8)
  #include "tpudrv8.h"
  #include "l1_rf8.h"
#endif

#if (RF==2)
  #include "l1_rf2.h"
#endif

#include <string.h>

// Import band configuration from Flash module (need to replace by an access function)
//extern UWORD8       std;
extern T_L1_CONFIG  l1_config;
extern T_RF rf;
extern T_RF_BAND rf_band[GSM_BANDS];
extern UWORD16 AGC_TABLE[AGC_TABLE_SIZE];
extern T_ADC adc;
extern T_ADCCAL adc_cal;
extern UWORD16 TM_ul_data[16]; //Uplink data to be stored into ABB Uplink buffer
extern T_STD_CONFIG std_config[];
static UWORD8 tm_band = 0;

// External function prototypes
void get_cal_from_nvmem (UWORD8 *ptr, UWORD16 len, UWORD8 id);
UWORD8 save_cal_in_nvmem (UWORD8 *ptr, UWORD16 len, UWORD8 id);
void Cust_init_std(void);
void l1_tpu_init_light(void);

enum {
  TM_RF_ID        = 0,
  TM_ADC_ID       = 1
};

typedef signed char effs_t;
// external FFS function prototypes
effs_t ffs_mkdir(const char *pathname);
void config_ffs_write(char type);

/***********************************************************************/
/*                           TESTMODE 4.X                              */
/***********************************************************************/


/*----------------------------------------------------------*/
/* Cust_tm_init()                                           */
/*----------------------------------------------------------*/
/* Parameters :                                             */
/* Return     :                                             */
/* Functionality : Init default configuration for TM params */
/*----------------------------------------------------------*/

void Cust_tm_init(void)
{
  UWORD32 i;

  l1_config.adc_enable                             = ADC_ENABLE; // ADC readings enabled
  l1_config.agc_enable                             = AGC_ENABLE; // AGC algo enabled
  l1_config.afc_enable                             = AFC_ENABLE; // AFC algo enabled
  l1_config.tmode.rf_params.bcch_arfcn             = TM_BCCH_ARFCN;
  l1_config.tmode.rf_params.tch_arfcn              = TM_TCH_ARFCN;
  l1_config.tmode.rf_params.mon_arfcn              = TM_MON_ARFCN;
  l1_config.tmode.rf_params.channel_type           = TM_CHAN_TYPE; // TCH_F
  l1_config.tmode.rf_params.subchannel             = TM_SUB_CHAN;
  l1_config.tmode.rf_params.reload_ramps_flag      = 0;
  l1_config.tmode.rf_params.tmode_continuous       = TM_NO_CONTINUOUS;
  l1_config.tmode.rx_params.slot_num               = TM_SLOT_NUM; // Time Slot
  l1_config.tmode.rx_params.agc                    = TM_AGC_VALUE; //This may be outside the range of the RF chip used
  l1_config.tmode.rx_params.pm_enable              = TM_PM_ENABLE;
  l1_config.tmode.rx_params.lna_off                = TM_LNA_OFF;
  l1_config.tmode.rx_params.number_of_measurements = TM_NUM_MEAS;  
  l1_config.tmode.rx_params.place_of_measurement   = TM_WIN_MEAS;
  l1_config.tmode.tx_params.txpwr                  = TM_TXPWR; // Min power level for GSM900
  l1_config.tmode.tx_params.txpwr_skip             = TM_TXPWR_SKIP;
  l1_config.tmode.tx_params.timing_advance         = TM_TA;
  l1_config.tmode.tx_params.burst_type             = TM_BURST_TYPE; // default is normal up-link burst
  l1_config.tmode.tx_params.burst_data             = TM_BURST_DATA; // default is all zeros
  l1_config.tmode.tx_params.tsc                    = TM_TSC; // Training Sequence ("BCC" on BSS)
  #if (CODE_VERSION != SIMULATION)
    l1_config.tmode.stats_config.num_loops         = TM_NUM_LOOPS; // 0 actually means infinite
  #else
    l1_config.tmode.stats_config.num_loops         = 4; // 0 actually means infinite
  #endif
  l1_config.tmode.stats_config.auto_result_loops   = TM_AUTO_RESULT_LOOPS; // 0 actually means infinite
  l1_config.tmode.stats_config.auto_reset_loops    = TM_AUTO_RESET_LOOPS; // 0 actually means infinite
  l1_config.tmode.stats_config.stat_type           = TM_STAT_TYPE;
  l1_config.tmode.stats_config.stat_bitmask        = TM_STAT_BITMASK;

  #if (CODE_VERSION != SIMULATION)
    // Initialize APCDEL1 register of Omega
    ABB_Write_Register_on_page(PAGE0, APCDEL1, (C_APCDEL1 - 0x0004) >> 6);
  #endif

  l1tm.tm_msg_received                       = FALSE;

  for (i=0;i<16;i++)
    TM_ul_data[i]=0;

  #if L1_GPRS
    l1_config.tmode.rf_params.pdtch_arfcn          = TM_PDTCH_ARFCN;
    l1_config.tmode.rf_params.multislot_class      = TM_MULTISLOT_CLASS;
    l1_config.tmode.stats_config.stat_gprs_slots   = TM_STAT_GPRS_SLOTS;
    l1_config.tmode.rx_params.timeslot_alloc       = TM_RX_ALLOCATION;
    l1_config.tmode.rx_params.coding_scheme        = TM_RX_CODING_SCHEME;
    l1_config.tmode.tx_params.timeslot_alloc       = TM_TX_ALLOCATION;
    l1_config.tmode.tx_params.coding_scheme        = TM_TX_CODING_SCHEME;
    for (i=0; i<8; i++)
      l1_config.tmode.tx_params.txpwr_gprs[i]      = TM_TXPWR_GPRS;

    for (i=0; i<27; i++)
      l1_config.tmode.tx_params.rlc_buffer[i] = 0;
  #endif
}


/**********************************************************************/
/* Test mode functions used for RF calibration                        */
/**********************************************************************/

void Cust_tm_rf_param_write(T_TM_RETURN *tm_return, WORD16 index, UWORD16 value)
{
  switch (index)
  {
    case STD_BAND_FLAG:
    {
      UWORD8 std_temp, band_temp;

      std_temp  = value & 0xff;  // tm_band = b7..0 of value
      band_temp = value >> 8;    // band    = b15..8 of value
          // get define 
      //if (sizeof(std_config)/sizeof(T_STD_CONFIG) <= std_temp)
      if (9 <= std_temp)  // std max
      {
        tm_return->status = E_BADINDEX;
        break;
      }
      else if ( GSM_BANDS <= band_temp)
      {
        tm_return->status = E_BADINDEX;
        break;
      }
      else if ( BAND_NONE == std_config[std_temp].band[band_temp])
      {
        tm_return->status = E_BADINDEX;
        break;
      } 
      else
      {
        l1_config.std.id = std_temp;  
        tm_band = band_temp; 
        // update RAM struct with either default or ffs
        Cust_init_std();
        l1_tpu_init_light();
        tm_return->status = E_OK;
        break;
      }
    }
    
    case INITIAL_AFC_DAC:
    {
      rf.afc.eeprom_afc = (WORD16) value << 3; // shift to put into F13.3 format
      l1_config.params.eeprom_afc = rf.afc.eeprom_afc;

      tm_return->status = E_OK;
      break;
    }
    default:
    {
      tm_return->status = E_BADINDEX;
      break;
    }
  } // end switch
}

void Cust_tm_rf_param_read(T_TM_RETURN *tm_return, WORD16 index)
{
  volatile UWORD16 value;

  switch (index)
  {
    case STD_BAND_FLAG:
    {
       value = ((tm_band << 8) | (l1_config.std.id) ); // return global std, tm_band (intel format)
      break;
    }
    case INITIAL_AFC_DAC:
    {
      value = rf.afc.eeprom_afc >> 3; // returned as F13.3
      break;
    }
    default:
    {
      tm_return->size = 0;
      tm_return->status = E_BADINDEX;
      return;
    }
  } // end switch

  memcpy(tm_return->result, (UWORD8 *)&value, 2);
  tm_return->size = 2;
  tm_return->status = E_OK;
}

void Cust_tm_rf_table_write(T_TM_RETURN *tm_return, WORD8 index, UWORD8 size, UWORD8 table[])
{
  UWORD8 band=0;

  tm_return->index = index;  // store index before it gets modified
  tm_return->size = 0;

  switch (index)
  {
    case RX_AGC_TABLE:
    {
      if (size != sizeof(AGC_TABLE))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&AGC_TABLE[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case AFC_PARAMS:
    {

      #if (VCXO_ALGO == 1)
        if (size != 24)  //  4 UWORD32 + 4 WORD16 values
      #else
        if (size != 16)  // 4 UWORD32 values
      #endif
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf.afc.psi_sta_inv, table, size);
      l1_config.params.psi_sta_inv  = rf.afc.psi_sta_inv;
      l1_config.params.psi_st       = rf.afc.psi_st;
      l1_config.params.psi_st_32    = rf.afc.psi_st_32;
      l1_config.params.psi_st_inv   = rf.afc.psi_st_inv;

    #if (CODE_VERSION == NOT_SIMULATION)
      #if (VCXO_ALGO == 1)
        l1_config.params.afc_dac_center = rf.afc.dac_center;
        l1_config.params.afc_dac_min    = rf.afc.dac_min;   
        l1_config.params.afc_dac_max    = rf.afc.dac_max;   
        l1_config.params.afc_snr_thr    = rf.afc.snr_thr;   
      #endif
    #endif

      tm_return->status = E_OK;
      break;
    }
    case RX_AGC_GLOBAL_PARAMS:
    {
      if (size != 8)  // 4 UWORD16 values
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf.rx.agc.low_agc_noise_thr, table, size);
      l1_config.params.low_agc_noise_thr  = rf.rx.agc.low_agc_noise_thr;
      l1_config.params.high_agc_sat_thr   = rf.rx.agc.high_agc_sat_thr;
      l1_config.params.low_agc            = rf.rx.agc.low_agc;
      l1_config.params.high_agc           = rf.rx.agc.high_agc;

      tm_return->status = E_OK;
      break;
    }
    case RX_IL_2_AGC_MAX:
    {
      if (size != sizeof(rf.rx.agc.il2agc_max))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf.rx.agc.il2agc_max[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case RX_IL_2_AGC_PWR:
    {
      if (size != sizeof(rf.rx.agc.il2agc_pwr))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf.rx.agc.il2agc_pwr[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case RX_IL_2_AGC_AV:
    {
      if (size != sizeof(rf.rx.agc.il2agc_av))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf.rx.agc.il2agc_av[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case TX_LEVELS:
    {
      if (size != sizeof(rf_band[tm_band].tx.levels))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf_band[tm_band].tx.levels[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case TX_CAL_CHAN: // generic for all bands
    {
      if (size != sizeof(rf_band[tm_band].tx.chan_cal_table))
      {
        tm_return->status = E_BADSIZE;
        break;
      }
      
      memcpy(&rf_band[tm_band].tx.chan_cal_table[0][0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case TX_CAL_TEMP: // generic for all bands
    {
      if (size != sizeof(rf_band[tm_band].tx.temp))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf_band[tm_band].tx.temp[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case RX_CAL_CHAN:  // generic for all bands
    {
      if (size != sizeof(rf_band[tm_band].rx.agc_bands))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf_band[tm_band].rx.agc_bands[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case RX_CAL_TEMP:  // generic for all bands
    {
      if (size != sizeof(rf_band[tm_band].rx.temp))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf_band[tm_band].rx.temp[0], table, size);
      tm_return->status = E_OK;
      break;
    }
    case RX_AGC_PARAMS:
    {
      if (size != sizeof(rf_band[tm_band].rx.rx_cal_params))
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&rf_band[tm_band].rx.rx_cal_params, table, size);
      if (tm_band == 0)
      {
          l1_config.std.g_magic_band1             = rf_band[tm_band].rx.rx_cal_params.g_magic;
          l1_config.std.lna_att_band1             = rf_band[tm_band].rx.rx_cal_params.lna_att;
          l1_config.std.lna_switch_thr_low_band1  = rf_band[tm_band].rx.rx_cal_params.lna_switch_thr_low;
          l1_config.std.lna_switch_thr_high_band1 = rf_band[tm_band].rx.rx_cal_params.lna_switch_thr_high;
        }
      else if (tm_band == 1)
        {
          l1_config.std.g_magic_band2             = rf_band[tm_band].rx.rx_cal_params.g_magic;
          l1_config.std.lna_att_band2             = rf_band[tm_band].rx.rx_cal_params.lna_att;
          l1_config.std.lna_switch_thr_low_band2  = rf_band[tm_band].rx.rx_cal_params.lna_switch_thr_low;
          l1_config.std.lna_switch_thr_high_band2 = rf_band[tm_band].rx.rx_cal_params.lna_switch_thr_high;
        }
        else
        {
          tm_return->status = E_INVAL;
          break;
        }

      tm_return->status = E_OK;
      break;
    }
    case TX_CAL_EXTREME:
    case RX_CAL_LEVEL:
    {
      tm_return->status = E_NOSUBSYS;
      break;
    }
    #if L1_GPRS
      case RLC_TX_BUFFER_CS1:
      case RLC_TX_BUFFER_CS2:
      case RLC_TX_BUFFER_CS3:
      case RLC_TX_BUFFER_CS4:
      {
        UWORD8 i, buffer_size;

        tm_return->index = index;  // store index before it gets modified
        tm_return->size  = 0;

        buffer_size = size/2 + size%2;  // bytes will be concatenated into UWORD16

        if (buffer_size > 27)  //max. number of data bytes
        {
          tm_return->status = E_BADSIZE;
          break;
        }

        // make sure that last byte is zero in case of odd number of bytes
        table[size] = 0; 
 
        // init the whole buffer before downloading new data
        for (i=0; i<27; i++)
          l1_config.tmode.tx_params.rlc_buffer[i] = 0;

        for (i=0; i<buffer_size; i++)
        {
          l1_config.tmode.tx_params.rlc_buffer[i] = (table[2*i+1] << 8) | table[2*i];
        }
        l1_config.tmode.tx_params.rlc_buffer_size = buffer_size;

        tm_return->status = E_OK;
        break;
      }
    #endif
    case TX_DATA_BUFFER:
    {
      UWORD8 i;

      tm_return->index = index;  // store index before it gets modified
      tm_return->size  = 0;

      if (size != 32)  // 16 UWORD16 (containing 10 data bits each)
      {
        tm_return->status = E_BADSIZE;
        break;
      }

      memcpy(&TM_ul_data, table, size);

      for (i=0; i<16; i++)
      {
        TM_ul_data[i] = TM_ul_data[i] << 6;
      }        	    

      tm_return->status = E_OK;
      break;
    }  
    default:
    {
      tm_return->status = E_BADINDEX;
      break;
    }
  } // end switch
}

void Cust_tm_rf_table_read(T_TM_RETURN *tm_return, WORD8 index)
{
  switch (index)
  {
    case RX_AGC_TABLE:
    {
      tm_return->size = sizeof(AGC_TABLE);
      memcpy(tm_return->result, &AGC_TABLE[0], tm_return->size);
      break;
    }
    case AFC_PARAMS:
    {
      #if (VCXO_ALGO == 1)
      tm_return->size = 24;  // 4 UWORD32's + 4 WORD16
      #else
      tm_return->size = 16;  // 4 UWORD32's
      #endif
      memcpy(tm_return->result, &rf.afc.psi_sta_inv, tm_return->size);
      break;
    }
    case RX_AGC_GLOBAL_PARAMS:
    {
      tm_return->size = 8;  // 4 UWORD16's
      memcpy(tm_return->result, &rf.rx.agc.low_agc_noise_thr, tm_return->size);

      break;
    }
    case RX_IL_2_AGC_MAX:
    {
      tm_return->size = sizeof(rf.rx.agc.il2agc_max);
      memcpy(tm_return->result, &rf.rx.agc.il2agc_max[0], tm_return->size);
      break;
    }
    case RX_IL_2_AGC_PWR:
    {
      tm_return->size = sizeof(rf.rx.agc.il2agc_pwr);
      memcpy(tm_return->result, &rf.rx.agc.il2agc_pwr[0], tm_return->size);
      break;
    }
    case RX_IL_2_AGC_AV:
    {
      tm_return->size = sizeof(rf.rx.agc.il2agc_av);
      memcpy(tm_return->result, &rf.rx.agc.il2agc_av[0], tm_return->size);
      break;
    }
    case TX_LEVELS:
    {
      tm_return->size = sizeof(rf_band[tm_band].tx.levels);
      memcpy(tm_return->result, &rf_band[tm_band].tx.levels[0], tm_return->size);
      break;
    }
    case TX_CAL_CHAN: // generic for all bands
    {
      tm_return->size = sizeof(rf_band[tm_band].tx.chan_cal_table);
      memcpy(tm_return->result, &rf_band[tm_band].tx.chan_cal_table[0][0], tm_return->size);
      break;
    }
    case TX_CAL_TEMP:  // generic for all bands
    {
      tm_return->size = sizeof(rf_band[tm_band].tx.temp);
      memcpy(tm_return->result, &rf_band[tm_band].tx.temp[0], tm_return->size);
      break;
    }
    case RX_CAL_CHAN:  // generic for all bands
    {
      tm_return->size = sizeof(rf_band[tm_band].rx.agc_bands);
      memcpy(tm_return->result, &rf_band[tm_band].rx.agc_bands[0], tm_return->size);
      break;
    }
    case RX_CAL_TEMP:  // generic for all bands
    {
      tm_return->size = sizeof(rf_band[tm_band].rx.temp);
      memcpy(tm_return->result, &rf_band[tm_band].rx.temp[0], tm_return->size);
      break;
    }
    case RX_AGC_PARAMS:
    {
      // WARNING: sizeof(rf.rx.rx_cal_params[band]) returns 12 because of alignment
      tm_return->size = 10;  // five UWORD16's
      memcpy(tm_return->result, &rf_band[tm_band].rx.rx_cal_params, tm_return->size);
      break;
    }
    case TX_CAL_EXTREME:
    case RX_CAL_LEVEL:
    {
      tm_return->size = 0;
      tm_return->status = E_NOSUBSYS;
      return;
    }

    #if L1_GPRS
      case RLC_TX_BUFFER_CS1:
      case RLC_TX_BUFFER_CS2:
      case RLC_TX_BUFFER_CS3:
      case RLC_TX_BUFFER_CS4:
      {
        tm_return->size = l1_config.tmode.tx_params.rlc_buffer_size * 2; // UWORD16's
        memcpy(tm_return->result, &l1_config.tmode.tx_params.rlc_buffer[0], tm_return->size);
        break;
      }
    #endif

    case TX_DATA_BUFFER:
    {
      UWORD8 i;
      for (i=0; i<16; i++)
      {
        tm_return->result[2*i]=(TM_ul_data[i] >> 6) & 0x00FF;
        tm_return->result[2*i+1]=(TM_ul_data[i] >> 14) & 0x0003;
      }

      tm_return->size = 32; //16*UWORD16
      break;
    }

    #if (RF==35)
    case RX_PLL_TUNING_TABLE:
    {
      tm_return->size = sizeof(pll_tuning); //6*UWORD16
      memcpy(tm_return->result, &pll_tuning, tm_return->size);
      pll_tuning.enable    = 0;
      break;
    }
    #endif

    default:
    {
      tm_return->size = 0;
      tm_return->status = E_BADINDEX;
      return;
    }
  }  // end switch

  tm_return->index  = index;
  tm_return->status = E_OK;
}

void Cust_tm_rx_param_write(T_TM_RETURN *tm_return, WORD16 index, UWORD16 value)
{
  switch (index)
  {
    case RX_FRONT_DELAY:
    {
      //delay for dual band not implemented yet
      rf.tx.prg_tx = value;
      l1_config.params.prg_tx_gsm = rf.tx.prg_tx;
      l1_config.params.prg_tx_dcs = rf.tx.prg_tx;

      tm_return->status = E_OK;
      break;
    }
    default:
    {
      tm_return->status = E_BADINDEX;
      break;
    }
  } // end switch
}

void Cust_tm_rx_param_read(T_TM_RETURN *tm_return, WORD16 index)
{
  volatile UWORD16 value;

  switch (index)
  {
    case RX_FRONT_DELAY:
    {
      value = rf.tx.prg_tx;
      break;
    }
    default:
    {
      tm_return->status = E_BADINDEX;
      tm_return->size = 0;
      return;
    }
  } // end switch

  memcpy(tm_return->result, (UWORD8 *)&value, 2);
  tm_return->size = 2;
  tm_return->status = E_OK;
}

void Cust_tm_tx_param_write(T_TM_RETURN *tm_return, WORD16 index, UWORD16 value, UWORD8 band)
{
  switch (index)
  {
    case TX_APC_DAC:
    {
      // generic for all bands
      rf_band[tm_band].tx.levels[l1_config.tmode.tx_params.txpwr].apc = value;

      tm_return->status = E_OK;
      break;
    }
    case TX_RAMP_TEMPLATE:
    {
      if (value >= sizeof(rf_band[tm_band].tx.ramp_tables)/sizeof(rf_band[tm_band].tx.ramp_tables[0]))  // [0..15]
      {
        tm_return->status = E_INVAL;
        break;
      }

      // generic for all bands
        rf_band[tm_band].tx.levels[l1_config.tmode.tx_params.txpwr].ramp_index = value;

      tm_return->status = E_OK;
      l1_config.tmode.rf_params.reload_ramps_flag = 1;
      break;
    }
    case TX_CHAN_CAL_TABLE:
    {
      if (value >= sizeof(rf_band[tm_band].tx.chan_cal_table)/sizeof(rf_band[tm_band].tx.chan_cal_table[0]))
      {
        tm_return->status = E_INVAL;
        break;
      }

      // generic for all bands
      rf_band[tm_band].tx.levels[l1_config.tmode.tx_params.txpwr].chan_cal_index = value;

        tm_return->status = E_OK;
      
      break;
      }
    case TX_BURST_TYPE:
      {
      if (value > 1)  // [0..1]
      {
        tm_return->status = E_INVAL;
        break;
      }
      l1_config.tmode.tx_params.burst_type = value;
      tm_return->status = E_OK;
      break;
    }
    case TX_BURST_DATA:
    {
      // range is [0..10], currently we support [0..13] at the moment
      if (value > 13)
      {
        tm_return->status = E_INVAL;
        break;
      }
      l1_config.tmode.tx_params.burst_data = value;
      tm_return->status = E_OK;
      break;
    }
    case TX_TRAINING_SEQ:
    {
      if (value > 7)  // [0..7]
      {
        tm_return->status = E_INVAL;
        break;
      }
      l1_config.tmode.tx_params.tsc = value;
      tm_return->status = E_OK;
      break;
    }
    default:
    {
      tm_return->status = E_BADINDEX;
      break;
    }
  } // end switch
}

void Cust_tm_tx_param_read(T_TM_RETURN *tm_return, WORD16 index, UWORD8 band)
{
  volatile UWORD16 value;

  switch (index)
  {
    case TX_PWR_LEVEL:
    {
      value = l1_config.tmode.tx_params.txpwr;
      break;
    }
    case TX_APC_DAC:
    {
      value = rf_band[tm_band].tx.levels[l1_config.tmode.tx_params.txpwr].apc;
      break;
    }
    case TX_RAMP_TEMPLATE:
    {
      value = rf_band[tm_band].tx.levels[l1_config.tmode.tx_params.txpwr].ramp_index;
      break;
    }
    case TX_CHAN_CAL_TABLE:
    {
      value = rf_band[tm_band].tx.levels[l1_config.tmode.tx_params.txpwr].chan_cal_index;
      break;
    }
    case TX_BURST_TYPE:
    {
      value = l1_config.tmode.tx_params.burst_type;
      break;
    }
    case TX_BURST_DATA:
    {
      value = l1_config.tmode.tx_params.burst_data;
      break;
    }
    case TX_TIMING_ADVANCE:
    {
      value = l1_config.tmode.tx_params.timing_advance;
      break;
    }
    case TX_TRAINING_SEQ:
    {
      value = l1_config.tmode.tx_params.tsc;
      break;
    }
    case TX_PWR_SKIP:
    {
     value = l1_config.tmode.tx_params.txpwr_skip;
     break;
    }
    #if L1_GPRS
      case TX_GPRS_POWER0:
      case TX_GPRS_POWER1:
      case TX_GPRS_POWER2:
      case TX_GPRS_POWER3:
      case TX_GPRS_POWER4:
      case TX_GPRS_POWER5:
      case TX_GPRS_POWER6:
      case TX_GPRS_POWER7:
      {
        value = l1_config.tmode.tx_params.txpwr_gprs[index - TX_GPRS_POWER0];
        break;
      }
      case TX_GPRS_SLOTS:
      {
        value = l1_config.tmode.tx_params.timeslot_alloc;
        break;
      }
      case TX_GPRS_CODING:
      {
        value = l1_config.tmode.tx_params.coding_scheme;
        break;
      }
    #endif
    default:
    {
      tm_return->status = E_BADINDEX;
      tm_return->size = 0;
      return;
    }
  } // end switch

  memcpy(tm_return->result, (UWORD8 *)&value, 2);
  tm_return->size = 2;
  tm_return->status = E_OK;
}

void Cust_tm_tx_template_write(T_TM_RETURN *tm_return, WORD8 index, UWORD8 size, UWORD8 table[])
{
  if (index >= sizeof(rf_band[tm_band].tx.ramp_tables)/sizeof(T_TX_RAMP))
  {
    tm_return->status = E_BADINDEX;
  }
  else if (size != sizeof(T_TX_RAMP))
  {
    // We are writing both the up and down ramps; size must be exact.
    tm_return->status = E_BADSIZE;
  }
  else
  {
    memcpy(rf_band[tm_band].tx.ramp_tables[index].ramp_up, &table[0], size/2);
    memcpy(rf_band[tm_band].tx.ramp_tables[index].ramp_down, &table[size/2], size/2);
    tm_return->status = E_OK;
    l1_config.tmode.rf_params.reload_ramps_flag = 1;
  }

  tm_return->index = index;
  tm_return->size = 0;
}

void Cust_tm_tx_template_read(T_TM_RETURN *tm_return, WORD8 index)
{
  tm_return->index = index;

  if (index >= sizeof(rf_band[tm_band].tx.ramp_tables)/sizeof(T_TX_RAMP))
  {
    tm_return->status = E_BADINDEX;
    tm_return->size = 0;
    return;
  }
   
  memcpy(&tm_return->result[0], rf_band[tm_band].tx.ramp_tables[index].ramp_up, sizeof(rf_band[tm_band].tx.ramp_tables[index].ramp_up));
  memcpy(&tm_return->result[sizeof(rf_band[tm_band].tx.ramp_tables[index].ramp_up)], rf_band[tm_band].tx.ramp_tables[index].ramp_down, sizeof(rf_band[tm_band].tx.ramp_tables[index].ramp_down));
  tm_return->size = sizeof(rf_band[tm_band].tx.ramp_tables[index]);
  tm_return->status = E_OK;
}

void Cust_tm_misc_param_write(T_TM_RETURN *tm_return, WORD16 index, UWORD16 value)
{
  switch (index)
  {
    case GPIOSTATE0:
    case GPIODIR0:
    case GPIOSTATE1:
    case GPIODIR1:
    case GPIOSTATE0P:
    case GPIODIR0P:
    case GPIOSTATE1P:
    case GPIODIR1P:
    {
      tm_return->status = E_NOSUBSYS;
      break;
    }
    case CONVERTED_ADC0:
    case CONVERTED_ADC1:
    case CONVERTED_ADC2:
    case CONVERTED_ADC3:
    case CONVERTED_ADC4:
    case CONVERTED_ADC5:
    case CONVERTED_ADC6:
    case CONVERTED_ADC7:
    case CONVERTED_ADC8:
    {
      adc.converted[index - CONVERTED_ADC0] = value;
      tm_return->status = E_OK;
      break;
    }
    
    case RAW_ADC0:
    case RAW_ADC1:
    case RAW_ADC2:
    case RAW_ADC3:
    case RAW_ADC4:
    case RAW_ADC5:
    case RAW_ADC6:
    case RAW_ADC7:
    case RAW_ADC8:
    {
      adc.raw[index - RAW_ADC0]  = value;
      tm_return->status = E_OK;
      break;
    }
    
    case ADC0_COEFF_A:
    case ADC1_COEFF_A:
    case ADC2_COEFF_A:
    case ADC3_COEFF_A:
    case ADC4_COEFF_A:
    case ADC5_COEFF_A:
    case ADC6_COEFF_A:
    case ADC7_COEFF_A:
    case ADC8_COEFF_A:
    {
      adc_cal.a[index - ADC0_COEFF_A]  = value;
      tm_return->status = E_OK;
      break;
    }
    
    case ADC0_COEFF_B:
    case ADC1_COEFF_B:
    case ADC2_COEFF_B:
    case ADC3_COEFF_B:
    case ADC4_COEFF_B:
    case ADC5_COEFF_B:
    case ADC6_COEFF_B:
    case ADC7_COEFF_B:
    case ADC8_COEFF_B:
    {
      adc_cal.b[index - ADC0_COEFF_B]  = value;
      tm_return->status = E_OK;
      break;
    }
    case SLEEP_MODE:
    {
      tm_return->status = E_NOSUBSYS;
      break;
    }
    default:
    {
      tm_return->status = E_BADINDEX;
      break;
    }
  } // end switch
}

void Cust_tm_misc_param_read(T_TM_RETURN *tm_return, WORD16 index)
{
  volatile UWORD16 value;

  switch (index)
  {
    case GPIOSTATE0:
    case GPIODIR0:
    case GPIOSTATE1:
    case GPIODIR1:
    case GPIOSTATE0P:
    case GPIODIR0P:
    case GPIOSTATE1P:
    case GPIODIR1P:
    {
      tm_return->status = E_NOSUBSYS;
      tm_return->size = 0;
      return;
    }
    case CONVERTED_ADC0:
    case CONVERTED_ADC1:
    case CONVERTED_ADC2:
    case CONVERTED_ADC3:
    case CONVERTED_ADC4:
    case CONVERTED_ADC5:
    case CONVERTED_ADC6:
    case CONVERTED_ADC7:
    case CONVERTED_ADC8:
    {
      value = adc.converted[index - CONVERTED_ADC0];
      break;
    }
    case RAW_ADC0:
    case RAW_ADC1:
    case RAW_ADC2:
    case RAW_ADC3:
    case RAW_ADC4:
    case RAW_ADC5:
    case RAW_ADC6:
    case RAW_ADC7:
    case RAW_ADC8:
    {
      value = adc.raw[index - RAW_ADC0];
      break;
    }
    case ADC0_COEFF_A:
    case ADC1_COEFF_A:
    case ADC2_COEFF_A:
    case ADC3_COEFF_A:
    case ADC4_COEFF_A:
    case ADC5_COEFF_A:
    case ADC6_COEFF_A:
    case ADC7_COEFF_A:
    case ADC8_COEFF_A:
    {
      value = adc_cal.a[index - ADC0_COEFF_A];
      break;
    }
    case ADC0_COEFF_B:
    case ADC1_COEFF_B:
    case ADC2_COEFF_B:
    case ADC3_COEFF_B:
    case ADC4_COEFF_B:
    case ADC5_COEFF_B:
    case ADC6_COEFF_B:
    case ADC7_COEFF_B:
    case ADC8_COEFF_B:
    {
      value = adc_cal.b[index - ADC0_COEFF_B];
      break;
    }
    case SLEEP_MODE:
    {
      tm_return->status = E_NOSUBSYS;
      tm_return->size = 0;
      return;
    }
    default:
    {
      tm_return->status = E_BADINDEX;
      tm_return->size = 0;
      return;
    }
  } // end switch

  memcpy(tm_return->result, (UWORD8 *)&value, 2);
  tm_return->size = 2;
  tm_return->status = E_OK;
}

void Cust_tm_misc_enable(T_TM_RETURN *tm_return, WORD16 action)
{
    UWORD8 status;
  
    // FIXME: This enum really should go into testmode header file.
    enum ME_CFG_WRITE_E {
        CFG_WRITE_MKDIRS   = 100,
        CFG_WRITE_RF_CAL   = 102,
        CFG_WRITE_RF_CFG   = 103,
        CFG_WRITE_TX_CAL   = 104,
        CFG_WRITE_TX_CFG   = 105,
        CFG_WRITE_RX_CAL   = 106,
        CFG_WRITE_RX_CFG   = 107,
        CFG_WRITE_SYS_CAL  = 108,
        CFG_WRITE_SYS_CFG  = 109
  };

  tm_return->size = 0;
  tm_return->index = action;
  tm_return->status = E_OK;

    // FIXME: This code should actually be in misc_enable()
  switch(action)
  {
    case CFG_WRITE_MKDIRS:
        ffs_mkdir("/gsm");
        ffs_mkdir("/pcm");
        ffs_mkdir("/sys");
        ffs_mkdir("/mmi");
        ffs_mkdir("/vos");
        ffs_mkdir("/var");
        ffs_mkdir("/gsm/rf");
        ffs_mkdir("/gsm/com");
        ffs_mkdir("/vos/vm");
        ffs_mkdir("/vos/vrm");
        ffs_mkdir("/vos/vrp");
        ffs_mkdir("/var/log");
        ffs_mkdir("/var/tst");
        ffs_mkdir("/gsm/rf/tx");
        ffs_mkdir("/gsm/rf/rx");
      break;
    case CFG_WRITE_RF_CAL:   config_ffs_write('f'); break;
    case CFG_WRITE_RF_CFG:   config_ffs_write('F'); break;
    case CFG_WRITE_TX_CAL:   config_ffs_write('t'); break;
    case CFG_WRITE_TX_CFG:   config_ffs_write('T'); break;
    case CFG_WRITE_RX_CAL:   config_ffs_write('r'); break;
    case CFG_WRITE_RX_CFG:   config_ffs_write('R'); break;
    case CFG_WRITE_SYS_CAL:  config_ffs_write('s'); break;
    case CFG_WRITE_SYS_CFG:  config_ffs_write('S'); break;
    default:
      tm_return->status = E_BADINDEX;
    }
}

void Cust_tm_special_param_write(T_TM_RETURN *tm_return, WORD16 index, UWORD16 value)
{
  tm_return->size = 0;
  tm_return->index = index;
  tm_return->status = E_NOSYS;
}

void Cust_tm_special_param_read(T_TM_RETURN *tm_return, WORD16 index)
{
  tm_return->size = 0;
  tm_return->index = index;
  tm_return->status = E_NOSYS;
}

void Cust_tm_special_table_write(T_TM_RETURN *tm_return, WORD8 index, UWORD8 size, UWORD8 table[])
{
  tm_return->size = 0;
  tm_return->index = index;
  tm_return->status = E_NOSYS;
}

void Cust_tm_special_table_read(T_TM_RETURN *tm_return, WORD8 index)
{
  tm_return->size = 0;
  tm_return->index = index;
  tm_return->status = E_NOSYS;
}

void Cust_tm_special_enable(T_TM_RETURN *tm_return, WORD16 action)
{
  tm_return->size = 0;
  tm_return->index = action;
  tm_return->status = E_NOSYS;
}

#endif // TESTMODE