/***************************************************************************
 * sdla_bri.c	WANPIPE(tm) 
 *		ISDN-BRI support module for "Cologne XHFC-2SU" chip.
 *
 * Author(s): 	David Rokhvarg   <davidr@sangoma.com>
 *
 * Copyright:	(c) 1984 - 2007 Sangoma Technologies Inc.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 *
 * ============================================================================
 * March	12, 2007	David Rokhvarg
 *				v1.0 Initial version.
 *
 * February	26, 2008	David Rokhvarg	
 *				v1.1 Imrovements in SU State transition code.
 *				Re-implemented T3 and T4 timers.
 *
 * March	10, 2008	David Rokhvarg	
 *				v1.2 Added 'loopback' commands.
 *				Added commands to switch between line recovery
 *				clock and internal oscillator clock.
 *
 * April	1,  2008	David Rokhvarg	
 *				v1.3 In TE mode, when in F3 state, Do NOT
 *				indicate 'disconnect' right away, do it when 
 *				T4 expires.
 *
 * July		21  2009	David Rokhvarg	
 *				v1.4 Implemented T1 timer for NT.
 *
 * January	24  2011	David Rokhvarg	
 *				v1.5 The T3 timer was broken by changes in AFT Core - fixed it
 *					 by adding T3_TIMER_EXPIRED bit.
 *					 The T3_TIMER_EXPIRED bit resolved "failed TE activation"
 *					 issue on some BRI lines.
 ******************************************************************************
 */

/*******************************************************************************
**			   INCLUDE FILES
*******************************************************************************/

# include "wanpipe_includes.h"
# include "wanpipe_defines.h"
# include "wanpipe_debug.h"
# include "wanpipe_abstr.h"
# include "wanpipe_common.h"
# include "wanpipe_events.h"
# include "wanpipe.h"
# include "wanpipe_events.h"
# include "if_wanpipe_common.h"	/* for 'wanpipe_common_t' used in 'aft_core.h'*/
# include "aft_core.h"		/* for 'private_area_t' */
# include "sdla_bri.h"


/* DEBUG macro definitions */
#define DEBUG_HFC_INIT		if(0)DEBUG_EVENT
#define DEBUG_HFC_MODE		if(0)DEBUG_EVENT
#define DEBUG_HFC_IRQ		if(0)DEBUG_EVENT
#define DEBUG_HFC_SU_IRQ	if(0)DEBUG_EVENT

#define DEBUG_HFC_TX		if(0)DEBUG_EVENT
#define DEBUG_TX_DATA		if(0)DEBUG_EVENT
#define TX_EXTRA_DBG		if(0)DEBUG_EVENT
#define TX_FAST_DBG			if(0)DEBUG_EVENT

#define DEBUG_HFC_RX		if(0)DEBUG_EVENT
#define RX_EXTRA_DBG		if(0)DEBUG_EVENT
#define DEBUG_RX1			if(0)DEBUG_EVENT
#define DBG_RX_DATA			if(0)DEBUG_EVENT

#define DEBUG_HFC_CLOCK		if(0)DEBUG_EVENT
#define DBG_MODULE_TESTER	if(0)DEBUG_EVENT

#define NT_STATE_FUNC()		if(0)DEBUG_EVENT("%s(): line: %d\n", __FUNCTION__, __LINE__)
#define CLOCK_FUNC()		if(0)DEBUG_EVENT("%s(): line: %d\n", __FUNCTION__, __LINE__)

#define DBG_SPI				if(0)DEBUG_EVENT

#define DEBUG_FE_STATUS		if(0)DEBUG_EVENT

#define DEBUG_LOOPB			if(0)DEBUG_EVENT

#define DEBUG_TE_STATES		if(0)DEBUG_EVENT

#define FIFO_THRESHOLD_INDEX	1

typedef enum _nt_states{
	NT_STATE_RESET_G0 = 0,
	NT_STATE_DEACTIVATED_G1,
	NT_STATE_PENDING_ACTIVATION_G2,
	NT_STATE_ACTIVE_G3,
	NT_STATE_PENDING_DEACTIVATION_G4
}nt_states_t;

typedef enum _te_states{
	TE_STATE_RESET_F0 = 0,
	TE_STATE_SENSING_F2 = 2,
	TE_STATE_DEACTIVATED_F3 = 3,
	TE_STATE_AWAITING_SIGNAL_F4 = 4,
	TE_STATE_IDENTIFYING_INPUT_F5 = 5,
	TE_STATE_SYNCHRONIZED_F6 = 6,
	TE_STATE_ACTIVATED_F7 = 7,
	TE_STATE_LOST_FRAMING_F8 = 8
}te_states_t;

#define WP_DECODE_TE_STATE(te_state)	\
(te_state == TE_STATE_RESET_F0) ? "TE_STATE_RESET_F0" :	\
(te_state == TE_STATE_SENSING_F2) ? "TE_STATE_SENSING_F2" :	\
(te_state == TE_STATE_DEACTIVATED_F3) ? "TE_STATE_DEACTIVATED_F3" :	\
(te_state == TE_STATE_AWAITING_SIGNAL_F4) ? "TE_STATE_AWAITING_SIGNAL_F4" :	\
(te_state == TE_STATE_IDENTIFYING_INPUT_F5) ? "TE_STATE_IDENTIFYING_INPUT_F5" :	\
(te_state == TE_STATE_SYNCHRONIZED_F6) ? "TE_STATE_SYNCHRONIZED_F6" :	\
(te_state == TE_STATE_ACTIVATED_F7) ? "TE_STATE_ACTIVATED_F7" :	\
(te_state == TE_STATE_LOST_FRAMING_F8) ? "TE_STATE_LOST_FRAMING_F8" :	\
"Invalid TE State"


#define CHECK_DATA 0
#if CHECK_DATA
static void dump_chip_SRAM(sdla_fe_t *fe, u8 mod_no, u8 port_no);
static void dump_data(u8 *data, int data_len);
static int check_data(u8 *data, int data_len);
#endif

typedef enum _wp_bri_critical_bits {
	WP_BCB_BRI_CONFIG,
	WP_BCB_BRI_POST_INIT
} wp_bri_critical_bits_t;

/*******************************************************************************
**			  DEFINES AND MACROS
*******************************************************************************/
#define REPORT_MOD_NO(mod_no)	(mod_no+1)

static u8 validate_fe_line_no(sdla_fe_t *fe, const char *caller_name)
{
	if ((int32_t)WAN_FE_LINENO(fe) < 0 || WAN_FE_LINENO(fe) > MAX_BRI_LINES){
		DEBUG_EVENT("%s(): %s: ISDN BRI: Invalid FE line number %d (Min=1 Max=%d)\n",
			caller_name, fe->name, WAN_FE_LINENO(fe)+1, MAX_BRI_LINES);
		return 1;
	}
	return 0;
}

static int32_t validate_physical_mod_no(u32 mod_no, const char *caller_name)
{
	if(mod_no % 2){
		DEBUG_ERROR("%s(): Error: mod_no (%d) is not divisible by 2!!\n", 
			caller_name, mod_no);
		return 1;
	}

	if(mod_no >= MAX_BRI_LINES){
		DEBUG_ERROR("%s(): Error: mod_no (%d) is greate than maximum of %d!!\n", 
			caller_name, mod_no, MAX_BRI_LINES - 1);
		return 1;
	}
	return 0;
}

/* Translate FE_LINENO to physical module number divisible by BRI_MAX_PORTS_PER_CHIP. */
static u8 fe_line_no_to_physical_mod_no(sdla_fe_t *fe)
{
	u8 mod_no;

	mod_no = (u8)WAN_FE_LINENO(fe);
	/* get quotient between 0 and 11 (including) */
	mod_no = mod_no / BRI_MAX_PORTS_PER_CHIP; 
	/* here WAN_FE_LINENO(fe) is translated into an EVEN number between 0 and 22 (including). */
	mod_no *= BRI_MAX_PORTS_PER_CHIP;
	
	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 0;
	}

	return mod_no;
}

/* Translate FE_LINENO to port number on the module. can be only 0 or 1. */
static u8 fe_line_no_to_port_no(sdla_fe_t *fe)
{
	return WAN_FE_LINENO(fe) % BRI_MAX_PORTS_PER_CHIP;
}


/*******************************************************************************/
/* SPI access functions and dbg macros */

static
__u8 _read_xhfc(sdla_fe_t *fe, u32 mod_no, u8 reg, const char *caller_name, int32_t file_lineno);

static 
int32_t _write_xhfc(sdla_fe_t *fe, u32 mod_no, u8 reg, u8 val, 
		    const char *caller_name, int32_t file_lineno);

/* Read/Write to front-end register */
#define WRITE_REG(reg,val)	_write_xhfc(fe, mod_no, reg, val, __FUNCTION__, __LINE__)
#define READ_REG(reg)		_read_xhfc(fe, mod_no, reg, __FUNCTION__, __LINE__)

/*******************************************************************************/


/*******************************************************************************
**			STRUCTURES AND TYPEDEFS
*******************************************************************************/


/*******************************************************************************
**			   GLOBAL VARIABLES
*******************************************************************************/
#if !defined(__WINDOWS__)
extern WAN_LIST_HEAD(, wan_tdmv_) wan_tdmv_head;
#endif


/*******************************************************************************
**			  FUNCTION PROTOTYPES
*******************************************************************************/
static int32_t	bri_global_config(void* pfe);
static int32_t	bri_global_unconfig(void* pfe);

static int32_t	wp_bri_config(void *pfe);
static int32_t	wp_bri_post_init(void *pfe);

static int32_t	wp_bri_unconfig(void *pfe);
static int32_t	wp_bri_post_unconfig(void* pfe);

static int32_t	wp_bri_if_config(void *pfe, u32 mod_map, u8);
static int32_t	wp_bri_if_unconfig(void *pfe, u32 mod_map, u8);

static int32_t	wp_bri_disable_irq(sdla_fe_t *fe, u32 mod_no, u8 port_no);
static int	wp_bri_disable_fe_irq(void *fe);
static void	bri_enable_interrupts(sdla_fe_t *fe, u32 mod_no, u8 port_no);
static int32_t	wp_bri_intr(sdla_fe_t *); 
static int32_t	wp_bri_check_intr(sdla_fe_t *); 
static int32_t	wp_bri_polling(sdla_fe_t*);
static int32_t	wp_bri_udp(sdla_fe_t*, void*, u8*);
static u32	wp_bri_active_map(sdla_fe_t* fe, u8 line_no);
static u8	wp_bri_fe_media(sdla_fe_t *fe);
static int32_t	wp_bri_set_dtmf(sdla_fe_t*, int32_t, u8);

static int	wp_bri_intr_ctrl(sdla_fe_t *fe, int, u_int8_t, u_int8_t, unsigned int);
static int	wp_bri_event_ctrl(sdla_fe_t*, wan_event_ctrl_t*);

static int32_t	wp_bri_dchan_tx(sdla_fe_t *fe, void *src_data_buffer, u32 buffer_len);

static void	*wp_bri_dchan_rx(sdla_fe_t *fe, u8 mod_no, u8 port_no, u32 * pending_data);

static int	wp_bri_get_fe_status(sdla_fe_t *fe, unsigned char *status, int notused);
static int	wp_bri_set_fe_status(sdla_fe_t *fe, unsigned char status);

static int	wp_bri_control(sdla_fe_t *fe, u32 command);

/*******************************************************************************/
/* TE timer routines */
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static void l1_timer_expire_t3(void* pfe);
static void l1_timer_expire_t4(void* pfe);
#elif defined(__WINDOWS__)
static void l1_timer_expire_t3(IN PKDPC Dpc, void* pfe, void* arg2, void* arg3);
static void l1_timer_expire_t4(IN PKDPC Dpc, void* pfe, void* arg2, void* arg3);
#elif defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
static void l1_timer_expire_t3(struct timer_list *t);
static void l1_timer_expire_t4(struct timer_list *t);
#else
static void l1_timer_expire_t3(unsigned long pfe);
static void l1_timer_expire_t4(unsigned long pfe);
#endif

static void l1_timer_start_t3(void *pport);
static void l1_timer_stop_t3(void *pport);
static void l1_timer_start_t4(void *pport);
static void l1_timer_stop_t4(void *pport);

static void __l1_timer_expire_t3(sdla_fe_t *fe);

/* NT timer routines */
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static void l1_timer_expire_t1(void* pfe);
#elif defined(__WINDOWS__)
static void l1_timer_expire_t1(IN PKDPC Dpc, void* pfe, void* arg2, void* arg3);
#elif defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
static void l1_timer_expire_t1(struct timer_list *t);
#else
static void l1_timer_expire_t1(unsigned long pfe);
#endif

static void l1_timer_start_t1(void *pport);
static void l1_timer_stop_t1(void *pport);
static void __l1_timer_expire_t1(sdla_fe_t *fe);

/*******************************************************************************/

static int32_t	wp_bri_spi_bus_reset(sdla_fe_t	*fe);
static int32_t	reset_chip(sdla_fe_t *fe, u32 mod_no);
static int32_t	init_xfhc(sdla_fe_t *fe, u32 mod_no);
static void	xhfc_select_fifo(sdla_fe_t *fe, u32 mod_no, u8 fifo);
static void	xhfc_waitbusy(sdla_fe_t *fe, u32 mod_no);
static void	xhfc_select_pcm_slot(sdla_fe_t *fe, u32 mod_no, u8 slot, u8 direction);
static void	xhfc_increment_fifo(sdla_fe_t *fe, u32 mod_no);

static int32_t	__config_clock_routing(sdla_fe_t *fe, u32 mod_no, u8 master_mode);
static int32_t	config_clock_routing(sdla_fe_t *fe, u8 master_mode);

static void xhfc_ph_command(sdla_fe_t *fe, bri_xhfc_port_t *port, u_char command);

static u8 __su_new_state(sdla_fe_t *fe, u8 mod_no, u8 port_no);
static void sdla_bri_set_status(sdla_fe_t* fe, u8 mod_no, u8 port_no, u8 status);

/* for selecting PCM direction */
#define XHFC_DIRECTION_TX 0
#define XHFC_DIRECTION_RX 1

#if 0
static void xhfc_select_xhfc_channel(sdla_fe_t *fe, u32 mod_no,
				  u8 channel, u8 direction, u8 vrout_bitmap);
#endif

static int32_t check_f0cl_increment(sdla_fe_t *fe, u8 old_f0cl, u8 new_f0cl, int32_t *diff);

/*******************************************************************************
**			  FUNCTION DEFINITIONS
*******************************************************************************/


/*******************************************************************************/

static
u8 _read_xhfc(sdla_fe_t *fe, u32 mod_no, u8 reg, const char *caller_name, int32_t file_lineno)
{				
	u8 val = READ_BRI_REG(mod_no, reg);
	return val;
}

static 
int32_t _write_xhfc(sdla_fe_t *fe, u32 mod_no, u8 reg, u8 val, const char *caller_name, int32_t file_lineno)
{
	return WRITE_BRI_REG(mod_no, reg, val);
}

/*******************************************************************************/

static void xhfc_waitbusy(sdla_fe_t *fe, u32 mod_no)
{	
	u32	wait_counter = 0;
#define MAX_XHFC_WAIT_COUNTER	200

	do{
		if(!(READ_REG(R_STATUS) & M_BUSY)){
			break;
		}
		WP_DELAY(10);
	}while(wait_counter++ < MAX_XHFC_WAIT_COUNTER);

	if(wait_counter >= MAX_XHFC_WAIT_COUNTER){
		DEBUG_EVENT("%s: %s() time out!\n", fe->name, __FUNCTION__);		
	}
}

static inline void xhfc_resetfifo_flag(sdla_fe_t *fe, u32 mod_no, u8 flag)
{
	WRITE_REG(A_INC_RES_FIFO, flag);
	xhfc_waitbusy(fe, mod_no);
}

static inline void xhfc_resetfifo(sdla_fe_t *fe, u32 mod_no)
{
    return xhfc_resetfifo_flag(fe, mod_no,M_RES_FIFO | M_RES_FIFO_ERR);
}

static void xhfc_select_fifo(sdla_fe_t *fe, u32 mod_no, u8 fifo)
{
	WRITE_REG(R_FIFO, fifo);
	xhfc_waitbusy(fe, mod_no);
}

static void xhfc_select_pcm_slot(sdla_fe_t *fe, u32 mod_no, u8 slot, u8 direction)
{
	reg_r_slot	r_slot;

	memset(&r_slot, 0, sizeof(reg_r_slot));
	
	r_slot.bit.v_sl_dir = direction;
	r_slot.bit.v_sl_num = slot;
	
	WRITE_REG(R_SLOT, r_slot.reg);
	xhfc_waitbusy(fe, mod_no);
}

#if 0
static void xhfc_select_xhfc_channel(sdla_fe_t *fe, u32 mod_no,
				  u8 channel, u8 direction, u8 vrout_bitmap)
{
	reg_a_sl_cfg	a_sl_cfg;
	memset(&a_sl_cfg, 0, sizeof(reg_a_sl_cfg));

	a_sl_cfg.bit.v_ch_sdir	= direction;	/* 1 bit */
	a_sl_cfg.bit.v_ch_snum	= channel;	/* 5 bits */
	a_sl_cfg.bit.v_rout	= vrout_bitmap;

	WRITE_REG(A_SL_CFG, a_sl_cfg.reg);
	xhfc_waitbusy(fe, mod_no);
}
#endif

static void xhfc_increment_fifo(sdla_fe_t *fe, u32 mod_no)
{
	WRITE_REG(A_INC_RES_FIFO, M_INC_F);
	xhfc_waitbusy(fe, mod_no);
}

static u32 calculate_pcm_timeslot(u32 mod_no, u8 port_no, u8 bchan)
{
	u32	pcm_slot;
#if 0
	DEBUG_HFC_INIT("port_no: %d, bchan: %d\n", port_no, bchan);
#endif
	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 0;
	}

	pcm_slot = 2*port_no + bchan;	/* 0, 1, 2, 3 */
	pcm_slot *= 2;			/* 0, 2, 4, 6 */
	pcm_slot += (4*mod_no);		/* mod_no is 0,2,4.... -->0+0, 0+8, 0+16 */

	return pcm_slot;
}

static int32_t reset_chip(sdla_fe_t *fe, u32 mod_no)
{
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	int32_t				err = 0, maximum_reset_wait_counter;
	reg_r_fifo_thres	r_fifo_thres;

	DEBUG_HFC_INIT("%s(): mod_no: %d\n", __FUNCTION__, mod_no);

	WAN_ASSERT(fe->write_fe_reg == NULL);
	WAN_ASSERT(fe->read_fe_reg == NULL);

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 1;
	}

	bri_module = &bri->mod[mod_no];

	/* read ChipID from Read Only register R_CHIP_ID */
	fe->fe_chip_id = READ_REG(R_CHIP_ID);
	switch (fe->fe_chip_id)
	{
	case CHIP_ID_2SU:
		DEBUG_EVENT("%s: Detected XHFC-2SU chip.\n", fe->name);
		bri_module->num_ports = 2;
		bri_module->max_fifo = 8;
		bri_module->max_z = 0x7F;
		DBG_MODULE_TESTER("configure FIFOs\n");
		/* 01 = 8 FIFOs with 128 bytes for TX and RX each. page 125 */
		WRITE_REG( R_FIFO_MD, M1_FIFO_MD * 1);
		break;
	default:
		err = 1;
		DEBUG_EVENT("%s: %s(): unknown Chip ID 0x%x!\n",
		       fe->name, __FUNCTION__, fe->fe_chip_id);
		return err;
	}

	/* general soft chip reset */
	WRITE_REG(R_CIRM, M_SRES);
	WP_DELAY(50);
	WRITE_REG(R_CIRM, 0);
	WP_DELAY(2000);

	/* wait for XHFC init seqeuence to be finished. should take ~40 microseconds (p.285). */
	maximum_reset_wait_counter = 1000;
	while ((READ_REG(R_STATUS) & (M_BUSY | M_PCM_INIT)) && (maximum_reset_wait_counter)){
		WP_DELAY(10);/* 10 microsecond delay */
		maximum_reset_wait_counter--;
	}

	if (!(maximum_reset_wait_counter)) {
		DEBUG_ERROR("%s: %s(): Error: chip initialization sequence timeout!\n",
			       fe->name, __FUNCTION__);
		return 1;
	}

	/* amplitude */
	WRITE_REG(R_PWM_MD, 0x80);
	WRITE_REG(R_PWM1, 0x18);

	/* Set FIFO threshold. page 124.*/
	r_fifo_thres.reg = 0;
	r_fifo_thres.bit.v_thres_tx = r_fifo_thres.bit.v_thres_rx = FIFO_THRESHOLD_INDEX;
	WRITE_REG(R_FIFO_THRES, r_fifo_thres.reg);

	WP_DELAY(2000);
	return 0;
}


/******************************************************************************
*			__config_clock_routing()	
*
* Description	: TE mode: if 'master_mode' is set to WANOPT_YES, clock
*		  recovered from the line will be routed to ALL other BRI
*		  modules on the card.
*		  NT mode: the only valid value for 'master_mode' is WANOPT_NO.
*
* Arguments	:  pfe - pointer to Front End structure.
*		   mod_no - module number.
*		   master_mode - Yes or No
*
* Returns	:  0 - configred successfully, otherwise non-zero value.
*******************************************************************************/
static int32_t __config_clock_routing(sdla_fe_t *fe, u32 mod_no, u8 master_mode)
{
	reg_r_pcm_md0		pcm_md0;
	reg_r_pcm_md2		r_pcm_md2;
	reg_r_gpio_sel		r_gpio_sel;
	reg_r_gpio_en0		r_gpio_en0;

	DEBUG_HFC_CLOCK("%s(): mod_no: %d\n", __FUNCTION__, mod_no);

	WAN_ASSERT(fe->write_fe_reg == NULL);
	WAN_ASSERT(fe->read_fe_reg == NULL);

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 1;
	}

	if (fe->bri_param.mod[mod_no].type != MOD_TYPE_TE && master_mode) {
		DEBUG_ERROR("%s: Module %d: error configuring clock routing on NT\n",
			fe->name, mod_no);
		return 1;
	}

	DEBUG_EVENT("%s: %s Clock line recovery Module=%d Port=%d: %s\n",
		fe->name, WP_BRI_DECODE_MOD_TYPE(fe->bri_param.mod[mod_no].type),
		REPORT_MOD_NO(mod_no), fe_line_no_to_port_no(fe)+1,
		(master_mode == WANOPT_YES ? "Enabled" : "Disabled"));


#if 0
	/************************************************************************/
	{
		reg_r_su_sync		r_su_sync;
		r_su_sync.reg = 0;
		if(master_mode == WANOPT_YES){
			CLOCK_FUNC();
			/*r_su_sync.bit.v_sync_sel = 0;	//000 = source is line interface 0
			r_su_sync.bit.v_man_sync = 1;
			r_su_sync.bit.v_auto_synci = 1;*/
		}else{
			CLOCK_FUNC();

		}
		WRITE_REG(R_SU_SYNC, r_su_sync.reg);
	}
#endif
	/************************************************************************/
	r_gpio_sel.reg = 0;
	r_gpio_en0.reg = 0;
	if(master_mode == WANOPT_YES){
		CLOCK_FUNC();
		/* select 1-st function of pin 16 - SYNC_O. page 315. */
		r_gpio_sel.bit.v_gpio_sel6 = 0;	

		/* enable output on pin 16 - page 314.*/
		r_gpio_en0.bit.v_gpio_en6 = 1;
	}else{
		CLOCK_FUNC();
		/* if in NORMAL mode, do NOT provide output on 16, 
		so there is only one clock source - the master. */

		/* select 2-nd function of pin 16 - GPIO6. page 315.*/
		r_gpio_sel.bit.v_gpio_sel6 = 1;	

		/* DISABLE output on pin 16 - page 314. */
		r_gpio_en0.bit.v_gpio_en6 = 0;
	}
	WRITE_REG(R_GPIO_SEL, r_gpio_sel.reg);
	WRITE_REG(R_GPIO_EN0, r_gpio_en0.reg);

	/************************************************************************/
	pcm_md0.reg = 0;
	pcm_md0.bit.v_pcm_idx = 0xA;	/* get access to R_PCM_MD2 */
#if 0
	/* PCM master mode test */
	pcm_md0.bit.v_pcm_md = 0x1;	/* PCM bus mode.
					0 = slave (pins C4IO and F0IO are inputs)
					1 = master (pins C4IO and F0IO are outputs)
					If no external C4IO and F0IO signal is provided
					this bit must be set for operation. */
#endif

	WRITE_REG(R_PCM_MD0, pcm_md0.reg);

	r_pcm_md2.reg = 0;
	if(master_mode == WANOPT_YES){
		CLOCK_FUNC();
			
		if(fe->bri_param.use_512khz_recovery_clock == 1){
			DEBUG_EVENT("%s: Module=%d Port=%d: using 512khz from PLL\n",
				fe->name, REPORT_MOD_NO(mod_no), fe_line_no_to_port_no(fe)+1);

			r_pcm_md2.bit.v_sync_out1 = 1;/* 1 = SYNC_O is either 512 kHz from the PLL or
							the received multiframe / superframe
							synchronization pulse. page 244. */
		}else{
			DEBUG_EVENT("%s: Module=%d Port=%d: SYNC_O -> SYNC_I / Sync Pulse.\n",
				fe->name, REPORT_MOD_NO(mod_no), fe_line_no_to_port_no(fe)+1);

			r_pcm_md2.bit.v_sync_out1 = 0;/* 0 = SYNC_O is either SYNC_I or the received
							synchronization pulse. page 244. */
		}
	}
	r_pcm_md2.bit.v_sync_out2 = 0;/* SYNC_O output selection
					0 = ST/Up receive from the selected line interface
					in TE mode (see R_SU_SYNC register for synchronization source selection)
					1 = SYNC_I is connected to SYNC_O. page 244. */
#if 0
	if(master_mode == WANOPT_NO){
		r_pcm_md2.bit.v_sync_src =	1;	/*V_SYNC_SRC PCM PLL synchronization source selection
							0 = line interface (see R_SU_SYNC for further
							synchronization configuration)
							1 = SYNC_I input (8 kHz). page 244.
							*/
	}
#endif

	WRITE_REG(R_PCM_MD2, r_pcm_md2.reg);
	/************************************************************************/

	return 0;
}

static int config_clock_routing(sdla_fe_t *fe, u8 master_mode)
{
	u8	mod_no;

	mod_no = fe_line_no_to_physical_mod_no(fe);	

	return __config_clock_routing(fe, mod_no, master_mode);
}


/******************************************************************************
*				init_xfhc()	
*
* Description	: Physical module global configuration. Should be done only one
*		  time per-module.
*		  Assuming chip was already reset.
*
* Arguments	:  pfe - pointer to Front End structure.
*		   mod_no - module number.
*
* Returns	:  0 - configred successfully, otherwise non-zero value.
*******************************************************************************/
static int32_t init_xfhc(sdla_fe_t *fe, u32 mod_no)
{
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	int32_t			err = 0, i;

	u8			port_no, bchan;
	reg_a_su_ctrl0		a_su_ctrl0;
	reg_a_su_rd_sta		a_su_rd_sta;
		
	DEBUG_HFC_INIT("%s(): mod_no: %d\n", __FUNCTION__, mod_no);

	WAN_ASSERT(fe->write_fe_reg == NULL);
	WAN_ASSERT(fe->read_fe_reg == NULL);

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 1;
	}

	bri_module = &bri->mod[mod_no];

	DBG_MODULE_TESTER("read ChipID from Read Only register R_CHIP_ID: must be 0x61\n");

	if((err = reset_chip(fe, mod_no))){
		return err;
	}

	a_su_ctrl0.reg = 0;
	a_su_rd_sta.reg = 0;

	/* PCM: 1. Slave mode 2. PCM64 (4MBit/s data rate). */
	/* slow PCM adjust speed */
	bri_module->pcm_md1.bit.v_pll_adj = 3;
	bri_module->pcm_md1.bit.v_pcm_dr = 1; /* dr stands for 'data rate' (64) */ 

	WRITE_REG(R_PCM_MD0, bri_module->pcm_md0.reg + 0x90); /* get access to R_PCM_MD1 */
	WRITE_REG(R_PCM_MD1, bri_module->pcm_md1.reg);

	/* After chip reset SYNC_O is set for OUTPUT by default, make sure
	   SYNC_O is set for INPUT! Otherwise may cause clock conflict. */	
	__config_clock_routing(fe, mod_no, WANOPT_NO);

	DEBUG_HFC_INIT("\n%s: configuring B-channels FIFOs...\n", fe->name);
	/* configure B channel fifos for ST<->PCM data flow */
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {/* 2 ports */
		DEBUG_HFC_INIT("=========== port_no: %d ========\n", port_no);
		for (bchan = 0; bchan < 2; bchan++) {	/* 2 B channels on each port_no */
			DEBUG_HFC_INIT("port_no: %d, bchan: %d\n", port_no, bchan);
			/* B chan - Tx of port_no */
			xhfc_select_fifo(fe, mod_no, port_no*8 + bchan*2);
			/* ST-->PCM, channel enabled */
			WRITE_REG(A_CON_HDLC, 0xde);/* page 83: Tx, Transparent, ST/U-->PCM */
			/* 64kbit/s */
			WRITE_REG(A_SUBCH_CFG, 0);
			/* no interrupts */
			WRITE_REG(A_FIFO_CTRL, 0);
	
			/* B chan - Rx of port_no */
			xhfc_select_fifo(fe, mod_no, port_no*8 + bchan*2 + 1);
			/* ST<--PCM, channel enabled */
			WRITE_REG(A_CON_HDLC, 0xde);/* page 83: Rx, Transparent, ST/U<--PCM */
			/* 64kbit/s */
			WRITE_REG(A_SUBCH_CFG, 0);
			/* no interrupts */
			WRITE_REG(A_FIFO_CTRL, 0);
		}
	}
	DEBUG_HFC_INIT("\nDone\n");

	DEBUG_HFC_INIT("\nconfiguring D-channel FIFOs...\n");
	/* configure D channel fifos */
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
		reg_a_con_hdlc	a_con_hdlc;

		a_con_hdlc.reg = 0;
			
		a_con_hdlc.bit.v_iff = 1;/* InterFrameFill=ones */
		a_con_hdlc.bit.v_fifo_irq = 1;  	/* an interrupt is generated */

		DEBUG_HFC_INIT("=========== port_no: %d ========\n", port_no);
		/* D - Tx of port_no */
		xhfc_select_fifo(fe, mod_no, port_no*8 + 4);
		/* FIFO-->ST, channel enabled, IFF=ones */
		WRITE_REG(A_CON_HDLC, a_con_hdlc.reg);
		/* 16kbit/s */
		WRITE_REG(A_SUBCH_CFG, 2);
#if 1
		/* interrupts at end of frame only */
		WRITE_REG(A_FIFO_CTRL, 0x1);
#else
		/* Interrupts at end of frame AND at fifo threshold.
		   Will work as 'transmit interrupt'. */
		WRITE_REG(A_FIFO_CTRL, 0x5);
#endif

		/* D - Rx of port_no */
		xhfc_select_fifo(fe, mod_no, port_no*8 + 4 + 1);
		/* ST--> FIFO, channel enabled */
		WRITE_REG(A_CON_HDLC, a_con_hdlc.reg);
		/* 16kbit/s */
		WRITE_REG(A_SUBCH_CFG, 2);

#if 1
		/* interrupts at end of frame only */
		WRITE_REG(A_FIFO_CTRL, 0x1);
#else
		/* interrupts at end of frame AND at fifo threshold */
		WRITE_REG(A_FIFO_CTRL, 0x5);
#endif
	}
	DEBUG_HFC_INIT("\nDone\n");

	DEBUG_HFC_INIT("Configure PCM time slots\n");
	/*	Configure PCM time slots. 
		B-chan data will use PCM64 bus. D-chan data will use SPI. 
	*/
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
		for (bchan = 0; bchan < 2; bchan++) {

			u8	pcm_slot;

			DEBUG_HFC_INIT("port_no: %d, bchan: %d\n", port_no, bchan);

			if(mod_no >= MAX_BRI_MODULES){
				/* adjust mod_no to be between 0 and 10 (including)*/
				pcm_slot = (u8)calculate_pcm_timeslot(mod_no - MAX_BRI_MODULES, port_no, bchan);
				/* AFT Line 1 will use odd PCM timeslots */
				pcm_slot += 1;
			}else{
				/* AFT Line 0 will use even PCM timeslots */
				pcm_slot = (u8)calculate_pcm_timeslot(mod_no, port_no, bchan);
			}

			DEBUG_HFC_INIT("selecting TX pcm_slot: %d\n", pcm_slot);

			/*****************************************************************************************/
			/*	transmit slot - select direction TX */
			xhfc_select_pcm_slot(fe, mod_no, pcm_slot, XHFC_DIRECTION_TX);

			/*	Connect time slot with channel and pin.
				PCM data output on pin STIO2 (+0x40 swap pins)
				0x80+0x40==0xC0==11000000 -> v_rout==0x3 -> "output buffer for STIO2 enabled" page 257.
				0*8 + 0*2 = 0 --> HFC channel 0.

				Assign HFC channel (from 0 to 15) to the selected PCM slot.
			*/
			WRITE_REG(A_SL_CFG,0x80+0x40+port_no*8+bchan*2); /* assign HFC channel (from 0 to 15) 
									to the selected PCM slot. */

			/*****************************************************************************************/
			/* receive slot - select direction RX */
			DEBUG_HFC_INIT("selecting RX pcm_slot: %d\n", pcm_slot);

			xhfc_select_pcm_slot(fe, mod_no, pcm_slot, XHFC_DIRECTION_RX);

			/*	Connect time slot with channel and pin.
				PCM data input from pin STIO1  (+0x40 swap pins).
				Assign HFC channel (from 0 to 15) to the selected PCM slot.
			*/

			WRITE_REG(A_SL_CFG,0x80+0x40+port_no*8+bchan*2+1);/* assign HFC channel (from 0 to 15) 
									  to the selected PCM slot. */
		}/* for (bchan = 0; bchan < 2; bchan++) */
	}/* for (port_no = 0; port_no < bri_module->num_ports; port_no++) */

	DBG_MODULE_TESTER("configure ST ports\n");
	/* configure ST ports */
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
		u8 old_f0cl, new_f0cl;
		int32_t f0cl_diff;

		DEBUG_HFC_INIT("%s(): configurig ST port_no: %d\n", __FUNCTION__, port_no);

		WRITE_REG(R_SU_SEL, port_no);
		
		switch(bri_module->type)
		{
		case MOD_TYPE_TE:
			WRITE_REG(A_SU_CLK_DLY, CLK_DLY_TE);
			
			/* TE, ST B1+B2 tx enabled, end of pulse control enabled */
			WRITE_REG(A_SU_CTRL0, 0x43);
			break;

		case MOD_TYPE_NT:
			WRITE_REG(A_SU_CLK_DLY, CLK_DLY_NT);
			
			/* NT, ST B1+B2 tx enabled, end of pulse control enabled */
			a_su_ctrl0.bit.v_b1_tx_en = 1;		
			a_su_ctrl0.bit.v_b2_tx_en = 1;		
			a_su_ctrl0.bit.v_su_md = 1;
			WRITE_REG(A_SU_CTRL0, a_su_ctrl0.reg);
			break;
		}
		
		/* reset default. TE and NT */
		WRITE_REG(A_SU_CTRL1, 0x0);

		switch(bri_module->type)
		{
		case MOD_TYPE_NT:
			/* enables automatic transition G2->G3 */
			WRITE_REG(A_SU_CTRL1, M_G2_G3_EN);
			break;
		}

		/* ST B1+B2 rx enabled */
		WRITE_REG(A_SU_CTRL2, 0x3);
		
		/* end of pulse control for layer 1 compliance */
		WRITE_REG(A_ST_CTRL3, 0xf8);

		/* WRITE_REG(R_SU_SEL, port_no); */
#if defined(BUILD_MOD_TESTER)
		DBG_MODULE_TESTER("Activate ST port_no state machines (NT only!!)\n");
		/* try to activate port_no */
		switch(bri_module->type)
		{
		case MOD_TYPE_TE:
			/*WRITE_REG(A_SU_WR_STA, STA_ACTIVATE);*/
			break;
		case MOD_TYPE_NT:
			WRITE_REG(A_SU_WR_STA, STA_ACTIVATE | M_SU_SET_G2_G3);
			break;
		}
#endif
		DBG_MODULE_TESTER("poll R_F0_CNTL to make sure PCM is connected\n");

		/* poll R_F0_CNTL to make sure PCM is connected */
		for (i = 0; i < 10; i++) {	
		
			DBG_MODULE_TESTER("get current R_F0_CNTL\n");

			old_f0cl=READ_REG(R_F0_CNTL);
			DBG_MODULE_TESTER("wait for 10ms - f0cl should be incremented by 80 (+- 10 is ok)\n");
			/* wait for 10ms - f0cl should be incremented by 80 (+- 10 is ok) */
			WP_MDELAY(10);

			DBG_MODULE_TESTER("get the R_F0_CNTL after the wait\n");
			new_f0cl=READ_REG(R_F0_CNTL);
		
			if(check_f0cl_increment(fe, old_f0cl, new_f0cl, &f0cl_diff)){
				return 1;
			}
		}
	
		DEBUG_EVENT("%s: Module: %d, PCM 125us pulse ok. (f0cl diff: %d)\n", 
			fe->name, REPORT_MOD_NO(mod_no) + port_no, f0cl_diff);	

	}/* for (port_no = 0; port_no < bri_module->num_ports; port_no++) */

	/* init line interfaces state machines (in software only!) */
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
	
		bri_module->port[port_no].idx = port_no;
		bri_module->port[port_no].hw = bri_module;
		
		/*wan_set_bit(HFC_L1_ACTIVATING, &bri_module->port[port_no].l1_flags);*/

	}/* for (port_no = 0; port_no < bri_module->num_ports; port_no++) */

#if defined(BUILD_MOD_TESTER)
	/* for debugging only - read line status */
	WP_MDELAY(300);

	DBG_MODULE_TESTER("read line status - Connected/Disconnected\n");
	/* read line status - Connected/Disconnected */
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {

		DBG_MODULE_TESTER("select port_no %d\n", port_no);
		WRITE_REG(R_SU_SEL, port_no);

		/* poll line status register for around 5ms */
		for (i = 0; i < 5; i++) {	
			u8	line_status;

			DBG_MODULE_TESTER("read ST line status for port_no %d\n", port_no);
			/* read ST line status*/
			a_su_rd_sta.reg = line_status = READ_REG(A_SU_RD_STA);
	
			if(bri_module->type == MOD_TYPE_TE){
				DEBUG_HFC_S0_STATES("%d: TE: force F7 - pt:%d line status: 0x%02x, v_su_fr_sync: %d\n",
					i, port_no, line_status, a_su_rd_sta.bit.v_su_fr_sync);
			}else{
				DEBUG_HFC_S0_STATES("%d: NT: force G3 - pt:%d line status: 0x%02x, v_su_fr_sync: %d\n", 
					i, port_no, line_status, a_su_rd_sta.bit.v_su_fr_sync);
			}

			DBG_MODULE_TESTER("a_su_rd_sta.bit.v_su_fr_sync: %d (0-Disconnected, 1-Connected)\n", a_su_rd_sta.bit.v_su_fr_sync);

			WP_MDELAY(200);
		}
	}
#endif
	DBG_MODULE_TESTER("%s(): finished\n", __FUNCTION__);

	return 0;
}

static int32_t check_f0cl_increment(sdla_fe_t *fe, u8 old_f0cl, u8 new_f0cl, int32_t *diff)
{
	*diff = new_f0cl - old_f0cl;
	if(*diff < 0){
		*diff = 255 - old_f0cl;
		*diff += new_f0cl;
	}

	/* should be between 70 and 90 over 10ms time */
	if(*diff == 0){
		DEBUG_ERROR("%s: PCM ERROR: BRI Modlue NO CLOCK found! 125us pulse f0cl diff: %d\n",
			fe->name, *diff);	
		return 1;
	}
	
	if(*diff > 150 || *diff < 70){
		DEBUG_WARNING("%s: PCM Warning 125us pulse count f0cl diff: %d\n",
			fe->name, *diff);	
	}

	DBG_MODULE_TESTER("f0cl diff: %d\n", *diff);
	return 0;
}

// these functions are used for making sure indexes are properly read

static u8 read_reg_volatile(sdla_fe_t *fe,  u8 mod_no, u8 reg)
{
	const u16 max_num = 128;
	      u16 cur_num = max_num+1;

	u8  res = READ_REG(reg);

	while (--cur_num)
	{
		const u8 chk = READ_REG(reg);

		if (res == chk)
		{
			if (cur_num != max_num) {
				DEBUG_RX1("%s: stable 0x%02X (0x%02X) after %u reads\n",
				          fe->name, reg, res, max_num - cur_num + 1);
			}

			return res;
		}

		res = chk;
	}

	DEBUG_WARNING("%s: unable to get stable read from 0x%02X (using 0x%02X)\n", fe->name, reg, res);
	return res;
}

#define READ_REG_VOLATILE(reg) \
	read_reg_volatile(fe, mod_no, reg)


typedef enum _DCHAN_RC{
	DCHAN_STATUS_BUSY = 1,
	DCHAN_STATUS_COMPLETE,
	DCHAN_STATUS_INCOMPLETE,
	DCHAN_STATUS_EMPTY
}DCHAN_RC;

#define TX_EMPTY_FIFO	1

static u8 xhfc_write_fifo_dchan(sdla_fe_t *fe,	u8 mod_no,
			wp_bri_module_t *bri_module, bri_xhfc_port_t *port, 
			u8 *free_space)
{
	u8	*buf = NULL;
	int 	*len = NULL,
		*idx = NULL;

	u8	fcnt, tcnt, i;
	u8	f1, f2;
	u8  z1, z2;

	reg_a_fifo_sta	fstat;

	u8	*data;	
	u8	rc;
	sdla_t	*card = (sdla_t*)fe->card;
	private_area_t *chan=NULL;

	u8	fifo_free;
	u8	fifo_usage;

	buf = port->dtxbuf;		/* data buffer */
	len = &port->bytes2transmit;	/* hdlc packet len */
	idx = &port->dtx_indx;		/* already transmitted */

	DEBUG_HFC_TX("%s(): *len: %d\n", __FUNCTION__, *len);

	/* select the D-channel TX fifo */
	xhfc_select_fifo(fe, mod_no, (port->idx*8+4));

	fstat.reg = READ_REG_VOLATILE(A_FIFO_STA);

	if (fstat.reg & (M_FIFO_ERR|M_ABO_DONE)) {
		xhfc_resetfifo_flag(fe, mod_no, M_RES_FIFO_ERR);
		TX_FAST_DBG("%s(%d, %d) TX FIFO ERROR (%02X)\n", __FUNCTION__, mod_no, port->idx, fstat.reg);
	}

	z1 = READ_REG_VOLATILE( A_Z1);
	z2 = READ_REG_VOLATILE( A_Z2);

	fifo_usage = z1 >= z2 ? (z1 - z2) : (bri_module->max_z - (z2 - z1)) + 1;
	fifo_free = (bri_module->max_z - fifo_usage);

	*free_space = fifo_free;

	TX_FAST_DBG("%s(): Line:%d: free: %d\n", __FUNCTION__, __LINE__, fifo_free);

	tcnt = ((fifo_free >= (*len - *idx)) ? (*len - *idx) : fifo_free);

	TX_EXTRA_DBG("%s(): tcnt: %d, *idx: %d, *len %d, fstat.reg: 0x%X, free %d, usage: %d\n",
	             __FUNCTION__, tcnt, *idx, *len, fstat.reg, fifo_free, fifo_usage);

	if (tcnt) {
		f1 = READ_REG_VOLATILE( A_F1);
		f2 = READ_REG_VOLATILE( A_F2);

		/******** free frame/byte count in FIFOs *********
		*                                                *
		*   condition          buffer sections           *
		*               0                           LAST *
		*   f1 >= f2 -> | (free) F2 (used) F1 (free) |   *
		*   f1 <  f2 -> | (busy) F1 (free) F2 (busy) |   *
		*                                                *
		**************************************************/

		fcnt = (f1 >= f2 ? (0x07 - f1) + f2 : (f2 - f1) - 1);

		TX_FAST_DBG("%s(): free: %d, fcnt: %d, tcnt: %d\n", __FUNCTION__, fifo_free, fcnt, tcnt);

		TX_EXTRA_DBG("%s(): START: usage: 0x%X, z1: 0x%X z2: 0x%X f1: 0x%X: f2:0x%X f0c:0x%X\n", 
		             __FUNCTION__, READ_REG( A_USAGE),READ_REG( A_Z1),READ_REG( A_Z2),f1,f2,READ_REG( R_F0_CNTL));

		if (fifo_free && fcnt) {
			data = buf + *idx;
			*idx += tcnt;

			/* write data to FIFO */
			i=0;
			while (i<tcnt) {
				WRITE_REG(A_FIFO_DATA, *(data+i));
				i++;
			}

			/* terminate frame */
			if (*idx == *len) {
				xhfc_increment_fifo(fe, mod_no);/* incrementing fifo sends the frame out */
#if !TX_EMPTY_FIFO
				*len = 0;
				*idx = 0;
#endif			
				DEBUG_HFC_TX("%s(%d, %d): finished frame transmission.\n", __FUNCTION__, mod_no, port->idx);
				rc = DCHAN_STATUS_COMPLETE;
#if !TX_EMPTY_FIFO
				chan=(private_area_t*)card->u.aft.dev_to_ch_map[BRI_DCHAN_LOGIC_CHAN];
				if (chan) {
					*len = 0;
					*idx = 0;
					TX_FAST_DBG("%s(): calling wanpipe_wake_stack()\n", __FUNCTION__);
					wanpipe_wake_stack(chan);
				}
#endif

			}else{
				xhfc_select_fifo(fe, mod_no, (port->idx*8+4));/* addition ?? */

				DEBUG_HFC_TX(KERN_INFO "%s(%d, %d): transmitted part of a frame.\n", __FUNCTION__, mod_no, port->idx);
				rc = DCHAN_STATUS_INCOMPLETE;
			}
		}else{
			DEBUG_HFC_TX(KERN_INFO "%s(%d, %d): NO space (free=%u, usage=%u, fcnt=%u, tcnt=%u)!\n",
			             __FUNCTION__, mod_no, port->idx, fifo_free, fifo_usage, fcnt, tcnt);

			rc = DCHAN_STATUS_BUSY; /* there is NO free space in tx fifo */
		}
	} else {
		DEBUG_HFC_TX("%s(%d, %d): nothing to send (free=%u, usage=%u, fcnt=%u, tcnt=%u)\n",
		      __FUNCTION__, mod_no, port->idx, fifo_free, fifo_usage, fcnt, tcnt);

		rc = DCHAN_STATUS_COMPLETE;
	}

	TX_EXTRA_DBG("%s(): END: eof usage: 0x%X, z1: 0x%X z2: 0x%X f1: 0x%X: f2:0x%X f0c:0x%X\n",
		__FUNCTION__, 
		READ_REG(A_USAGE), READ_REG( A_Z1), READ_REG(A_Z2),f1,f2,READ_REG( R_F0_CNTL));

#if TX_EMPTY_FIFO
	chan=(private_area_t*)card->u.aft.dev_to_ch_map[BRI_DCHAN_LOGIC_CHAN];

	z1 = READ_REG_VOLATILE( A_Z1);
	z2 = READ_REG_VOLATILE( A_Z2);

	fifo_usage = z1 >= z2 ? (z1 - z2) : bri_module->max_z - (z2 - z1);
	fifo_free = (bri_module->max_z - fifo_usage);

	if (chan) {
		if(fifo_usage < (bri_module->max_z >> 1)) {
			/* FIFO has enough space */
			/*WP_DELAY(10000);*/
			*len = 0;
			*idx = 0;

			TX_FAST_DBG("%s(): calling wanpipe_wake_stack()\n", __FUNCTION__);
			wanpipe_wake_stack(chan);
		}
	}

	TX_EXTRA_DBG("%s(): tcnt: %d, *idx: %d, *len %d, fstat.reg: 0x%X, free %d, usage: %d\n",
	            __FUNCTION__, tcnt, *idx, *len, fstat.reg, fifo_free, fifo_usage);
#endif

	DEBUG_HFC_TX("%s(): returning: %d.\n", __FUNCTION__, rc);
	return rc;
}

/*
Transmit D channel frames.
Transmitting fifo data requires running PCM clocks with signal at C4IO and F0IO.
*/
static int32_t wp_bri_dchan_tx(sdla_fe_t *fe, void *src_data_buffer, u32 buffer_len)
{
	u8			mod_no, port_no, rc;
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	bri_xhfc_port_t		*port_ptr;
	u8			free_space;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_TX_DATA("%lu: %s(): Module: %d, port_no: %d. fe->name: %s blen=%i\n",
		jiffies, __FUNCTION__, mod_no, port_no, fe->name, buffer_len);

#if defined(BUILD_MOD_TESTER)
	{
		u32	i;
		u8	*tmp = (u8*)src_data_buffer;

		DEBUG_TX_DATA("TX data:\n");

		for(i = 0; i < buffer_len; i++) {
			_DEBUG_EVENT("%02x ", tmp[i]);
			if(i && ((i % 20) == 0)){
				DEBUG_EVENT("\n");
			}
		}
		DEBUG_EVENT("\n");
	}
#endif

	bri_module = &bri->mod[mod_no];
	port_ptr = &bri_module->port[port_no];

	if(src_data_buffer == NULL){
		/* Caller is interested in tx buffer space, 
		   return how many bytes is still left to transmit */
		return port_ptr->bytes2transmit;
	}

	if(MAX_DFRAME_LEN_L1 <= buffer_len){
		DEBUG_EVENT("%s: %s(): Tx data length %d exceeds maximum of %d bytes!\n",
			fe->name, __FUNCTION__, buffer_len, MAX_DFRAME_LEN_L1);
		return -EINVAL;
	}

	if(port_ptr->bytes2transmit){
		TX_EXTRA_DBG("%s(): %s: transmit EBUSY (queued = %u)\n",
		           fe->name, __FUNCTION__, port_ptr->bytes2transmit);
		/* still transmitting previous frame */
		return -EBUSY;
	}
/*
	{
		int ind;
		u8 * u8_src_data_buffer = (u8*)src_data_buffer;

		for(ind = 0; ind < buffer_len; ind++){
			if(u8_src_data_buffer[ind] != ind){
				DEBUG_RX1("tx: u8_src_data_buffer[0x%x] is: 0x%x != 0x%x!!\n", 
					ind, u8_src_data_buffer[ind], ind);
			}
		}
	}
*/

	memcpy(port_ptr->dtxbuf, src_data_buffer, buffer_len);
	port_ptr->bytes2transmit = buffer_len;
		
	rc = xhfc_write_fifo_dchan(fe, mod_no, bri_module, port_ptr, &free_space);

	/* The frame was accepted for transmission.
	 * Return 0 - Successful tx but now queue is full.
	 * Note that the frame maybe actually transmitted in parts, depending on the length. 
	 */
	return 0;
}

#if CHECK_DATA
static void dump_chip_SRAM(sdla_fe_t *fe, u8 mod_no, u8 port_no)
{
	u8 db;
	u16 a;
	/*
	XHFC dump of internal RAM
	
	the dump shows content of internal array registers
	Z and F counters and other internal variables
	
	please select address range for D channel that shows the errors

	128 bytes address range for receive buffer of channel  5, D rx port_no 0
	*/
	/* wrong offset
	u16 start_addr = 0x580;
	u16 end_addr   = 0x600;
	*/
	u16 start_addr = 0x280;
	u16 end_addr   = 0x300;

/*
	128 bytes address range for receive buffer of channel 13, D rx port_no 1
	u16 start_addr = 0xd80;
	u16 end_addr   = 0xe00;
*/
	for (a = start_addr; a < end_addr; a++)
	{
		WRITE_REG(R_RAM_ADDR, (a & 0xff));
		WRITE_REG(R_RAM_CTRL, ((a >> 8) & 0xff));
		db=READ_REG(R_RAM_DATA);

		/* dummy read to add some delay */
		/*db=READ_REG(R_INT_DATA);
		db=READ_REG(R_INT_DATA);*/

		if((a % 16) == 0){
			_DEBUG_EVENT("\n %04x",a);
		}
		_DEBUG_EVENT(" %02x",db);
	}
	DBG_RX_DATA("\n");
}

static int check_data(u8 *data, int data_len)
{
	int i, rc = 0;

	/*DBG_RX_DATA("%s(): data_len: %d\n", __FUNCTION__, data_len);*/

	for(i = 0; i < data_len; i++){
		if(data[i] == 0xFF){
			rc = 1;
			break;
		}
	}
	return rc;
}

static void dump_data(u8 *data, int data_len)
{
	int i;

	DBG_RX_DATA("%s(): data_len: %d\n", __FUNCTION__, data_len);
	
	for(i = 0; i < data_len; i++){
		if((i%16) == 0){
			_DEBUG_EVENT("\n %04X", i);
		}

		_DEBUG_EVENT(" %02X", data[i]);

		if (i == (data_len - 4)){
			printk (" -");
		}
	}
	_DEBUG_EVENT("\n");
}
#endif/* CHECK_DATA */

static int xhfc_read_fifo_dchan(sdla_fe_t *fe,	u8 mod_no,
			wp_bri_module_t *bri_module, bri_xhfc_port_t *port, u32 *rx_data_len)
{
	u8	*buf = port->drxbuf;
	int	*idx = &port->drx_indx;
	u8	*data; /* pointer for new data */
	u8	rcnt, fcnt, i;
	u8	f1=0, f2=0, z1=0, z2=0;

	reg_a_fifo_sta	fstat;

	/* select D-RX fifo */
	xhfc_select_fifo(fe, mod_no, (port->idx * 8) + 5);

	fstat.reg = READ_REG(A_FIFO_STA);

	if (fstat.reg & M_FIFO_ERR) {
		xhfc_resetfifo_flag(fe, mod_no, M_RES_FIFO_ERR);
		RX_EXTRA_DBG("%s(%d, %d) RX FIFO ERROR (%u)\n", __FUNCTION__, mod_no, port->idx, fstat.reg);
	}

	/* hdlc rcnt */
	f1 = READ_REG_VOLATILE( A_F1);
	f2 = READ_REG_VOLATILE( A_F2);

	z1 = READ_REG_VOLATILE( A_Z1);
	z2 = READ_REG_VOLATILE( A_Z2);

	/* used frame + index count in rx fifo                                 *
     *                                                                     *
	 * same logic as tx, but reversed (free instead of used)               *
     *                                                                     *
     * when overlap happens we need to add one (+1) to the used byte/frame *
     * count for considering the extra "empty" slot represented by f1==f2  */

	fcnt = f1 >= f2 ? (f1 - f2) : (0x07 - f2) + f1 + 1;
	rcnt = z1 >= z2 ? (z1 - z2) : (bri_module->max_z - z2) + z1 + 1;

	RX_EXTRA_DBG("%s(%d, %d): fcnt: %d, rcnt: %d, *idx %d, z1 %d, z2 %d, f1 %d, f2 %d\n",
	             __FUNCTION__, mod_no, port->idx, fcnt, rcnt, *idx, z1, z2, f1, f2);

		/* debug message of F and Z counters */
	RX_EXTRA_DBG("%s(): START: usage: 0x%X, z1: 0x%X z2: 0x%X f1: 0x%X: f2:0x%X f0c:0x%X\n",
		__FUNCTION__,
		READ_REG(A_USAGE), READ_REG( A_Z1), READ_REG(A_Z2),f1,f2,READ_REG( R_F0_CNTL));

	if (!fcnt)
	{
		RX_EXTRA_DBG("%s(): END: eof usage: 0x%X, z1: 0x%X z2: 0x%X f1: 0x%X: f2:0x%X f0c:0x%X\n",
				__FUNCTION__,
				READ_REG(A_USAGE), READ_REG( A_Z1), READ_REG(A_Z2),f1,f2,READ_REG( R_F0_CNTL));

		*rx_data_len = rcnt;

		if (rcnt != 0) {
			RX_EXTRA_DBG("%s: no frame, %u bytes\n", fe->name, rcnt);
		} else {
			RX_EXTRA_DBG("%s(): RX FIFO is empty! (z1=%u,z2=%u,f1=%u,f2=%u)\n", __FUNCTION__, z1, z2, f1, f2);
		}
		return DCHAN_STATUS_EMPTY;
	} else {
		// extra byte after end of frame
		++rcnt;
	}

	data = buf + *idx;
	*idx += rcnt;
	if(*idx >= MAX_DFRAME_LEN_L1){
		if (0 && WAN_NET_RATELIMIT()) {
			DEBUG_EVENT("%s: %s(): frame in mod_no %d, port_no %d > maximum size of %d bytes!\n",
				fe->name, __FUNCTION__, mod_no, port->idx, MAX_DFRAME_LEN_L1);
		}
		*idx = 0;
		*rx_data_len = 0;
		return DCHAN_STATUS_EMPTY;
	}

	DEBUG_HFC_RX("rcnt: %d\n", rcnt);
	/* read data from FIFO */
	i=0;
	while (i < rcnt) {
		*(data+i) = READ_REG(A_FIFO_DATA);
		i++;
	}

	/* hdlc frame termination */	
	if (fcnt) {
		xhfc_increment_fifo(fe, mod_no);
		/* check minimum frame size */
		if (*idx < 4) {
			if (0 && WAN_NET_RATELIMIT()) {
				DEBUG_EVENT("%s: %s(): frame in mod_no %d, port_no %d < minimum size of 4 bytes (%u)!\n",
				            fe->name, __FUNCTION__, mod_no, port->idx, *idx);
			}
			*idx = 0;
			*rx_data_len = 0;
			return DCHAN_STATUS_EMPTY;
		}

		/* check crc */
		if (buf[(*idx) - 1]) {
			if (0 && WAN_NET_RATELIMIT()) {
				DEBUG_EVENT("%s: %s(): CRC-error in frame in mod_no %d, port_no %d!\n",
				            fe->name, __FUNCTION__, mod_no, port->idx);
			}
			*idx = 0;
			*rx_data_len = 0;
			return DCHAN_STATUS_EMPTY;
		}

#if 0
		/* D-Channel debug to syslog */
		DBG_RX_DATA("%lu:D-RX len(%02i):\n", jiffies, (*idx));
		i = 0;
		while(i < (*idx)){
			printk("%02x ", buf[i++]);
			if (i == (*idx - 3)){
				printk ("- ");
			}
		}
		printk("\n");
#endif
		*rx_data_len = *idx - 3;/* discard CRC and STATUS - 3 bytes */
					/* STATUS is the last byte of a frame in the fifo
					   which is used to check if CRC is correct or not.

			After the ending flag of a frame the XHFC-2SU checks the HDLC CRC checksum.
			If it is correct one byte with all �0�s is inserted behind
			the CRC data in the FIFO named STAT (see Fig. 4.2). This last byte of a frame in the FIFO is different
			from all �0�s if there is no correct CRC field at the end of the frame.
			If the STAT value is 0xFF, the HDLC frame ended with at least 8 bits �1�s. This is similar to an abort
			HDLC frame condition.
			The ending flag of a HDLC frame can also be the starting flag of the next frame.
			page 122.
		*/
#if CHECK_DATA
		if(check_data(port->drxbuf, *rx_data_len)){
			dump_chip_SRAM(fe, mod_no, port->idx);
			dump_chip_SRAM(fe, mod_no, port->idx);
			reset_chip(fe, mod_no);
			dump_data(port->drxbuf, *idx);
		}
#endif
		*idx = 0;

		RX_EXTRA_DBG("%s(): END: eof usage: 0x%X, z1: 0x%X z2: 0x%X f1: 0x%X: f2:0x%X f0c:0x%X\n",
			__FUNCTION__, 
			READ_REG(A_USAGE), READ_REG( A_Z1), READ_REG(A_Z2),f1,f2,READ_REG( R_F0_CNTL));

		DEBUG_HFC_RX("%s(): finished receiving a frame.\n", __FUNCTION__);
		return DCHAN_STATUS_COMPLETE;
	} else {
		RX_EXTRA_DBG("%s(): END: eof usage: 0x%X, z1: 0x%X z2: 0x%X f1: 0x%X: f2:0x%X f0c:0x%X\n",
			__FUNCTION__, 
			READ_REG(A_USAGE), READ_REG( A_Z1), READ_REG(A_Z2),f1,f2,READ_REG( R_F0_CNTL));

		DEBUG_HFC_RX("%s(): received a part of frame.\n", __FUNCTION__);
		return DCHAN_STATUS_INCOMPLETE;
	}
}


static void *wp_bri_dchan_rx(sdla_fe_t *fe, u8 mod_no, u8 port_no, u32 * rx_data_len)
{
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	bri_xhfc_port_t		*port_ptr;
	netskb_t		*skb;
	u8			*skb_data_area;
	int			rc;

	/* Note: D channel is slow (less than 2 bytes / ms!!) */
	DEBUG_HFC_RX("%s(): line: %d, mod_no: %d port_no: %d\n", __FUNCTION__, __LINE__, mod_no, port_no);

	bri_module = &bri->mod[mod_no];
	port_ptr = &bri_module->port[port_no];

	*rx_data_len = 0;

	rc = xhfc_read_fifo_dchan(fe, mod_no, bri_module, port_ptr, rx_data_len);

	skb = NULL;
	if((rc == DCHAN_STATUS_COMPLETE) && (*rx_data_len < MAX_DFRAME_LEN_L1) && (*rx_data_len > 0)){
		/**************************/
		skb = wan_skb_alloc(*rx_data_len);
		if(skb == NULL){
			return NULL;
		}
	
		skb_data_area = wan_skb_put(skb, *rx_data_len);
		memcpy(skb_data_area, port_ptr->drxbuf, *rx_data_len);

#if 0
		{
			int ind;
			for(ind = 0; ind < *rx_data_len; ind++){
				if(skb_data_area[ind] != ind){
					DEBUG_RX1("rx: skb_data_area[0x%x] is: 0x%x != 0x%x!!\n", 
						ind, skb_data_area[ind], ind);
				}
			}
		}
#endif
	}

	return skb;
}


/******************************************************************************
** wp_bri_iface_init) - 
**
**	OK
*/
int32_t wp_bri_iface_init(void *pfe_iface)
{
	sdla_fe_iface_t	*fe_iface = (sdla_fe_iface_t*)pfe_iface;

	BRI_FUNC();
	fe_iface->global_config		= &bri_global_config;	/* not used in BRI */
	fe_iface->global_unconfig	= &bri_global_unconfig;	/* not used in BRI */

	fe_iface->config		= &wp_bri_config;
	fe_iface->unconfig		= &wp_bri_unconfig;

	fe_iface->post_unconfig		= &wp_bri_post_unconfig;

	fe_iface->post_init		= &wp_bri_post_init;

	fe_iface->if_config		= &wp_bri_if_config;
	fe_iface->if_unconfig		= &wp_bri_if_unconfig;

	fe_iface->active_map		= &wp_bri_active_map;

	fe_iface->set_fe_status		= &wp_bri_set_fe_status;
	fe_iface->get_fe_status		= &wp_bri_get_fe_status;

	fe_iface->isr			= &wp_bri_intr;

	fe_iface->disable_irq		= &wp_bri_disable_fe_irq; 

	fe_iface->check_isr		= &wp_bri_check_intr;

	fe_iface->polling		= &wp_bri_polling;
	fe_iface->process_udp		= &wp_bri_udp;
	fe_iface->get_fe_media		= &wp_bri_fe_media;

	fe_iface->set_dtmf		= &wp_bri_set_dtmf;
	fe_iface->intr_ctrl		= &wp_bri_intr_ctrl;
	fe_iface->event_ctrl		= &wp_bri_event_ctrl;

	fe_iface->isdn_bri_dchan_tx	= &wp_bri_dchan_tx;

	fe_iface->clock_ctrl		= &config_clock_routing;

	return 0;
}

/* Should be done only ONCE per card. */
static int32_t wp_bri_spi_bus_reset(sdla_fe_t	*fe)
{
	sdla_t	*card = (sdla_t*)fe->card;

	BRI_FUNC();

	DEBUG_EVENT("%s: Executing SPI bus reset....\n", fe->name);

	card->hw_iface.bus_write_4(	card->hw,
					SPI_INTERFACE_REG,
					MOD_SPI_RESET);
	WP_DELAY(1000);
	card->hw_iface.bus_write_4(	card->hw,
					SPI_INTERFACE_REG,
					0x00000000);
	WP_DELAY(1000);
	return 0;
}

/******************************************************************************
*				scan_modules()	
*
* Description	: Scan for installed modules.
*
* Arguments	: pfe - pointer to Front End structure.	
*
* Returns	: number of discovered modules.
*******************************************************************************/
static u_int8_t scan_modules(sdla_fe_t *fe, u_int8_t rm_no)
{
	u_int8_t mod_no = RM_BRI_STATUS_READ/*0x3*/;/* to read remora status register ALWAYS put 0x3 into mod_addr. */
	u_int8_t value, ind, mod_counter = 0;
	u_int8_t mod_no_index;	/* index in the array of ALL modules (NOT lines) on ALL remoras. From 0 to 11 */

	/* format of remora status register: 
		bit 0 == 1	- module 1 active (exist)
		bit 1		- type of module 1 (0 - NT, 1 - TE)

		bit 2 == 1	- module 2 active (exist)
		bit 3		- type of module 1 (0 - NT, 1 - TE)

  		bit 4 == 1	- module 3 active (exist)
		bit 5		- type of module 1 (0 - NT, 1 - TE)

		bit 6,7		- has to be zeros for active remora. if non-zero, remora does not exist.
	*/

	value = fe->read_fe_reg(fe->card, 
				mod_no,
				MOD_TYPE_NONE,	
				rm_no,
				0);

#define MODULE1	0
#define MODULE2	2
#define MODULE3	4

	DEBUG_BRI_INIT("remora number: %d, remora status register: 0x%02X\n", rm_no, value);

	if((value >> 6) == 0x2){
		DEBUG_EVENT("%s: Remora number %d: Found 512khz Recovery clock remora.\n", fe->name, rm_no);
		fe->bri_param.use_512khz_recovery_clock = 1;
	}else{

		if(((value >> 7) & 0x01)){
			DEBUG_EVENT("%s: Remora number %d does not exist.\n", fe->name, rm_no);
			return 0;
		}else{
			DEBUG_EVENT("%s: Remora number %d exist.\n", fe->name, rm_no);
		}
	}

	for(ind = 0; ind < 6; ind++){

		switch(ind){

		case MODULE1:
		case MODULE2:
		case MODULE3:
			DEBUG_BRI_INIT("module Number on REMORA (0-2): %d\n", ind / 2);

			/* 0-11, all (even and odd) numbers */
			mod_no_index = rm_no * MAX_BRI_MODULES_PER_REMORA + ind / 2;
			DEBUG_BRI_INIT("mod_no_index on CARD (should be 0-11): %d\n", mod_no_index);

			/* 0-23, only even numbers */
			mod_no_index = mod_no_index * 2;
			DEBUG_BRI_INIT("mod_no_index (line number) on CARD (should be 0-23): %d\n", mod_no_index);

			if(mod_no_index >= MAX_BRI_LINES){
					DEBUG_ERROR("%s: Error: Module %d/%d exceeds maximum (%d)\n",
						fe->name, mod_no_index, mod_no_index, MAX_BRI_LINES);
				return 0;
			}

			if((value >> ind) & 0x01){

				mod_counter++;

				if((value >> (ind + 1)) & 0x01){
					
					DEBUG_BRI_INIT("%s: Module %d type is: TE\n",
						fe->name, mod_no_index);

					fe->bri_param.mod[mod_no_index].type = MOD_TYPE_TE;

					fe->bri_param.mod[mod_no_index].port[PORT_0].mode |= PORT_MODE_TE;
					fe->bri_param.mod[mod_no_index].port[PORT_1].mode |= PORT_MODE_TE;

				}else{
					DEBUG_BRI_INIT("%s: Module %d type is: NT\n",
						fe->name, mod_no_index);

					fe->bri_param.mod[mod_no_index].type = MOD_TYPE_NT;

					fe->bri_param.mod[mod_no_index].port[PORT_0].mode |= PORT_MODE_NT;
					fe->bri_param.mod[mod_no_index].port[PORT_1].mode |= PORT_MODE_NT;
				}

				/*Copy information from (even numbered) 'mod_no_index' to 
				(odd numbered) 'mod_no_index + 1' because for each module there are two lines.*/
				memcpy(&fe->bri_param.mod[mod_no_index + 1], &fe->bri_param.mod[mod_no_index], 
					sizeof(wp_bri_module_t));
			}else{
				DEBUG_BRI_INIT("%s: Module %d is not installed\n",
						fe->name, mod_no_index);
			}
			DEBUG_BRI_INIT("=================================\n\n");
			break;
		}/* switch() */
	}/* for() */

	return mod_counter;
}

/******************************************************************************
*				scan_remoras_and_modules()	
*
* Description	: Scan for installed Remoras and modules.
*
* Arguments	: pfe - pointer to Front End structure.	
*
* Returns	: 0 - configred successfully, otherwise non-zero value.
*******************************************************************************/
static int32_t scan_remoras_and_modules(void* pfe)
{
	sdla_fe_t	*fe = (sdla_fe_t*)pfe;
	u8		rm_no, modules_counter = 0;

	BRI_FUNC();

	for(rm_no = 0; rm_no < MAX_BRI_REMORAS; rm_no++){

		modules_counter += scan_modules(fe, rm_no);
	}

	if(modules_counter == 0){
		DEBUG_ERROR("%s: Error: modules counter is zero!\n", fe->name);
		return 1;
	}else{
		DEBUG_EVENT("%s: Total number of modules: %d.\n", fe->name, modules_counter);
		return 0;
	}
}

/******************************************************************************
*				bri_global_config()	
*
* Description	: Global configuration for Sangoma BRI board.
*
* 		Notes	: 1. This routine runs only ONCE for a physical 'base' CARD,
*					 not for 'additional' cards. 
*				  2. reset card's SPI.
*				  3. Scan for installed Remoras and modules.
*
* Arguments	: pfe - pointer to Front End structure.	
*
* Returns	: 0 - configred successfully, otherwise non-zero value.
*******************************************************************************/
static int32_t bri_global_config(void* pfe)
{
	sdla_fe_t	*fe = (sdla_fe_t*)pfe;

	BRI_FUNC();

	DEBUG_EVENT("%s: %s Global Front End configuration\n", 
			fe->name, FE_MEDIA_DECODE(fe));

	return 0;
}

/*******************************************************************************
*				bri_global_unconfig()	
*
* Description	: Global un-configuration for Sangoma BRI board.
* 			Note: This routne runs only ONCE for a physical card.
*
* Arguments	:	
*
* Returns	: 0
*******************************************************************************/
static int32_t bri_global_unconfig(void* pfe)
{
	sdla_fe_t	*fe = (sdla_fe_t*)pfe;

	BRI_FUNC();

	DEBUG_EVENT("%s: %s Global Front End unconfiguration!\n",
				fe->name, FE_MEDIA_DECODE(fe));

	return 0;
}


/*
 ******************************************************************************
 *			wp_bri_post_unconfig()	
 *
 * Description: Free resources used by 'fe'. This function is NOT locked and
 *				it must NOT be called more than one time.
 * Arguments:
 * Returns:
 ******************************************************************************
 */
static int wp_bri_post_unconfig(void* pfe)
{
	sdla_fe_t		*fe = (sdla_fe_t*)pfe;
	wp_bri_module_t		*bri_module;
	sdla_bri_param_t	*bri = &fe->bri_param;
	u8			mod_no, port_no;
	bri_xhfc_port_t		*port_ptr;

	if (!wan_test_bit(WP_BCB_BRI_POST_INIT, &fe->bri_param.critical)) {
		return 1;
	}

	if(validate_fe_line_no(fe, __FUNCTION__)){
		return 1;
	}

	DEBUG_EVENT("%s: Running post-unconfig...\n", fe->name);

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_HFC_INIT("%s(): mod_no: %i, port_no: %i\n", __FUNCTION__, mod_no, port_no);
	
	bri_module = &bri->mod[mod_no];

	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
		port_ptr = &bri_module->port[port_no];
	
		wan_del_timer(&port_ptr->t3_timer);
		wan_del_timer(&port_ptr->t4_timer);
		wan_del_timer(&port_ptr->t1_timer);		

	}/* for (port_no = 0; port_no < bri_module->num_ports; port_no++) */

	wan_clear_bit(WP_BCB_BRI_POST_INIT, &fe->bri_param.critical);

	return 0;
}

/******************************************************************************
*			wp_bri_config() - initialise the XHFC ISDN Chip.
*
* Description	:  Configure the PHYSICAL module ONE time. 
*		   On each module 2 lines will be configured 
*		   in exactly the same way.
*
* Arguments	:  pfe - pointer to Front End structure.
*
* Returns	:  0 - configred successfully, otherwise non-zero value.
*******************************************************************************/
static int32_t wp_bri_config(void *pfe)
{
	sdla_fe_t		*fe = (sdla_fe_t*)pfe;
	sdla_t 			*card = (sdla_t*)fe->card;
	u8			mod_no, port_no, mod_cnt = 0;
	/*sdlahw_t* 		hw = (sdlahw_t*)card->hw;*/
	int32_t			err = 0, aft_line_no = 0;
	u16			physical_module_config_counter;
	u32			physical_card_config_counter;
	wp_bri_module_t		*bri_module;
	sdla_bri_param_t	*bri = &fe->bri_param;

	BRI_FUNC();

	if (wan_test_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical)) {
		DEBUG_ERROR("%s: %s: Error: Line already configred! Line number %i !\n", 
			fe->name, FE_MEDIA_DECODE(fe), WAN_FE_LINENO(fe)+1);
		return 1;
	}

	fe->fe_status = FE_UNITIALIZED;

	if(validate_fe_line_no(fe, __FUNCTION__)){
		DEBUG_ERROR("%s: %s: Error: Invalid Front End Line number %i !\n", 
			fe->name, FE_MEDIA_DECODE(fe), WAN_FE_LINENO(fe)+1);
		return 1;
	}

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	
	DEBUG_EVENT("%s: %s: Front End configuration Line=%d Mod=%i\n", 
			fe->name, FE_MEDIA_DECODE(fe), WAN_FE_LINENO(fe) + 1, mod_no);

	DEBUG_TEST("%s(): mod_no: %i, port_no: %i\n", __FUNCTION__, mod_no, port_no);
	
	WAN_ASSERT(fe->write_fe_reg == NULL);
	WAN_ASSERT(fe->read_fe_reg == NULL);

	fe->bri_param.max_fe_channels 	= MAX_TIMESLOTS;
	fe->bri_param.module_map[0]	= fe->bri_param.module_map[1] = 0;
		
	bri_module = &bri->mod[mod_no];

	card->hw_iface.getcfg(card->hw, SDLA_HWTYPE_USEDCNT, &physical_card_config_counter);

	if(physical_card_config_counter == 1){
		/* Per-card initialization. Important to do only ONCE.*/
		wp_bri_spi_bus_reset(fe);
	}

	fe->bri_param.use_512khz_recovery_clock = 0;

	if(scan_remoras_and_modules(fe)){
		return 1;
	} 

#if defined(BUILD_MOD_TESTER)
	/* for Production Test nothing else should be done */
	return 0;
#endif

	/*Event lock */
	WAN_LIST_INIT(&fe->event);
	/* wan_spin_lock_init(&fe->lock, "wp_bri_lock"); FIXME: 'lock' is not there, but what is there? */

	card->hw_iface.getcfg(card->hw, SDLA_HWLINEREG, &physical_module_config_counter);

	bri_module->fe = fe;

	switch(fe->bri_param.mod[mod_no].type)
	{
	case MOD_TYPE_TE:
	case MOD_TYPE_NT:
		if(physical_module_config_counter == 1){

			if((err = init_xfhc(fe, mod_no))){
				return err;
			}
		}else{
			bri_module->num_ports = BRI_MAX_PORTS_PER_CHIP;
			bri_module->max_fifo = 8;
			bri_module->max_z = 0x7F;

			/* init line interfaces state machines (in software only!) */
			for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
				
				bri_module->port[port_no].idx = port_no;
				bri_module->port[port_no].hw = bri_module;
				
			}/* for (port_no = 0; port_no < bri_module->num_ports; port_no++) */
		}
		break;

	default:
		DEBUG_ERROR("%s(): %s: Error: Module %d (AFT Line: %d): Not Installed!!\n",
			__FUNCTION__, fe->name, REPORT_MOD_NO(mod_no), aft_line_no);
		return 1;
	}
	
	/**************************************************************************/
	/* Set active modules (channels) bitmap for all installed LOGICAL modules */

#define BCHAN_BITMAP	0x3 /* each BRI line has 2 b-channels --> 2 bits */

	if(mod_no < (MAX_BRI_LINES / 2)){
		/* 1-st aft line has timeslots 0-23 */
		aft_line_no = 0;
	}else{
		/* 2-nd aft line has timeslots 24-47 */
		aft_line_no = 1;
	}

	fe->bri_param.mod[mod_no].mod_no = mod_no;

	switch(fe->bri_param.mod[mod_no].type)
	{
	case MOD_TYPE_TE:
	case MOD_TYPE_NT:
		fe->bri_param.module_map[aft_line_no] |= (BCHAN_BITMAP << (mod_no*2));

		DEBUG_EVENT("%s: Module %d (AFT Line: %d): Installed -- %s. Timeslots map: 0x%08X\n",
				fe->name, REPORT_MOD_NO(mod_no), aft_line_no,
				WP_BRI_DECODE_MOD_TYPE(fe->bri_param.mod[mod_no].type),
				(BCHAN_BITMAP << (mod_no*2)));	
		mod_cnt++;
		break;

	default:
		DEBUG_WARNING("%s(): %s: Warning: Module %d (AFT Line: %d): Not Installed.\n",
			__FUNCTION__, fe->name, REPORT_MOD_NO(mod_no), aft_line_no);	
		break;
	}

	/*******************************************************/
	/* Enable interrupts on the installed PHYSICAL module. */
	switch(fe->bri_param.mod[mod_no].type)
	{
	case MOD_TYPE_TE:
	case MOD_TYPE_NT:
		if(physical_module_config_counter == 1){
/* DAVIDR-consider moving call bri_enable_interrupts() to wp_bri_post_init() */
			bri_enable_interrupts(fe, mod_no, port_no);
		}else{
			DEBUG_HFC_INIT("%s(): the other port_no is ALREADY running, interrupts are enabled.\n",
				__FUNCTION__);
		}
		break;
	default:
		DEBUG_WARNING("%s(): %s: Warning: Module %d (AFT Line: %d): Not Installed.\n",
			__FUNCTION__, fe->name, REPORT_MOD_NO(mod_no), aft_line_no);	
		break;
	}

	/******************************************************/
	/*------------------ CLOCK RECOVERY ------------------*/
	if(physical_card_config_counter == 1){
		/* Per-card initialization. Important to do only ONCE.*/
		u32 i;

		/* ALL modules MUST have GPIO6 function switched
		   from the default 'output' to 'input'.
		   Otherwise there will be a clock conflict!*/

		for(i = 0; i < MAX_BRI_LINES; i += BRI_MAX_PORTS_PER_CHIP){

			switch(fe->bri_param.mod[i].type)
			{
			case MOD_TYPE_TE:
			case MOD_TYPE_NT:
				__config_clock_routing(fe, i, WANOPT_NO);
				break;
			default:
				continue;
			}/* switch() */
		}/* for() */
	}/* if() */
	/*----------- END OF CLOCK RECOVERY ------------------*/
	/******************************************************/

	fe->fe_status = FE_DISCONNECTED;

	wan_set_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical);

	return 0;
}

/******************************************************************************
** wp_bri_unconfig() - 
**
**	OK
*/
static int32_t wp_bri_unconfig(void *pfe)
{
	sdla_fe_t	*fe = (sdla_fe_t*)pfe;
	u8		mod_no, port_no;
	u16		physical_module_config_counter;
	sdla_t		*card = (sdla_t*)fe->card;
	sdla_bri_param_t *bri = &fe->bri_param;
	wp_bri_module_t	*bri_module;
	bri_xhfc_port_t	*port_ptr;

	if(validate_fe_line_no(fe, __FUNCTION__)){
		return 1;
	}

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_HFC_INIT("%s(): mod_no: %d, port_no: %d\n", __FUNCTION__, mod_no, port_no);

	/* Check if port was configured. If not, return. */
	if (!wan_test_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical)) {
		DEBUG_BRI("%s: %s(): Warning: Front End initialization was incomplete OR not a first call to UnConfig!\n", 
			fe->name, __FUNCTION__);
		return 1;
	}

	DEBUG_EVENT("%s: Unconfiguring BRI Front End...\n", fe->name);

	bri_module = &bri->mod[mod_no];
	port_ptr   = &bri_module->port[port_no];

	card->hw_iface.getcfg(card->hw, SDLA_HWLINEREG, &physical_module_config_counter);

	WAN_ASSERT(fe->write_fe_reg == NULL);
	WAN_ASSERT(fe->read_fe_reg == NULL);

	/******************************************************************/
	switch(fe->bri_param.mod[mod_no].type)
	{
	case MOD_TYPE_TE:
	case MOD_TYPE_NT:
		
		if(physical_module_config_counter == 1){
			wp_bri_disable_irq(fe, mod_no, port_no);
		}else{
			DEBUG_HFC_INIT("%s(): the other port_no is still running, leave interrupts enabled.\n",
				__FUNCTION__);
		}
		break;
	default:
		/* for missing (not installed) modules - do nothing  */
		DEBUG_EVENT("%s(): %s: Warining: unknown module type!\n", 
			__FUNCTION__, fe->name);
		break;
	}

#if 1
	{
	reg_a_su_wr_sta	a_su_wr_sta;
	/******************************************************************/
	/* When we stop a port (because not used anymore), force the 
	deactivation of the line interface by writing the deactivated state
	into the A_SU_WR_STA register.
	For a port that is configured in NT mode, write 0x11 to A_SU_WR_STA (force G1) 
	and for a TE port write 0x13 (force F3) to this register.
	In these states the port will send only INFO0 (no signal) and no 
	line interface state change interrupts will be generated. */
	WRITE_REG(R_SU_SEL, port_no);

	DEBUG_HFC_INIT("%s(): port_no: %i, port_ptr: 0x%p, port_ptr->mode: %i\n", __FUNCTION__, port_no, port_ptr, port_ptr->mode);

	a_su_wr_sta.reg = 0;
	if (port_ptr->mode & PORT_MODE_TE) {
		/* TE to F3 */
		a_su_wr_sta.bit.v_su_set_sta = 0x3;
		a_su_wr_sta.bit.v_su_ld_sta  = 0x1;
	}else{
		/* NT to G1 */
		a_su_wr_sta.bit.v_su_set_sta = 0x1;
		a_su_wr_sta.bit.v_su_ld_sta  = 0x1;
	}

	WRITE_REG(A_SU_WR_STA, a_su_wr_sta.reg);
	/* Immediatly after that we get SU state interrupt - clear it! (for all ports) */
	/*READ_REG(R_SU_IRQ);*/
	}
#endif
	/******************************************************************/
	
	fe->fe_status = FE_UNITIALIZED;
	
	wan_clear_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical);

	return 0;
}

/******************************************************************************
** wp_bri_post_init() - 
**
**	OK
*/
static int32_t wp_bri_post_init(void *pfe)
{
	sdla_fe_t		*fe = (sdla_fe_t*)pfe;
	wp_bri_module_t		*bri_module;
	sdla_bri_param_t	*bri = &fe->bri_param;
	u8			mod_no, port_no;
	bri_xhfc_port_t		*port_ptr;

	if (wan_test_bit(WP_BCB_BRI_POST_INIT, &fe->bri_param.critical)) {
		return 1;
	}

	if(validate_fe_line_no(fe, __FUNCTION__)){
		return 1;
	}

	DEBUG_EVENT("%s: Running post initialization...\n", fe->name);

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_HFC_INIT("%s(): mod_no: %i, port_no: %i\n", __FUNCTION__, mod_no, port_no);
	
	bri_module = &bri->mod[mod_no];

	/* */
	for (port_no = 0; port_no < bri_module->num_ports; port_no++) {
		port_ptr = &bri_module->port[port_no];

		wan_init_timer(&port_ptr->t3_timer, l1_timer_expire_t3, (wan_timer_arg_t)port_ptr);
		wan_init_timer(&port_ptr->t4_timer, l1_timer_expire_t4, (wan_timer_arg_t)port_ptr);
		wan_init_timer(&port_ptr->t1_timer, l1_timer_expire_t1, (wan_timer_arg_t)port_ptr);
	}/* for (port_no = 0; port_no < bri_module->num_ports; port_no++) */

#if 1
	{
		wan_smp_flag_t	smp_flags1;
		sdla_t		*card = (sdla_t*)fe->card;

		/* Try to activate the port_no - if cable is in, line will get activated.
		   If no cable, the application will have to call wp_bri_set_fe_status()
		   to get line activated. */
		card->hw_iface.hw_lock(card->hw,&smp_flags1);   
		wp_bri_set_fe_status(fe, WAN_FE_CONNECTED);
		card->hw_iface.hw_unlock(card->hw,&smp_flags1);   
	}
#endif

	wan_set_bit(WP_BCB_BRI_POST_INIT, &fe->bri_param.critical);

	return 0;
}

/**
 * l1_timer_start_t3
 */
static void l1_timer_start_t3(void *pport)
{
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
	wp_bri_module_t	*bri_module;
	sdla_fe_t	*fe;
	u8		mod_no;

	WAN_ASSERT_VOID(port_ptr == NULL);

	WAN_ASSERT_VOID(port_ptr->hw == NULL);
	bri_module = port_ptr->hw;

	WAN_ASSERT_VOID(bri_module->fe == NULL);
	fe = (sdla_fe_t*)bri_module->fe;

	mod_no = (u8)bri_module->mod_no;

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	if (!wan_test_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical)) {
		/* may get here during unload!! */
		return;
	}

	if (!wan_test_bit(T3_TIMER_ACTIVE,  &port_ptr->timer_flags) &&
		!wan_test_bit(T3_TIMER_EXPIRED, &port_ptr->timer_flags)){

		DEBUG_HFC_S0_STATES("Starting T3 timer...\n");

		wan_set_bit(T3_TIMER_ACTIVE, &port_ptr->timer_flags); 

		wan_add_timer(&port_ptr->t3_timer, (XHFC_TIMER_T3 * HZ) / 1000);
	}
}

/**
 * l1_timer_stop_t3
 */
static void l1_timer_stop_t3(void *pport)
{
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
	wp_bri_module_t	*bri_module = port_ptr->hw;
	u8		mod_no = (u8)bri_module->mod_no;

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	if(wan_test_bit(T3_TIMER_ACTIVE, &port_ptr->timer_flags)){	

		wan_clear_bit(T3_TIMER_ACTIVE,  &port_ptr->timer_flags);
		wan_clear_bit(T3_TIMER_EXPIRED, &port_ptr->timer_flags);

		wan_clear_bit(HFC_L1_ACTIVATING, &port_ptr->l1_flags);
		wan_del_timer(&port_ptr->t3_timer);
	}
}

/*
 ******************************************************************************
 *				l1_timer_expire_t3()	
 *
 * Description: called when timer t3 expires.
 *				Activation failed, force clean L1 deactivation.
 * Arguments:
 * Returns:
 ******************************************************************************
 */
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static void l1_timer_expire_t3(void* pport)
#elif defined(__WINDOWS__)
static void l1_timer_expire_t3(IN PKDPC Dpc, void* pport, void* arg2, void* arg3)
#elif defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
static void l1_timer_expire_t3(struct timer_list *t)
#else
static void l1_timer_expire_t3(unsigned long pport)
#endif
{
#if defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
	bri_xhfc_port_t	*port_ptr = from_timer(port_ptr, t, t3_timer.timer_info);
#else
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
#endif
	wp_bri_module_t	*bri_module = port_ptr->hw;
	sdla_fe_t	*fe = (sdla_fe_t*)bri_module->fe;
	sdla_t 		*card = (sdla_t*)fe->card;
	wan_device_t	*wandev = &card->wandev;

	DEBUG_HFC_S0_STATES("%s()\n", __FUNCTION__);

	if (wandev->fe_enable_timer){

		wan_set_bit(T3_TIMER_EXPIRED, &port_ptr->timer_flags);

		wandev->fe_enable_timer(fe->card);
	}
}

static void __l1_timer_expire_t3(sdla_fe_t *fe)
{
	sdla_bri_param_t *bri = &fe->bri_param;
	wp_bri_module_t	*bri_module;
	bri_xhfc_port_t	*port_ptr;
	u8		mod_no, port_no;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	bri_module = &bri->mod[mod_no];
	port_ptr   = &bri_module->port[port_no];

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	wan_clear_bit(T3_TIMER_ACTIVE, &port_ptr->timer_flags);
	wan_clear_bit(T3_TIMER_EXPIRED, &port_ptr->timer_flags);

	wan_clear_bit(HFC_L1_ACTIVATING, &port_ptr->l1_flags);
	xhfc_ph_command(fe, port_ptr, HFC_L1_FORCE_DEACTIVATE_TE);
}


/**
 * l1_timer_start_t4
 */
static void l1_timer_start_t4(void *pport)
{
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
	wp_bri_module_t	*bri_module;
	sdla_fe_t	*fe;
	u8		mod_no;

	WAN_ASSERT_VOID(port_ptr == NULL);

	WAN_ASSERT_VOID(port_ptr->hw == NULL);
	bri_module = port_ptr->hw;

	WAN_ASSERT_VOID(bri_module->fe == NULL);
	fe = (sdla_fe_t*)bri_module->fe;

	mod_no = (u8)bri_module->mod_no;

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	if (!wan_test_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical)) {
		/* may get here during unload!! */
		return;
	}

	if(!wan_test_and_set_bit(T4_TIMER_ACTIVE, &port_ptr->timer_flags)){
		DEBUG_HFC_S0_STATES("Starting T4 timer...\n");
		wan_set_bit(HFC_L1_DEACTTIMER, &port_ptr->l1_flags);
		wan_add_timer(&port_ptr->t4_timer, (XHFC_TIMER_T4 * HZ) / 1000);
	}
}


/**
 * l1_timer_stop_t4
 */
static void l1_timer_stop_t4(void *pport)
{
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
	wp_bri_module_t	*bri_module = port_ptr->hw;
	u8		mod_no = (u8)bri_module->mod_no;

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	if(wan_test_bit(T4_TIMER_ACTIVE, &port_ptr->timer_flags)){	
		wan_clear_bit(T4_TIMER_ACTIVE, &port_ptr->timer_flags);
		wan_clear_bit(HFC_L1_DEACTTIMER, &port_ptr->l1_flags);
		wan_del_timer(&port_ptr->t4_timer);
	}
}


/*
 ******************************************************************************
 *				l1_timer_expire_t4()	
 *
 * Description: l1_timer_expire_t4 - called when timer t4 expires.
 *		Send (PH_DEACTIVATE | INDICATION) to upper layer.
 *
 *		Note that this function does NOT access the hardware so we
 *		don't have to use wandev->fe_enable_timer() as it is done
 *		for T1 and T3.
 *
 * Arguments:
 * Returns:
 ******************************************************************************
 */
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static void l1_timer_expire_t4(void* pport)
#elif defined(__WINDOWS__)
static void l1_timer_expire_t4(IN PKDPC Dpc, void* pport, void* arg2, void* arg3)
#elif defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
static void l1_timer_expire_t4(struct timer_list *t)
#else
static void l1_timer_expire_t4(unsigned long pport)
#endif
{
#if defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
	bri_xhfc_port_t	*port_ptr = from_timer(port_ptr, t, t4_timer.timer_info);
#else
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
#endif
	wp_bri_module_t	*bri_module = port_ptr->hw;
	sdla_fe_t	*fe = bri_module->fe;
	u8		mod_no, port_no;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	wan_clear_bit(T4_TIMER_ACTIVE, &port_ptr->timer_flags);
	wan_clear_bit(HFC_L1_DEACTTIMER, &port_ptr->l1_flags);
	sdla_bri_set_status(fe, mod_no, port_no, FE_DISCONNECTED);
}


/*
 ******************************************************************************
 *				l1_timer_expire_t1()	
 *
 * Description: called when timer t1 expires.
 *				Activation failed, force clean L1 deactivation.
 * Arguments:
 * Returns:
 ******************************************************************************
 */
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static void l1_timer_expire_t1(void* pport)
#elif defined(__WINDOWS__)
static void l1_timer_expire_t1(IN PKDPC Dpc, void* pport, void* arg2, void* arg3)
#elif defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
static void l1_timer_expire_t1(struct timer_list *t)
#else
static void l1_timer_expire_t1(unsigned long pport)
#endif
{
#if defined(KERN_TIMER_SETUP) && KERN_TIMER_SETUP > 0
    bri_xhfc_port_t *port_ptr = from_timer(port_ptr, t, t1_timer.timer_info);
#else
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
#endif
	wp_bri_module_t	*bri_module = port_ptr->hw;
	sdla_fe_t	*fe = (sdla_fe_t*)bri_module->fe;
	sdla_t 		*card = (sdla_t*)fe->card;
	wan_device_t	*wandev = &card->wandev;

	DEBUG_HFC_S0_STATES("%s()\n", __FUNCTION__);

	if (wandev->fe_enable_timer){

		wan_set_bit(T1_TIMER_EXPIRED, &port_ptr->timer_flags);

		wandev->fe_enable_timer(fe->card);
	}
}

static void __l1_timer_expire_t1(sdla_fe_t *fe)
{
	sdla_bri_param_t *bri = &fe->bri_param;
	wp_bri_module_t	*bri_module;
	bri_xhfc_port_t	*port_ptr;
	u8		mod_no, port_no, connected = 0;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	bri_module = &bri->mod[mod_no];
	port_ptr   = &bri_module->port[port_no];

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	wan_clear_bit(T1_TIMER_EXPIRED, &port_ptr->timer_flags);
	wan_clear_bit(T1_TIMER_ACTIVE,  &port_ptr->timer_flags);
	
	if(	(port_ptr->su_state.bit.v_su_info0)	||
		(!port_ptr->su_state.bit.v_su_fr_sync)){
		/* If receiving INFO0 or Lost Framing, after T1 expired, we must deactivate
		 * NT, because otherwise the user will be prevented from NT activation
		 * forever by the HFC_L1_ACTIVATED bit. */
		connected = 0;
	}else if(port_ptr->su_state.bit.v_su_fr_sync){
		/* Got synchronized. No automatic state change expected. */
		connected = 1;
	}

	if(!wan_test_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags)) {

		DEBUG_HFC_S0_STATES("%s(): (1) De-Activating NT...\n", __FUNCTION__);
		xhfc_ph_command(fe, port_ptr, HFC_L1_DEACTIVATE_NT);

	}else if(!connected){

		DEBUG_HFC_S0_STATES("%s(): (2) De-Activating NT...\n", __FUNCTION__);
		xhfc_ph_command(fe, port_ptr, HFC_L1_DEACTIVATE_NT);
		
	}else{
		/* T1 expired AFTER line become active. */
		DEBUG_HFC_S0_STATES("%s(): NT in Activated state. Doing nothing.\n", __FUNCTION__);
	}
}

/**
 * l1_timer_start_t1
 */
static void l1_timer_start_t1(void *pport)
{
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
	wp_bri_module_t	*bri_module;
	sdla_fe_t	*fe;
	u8		mod_no;

	WAN_ASSERT_VOID(port_ptr == NULL);

	WAN_ASSERT_VOID(port_ptr->hw == NULL);
	bri_module = port_ptr->hw;

	WAN_ASSERT_VOID(bri_module->fe == NULL);
	fe = (sdla_fe_t*)bri_module->fe;

	mod_no = (u8)bri_module->mod_no;

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	if (!wan_test_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical)) {
		/* may get here during unload!! */
		return;
	}

	if (!wan_test_bit(T1_TIMER_ACTIVE,  &port_ptr->timer_flags) &&
		!wan_test_bit(T1_TIMER_EXPIRED, &port_ptr->timer_flags)){

		DEBUG_HFC_S0_STATES("%s(): Starting T1 timer...\n", __FUNCTION__);

		wan_set_bit(T1_TIMER_ACTIVE, &port_ptr->timer_flags); 

		wan_add_timer(&port_ptr->t1_timer, (XHFC_TIMER_T1 * HZ) / 1000);

	}else{
		DEBUG_HFC_S0_STATES("%s(): the T1_TIMER_ACTIVE bit already set\n", __FUNCTION__);
	}
}

/**
* l1_timer_stop_t1
*/
static void l1_timer_stop_t1(void *pport)
{
	bri_xhfc_port_t	*port_ptr = (bri_xhfc_port_t*)pport;
	wp_bri_module_t	*bri_module = port_ptr->hw;
	u8		mod_no = (u8)bri_module->mod_no;
	
	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);
	
	if(wan_test_bit(T1_TIMER_ACTIVE, &port_ptr->timer_flags)){	

		wan_clear_bit(T1_TIMER_ACTIVE,  &port_ptr->timer_flags);
		wan_clear_bit(T1_TIMER_EXPIRED, &port_ptr->timer_flags);

		wan_del_timer(&port_ptr->t1_timer);
	}
}

/******************************************************************************
** wp_bri_if_config() - 
**
**	OK
*/
static int32_t wp_bri_if_config(void *pfe, u32 mod_map, u8 usedby)
{
	BRI_FUNC();
	return 0;
}


/******************************************************************************
** wp_bri_if_unconfig() - 
**
**	OK
*/
static int32_t wp_bri_if_unconfig(void *pfe, u32 mod_map, u8 usedby)
{
	BRI_FUNC();
	return 0;
}


/*******************************************************************************
*			bri_enable_interrupts()	
*
* Description: Enable BRI interrupts - start interrupt and set interrupt mask.
*
* Arguments:
*
* Returns:
*******************************************************************************/
static void bri_enable_interrupts(sdla_fe_t *fe, u32 mod_no, u8 port_no)
{
	sdla_bri_param_t 	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	reg_r_ti_wd		r_ti_wd;
	reg_r_misc_irqmsk	r_misc_irqmsk;
	reg_r_irq_ctrl		r_irq_ctrl;
	reg_r_su_irqmsk		r_su_irqmsk;

	BRI_FUNC();

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return;
	}

	DEBUG_EVENT("%s: Module: %d: Enabling %s Interrupts \n",
				fe->name, REPORT_MOD_NO(mod_no),
				FE_MEDIA_DECODE(fe));

	bri_module = &bri->mod[mod_no];

	WRITE_REG(R_SU_IRQMSK, 0);

	r_ti_wd.reg = 0x0;
	/* Configure Timer Interrupt - every 1 sec (page 289, 297).
	   Used for SU port_no state monitoring + polling fifo IRQs. */
	r_ti_wd.bit.v_ev_ts = 0xC;
	/* Watch Dog interrupt not used */
	r_ti_wd.bit.v_wd_ts = 0x0;
	WRITE_REG(R_TI_WD, r_ti_wd.reg);

	r_misc_irqmsk.reg = 0;
#if !defined(BUILD_MOD_TESTER)
	r_misc_irqmsk.bit.v_ti_irqmsk = 1;/* Enable (one) / Disable (zero) timer interrupts */
#endif
	WRITE_REG( R_MISC_IRQMSK, r_misc_irqmsk.reg);

	/* clear all pending interrupts bits */
	READ_REG( R_MISC_IRQ);
	READ_REG( R_SU_IRQ);
	READ_REG( R_FIFO_BL0_IRQ);
	READ_REG( R_FIFO_BL1_IRQ);
	READ_REG( R_FIFO_BL2_IRQ);
	READ_REG( R_FIFO_BL3_IRQ);

	/* unmask SU state interrupt for all ports */	
	r_su_irqmsk.reg = 0;
	r_su_irqmsk.bit.v_su0_irqmsk = 1;
	r_su_irqmsk.bit.v_su1_irqmsk = 1;
	WRITE_REG( R_SU_IRQMSK, r_su_irqmsk.reg);

	/* enable global (all) interrupts */
	r_irq_ctrl.reg = 0;
	r_irq_ctrl.bit.v_glob_irq_en = 1;
	r_irq_ctrl.bit.v_fifo_irq_en = 1;
	WRITE_REG( R_IRQ_CTRL, r_irq_ctrl.reg);
}


/******************************************************************************
** wp_bri_disable_fe_irq() - disable all interrupts by disabling M_GLOB_IRQ_EN
**
**	OK
*/

static int wp_bri_disable_fe_irq(void *pfe)
{
	sdla_fe_t	*fe = (sdla_fe_t*)pfe;
	u32 mod_no;
	u8  port_no;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	wp_bri_disable_irq(fe,mod_no,port_no);

	return 0;
}

/******************************************************************************
** wp_bri_disable_irq() - disable all interrupts by disabling M_GLOB_IRQ_EN
**
**	OK
*/
static int32_t wp_bri_disable_irq(sdla_fe_t *fe, u32 mod_no, u8 port_no)
{
	sdla_bri_param_t 	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	reg_r_su_irqmsk		r_su_irqmsk;
	reg_r_irq_ctrl		r_irq_ctrl;

	BRI_FUNC();

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 1;
	}

	DEBUG_EVENT("%s: Module: %d: Disabling %s Interrupts \n",
					fe->name, REPORT_MOD_NO(mod_no),
				FE_MEDIA_DECODE(fe));

	bri_module = &bri->mod[mod_no];

	/* disable SU state interrupt for all ports */	
	r_su_irqmsk.reg = 0;
	WRITE_REG( R_SU_IRQMSK, r_su_irqmsk.reg);

/*
	if (!wan_test_bit(WP_RM_CONFIGURED,(void*)&fe->bri_param.critical)){
		return -EINVAL;
	}
*/

	/* disable global (all) other interrupts */
	r_irq_ctrl.reg = 0;
	WRITE_REG( R_IRQ_CTRL, 0);

	return 0;
}

static u32 wp_bri_active_map(sdla_fe_t* fe, u8 line_no)
{
	BRI_FUNC();

	if(line_no >= 2){
		DEBUG_ERROR("%s: %s(): Error: Line number %d is out of range!\n",
					fe->name, __FUNCTION__, line_no);
		return 0;
	}
	
	DEBUG_TEST("%s: ACTIVE MAP Port=%i Returning 0x%08X\n",
			fe->name,  
			WAN_FE_LINENO(fe),
			 0x03 << (WAN_FE_LINENO(fe)%MAX_BRI_MODULES)*2);

	return 0x03 << (WAN_FE_LINENO(fe)%MAX_BRI_MODULES)*2;
}

/******************************************************************************
*				wp_bri_fe_status()	
*
* Description:
* Arguments:	
* Returns:
*******************************************************************************/
static u8 wp_bri_fe_media(sdla_fe_t *fe)
{
	BRI_FUNC();
	return fe->fe_cfg.media;
}

/******************************************************************************
*				wp_bri_set_dtmf()	
*
* Description:
* Arguments:	
* Returns:
*******************************************************************************/
static int32_t wp_bri_set_dtmf(sdla_fe_t *fe, int32_t mod_no, u8 val)
{
	BRI_FUNC();
#if 0

	if (mod_no > MAX_REMORA_MODULES){
		DEBUG_EVENT("%s: Module %d: Module number out of range!\n",
					fe->name, mod_no);
		return -EINVAL;
	}	
	if (!wan_test_bit(mod_no-1, &fe->bri_param.module_map)){
		DEBUG_EVENT("%s: Module %d: Not configures yet!\n",
					fe->name, mod_no);
		return -EINVAL;
	}
	
#endif
	return -EINVAL;
}

#if 0

/*******************************************************************************
*				sdla_bri_timer()	
*
* Description:
* Arguments:
* Returns:
*******************************************************************************/
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
static void wp_bri_timer(void* pfe)
#elif defined(__WINDOWS__)
static void wp_bri_timer(IN PKDPC Dpc, void* pfe, void* arg2, void* arg3)
#else
static void wp_bri_timer(void *pfe)
#endif
{
	BRI_FUNC();
	return;
}

/*******************************************************************************
*				wp_bri_enable_timer()	
*
* Description: Enable software timer interrupt in delay ms.
* Arguments:
* Returns:
*******************************************************************************/
static void wp_bri_enable_timer(sdla_fe_t* fe, u8 mod_no, u8 cmd, u32 delay)
{
	BRI_FUNC();
	return;	
}

static int32_t wp_bri_regdump(sdla_fe_t* fe, u8 *data)
{
	BRI_FUNC();
	return 0;
}

#endif /* if 0*/

static int32_t wp_bri_polling(sdla_fe_t* fe)
{
	sdla_bri_param_t *bri = &fe->bri_param;
	wp_bri_module_t	*bri_module;
	bri_xhfc_port_t	*port_ptr;
	u8		mod_no, port_no;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	bri_module = &bri->mod[mod_no];
	port_ptr   = &bri_module->port[port_no];

	/* Note that aft_core.c calls card->wandev.fe_iface.polling() 
	 * UNCONDITIONALLY, on each Front End interrupt. 
	 * It is a problem for BRI timers because we need an additional flag,
	 * which will indicate that "T1 timer really expired". 
	 * Only if the flag is set, we act as we should on timer expiration. */

	if (port_ptr->mode & PORT_MODE_TE) {
		/*************/
		/* TE timers */
		if(wan_test_bit(T3_TIMER_ACTIVE, &port_ptr->timer_flags)){
			DEBUG_HFC_S0_STATES("%s(): TE T3_TIMER_ACTIVE\n", __FUNCTION__);
		}

		if(wan_test_bit(T3_TIMER_EXPIRED, &port_ptr->timer_flags)){
			DEBUG_HFC_S0_STATES("%s(): TE T3_TIMER_EXPIRED\n", __FUNCTION__);
		}

		if (wan_test_bit(T3_TIMER_EXPIRED, &port_ptr->timer_flags) &&
			wan_test_bit(T3_TIMER_ACTIVE,  &port_ptr->timer_flags)){

			__l1_timer_expire_t3(fe);
		}
	}else{
		/*************/
		/* NT timers */
		if(wan_test_bit(T1_TIMER_ACTIVE, &port_ptr->timer_flags)){
			DEBUG_HFC_S0_STATES("%s(): NT T1_TIMER_ACTIVE\n", __FUNCTION__);
		}

		if(wan_test_bit(T1_TIMER_EXPIRED, &port_ptr->timer_flags)){
			DEBUG_HFC_S0_STATES("%s(): NT T1_TIMER_EXPIRED\n", __FUNCTION__);
		}
		
		if (wan_test_bit(T1_TIMER_EXPIRED, &port_ptr->timer_flags) &&
			wan_test_bit(T1_TIMER_ACTIVE,  &port_ptr->timer_flags)) {
			
			__l1_timer_expire_t1(fe);
		}
	}
	return 0;
}

/*******************************************************************************
*				wp_bri_udp()	
*
* Description:
* Arguments:
* Returns:
*******************************************************************************/
static int32_t wp_bri_udp(sdla_fe_t *fe, void* p_udp_cmd, u8* data)
{
	wan_cmd_t		*udp_cmd = (wan_cmd_t*)p_udp_cmd;
	wan_femedia_t		*fe_media;
	sdla_fe_timer_event_t	event;

	BRI_FUNC();
	memset(&event, 0, sizeof(sdla_fe_timer_event_t));
	switch(udp_cmd->wan_cmd_command){
		case WAN_GET_MEDIA_TYPE:
                fe_media = (wan_femedia_t*)data;
                memset(fe_media, 0, sizeof(wan_femedia_t));
                fe_media->media         = fe->fe_cfg.media;
                fe_media->sub_media     = fe->fe_cfg.sub_media;
                fe_media->chip_id       = 0x00;
                fe_media->max_ports     = 1;
                udp_cmd->wan_cmd_return_code = WAN_CMD_OK;
                udp_cmd->wan_cmd_data_len = sizeof(wan_femedia_t);
                break;
		default:
			udp_cmd->wan_cmd_return_code = WAN_UDP_INVALID_CMD;
				udp_cmd->wan_cmd_data_len = 0;
			break;
		}
	return 0;
}

/******************************************************************************
*wp_bri_get_fe_status()	
*
* Description	: Get current FE line state - is it Connected or Disconnected
*
* Arguments	: fe - pointer to Front End structure.	
*		  status - pointer to location where the FE line state will
*			be stored.
*		  notused - ignored
*
* Returns	: always zero.
*******************************************************************************/
static int wp_bri_get_fe_status(sdla_fe_t *fe, unsigned char *status, int notused)
{
	*status = fe->fe_status;
	return 0;
}


static int bchan_loopback_control(sdla_fe_t *fe, u8 bchan_no, u8 loopback_enable)
{
	u8			mod_no, port_no, pcm_slot;
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	bri_xhfc_port_t		*port_ptr;
	reg_a_sl_cfg		a_sl_cfg;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	bri_module = &bri->mod[mod_no];
	port_ptr = &bri_module->port[port_no];


	DEBUG_LOOPB("%s()\n", __FUNCTION__);

    if (!((bchan_no == 0) || (bchan_no == 1))) {
		DEBUG_LOOPB("%s %s(): port_no(%i) ERROR: bchan_no(%i) invalid!\n",
                       fe->name, __FUNCTION__, port_no, bchan_no);
		return 1;
	}

    DEBUG_LOOPB("%s %s(): %s loopback, port_no(%i), bchan_no(%i)\n",
		fe->name, __FUNCTION__,
		(loopback_enable) ? ("enable") : ("disable"), port_no, bchan_no);

	if(mod_no >= MAX_BRI_MODULES){
		/* adjust mod_no to be between 0 and 10 (including)*/
		pcm_slot = (u8)calculate_pcm_timeslot(mod_no - MAX_BRI_MODULES, port_no, bchan_no);
		/* AFT Line 1 will use odd PCM timeslots */
		pcm_slot += 1;
	}else{
		/* AFT Line 0 will use even PCM timeslots */
		pcm_slot = (u8)calculate_pcm_timeslot(mod_no, port_no, bchan_no);
	}

	DEBUG_LOOPB("selecting pcm_slot: %i, HFC channel: %i\n", pcm_slot, port_no*4+bchan_no);

	/*****************************************************************************************/
	/* transmit slot - select direction TX */
	xhfc_select_pcm_slot(fe, mod_no, pcm_slot, XHFC_DIRECTION_TX);

	/* Connect time slot with channel and pin.
	Assign HFC channel (from 0 to 15) to the selected PCM slot.*/

	a_sl_cfg.reg = 0;

	a_sl_cfg.bit.v_ch_sdir	= 0;
	a_sl_cfg.bit.v_ch_snum	= port_no*4+bchan_no;/*page 75 */
	a_sl_cfg.bit.v_rout	= (loopback_enable ? 0x01:0x03);/* page 257 */

	WRITE_REG(A_SL_CFG, a_sl_cfg.reg);


	/*****************************************************************************************/
	/* receive slot - select direction RX */
	xhfc_select_pcm_slot(fe, mod_no, pcm_slot, XHFC_DIRECTION_RX);

	/* Connect time slot with channel and pin.
	Assign HFC channel (from 0 to 15) to the selected PCM slot. */
	a_sl_cfg.reg = 0;

	a_sl_cfg.bit.v_ch_sdir	= 1;
	a_sl_cfg.bit.v_ch_snum	= port_no*4+bchan_no;/*page 75 */
	a_sl_cfg.bit.v_rout	= (loopback_enable ? 0x01:0x03);/* page 257 */

	WRITE_REG(A_SL_CFG,a_sl_cfg.reg);

	return 0;
}


/* Commands from user. */
static int wp_bri_control(sdla_fe_t *fe, u32 command)
{
	u8			mod_no, port_no;
	int			rc;
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	bri_xhfc_port_t		*port_ptr;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_LOOPB("%s(): Module: %d, port_no: %d. fe->name: %s, command: %i\n",
		__FUNCTION__, mod_no, port_no, fe->name, command);

	bri_module = &bri->mod[mod_no];
	port_ptr = &bri_module->port[port_no];

	switch(command)
	{
	case HFC_L1_ENABLE_LOOP_B1:
		DEBUG_LOOPB("HFC_L1_ENABLE_LOOP_B1\n");
		rc = bchan_loopback_control(fe, 0, 1);
		break;

	case HFC_L1_ENABLE_LOOP_B2:
		DEBUG_LOOPB("HFC_L1_ENABLE_LOOP_B2\n");
		rc = bchan_loopback_control(fe, 1, 1);
		break;

	case HFC_L1_DISABLE_LOOP_B1:
		DEBUG_LOOPB("HFC_L1_DISABLE_LOOP_B1\n");
		rc = bchan_loopback_control(fe, 0, 0);
		break;

	case HFC_L1_DISABLE_LOOP_B2:
		DEBUG_LOOPB("HFC_L1_DISABLE_LOOP_B2\n");
		rc = bchan_loopback_control(fe, 1, 0);
		break;

	default:
		DEBUG_ERROR("%s(): %s: Error: invalid command '%i'requested!\n",
			__FUNCTION__, fe->name, command);
		rc = 1;
		break;
	}	

	return rc;
}


/******************************************************************************
*				wp_bri_set_fe_status()	
*
* Description	: Set FE line state to Connected or Disconnected.
*		  In BRI this means Activate or Deactivate the line.
*
* Arguments	: fe - pointer to Front End structure.	
*		  new_status - the new FE line state.
*
* Returns	: 0 - success.
*		  1 - failure.
*******************************************************************************/
static int wp_bri_set_fe_status(sdla_fe_t *fe, unsigned char new_status)
{
	u8			mod_no, port_no;
	int			rc = 0;
	sdla_bri_param_t	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	bri_xhfc_port_t		*port_ptr;

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	DEBUG_FE_STATUS("%s(): Module: %d, port_no: %d. fe->name: %s, new status: %d (%s)\n",
		__FUNCTION__, mod_no, port_no, fe->name, new_status, FE_STATUS_DECODE(new_status));

	bri_module = &bri->mod[mod_no];
	port_ptr = &bri_module->port[port_no];

#if defined (BUILD_MOD_TESTER)
        sdla_bri_set_status(fe, mod_no, port_no, FE_CONNECTED);
        return 0;
#endif

	switch(new_status)
	{
	case WAN_FE_CONNECTED:
		DEBUG_L2_TO_L1_ACTIVATION("%s: L2->L1 -- ACTIVATE REQUEST\n", 
			fe->name);
		if (port_ptr->mode & PORT_MODE_TE) {
			if (wan_test_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags)) {
				/* The line is already in active state. Confirm to L2 that line is connected. */
				DEBUG_L2_TO_L1_ACTIVATION("%s: TE: L1->L2 -- ACTIVATE CONFIRM (line already active)\n",
					fe->name);
				sdla_bri_set_status(fe, mod_no, port_no, FE_CONNECTED);	
			} else {
				wan_test_and_set_bit(HFC_L1_ACTIVATING, &port_ptr->l1_flags);
				xhfc_ph_command(fe, port_ptr, HFC_L1_ACTIVATE_TE);
				l1_timer_start_t3(port_ptr);
			}
		} else {
			if(wan_test_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags)) {
				/* The line is already in active state. Confirm to L2 that line is connected. */
				DEBUG_L2_TO_L1_ACTIVATION("%s: NT: L1->L2 -- ACTIVATE CONFIRM (line already active)\n",
					fe->name);
				sdla_bri_set_status(fe, mod_no, port_no, FE_CONNECTED);	
			} else {
				xhfc_ph_command(fe, port_ptr, HFC_L1_ACTIVATE_NT);
				/* After activation, state will automatically change to G2, where
				 * T1 will be started. Don't start T1 here as recommended by Table 5.4. */
			}
        }
		break;

	case WAN_FE_DISCONNECTED:
		DEBUG_L2_TO_L1_ACTIVATION("%s: L2->L1 -- DEACTIVATE REQUEST\n", 
			fe->name);
		if (port_ptr->mode & PORT_MODE_TE) {
			/* no deact request in TE mode ! */
			DEBUG_ERROR("%s(): %s: Error: 'deactivate' request is invalid for TE!\n",
				__FUNCTION__, fe->name);
			rc = 1;
		} else {
			xhfc_ph_command(fe, port_ptr, HFC_L1_DEACTIVATE_NT);
		}
		break;

	default:
		DEBUG_ERROR("%s(): %s: Error: invalid new status '%d' (%s) requested!\n",
			__FUNCTION__, fe->name, new_status, FE_STATUS_DECODE(new_status));
		rc = 1;
		break;
	}	

	return rc;
}


/******************************************************************************
*				wp_bri_event_ctrl()	
*
* Description: Enable/Disable event types
* Arguments: mod_no -  Module number (1,2,3,... MAX_REMORA_MODULES)
* Returns:
******************************************************************************/
static int wp_bri_event_ctrl(sdla_fe_t *fe, wan_event_ctrl_t *ectrl)
{
	int err = 1;

	BRI_FUNC();

	WAN_ASSERT(ectrl == NULL);

	DEBUG_LOOPB("%s: Event Type: %s, Mode: %s.\n",
		fe->name, WAN_EVENT_TYPE_DECODE(ectrl->type),
		WAN_EVENT_MODE_DECODE(ectrl->mode));

	switch(ectrl->type)
	{
	case WAN_EVENT_BRI_CHAN_LOOPBACK:
		switch(ectrl->channel)
		{
		case WAN_BRI_BCHAN1:
			err = wp_bri_control(fe, (ectrl->mode == WAN_EVENT_ENABLE ? HFC_L1_ENABLE_LOOP_B1:HFC_L1_DISABLE_LOOP_B1));
			break;
		case WAN_BRI_BCHAN2:
			err = wp_bri_control(fe, (ectrl->mode == WAN_EVENT_ENABLE ? HFC_L1_ENABLE_LOOP_B2:HFC_L1_DISABLE_LOOP_B2));
			break;
		}
		break;
	}

	return err;
}

/******************************************************************************
*			wp_bri_watchdog()	
*
* Description:
* Arguments: mod_no -  Module number (1,2,3,... MAX_REMORA_MODULES)
* Returns:
******************************************************************************/
#if 0
static int32_t wp_bri_watchdog(sdla_fe_t *fe)
{
	BRI_FUNC();
	
	return 0;
}
#endif

/******************************************************************************
*				wp_bri_intr_ctrl()	
*
* Description: Enable/Disable extra interrupt types
* Arguments: mod_no -  Module number (1,2,3,... MAX_REMORA_MODULES)
* Returns:
******************************************************************************/
static int wp_bri_intr_ctrl(sdla_fe_t *fe, int mod_no, u_int8_t type, u_int8_t mode, unsigned int ts_map)
{
	int32_t		err = 0;

	BRI_FUNC();

	return err;

}

/****************************************************/
/* Physical S/U commands to control Line Interface  */
/****************************************************/
static void xhfc_ph_command(sdla_fe_t *fe, bri_xhfc_port_t *port, u_char command)
{
	wp_bri_module_t	*bri_module = port->hw;
	u8		mod_no = (u8)bri_module->mod_no;

	DEBUG_HFC_S0_STATES("%s(): command: 0x%X\n", __FUNCTION__, command);

	switch (command) 
	{
	case HFC_L1_ACTIVATE_TE:
		DEBUG_L2_TO_L1_ACTIVATION("HFC_L1_ACTIVATE_TE port(%i)\n", port->idx);

		WRITE_REG(R_SU_SEL, port->idx);
		WRITE_REG(A_SU_WR_STA, STA_ACTIVATE);
		break;

	case HFC_L1_FORCE_DEACTIVATE_TE:
		DEBUG_L2_TO_L1_ACTIVATION("HFC_L1_FORCE_DEACTIVATE_TE port(%i)\n", port->idx);
			        
		WRITE_REG(R_SU_SEL, port->idx);
		WRITE_REG(A_SU_WR_STA, STA_DEACTIVATE);
		break;

	case HFC_L1_ACTIVATE_NT:
		DEBUG_L2_TO_L1_ACTIVATION("HFC_L1_ACTIVATE_NT port(%i)\n", port->idx);

		WRITE_REG(R_SU_SEL, port->idx);
		WRITE_REG(A_SU_WR_STA, STA_ACTIVATE | M_SU_SET_G2_G3);
		break;

	case HFC_L1_DEACTIVATE_NT:
		DEBUG_L2_TO_L1_ACTIVATION("HFC_L1_DEACTIVATE_NT port(%i)\n", port->idx);

		WRITE_REG(R_SU_SEL, port->idx);
		WRITE_REG(A_SU_WR_STA, STA_DEACTIVATE);
		break;

	default:
		DEBUG_L2_TO_L1_ACTIVATION("Invalid command: %i !\n", command);
		break;
	}
}

/******************************************************************************
*				sdla_bri_set_status()	
*
* Description:	set line status to 'connected' or 'disconnected' and indicate
*		line state change to upper layer.
* Arguments:	fe, mod_no, port_no, new line status
* Returns:	nothing
******************************************************************************/
static void sdla_bri_set_status(sdla_fe_t* fe, u8 mod_no, u8 port_no, u8 new_status)
{
	sdla_t	*card = (sdla_t*)fe->card;

	BRI_FUNC();

	DEBUG_HFC_S0_STATES("%s(): new_status: %i, old status: %i\n",
		__FUNCTION__, new_status, fe->fe_status);

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return;
	}

	if (new_status == fe->fe_status){
		return;
	}

	fe->fe_status = new_status;

	if (new_status == FE_CONNECTED){

		DEBUG_EVENT("%s: %s Module: %d connected!\n", 
			fe->name,
			FE_MEDIA_DECODE(fe), REPORT_MOD_NO(mod_no) + port_no);

		if (card->wandev.te_report_alarms){
			card->wandev.te_report_alarms(card, 0);
		}
	}else{

		DEBUG_EVENT("%s: %s Module: %d disconnected!\n", 
			fe->name,
			FE_MEDIA_DECODE(fe), REPORT_MOD_NO(mod_no) + port_no);

		if (card->wandev.te_report_alarms){
			card->wandev.te_report_alarms(card, (1|WAN_TE_BIT_ALARM_RED));
		}
	}

	if (card->wandev.te_link_state){
		card->wandev.te_link_state(card);
	}

	return;
}


/******************************************************************************
*				su_new_state()	
*
* Description: handle SU port state interrupt on a physical module
*
* SU port state interrupt notes:
*	1. Chip automatically goes into inactive state if:
*		1.1 line is disconnected
*		1.2 line is deactivated
*				   
*	2. Because of (1) user application will have to activate the line,
*	   wait for the line to get 'connected' for about 1 second and if
*	   after 1 second line is not getting 'connected', it means line is
*	   actually disconnected and NOT simply deactivated.
*
* Arguments: fe, mod_no
*
* Returns:	nothing
******************************************************************************/
static void su_new_state(sdla_fe_t *fe, u8 mod_no, u8 port_no)
{
	bri_xhfc_port_t		*port;
	sdla_bri_param_t 	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	u8			connected = 0;

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return;
	}

	bri_module	= &bri->mod[mod_no];
	port		= &bri_module->port[port_no];

	connected = __su_new_state(fe, mod_no, port_no);
	if(connected == 1){
		sdla_bri_set_status(fe, mod_no, port_no, FE_CONNECTED);	
	}else{
		sdla_bri_set_status(fe, mod_no, port_no, FE_DISCONNECTED);
	}
}


static u8 __su_new_state(sdla_fe_t *fe, u8 mod_no, u8 port_no)
{
	bri_xhfc_port_t		*port_ptr;
	sdla_bri_param_t 	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	u8		connected = 0;
	u8		current_fe_status = fe->fe_status;
	

	BRI_FUNC();

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return connected;
	}

	bri_module	= &bri->mod[mod_no];
	port_ptr	= &bri_module->port[port_no];

	DEBUG_HFC_S0_STATES("%s(): mod_no: %i, port number: %i\n", __FUNCTION__, mod_no, port_ptr->idx);

	if (port_ptr->mode & PORT_MODE_TE) {
		DEBUG_HFC_S0_STATES("TE F%d\n", port_ptr->l1_state);

		if ((port_ptr->l1_state <= 3) || (port_ptr->l1_state >= 7)){
			l1_timer_stop_t3(port_ptr);
		}

		switch (port_ptr->l1_state) 
		{
		case (3):
			if (wan_test_and_clear_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags)){
			/* Do NOT indicate 'disconnect' right away, do it when 
				T4 expires. */
				if (fe->fe_status == FE_CONNECTED){
					connected = 1; /* keep the old state */
				}
				l1_timer_start_t4(port_ptr);
			}
			return connected;
			
		case (7):
			l1_timer_stop_t4(port_ptr);
			connected = 1;
			
			if (wan_test_and_clear_bit(HFC_L1_ACTIVATING, &port_ptr->l1_flags)) {
				DEBUG_HFC_S0_STATES("l1->l2 -- ACTIVATE CONFIRM\n");
				
				wan_set_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags);
				
			} else {
				if (!(wan_test_and_set_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags))) {
					DEBUG_HFC_S0_STATES("l1->l2 -- ACTIVATE INDICATION\n");
				} else {
					/* L1 was already activated (e.g. F8->F7) */
					return connected;
				}
			}
			break;
			
		case (8):/* framing is lost but not a disconnect yet */
			l1_timer_stop_t4(port_ptr);
			connected = 1;
			return connected;
			
		case (6):/* synchronized */
			connected = 1;
			return connected;
			
		default:
			return connected;
		}

	} else if (port_ptr->mode & PORT_MODE_NT) {

		DEBUG_HFC_S0_STATES("NT G%d\n", port_ptr->l1_state);

		/*	S/T state transition based Cologne recomendations:
		 *	T1 is a counter that can be reset. An other stop function is not needed.
		 *	There are only 3 states of T1:
		 *	1. Reset permanent (Stop)
		 *	2. Running
		 *	3. Expire
		 *
		 *	The easiest way to implement T1 is:
		 *	state		action for T1
		 *	0,1         T1 Reset
		 *	2			T1 Running
		 *	3,4			T1 Reset
		 */

		if(port_ptr->l1_state != NT_STATE_PENDING_ACTIVATION_G2){
			l1_timer_stop_t1(port_ptr);
		}

		/* S/T state transitions based on Table 5.4 */
		switch (port_ptr->l1_state) 
		{
		case NT_STATE_RESET_G0:
		case NT_STATE_DEACTIVATED_G1:
		case NT_STATE_PENDING_DEACTIVATION_G4:
			wan_clear_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags);
			connected = 0;
			break;

		case NT_STATE_PENDING_ACTIVATION_G2:
			/* Start 10 seconds software timer T1. If already started, function has no effect. */
			l1_timer_start_t1(port_ptr);

			if(!port_ptr->su_state.bit.v_su_info0){
				/* We are NOT receiving INFO0 and very likely we are receiving INFO3.
				 * That means link is on the way UP. Next state should be G3.
				 *
				 * Automatic G2->G3 transition is allowed by init_xfhc() -
				 * V_G2_G3_EN was set in the register A_SU_CTRL1.*/
			}else{
				/* Link on the way DOWN, but no automatic state change. 
				 * The state change will be triggered by T1 expiration,
				 * where the line is deactivated. */
				wan_clear_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags);
			}

			/* In any case, do NOT change the line state. */
			connected = (current_fe_status == FE_CONNECTED ? 1 : 0);
			break;

		case NT_STATE_ACTIVE_G3:
			if(	(port_ptr->su_state.bit.v_su_info0)	||
				(!port_ptr->su_state.bit.v_su_fr_sync)){
				/* If receiving INFO0 or Lost Framing, chip will automatically go into G2. */
				/* Wait for G2 to go into disconnected state, so here stay 'connected'. */
				wan_set_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags);
				connected = 1;
			}else if(port_ptr->su_state.bit.v_su_fr_sync){
				/* Got synchronized. No automatic state change expected. */
				wan_set_bit(HFC_L1_ACTIVATED, &port_ptr->l1_flags);
				connected = 1;
			}
			break;

		default:
			DEBUG_ERROR("%s: Error: invalid NT State: %d.\n", fe->name, port_ptr->l1_state);
			break;
		}

		if(connected == 1){
			DEBUG_HFC_S0_STATES("NT: l1->l2 -- ACTIVATE INDICATION\n");
		}else{
			DEBUG_HFC_S0_STATES("NT: l1->l2 (PH_DEACTIVATE | INDICATION)\n");
		}
	}

	return connected;
}

/******************************************************************************
*				get_FE_ptr_for_port()	
*
* Description	: get pointer to FE structure belonging to a 'port_no' on
*		  module 'mod_no'.
*		  Allows to handle FE interrupt of ALL ports on the SAME
*		  physical module.
*		  Note: the returned pointer is NOT the same as the pointer
*			in 'wp_bri_module_t->fe' because this function returns
*			'fe' related to the 'card'.
*
* Arguments	: fe, mod_no, port_no
*
* Returns	: fe pointer - found FE for the 'port_no'.
*		  NULL	     - FE for the 'port_no' not found (it means port_no
*			       is not used).
*
******************************************************************************/
sdla_fe_t *get_FE_ptr_for_port(sdla_fe_t *original_fe, u8 mod_no, u8 port_no)
{
	sdla_t	*card = (sdla_t*)original_fe->card;
	sdla_t	*tmp_card;
	void	**card_list;

	BRI_FUNC();

	if(!card || !card->hw){
		DEBUG_HFC_IRQ("%s(): card: 0x%p!!\n",  __FUNCTION__, card);
		return NULL;
	}
	DEBUG_HFC_IRQ("%s(): mod: %d, port_no: %d (sum: %d)\n", 
		__FUNCTION__, mod_no, port_no, mod_no + port_no);

	card_list=__sdla_get_ptr_isr_array(card->hw);
/*
{
	int i;
	for(i = 0; i < SDLA_MAX_PORTS; i++){
		DEBUG_HFC_IRQ("card_list[%d]: 0x%p\n", i, card_list[i]);
	}
}
*/
	tmp_card=(sdla_t*)card_list[mod_no + port_no];

	DEBUG_HFC_IRQ("%s(): card_list ptr: 0x%p\n", __FUNCTION__, card_list);
	DEBUG_HFC_IRQ("%s(): card ptr: 0x%p, tmp_card ptr: 0x%p\n", __FUNCTION__, card, tmp_card);

	if (!tmp_card){
		return NULL;
	}

	return &tmp_card->fe;
}

/******************************************************************************
*				xhfc_interrupt()	
*
* Description	: handle interrupt on a PHYSICAL module
*
* Arguments	: fe, mod_no
*
* Returns	: 1 - interrupt recognized and handled
*		  0 - interrupt not recognized (not generated by this module)
*
******************************************************************************/
static int32_t xhfc_interrupt(sdla_fe_t *fe, u8 mod_no)
{
	sdla_fe_t		*new_fe;
	uint32_t		fifo_irq;
	sdla_bri_param_t 	*bri = &fe->bri_param;
	wp_bri_module_t		*bri_module;
	u8			port_no, i;
	reg_a_su_rd_sta		new_su_state;
	reg_r_su_irq		r_su_irq;
	reg_r_misc_irq		r_misc_irq;
	uint32_t		rx_chan_bit;

	if(validate_physical_mod_no(mod_no, __FUNCTION__)){
		return 0;
	}

	bri_module = &bri->mod[mod_no];

#if 0
	DEBUG_HFC_SU_IRQ("%s(%lu): %s: mod_no: %d\n", __FUNCTION__, jiffies, fe->name, mod_no);
#endif

	/* clear SU state interrupt (for all ports) */
	r_su_irq.reg = READ_REG(R_SU_IRQ);

	/* clear 'misc' interrupts such as timer interrupt */
	r_misc_irq.reg = READ_REG(R_MISC_IRQ);

	/***************************************************************************/

	fifo_irq = 0;
	for (i = 0; i < bri_module->num_ports; i++)
	{
		new_fe = get_FE_ptr_for_port(fe, mod_no, i);

		if(new_fe == NULL){
			/* 'port_no' is not used by any 'wanpipe' */
			continue;
		}

		fe = new_fe;

		mod_no = fe_line_no_to_physical_mod_no(fe);	
		port_no = fe_line_no_to_port_no(fe);

		DBG_MODULE_TESTER("get fifo IRQ state for port_no %d\n", port_no);

		/* get fifo IRQ states in bundle */
		fifo_irq |= (READ_REG(R_FIFO_BL0_IRQ + port_no) << (port_no * 8));

		DBG_MODULE_TESTER("fifo_irq: 0x%X\n", fifo_irq);

	}

	if (bri_module->prev_fifoirq != fifo_irq)
	{
		DEBUG_HFC_IRQ("%s():%s: fifo IRQ: now = 0x%04X, prev = 0x%04X (%d)\n",
		              __FUNCTION__, fe->name,
		              fifo_irq, bri_module->prev_fifoirq, bri_module->num_ports);
	}

	/***************************************************************************/
	for (i = 0; i < bri_module->num_ports; i++) {

		/****************************************************************/
		new_fe = get_FE_ptr_for_port(fe, mod_no, i);
		DEBUG_HFC_IRQ("%s(): fe ptr: 0x%p\n", __FUNCTION__, fe);
		if(new_fe == NULL){
			/* 'port_no' is not used by any 'wanpipe' */
			continue;
		}
		fe = new_fe;

		mod_no = fe_line_no_to_physical_mod_no(fe);	
		port_no = fe_line_no_to_port_no(fe);

		rx_chan_bit = 1 << (port_no*8+5);

		/****************************************************************/

		if(r_misc_irq.reg & M_TI_IRQ){
			/*sdla_bri_param_t	*su_bri = &fe->bri_param;
			wp_bri_module_t		*su_bri_module = &su_bri->mod[mod_no];
			bri_xhfc_port_t		*port_ptr = &su_bri_module->port[port_no];*/

			/*DEBUG_HFC_SU_IRQ("Timer IRQ\n");*/
		}

		/****************************************************************/

		/* select the port on the chip */
		WRITE_REG(R_SU_SEL, port_no);
		new_su_state.reg = READ_REG(A_SU_RD_STA);

		DEBUG_HFC_SU_IRQ("%s(): SU State: sta:%d, v_su_fr_sync: %d, v_su_t2_exp: %d, v_su_info0: %d, v_g2_g3: %d\n",
			__FUNCTION__,
			new_su_state.bit.v_su_sta, new_su_state.bit.v_su_fr_sync, new_su_state.bit.v_su_t2_exp,
			new_su_state.bit.v_su_info0,	new_su_state.bit.v_g2_g3);

		if((r_su_irq.reg & (1 << port_no)) || (r_misc_irq.reg & M_TI_IRQ)){
			sdla_bri_param_t 	*su_bri = &fe->bri_param;
			wp_bri_module_t		*su_bri_module = &su_bri->mod[mod_no];
			bri_xhfc_port_t		*port_ptr = &su_bri_module->port[port_no];

			if (new_su_state.bit.v_su_sta != port_ptr->l1_state) {

				DEBUG_HFC_S0_STATES("%s():SU IRQ:%lu: %s: SU State: 0x%X, v_su_fr_sync: %d, v_su_info0: %d, v_g2_g3: %d\n",
					__FUNCTION__, jiffies,
					(port_ptr->mode & PORT_MODE_NT) ? "NT: G" : "TE: F",
					new_su_state.bit.v_su_sta, new_su_state.bit.v_su_fr_sync,
					new_su_state.bit.v_su_info0, new_su_state.bit.v_g2_g3);

#if 0
				if (port_ptr->mode & PORT_MODE_TE) {
					DEBUG_TE_STATES("v_su_sta: 0x%02X (%s), v_su_fr_sync: %d, v_su_info0: %d, v_g2_g3: %d\n",
						new_su_state.bit.v_su_sta, WP_DECODE_TE_STATE(new_su_state.bit.v_su_sta),
						new_su_state.bit.v_su_fr_sync,
						new_su_state.bit.v_su_info0,
						new_su_state.bit.v_g2_g3);
				}
#endif
 				port_ptr->l1_state = new_su_state.bit.v_su_sta;
				port_ptr->su_state = new_su_state;
				/* Handle S/U state change. */
				su_new_state(fe, mod_no, port_no);
			}

		}/* if(r_misc_irq.reg & M_TI_IRQ || (r_su_irq.reg & (1 << port_no))) */


		/* receive D-Channel Data */

		if (((fifo_irq | bri_module->prev_fifoirq) & rx_chan_bit) || (r_misc_irq.reg & M_TI_IRQ)) {
			uint32_t rx_chan_pending = 0;
			u8 rx_read_skb = 0;

			for (;;) {
				sdla_t	*card = (sdla_t*)fe->card;
				private_area_t *chan;

				netskb_t *skb = wp_bri_dchan_rx(fe, mod_no, port_no, &rx_chan_pending);

				if (!skb) break;

				DEBUG_HFC_IRQ("%s(): Module: %d, port_no: %d.\n", __FUNCTION__, mod_no, port_no);
				chan=(private_area_t*)card->u.aft.dev_to_ch_map[BRI_DCHAN_LOGIC_CHAN];

				DEBUG_HFC_IRQ("%s(): chan ptr: 0x%p\n", __FUNCTION__, chan);
				if (!chan){
					DEBUG_ERROR("%s: Error: BRI D-Chan: No Device for Rx data.(logical ch=%d)\n",
							card->devname, BRI_DCHAN_LOGIC_CHAN);
					break;
				}
				wan_skb_queue_tail(&chan->wp_rx_bri_dchan_complete_list, skb);

				if (skb || rx_chan_pending) {
					rx_read_skb = 1;
				}
			}

			if (rx_read_skb) {
				sdla_t	*card = (sdla_t*)fe->card;
				private_area_t *chan=(private_area_t*)card->u.aft.dev_to_ch_map[BRI_DCHAN_LOGIC_CHAN];

				DEBUG_HFC_IRQ("%s(): chan ptr: 0x%p\n", __FUNCTION__, chan);
				if (!chan){
					DEBUG_ERROR("%s: Error: BRI D-Chan: No Device for Rx data.(logical ch=%d)\n",
							card->devname, BRI_DCHAN_LOGIC_CHAN);
					break;
				}

				WAN_TASKLET_SCHEDULE((&chan->common.bh_task));
			}

			if (rx_read_skb || rx_chan_pending) {
				bri_module->prev_fifoirq |=  rx_chan_bit;
			} else {
				bri_module->prev_fifoirq &= ~rx_chan_bit;
			}
		}

		if(bri_module->port[port_no].bytes2transmit){
			u8 free_space;

			TX_FAST_DBG("%s(): port_no: %d, bytes2transmit: %d, dtx_indx: %d\n", 
				__FUNCTION__, port_no,	bri_module->port[port_no].bytes2transmit,
							bri_module->port[port_no].dtx_indx);

			xhfc_write_fifo_dchan(fe, mod_no, bri_module, &bri_module->port[port_no], &free_space);
		}
	}
	/***************************************************************************/

	return 1;
}

static int32_t wp_bri_check_intr(sdla_fe_t *fe)
{
	/* must return 1! */
	return 1;
} 

static int32_t wp_bri_intr(sdla_fe_t *fe)
{
	u8		mod_no, port_no;
	int32_t		interrupt_serviced = 0;

	BRI_FUNC();

	if (!wan_test_bit(WP_BCB_BRI_CONFIG, &fe->bri_param.critical)) {
		return 0;
	}

	mod_no = fe_line_no_to_physical_mod_no(fe);	
	port_no = fe_line_no_to_port_no(fe);

	switch(fe->bri_param.mod[mod_no].type)
	{
	case MOD_TYPE_TE:
	case MOD_TYPE_NT:
		if(xhfc_interrupt(fe, mod_no)){
			/* at least one module generated an interrupt */
			interrupt_serviced = 1;
		}
		break;
	default:
		/* for missing (not installed) modules - do nothing  */
		break;
	}

	return interrupt_serviced;
}



