/*****************************************************************************
* wanpipe_tdm_api.c
*
* 		WANPIPE(tm) AFT TE1 Hardware Support
*
* Authors: 	Nenad Corbic <ncorbic@sangoma.com>
*			David Rokhvarg	<davidr@sangoma.com>
*
* Copyright:	(c) 2003-2010 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.
* ============================================================================
*
*
* Feb 16, 2011	David Rokhvarg	<davidr@sangoma.com>
*				Added interface into HWEC module via WP_API_CMD_EC_IOCTL.
*	
* Nov 16, 2009	David Rokhvarg	<davidr@sangoma.com>
*				Enabled support for WP_API_CMD_SET_RX_GAINS and
*				WP_API_CMD_SET_TX_GAINS commands in Sangoma Windows Driver.
*
* Nov 19, 2008	David Rokhvarg	<davidr@sangoma.com>
*				Added RBS Chan/Span poll.
*
* Mar 10, 2008	David Rokhvarg	<davidr@sangoma.com>
*				Added BRI LoopBack control.
*
* Jul 27, 2006	David Rokhvarg	<davidr@sangoma.com>
*				Ported to Windows.
*
* Oct 04, 2005	Nenad Corbic	Initial version.
*****************************************************************************/


#include "wanpipe_includes.h"
#include "wanpipe_defines.h"
#include "wanpipe_debug.h"
#include "wanpipe_common.h"
#include "wanpipe_cfg.h"
#include "wanpipe.h"
#include "wanpipe_tdm_api.h"
#include "wanpipe_cdev_iface.h"
#include "wanpipe_timer_iface.h"
#include "wanpipe_mtp1.h"


/*==============================================================
  Defines
 */

#if !defined(__WINDOWS__)
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#  define BUILD_TDMV_API
# endif/* #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) */
#endif/* #if !defined(__WINDOWS__) */


#if defined(BUILD_TDMV_API)

#define WP_TDM_API_MAX_PERIOD 50 /* 50ms */

#define WP_TDM_MAX_RX_Q_LEN 10
#define WP_TDM_MAX_HDLC_RX_Q_LEN 40
#define WP_TDM_MAX_TX_Q_LEN 5
#define WP_TDM_MAX_HDLC_TX_Q_LEN 17

#define WP_TDM_MAX_EVENT_Q_TIMESLOT_MULTIPLE 6
#define WP_TDM_MIN_EVENT_Q_LEN 10

#define WP_TDM_MAX_CTRL_EVENT_Q_LEN 2000 /* 500 channels * 40 events (At same time) */
#define WP_TDM_MAX_RX_FREE_Q_LEN 10


#undef  DEBUG_API_WAKEUP
#undef  DEBUG_API_READ
#undef  DEBUG_API_WRITE
#undef  DEBUG_API_POLL

#define WP_TDM_DEF_FAKE_POLARITY_THRESHOLD 16000

#ifdef __WINDOWS__
# define wptdm_os_lock_irq(card,flags)   wan_spin_lock_irq(card,flags)
# define wptdm_os_unlock_irq(card,flags) wan_spin_unlock_irq(card,flags)
#else

#if 0
#warning "WANPIPE TDM API - OS LOCK DEFINED -- DEBUGGING"
#define wptdm_os_lock_irq(card,flags)	wan_spin_lock_irq(card,flags)
#define wptdm_os_unlock_irq(card,flags) wan_spin_unlock_irq(card,flags)
#else
#define wptdm_os_lock_irq(card,flags)	 
#define wptdm_os_unlock_irq(card,flags)  
#endif
#endif

#define MAX_TDM_API_CHANNELS 32

static int wp_tdmapi_global_cnt=0;
static wanpipe_tdm_api_dev_t tdmapi_ctrl;

/*==============================================================
  Prototypes
 */

static int wp_tdmapi_open(void *obj);
static int wp_tdmapi_read_msg(void *obj , netskb_t **skb,  wp_api_hdr_t *hdr, int count);
static int wp_tdmapi_write_msg(void *obj , netskb_t *skb, wp_api_hdr_t *hdr);
static int wp_tdmapi_release(void *obj);

static void wp_tdmapi_report_rbsbits(void* card_id, int channel, unsigned char rbsbits);
static void wp_tdmapi_report_alarms(void* card_id, uint32_t te_alarm);
static void wp_tdmapi_tone(void* card_id, wan_event_t*);
static void wp_tdmapi_hook(void* card_id, wan_event_t*);
static void wp_tdmapi_ringtrip(void* card_id, wan_event_t*);
static void wp_tdmapi_ringdetect (void* card_id, wan_event_t *event);
static void wp_tdmapi_linkstatus(void* card_id, wan_event_t*);
static void wp_tdmapi_polarityreverse(void* card_id, wan_event_t*);
static __inline int wp_tdmapi_check_fakepolarity(u8 *buf, int len, wanpipe_tdm_api_dev_t* tdm_api);
static void wp_tdmapi_report_fakepolarityreverse(void* card_id,u_int8_t channel);


static int store_tdm_api_pointer_in_card(sdla_t *card, wanpipe_tdm_api_dev_t *tdm_api);
static int remove_tdm_api_pointer_from_card(wanpipe_tdm_api_dev_t *tdm_api);


static int wanpipe_tdm_api_ioctl(void *obj, int cmd, void *udata);
static u_int32_t wp_tdmapi_poll(void *obj);
static int wanpipe_tdm_api_event_ioctl(wanpipe_tdm_api_dev_t*, wanpipe_api_cmd_t*);
static wanpipe_tdm_api_dev_t *wp_tdmapi_search(sdla_t *card, int fe_chan);

static int wanpipe_tdm_api_handle_event(wanpipe_tdm_api_dev_t *tdm_api, netskb_t *skb);
static int wp_tdmapi_unreg_notify_ctrl(wanpipe_tdm_api_dev_t *tdm_api);

#if defined(__LINUX__)
# if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
static void wp_api_task_func (void * tdmapi_ptr);
# else
static void wp_api_task_func (struct work_struct *work);
# endif
#elif defined(__WINDOWS__)
static void wp_api_task_func (IN PKDPC Dpc, IN PVOID tdmapi_ptr, IN PVOID SystemArgument1, IN PVOID SystemArgument2);
#else
static void wp_api_task_func (void * tdmapi_ptr, int arg);
#endif


#if 0
static int wp_tdm_api_mtp1_rx_data (void *priv_ptr, u8 *rx_data, int rx_len);
static int wp_tdm_api_mtp1_rx_reject (void *priv_prt, char *reason);
static int wp_tdm_api_mtp1_rx_suerm (void *priv_ptr);
static int wp_tdm_api_tmp1_wakup(void *priv_ptr);
#endif
static int wp_tdmapi_tx_timeout(void *obj);

/*==============================================================
  Global Variables
 */

enum {
	WP_API_FLUSH_INVAL,
	WP_API_FLUSH_ALL,
	WP_API_FLUSH_RX,
	WP_API_FLUSH_TX,
	WP_API_FLUSH_EVENT,
	WP_API_FLUSH_LOCK,
	WP_API_FLUSH_NO_LOCK
};

static wanpipe_cdev_ops_t wp_tdmapi_fops;


static void wp_tdmapi_init_buffs(wanpipe_tdm_api_dev_t *tdm_api, int option)
{

#define wptdm_queue_lock_irq(card,flags)    if (lock) wan_spin_lock_irq(card,flags)
#define wptdm_queue_unlock_irq(card,flags)  if (lock) wan_spin_unlock_irq(card,flags)

	netskb_t *skb;
	int hdr_sz=sizeof(wp_api_hdr_t);
	sdla_t *card; 
	wan_smp_flag_t flags;
	int lock=1;

	card = (sdla_t*)tdm_api->card;
	if (!card || tdm_api == &tdmapi_ctrl) {
     	lock=0;
	}
	flags=0;

	wptdm_queue_lock_irq(&card->wandev.lock,&flags);

	if (option == WP_API_FLUSH_ALL || option == WP_API_FLUSH_RX) {

		if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {
			do {
				skb=wan_skb_dequeue(&tdm_api->wp_rx_list);		
				if (skb) {
					wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
					continue;
				}
			} while(skb);

			do {
				skb=wan_skb_dequeue(&tdm_api->wp_rx_free_list);
				if (skb) {
					wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
					continue;
				}
			} while(skb);

			
			skb=tdm_api->rx_skb;
			tdm_api->rx_skb=NULL;
			if (skb) {
				wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
			}

		} else {
			 
			while((skb=wan_skb_dequeue(&tdm_api->wp_rx_list))) {
				wan_skb_init(skb,hdr_sz);
				wan_skb_trim(skb,0);
				wan_skb_queue_tail(&tdm_api->wp_rx_free_list,skb);
			}
			skb=tdm_api->rx_skb;
			tdm_api->rx_skb=NULL;
			if (skb) {
				wan_skb_init(skb,hdr_sz);
				wan_skb_trim(skb,0);
				wan_skb_queue_tail(&tdm_api->wp_rx_free_list,skb);
			}
			
		}
	}

	if (option == WP_API_FLUSH_ALL || option == WP_API_FLUSH_TX) {

		do {
			skb=wan_skb_dequeue(&tdm_api->wp_tx_free_list);
			if (skb) {
				wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
				continue;
			}
		} while(skb);

		do {
			skb=wan_skb_dequeue(&tdm_api->wp_tx_list);
			if (skb) {
				wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
				continue;
			}
		} while(skb);

		skb=tdm_api->tx_skb;
		tdm_api->tx_skb=NULL;
		if (skb){
			wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
		}

	}

	if (option == WP_API_FLUSH_ALL || option == WP_API_FLUSH_EVENT) {

		while((skb=wan_skb_dequeue(&tdm_api->wp_event_list))) {
			wan_skb_init(skb,hdr_sz);
			wan_skb_trim(skb,0);
			wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		}
	}

	wptdm_queue_unlock_irq(&card->wandev.lock,&flags);


	do {
		wptdm_queue_lock_irq(&card->wandev.lock,&flags);
		skb=wan_skb_dequeue(&tdm_api->wp_dealloc_list);
		wptdm_queue_unlock_irq(&card->wandev.lock,&flags);
		if (skb) {
			wan_skb_free(skb);
			continue;
		}
	} while(skb);
	


#undef wptdm_queue_lock_irq
#undef wptdm_queue_unlock_irq

}

static void wp_tdmapi_free_buffs(wanpipe_tdm_api_dev_t *tdm_api)
{
	wan_skb_queue_purge(&tdm_api->wp_rx_list);
	wan_skb_queue_purge(&tdm_api->wp_rx_free_list);
	wan_skb_queue_purge(&tdm_api->wp_tx_free_list);
	wan_skb_queue_purge(&tdm_api->wp_tx_list);
	wan_skb_queue_purge(&tdm_api->wp_event_list);
	wan_skb_queue_purge(&tdm_api->wp_event_bh_list);
	wan_skb_queue_purge(&tdm_api->wp_event_free_list);
	wan_skb_queue_purge(&tdm_api->wp_dealloc_list);

	if (tdm_api->rx_skb){
		wan_skb_free(tdm_api->rx_skb);
		tdm_api->rx_skb=NULL;
	}
	if (tdm_api->tx_skb){
		wan_skb_free(tdm_api->tx_skb);
		tdm_api->tx_skb=NULL;
	}

	if (tdm_api->rx_gain) {
		uint8_t *rx_gains=tdm_api->rx_gain;
		tdm_api->rx_gain=NULL;
		wan_free(rx_gains);
	}

	if (tdm_api->tx_gain){
		uint8_t *tx_gains=tdm_api->tx_gain;
		tdm_api->tx_gain=NULL;
		wan_free(tx_gains);
	}
}	



#ifdef DEBUG_API_WAKEUP
static int gwake_cnt=0;
#endif

static inline void wp_wakeup_rx_tdmapi(wanpipe_tdm_api_dev_t *tdm_api)
{
#ifdef DEBUG_API_WAKEUP
	if (tdm_api->tdm_span == 1 && tdm_api->tdm_chan == 1) {
		gwake_cnt++;
		if (gwake_cnt % 1000 == 0) {
			DEBUG_EVENT("%s: TDMAPI WAKING DEV \n",tdm_api->name);
		}
	}
#endif

	if (tdm_api->cdev) {
		wanpipe_cdev_rx_wake(tdm_api->cdev);
	}
}

static inline void wp_wakeup_event_tdmapi(wanpipe_tdm_api_dev_t *tdm_api)
{
	if (tdm_api->cdev) {
		wanpipe_cdev_event_wake(tdm_api->cdev);
	}
}

static inline void wp_wakeup_tx_tdmapi(wanpipe_tdm_api_dev_t *tdm_api)
{
#ifdef DEBUG_API_WAKEUP
	if (tdm_api->tdm_span == 1 && tdm_api->tdm_chan == 1) {
		gwake_cnt++;
		if (gwake_cnt % 1000 == 0) {
			DEBUG_EVENT("%s: TDMAPI WAKING DEV \n",tdm_api->name);
		}
	}
#endif

	if (tdm_api->cdev) {
		wanpipe_cdev_tx_wake(tdm_api->cdev);
	}
}


int wp_ctrl_dev_create(void)
{
	wanpipe_cdev_t *cdev;
	int err;
		
	cdev = wan_kmalloc(sizeof(wanpipe_cdev_t));
	if (!cdev) {
		return -ENOMEM;
	}
	
	memset(cdev, 0, sizeof(wanpipe_cdev_t));
	
	DEBUG_TDMAPI("%s(): Registering Wanpipe CTRL Device!\n",__FUNCTION__);
	
	memset(&tdmapi_ctrl, 0, sizeof(tdmapi_ctrl));
	
	wan_skb_queue_init(&tdmapi_ctrl.wp_rx_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_rx_free_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_tx_free_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_tx_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_event_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_event_bh_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_event_free_list);
	wan_skb_queue_init(&tdmapi_ctrl.wp_dealloc_list);
	
	WAN_TASKQ_INIT((&tdmapi_ctrl.wp_api_task), 0, wp_api_task_func, &tdmapi_ctrl);
	
	wan_set_bit(0,&tdmapi_ctrl.init);
	
	wan_mutex_lock_init(&tdmapi_ctrl.lock, "ctrldev_lock");
	wan_spin_lock_init(&tdmapi_ctrl.irq_lock, "ctrldev_irq_lock");
	
	cdev->dev_ptr=&tdmapi_ctrl;
	tdmapi_ctrl.cdev=cdev;
	
	err=wp_tdmapi_alloc_q(&tdmapi_ctrl, &tdmapi_ctrl.wp_event_free_list, WP_TDM_API_EVENT_MAX_LEN, WP_TDM_MAX_CTRL_EVENT_Q_LEN);
	if (err) {
		return err;
	}

	/*****************************************************************
	 Part of global initialization is setting the fops pointers
	 for ALL users of this file.
	******************************************************************/

	memset(&wp_tdmapi_fops, 0x00, sizeof(wp_tdmapi_fops));

	wp_tdmapi_fops.open		= wp_tdmapi_open;
	wp_tdmapi_fops.close	= wp_tdmapi_release;
	wp_tdmapi_fops.ioctl	= wanpipe_tdm_api_ioctl;
	wp_tdmapi_fops.poll		= wp_tdmapi_poll;
	wp_tdmapi_fops.read		= wp_tdmapi_read_msg;
	wp_tdmapi_fops.write	= wp_tdmapi_write_msg;
	/*****************************************************************/

	memcpy(&cdev->ops, &wp_tdmapi_fops, sizeof(wanpipe_cdev_ops_t));

	/* some functions are NOT valid for ctrl dev */
	cdev->ops.read	= NULL;
	cdev->ops.write	= NULL;
	
	err=wanpipe_cdev_tdm_ctrl_create(cdev);
	if(err){
		return err;
	}

	return 0;
}

static int wp_tdmapi_reg_globals(void)
{
	int err=0;

		/* Header Sanity Check */
	if ((sizeof(wp_api_hdr_t)!=WAN_MAX_HDR_SZ) ||
		(sizeof(wp_api_hdr_t)!=WAN_MAX_HDR_SZ)) {
		DEBUG_ERROR("%s: Internal Error: RxHDR=%i != Expected RxHDR=%i  TxHDR=%i != Expected TxHDR=%i\n",
				__FUNCTION__,sizeof(wp_api_hdr_t),WAN_MAX_HDR_SZ,sizeof(wp_api_hdr_t),WAN_MAX_HDR_SZ);
		return -EINVAL;
	}

	/* Internal Sanity Check */
	if (WANPIPE_API_CMD_SZ != sizeof(wanpipe_api_cmd_t)) {
		DEBUG_ERROR("%s: Internal Error: Wanpipe API CMD Structure Exceeds Limit=%i Size=%i!\n",
				__FUNCTION__, WANPIPE_API_CMD_SZ,sizeof(wanpipe_api_cmd_t));
		return -EINVAL;
	}

	/* Internal Sanity Check */
	if (WAN_MAX_EVENT_SZ != sizeof(wp_api_event_t)) {
		DEBUG_ERROR("%s: Internal Error: Wanpipe Event Structure Exceeds Limit=%i Size=%i!\n",
				__FUNCTION__, WAN_MAX_EVENT_SZ,sizeof(wp_api_event_t));
		return EINVAL;
	}

	/* Internal Sanity Check */
	if (WAN_MAX_HDR_SZ != sizeof(wp_api_hdr_t)) {
		DEBUG_ERROR("%s: Internal Error: Wanpipe Header Structure Exceeds Limit=%i Size=%i!\n",
				__FUNCTION__, WAN_MAX_HDR_SZ,sizeof(wp_api_event_t));
		return EINVAL;
	}

	/* Internal Sanity Check */
	if (WANPIPE_API_DEV_CFG_SZ != sizeof(wanpipe_api_dev_cfg_t)) {
		DEBUG_ERROR("%s: Internal Error: Wanpipe Dev Cfg Structure Exceeds Limit=%i Size=%i!\n",
				__FUNCTION__, WANPIPE_API_DEV_CFG_SZ,sizeof(wanpipe_api_dev_cfg_t));
		return EINVAL;
	}

	if (WANPIPE_API_DEV_CFG_MAX_SZ != sizeof(wanpipe_api_dev_cfg_t)) {
		DEBUG_ERROR("%s: Internal Error: Wanpipe Dev Cfg Structure Exceeds MAX Limit=%i Size=%i!\n",
				__FUNCTION__, WANPIPE_API_DEV_CFG_MAX_SZ,sizeof(wanpipe_api_dev_cfg_t));
		return EINVAL;
	}

	DEBUG_TEST("WANPIPE API: HDR_SZ=%i CMD_SZ=%i EVENT_SZ=%i DEV_CFG_SZ=%i\n",
			sizeof(wp_api_hdr_t),sizeof(wanpipe_api_cmd_t),
			sizeof(wp_api_event_t),sizeof(wanpipe_api_dev_cfg_t));


#if 0
/*  FIXME 1: timer dev is not complete 
	FIXME 2: timer dev is global and should be created/deleted
			similary to ctrl dev, not a Span/Chan dev.
*/
	err=wanpipe_wandev_timer_create();
#endif

	return err;
}


void wp_ctrl_dev_delete(void)
{
	unsigned long timeout=SYSTEM_TICKS;

	DEBUG_TDMAPI("%s(): Un-Registering Wanpipe CTRL Device!\n",__FUNCTION__);

	wan_clear_bit(0,&tdmapi_ctrl.init);
	
	while(wan_test_bit(0,&tdmapi_ctrl.used)) {
		if (SYSTEM_TICKS - timeout > HZ*2) {
			if(wan_test_bit(0,&tdmapi_ctrl.used)){
				DEBUG_WARNING("wanpipe_tdm_api: Warning: Ctrl Device still in use on shutdown!\n");
			}
			break;	
		}
		WP_DELAY(1000);
		if(wan_test_bit(0,&tdmapi_ctrl.used)){
			wp_wakeup_rx_tdmapi(&tdmapi_ctrl);
			WP_SCHEDULE("tdmapi_ctlr",10);
		}
	}

	wp_tdmapi_free_buffs(&tdmapi_ctrl);

	if (tdmapi_ctrl.cdev) {
		wanpipe_cdev_free(tdmapi_ctrl.cdev);
		wan_free(tdmapi_ctrl.cdev);
		tdmapi_ctrl.cdev=NULL;
	}
}

static int wp_tdmapi_unreg_globals(void)
{
	WAN_DEBUG_FUNC_START;

	DEBUG_TEST("%s(): Unregistering Global API Devices!\n",__FUNCTION__);

#if 0
/*  FIXME timer dev is not complete */
	wanpipe_wandev_timer_free();
#endif

	WAN_DEBUG_FUNC_END;

	return 0;
}

int wanpipe_tdm_api_reg(wanpipe_tdm_api_dev_t *tdm_api)
{
	sdla_t				*card = NULL;
	u8 					tmp_name[50];
	int 				err=-EINVAL;
	wanpipe_cdev_t 		*cdev=NULL;
	wanpipe_api_cmd_t 	wp_cmd;
	wanpipe_api_cmd_t 	*wp_cmd_ptr=NULL;
	sdla_fe_t			*fe =NULL;
	int 				tdm_api_queue_init=0;
	wanpipe_tdm_api_card_dev_t *tdm_card_dev=NULL;
	int					event_queue_len=0;

	wan_mutex_lock_init(&tdm_api->lock, "wan_tdmapi_lock");
	wan_spin_lock_init(&tdm_api->irq_lock, "wan_tdmapi_irq_lock");

	if (wp_tdmapi_global_cnt == 0){
		err=wp_tdmapi_reg_globals();
		if (err) {
			return err;
		}
	}
	wp_tdmapi_global_cnt++;

	cdev=wan_kmalloc(sizeof(wanpipe_cdev_t));
	if (!cdev) {
		err=-ENOMEM;
		goto tdm_api_reg_error_exit;
	}

	memset(cdev,0,sizeof(wanpipe_cdev_t));


	WAN_ASSERT(tdm_api == NULL);
	card = (sdla_t*)tdm_api->card;
	fe = &card->fe;

	if (!card->tdm_api_dev) {
		
		tdm_card_dev = wan_malloc(sizeof(wanpipe_tdm_api_card_dev_t));
		if (!tdm_card_dev) {
			goto tdm_api_reg_error_exit;
		}
		memset(tdm_card_dev,0,sizeof(wanpipe_tdm_api_card_dev_t));
		card->tdm_api_dev=tdm_card_dev;
		tdm_card_dev->ref_cnt++;

	} else {
		tdm_card_dev = card->tdm_api_dev;
	}

	if (!tdm_api->hdlc_framing) {
		unsigned int i,max_timeslot=0;
		if (!IS_E1_CARD(card)) {
			tdm_card_dev->sig_timeslot_map |= (tdm_api->active_ch >> 1);
		} else {
			tdm_card_dev->sig_timeslot_map |= tdm_api->active_ch;
		}

		for (i=0;i<32;i++) {
			if (wan_test_bit(i,&tdm_card_dev->sig_timeslot_map)) {
				max_timeslot=i+1;
			}
		}
		if (max_timeslot > tdm_card_dev->max_timeslots) {
			tdm_card_dev->max_timeslots=max_timeslot;
		}

		/* Calculate number of timeslots this channel has.
		   SPAN mode versus CHAN. We cannot assume 1 time slot */
		for (i=0;i<32;i++) {
			if (wan_test_bit(i,&tdm_api->active_ch)) {
				tdm_api->timeslots++;
			}
		}
	} else {
		/* Just initialize for hdlc channels */
		tdm_api->timeslots=1;
	}

	wan_skb_queue_init(&tdm_api->wp_rx_list);
	wan_skb_queue_init(&tdm_api->wp_rx_free_list);

	wan_skb_queue_init(&tdm_api->wp_tx_list);
	wan_skb_queue_init(&tdm_api->wp_tx_free_list);

	wan_skb_queue_init(&tdm_api->wp_event_list);
	wan_skb_queue_init(&tdm_api->wp_event_bh_list);
	wan_skb_queue_init(&tdm_api->wp_event_free_list);
	wan_skb_queue_init(&tdm_api->wp_dealloc_list);

	WAN_TASKQ_INIT((&tdm_api->wp_api_task),0,wp_api_task_func,tdm_api);

	tdm_api_queue_init=1;

	err=wp_tdmapi_alloc_q(tdm_api, &tdm_api->wp_rx_free_list, WP_TDM_API_MAX_LEN, 16);
	if (err) {
		goto tdm_api_reg_error_exit;
	}

	event_queue_len=tdm_api->timeslots * WP_TDM_MAX_EVENT_Q_TIMESLOT_MULTIPLE;
	if (event_queue_len < WP_TDM_MIN_EVENT_Q_LEN) {
    	event_queue_len=WP_TDM_MIN_EVENT_Q_LEN; 	
	}

	err=wp_tdmapi_alloc_q(tdm_api, &tdm_api->wp_event_free_list, WP_TDM_API_EVENT_MAX_LEN, event_queue_len);
	if (err) {
		goto tdm_api_reg_error_exit;
	}

	/* Default initialization */
	tdm_api->cfg.rx_queue_sz	=WP_TDM_MAX_RX_Q_LEN;
	tdm_api->tx_channelized		=0;
	tdm_api->buffer_multiplier = 1;

	DEBUG_TDMAPI("%s(): usr_period: %d, hw_mtu_mru: %d\n", __FUNCTION__, 
		tdm_api->cfg.usr_period, tdm_api->cfg.hw_mtu_mru);

	if (tdm_api->hdlc_framing) {

		if (IS_BRI_CARD(card)) {
			tdm_api->cfg.hw_mtu_mru		=300;
			tdm_api->cfg.usr_mtu_mru	=300;
			tdm_api->mtu_mru = 300;
		} else {
			tdm_api->cfg.hw_mtu_mru		=1500;
			tdm_api->cfg.usr_mtu_mru	=1500;
			tdm_api->mtu_mru = 1500;
		}

		tdm_api->cfg.usr_period		=0;
		tdm_api->cfg.tdm_codec		=WP_NONE;
		tdm_api->cfg.power_level	=0;
		tdm_api->cfg.rx_disable		=0;
		tdm_api->cfg.tx_disable		=0;
		tdm_api->cfg.ec_tap		=0;
		tdm_api->cfg.rbs_rx_bits	=-1;
		tdm_api->cfg.hdlc		=1;
		
		/* Rx queue size must be at least twice dma chain */
		tdm_api->cfg.rx_queue_sz 	= WP_TDM_MAX_HDLC_RX_Q_LEN;
		/* Tx queue size must be at least as bit as tx dma chan */
		tdm_api->cfg.tx_queue_sz	= WP_TDM_MAX_HDLC_TX_Q_LEN;

		/* We are expecting tx_q_len for hdlc
  		 * to be configured from upper layer */
		if (tdm_api->cfg.tx_queue_sz == 0) {
			DEBUG_ERROR("%s: Internal Error: Tx Q Len not specified by lower layer!\n",
					__FUNCTION__);
			err=-EINVAL;
			goto tdm_api_reg_error_exit;
		}

	} else {

		tdm_api->cfg.tdm_codec		=WP_NONE;
		tdm_api->cfg.power_level	=0;
		tdm_api->cfg.rx_disable		=0;
		tdm_api->cfg.tx_disable		=0;
		tdm_api->cfg.ec_tap			=0;
		tdm_api->cfg.rbs_rx_bits	=-1;
		tdm_api->cfg.hdlc			=0;
		if (!tdm_api->cfg.hw_mtu_mru ||
			!tdm_api->cfg.usr_period ||
			!tdm_api->cfg.usr_mtu_mru) {
			DEBUG_ERROR("%s: Internal Error: API hwmtu=%i period=%i usr_mtu_mru=%i!\n",
						__FUNCTION__, tdm_api->cfg.hw_mtu_mru, tdm_api->cfg.usr_period, tdm_api->cfg.usr_mtu_mru);
					
			err=-EINVAL;
			goto tdm_api_reg_error_exit;
		}

		if (WPTDM_CHAN_OP_MODE(tdm_api)) {
			
			tdm_api->cfg.rx_queue_sz 	= WP_TDM_MAX_RX_Q_LEN;
			tdm_api->cfg.tx_queue_sz	= WP_TDM_MAX_TX_Q_LEN;
			tdm_api->tx_channelized		=1;

			tdm_api->mtu_mru = tdm_api->cfg.usr_period*tdm_api->cfg.hw_mtu_mru+sizeof(wp_api_hdr_t);

		} else {

			/* We are expecting tx_q_len for hdlc
			* to be configured from upper layer */
			if (tdm_api->cfg.tx_queue_sz == 0) {
				DEBUG_ERROR("%s: Internal Error: Tx Q Len not specified by lower layer!\n",
						__FUNCTION__);
				err=-EINVAL;
				goto tdm_api_reg_error_exit;
			}
		
		}

		if (tdm_api->cfg.idle_flag == 0) {
			/* BRI card must always idle 0xFF regardless of coding */
			if (tdm_api->cfg.hw_tdm_coding == WP_MULAW || IS_BRI_CARD(card)) {
        		tdm_api->cfg.idle_flag=0xFF;
			} else {
				tdm_api->cfg.idle_flag=0xD5;	
			}
		}
	}


	tdm_api->critical=0;
	wan_clear_bit(0,&tdm_api->used);


	err=store_tdm_api_pointer_in_card(card, tdm_api);
	if (err){
		goto tdm_api_reg_error_exit;
	}
	
	sprintf(tmp_name,"wanpipe%d_if%d",tdm_api->tdm_span,tdm_api->tdm_chan);

	DEBUG_TDMAPI("%s: Configuring TDM API NAME=%s Qlen=%i TS=%i MTU=%i\n",
				 card->devname,tmp_name, tdm_api->cfg.tx_queue_sz, 
				 tdm_api->timeslots,tdm_api->mtu_mru);

	/* Initialize Event Callback functions */
	card->wandev.event_callback.rbsbits		= NULL; /*wp_tdmapi_rbsbits;*/
	card->wandev.event_callback.alarms		= NULL; /*wp_tdmapi_alarms;*/
	card->wandev.event_callback.hook		= wp_tdmapi_hook;
	card->wandev.event_callback.ringdetect	= wp_tdmapi_ringdetect;
	card->wandev.event_callback.ringtrip	= wp_tdmapi_ringtrip;
	card->wandev.event_callback.linkstatus	= wp_tdmapi_linkstatus;
	card->wandev.event_callback.polarityreverse	= wp_tdmapi_polarityreverse;

	card->wandev.te_report_rbsbits  = wp_tdmapi_report_rbsbits;
	card->wandev.te_report_alarms   = wp_tdmapi_report_alarms;

	/* Always initialize the callback pointer */
	card->wandev.event_callback.tone 		= wp_tdmapi_tone;

#if defined(__WINDOWS__)
	/* Analog always supports DTMF detection */
	tdm_api->dtmfsupport = WANOPT_YES;
	/* Need pointer to PnP Fdo Device Extension for 
	 * handling of PnP notifications. */
	cdev->PnpFdoPdx = card->level2_fdo_pdx;
#endif

	cdev->dev_ptr=tdm_api;
	tdm_api->cdev=cdev;

	cdev->span=tdm_api->tdm_span;
	cdev->chan=tdm_api->tdm_chan;
	cdev->operation_mode=tdm_api->api_mode;/* WP_TDM_OPMODE - Legacy...*/
	sprintf(cdev->name,tmp_name,sizeof(cdev->name));
	memcpy(&cdev->ops, &wp_tdmapi_fops, sizeof(wanpipe_cdev_ops_t));

#if 0
	if (tdm_api->operation_mode == WP_TDM_OPMODE_MTP1) {
		wp_mtp1_reg_t reg;
		
		reg.priv_ptr=tdm_api;
		reg.rx_data=wp_tdm_api_mtp1_rx_data;
		reg.rx_reject=wp_tdm_api_mtp1_rx_reject;
		reg.rx_suerm=wp_tdm_api_mtp1_rx_suerm;
		reg.wakeup=wp_tdm_api_tmp1_wakup;
		
		tdm_api->mtp1_dev = wp_mtp1_register(&reg);	
		if (tdm_api->mtp1_dev == NULL) {
			goto tdm_api_reg_error_exit;	
		}
	}
#endif
	
	if (tdm_api->hdlc_framing) {
		/* timeout may occur only on HDLC channels */
		cdev->ops.tx_timeout = wp_tdmapi_tx_timeout;
	}

	err=wanpipe_cdev_tdm_create(cdev);
	if (err) {
		goto tdm_api_reg_error_exit;
	}

	/* AT this point the registration should not fail */

	if (tdm_api->cfg.rbs_tx_bits) {
		DEBUG_EVENT("%s: Setting Tx RBS/CAS Idle Bits = 0x%02X\n",
			       	        tmp_name,
					tdm_api->cfg.rbs_tx_bits);
		tdm_api->write_rbs_bits(tdm_api->chan,
		                        tdm_api->tdm_chan,
					(u8)tdm_api->cfg.rbs_tx_bits);
	}

   /* Enable default events for analog */
	if (card->wandev.config_id == WANCONFIG_AFT_ANALOG) {
		memset(&wp_cmd, 0x00, sizeof(wp_cmd));
		wp_cmd.cmd = WP_API_CMD_SET_EVENT;
		wp_cmd.event.wp_api_event_mode = WP_API_EVENT_ENABLE;
		wp_cmd.chan = tdm_api->tdm_chan;
		wp_cmd_ptr=&wp_cmd;

		if (fe->rm_param.mod[(tdm_api->tdm_chan) - 1].type == MOD_TYPE_FXS) {
			/*Intialize timers for FXS Wink-Flash event*/
			fe->rm_param.mod[(tdm_api->tdm_chan) - 1].u.fxs.itimer=0;
			fe->rm_param.mod[(tdm_api->tdm_chan) - 1].u.fxs.rxflashtime=WP_RM_RXFLASHTIME;
			/*hook event*/
			wp_cmd.event.wp_api_event_type = WP_API_EVENT_RXHOOK;
			DEBUG_EVENT("%s: %s Loop Closure Event on Module %d\n",
				fe->name,
				WP_API_EVENT_MODE_DECODE(wp_cmd.event.wp_api_event_mode),
				tdm_api->tdm_chan);
			wanpipe_tdm_api_event_ioctl(tdm_api,wp_cmd_ptr);
			/*Ring/Trip event */
			wp_cmd.event.wp_api_event_type = WP_API_EVENT_RING_TRIP_DETECT;
			DEBUG_EVENT("%s: %s Ring Trip Detection Event on Module %d\n",
				fe->name,
				WP_API_EVENT_MODE_DECODE(wp_cmd.event.wp_api_event_mode),
				tdm_api->tdm_chan);
			wanpipe_tdm_api_event_ioctl(tdm_api,wp_cmd_ptr);
		} else if (fe->rm_param.mod[(tdm_api->tdm_chan) - 1].type == MOD_TYPE_FXO ) {
			/*ring detect event */
			wp_cmd.event.wp_api_event_type = WP_TDMAPI_EVENT_RING_DETECT;
			DEBUG_EVENT("%s: %s Ring Detection Event on module %d\n",
				fe->name,
				WP_API_EVENT_MODE_DECODE(wp_cmd.event.wp_api_event_mode),
				tdm_api->tdm_chan);
			wanpipe_tdm_api_event_ioctl(tdm_api,wp_cmd_ptr);
		}
	}

   	tdm_api->cfg.fe_alarms = 0;
	if (IS_BRI_CARD(card) || IS_TE1_CARD(card)) {
   		if (fe->fe_status == FE_DISCONNECTED) {
   			tdm_api->cfg.fe_alarms = (1 | WAN_TE_BIT_ALARM_RED);
   		}
	} 

	return err;


tdm_api_reg_error_exit:

	if (wp_tdmapi_global_cnt > 0) {
		wp_tdmapi_global_cnt--;
	}

	if (wp_tdmapi_global_cnt == 0){
		wp_tdmapi_unreg_globals();
	}

	if (card->tdm_api_dev) {
		wanpipe_tdm_api_card_dev_t *tdm_card_dev = card->tdm_api_dev;

		if (tdm_card_dev->ref_cnt > 0){
			tdm_card_dev->ref_cnt--;
		}
		if (tdm_card_dev->ref_cnt == 0) {
			wan_free(card->tdm_api_dev);
			card->tdm_api_dev=NULL;
		}
	}

	if (tdm_api_queue_init) {
		wp_tdmapi_free_buffs(tdm_api);
	}

	if (cdev) {
		wan_free(cdev);
	}

	return err;
}


int wanpipe_tdm_api_unreg(wanpipe_tdm_api_dev_t *tdm_api)
{
	sdla_t *card = tdm_api->card;
	int err;

	WAN_DEBUG_FUNC_START;

	wan_set_bit(WP_TDM_DOWN,&tdm_api->critical);

	if (wan_test_bit(0,&tdm_api->used)) {
#if defined(__WINDOWS__)
		/* During transition to System StandBy state an application may keep
		 * open file descriptors. Do NOT fail unregister in this case. */
		DEBUG_WARNING("%s: Warning: unregister request for Span=%i Chan=%i while Open Handles exist!\n",
			tdm_api->name, tdm_api->tdm_span, tdm_api->tdm_chan);
#else
		DEBUG_EVENT("%s Failed to unreg Span=%i Chan=%i: BUSY!\n",
			tdm_api->name,tdm_api->tdm_span,tdm_api->tdm_chan);
		return -EBUSY;
#endif
	}


#if defined(WAN_TASKQ_STOP)
	err=WAN_TASKQ_STOP((&tdm_api->wp_api_task));
	DEBUG_TDMAPI("%s: %s: %s\n",card->devname, tdm_api->name, err?"TASKQ Successfully Stopped":"TASKQ Not Running");
#endif

	wp_tdmapi_unreg_notify_ctrl(tdm_api);

	remove_tdm_api_pointer_from_card(tdm_api);

	wp_tdmapi_free_buffs(tdm_api);

	if (tdm_api->cdev) {
		wanpipe_cdev_free(tdm_api->cdev);
		wan_free(tdm_api->cdev);
		tdm_api->cdev=NULL;
	}

	if (card->tdm_api_dev) {
		wanpipe_tdm_api_card_dev_t *tdm_card_dev = card->tdm_api_dev;

		if (tdm_card_dev->ref_cnt > 0){
			tdm_card_dev->ref_cnt--;
		}
		if (tdm_card_dev->ref_cnt == 0) {

			if (tdm_card_dev->rbs_poll_timeout) {
				wan_smp_flag_t smp_flags1;
				card->hw_iface.hw_lock(card->hw,&smp_flags1);
				if (card->wandev.fe_iface.set_fe_sigctrl) {
					card->wandev.fe_iface.set_fe_sigctrl(
							&card->fe,
							WAN_TE_SIG_POLL,
							ENABLE_ALL_CHANNELS,
							WAN_DISABLE);
				}
				card->hw_iface.hw_unlock(card->hw,&smp_flags1);
			}

			card->tdm_api_dev=NULL;
			wan_free(tdm_card_dev);
		}
	}

	wp_tdmapi_global_cnt--;
	if (wp_tdmapi_global_cnt == 0) {
		wp_tdmapi_unreg_globals();
	}

	WAN_DEBUG_FUNC_END;

	return 0;
}

int wanpipe_tdm_api_kick(wanpipe_tdm_api_dev_t *tdm_api)
{
	int rc=0;

	if (tdm_api == NULL || !wan_test_bit(0,&tdm_api->init)){
		return -ENODEV;
	}

	if (tdm_api->tx_channelized) {
		if (is_tdm_api_stopped(tdm_api) || wan_skb_queue_len(&tdm_api->wp_tx_list) >= (int)tdm_api->cfg.tx_queue_sz){
			wp_tdm_api_start(tdm_api);
			if (wan_test_bit(0,&tdm_api->used)) {
				wp_wakeup_tx_tdmapi(tdm_api);
			}
		}
	} else {
			/* By default tdm_api->buffer_multiplier is set to 1 therefore we
               can use it to check it tx is available */
			if (tdm_api->write_hdlc_check) {
				rc=tdm_api->write_hdlc_check(tdm_api->chan, 0, tdm_api->buffer_multiplier);
			}
			if (rc == 0 ||
				wan_skb_queue_len(&tdm_api->wp_rx_list) >= (int)tdm_api->buffer_multiplier ||
				wan_skb_queue_len(&tdm_api->wp_event_list)) {
				wp_tdm_api_start(tdm_api);
				if (wan_test_bit(0,&tdm_api->used)) {
					wp_wakeup_tx_tdmapi(tdm_api);
				}
			}
	}

	return 0;
}


int wanpipe_tdm_api_is_rbsbits(sdla_t *card)
{
	wanpipe_tdm_api_card_dev_t *tdm_card_dev;

	WAN_ASSERT(card == NULL);

	if (!card->tdm_api_dev) {
		return 0;
	}

	tdm_card_dev = card->tdm_api_dev;

	if (!tdm_card_dev || !tdm_card_dev->rbs_poll_timeout) {
		return 0;
	}

	tdm_card_dev->rbscount++;

	if ((SYSTEM_TICKS - tdm_card_dev->rbs_poll_cnt) < tdm_card_dev->rbs_poll_timeout) {
		return 0;
	}

	tdm_card_dev->rbs_poll_cnt=SYSTEM_TICKS;

	return 1;
}



/* Note: This functin is hw_locked from the core */
int wanpipe_tdm_api_rbsbits_poll(sdla_t * card)
{
	wanpipe_tdm_api_card_dev_t *tdm_card_dev;
	unsigned int x;

	WAN_ASSERT(card == NULL);

	if (!card->tdm_api_dev) {
		return 0;
	}

	tdm_card_dev = card->tdm_api_dev;

	if ((SYSTEM_TICKS - tdm_card_dev->rbs_long_poll_cnt) > HZ*2) {

		tdm_card_dev->rbs_long_poll_cnt=SYSTEM_TICKS;

		DEBUG_TEST("%s: RBS POLL MaxSlots=%i MAP=0x%08X\n",card->devname,tdm_card_dev->max_timeslots,tdm_card_dev->sig_timeslot_map);

		for(x = 0; x < tdm_card_dev->max_timeslots; x++){
			if (wan_test_bit(x, &tdm_card_dev->sig_timeslot_map)){
				if (card->wandev.fe_iface.read_rbsbits) {
					card->wandev.fe_iface.read_rbsbits(
						&card->fe,
						x,
						WAN_TE_RBS_UPDATE|WAN_TE_RBS_REPORT);
				} else {
					DEBUG_ERROR("%s: Internal Error [%s:%d]!\n", card->devname,	__FUNCTION__,__LINE__);
				}
			}
		}

	} else {

		if (card->wandev.fe_iface.check_rbsbits == NULL){
                	DEBUG_ERROR("%s: Internal Error [%s:%d]!\n",
					card->devname,
					__FUNCTION__,__LINE__); 
			return -EINVAL;
		}

		card->wandev.fe_iface.check_rbsbits(
				&card->fe, 
				1, tdm_card_dev->sig_timeslot_map, 1);
		card->wandev.fe_iface.check_rbsbits(
				&card->fe, 
				9, tdm_card_dev->sig_timeslot_map, 1);
		card->wandev.fe_iface.check_rbsbits(
				&card->fe, 
				17, tdm_card_dev->sig_timeslot_map, 1);
		if (IS_E1_CARD(card)){
			card->wandev.fe_iface.check_rbsbits(
					&card->fe, 
					25, tdm_card_dev->sig_timeslot_map, 1);
		}
	}

	return 0;
}


static int wp_tdmapi_open(void *obj)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	int cnt;

	if (!tdm_api) {
		return -ENODEV;
	}

	if (wan_test_bit(WP_TDM_DOWN,&tdm_api->critical)) {
		return -ENODEV;
	}

	if (!tdm_api->card) {
		if(tdm_api != &tdmapi_ctrl){
			DEBUG_ERROR("%s: Error: Wanpipe API Device does not have a 'card' pointer! Internal error!\n",
				tdm_api->name);
			return -ENODEV;
		}
	}

	cnt = wp_tdm_inc_open_cnt(tdm_api);

	if (cnt == 1) {

		wp_tdmapi_init_buffs(tdm_api, WP_API_FLUSH_ALL);
	
		tdm_api->cfg.loop=0;
	}

	wan_set_bit(0,&tdm_api->used);
	wp_tdm_api_start(tdm_api);

	DEBUG_TDMAPI ("%s: DRIVER OPEN S/C(%i/%i) API Ptr=%p\n",
		__FUNCTION__, tdm_api->tdm_span, tdm_api->tdm_chan, tdm_api);

	return 0;
}

#ifdef DEBUG_API_READ
static int gread_cnt=0;
#endif

static __inline int wp_tdmapi_set_gain(netskb_t *skb, char *gain)
{
 	if (skb && gain && wan_skb_len(skb) > sizeof(wp_api_hdr_t)) {
		int i;
		int len = wan_skb_len(skb)- sizeof(wp_api_hdr_t);
		u8 *buf=(u8*)wan_skb_data(skb);
		buf=&buf[sizeof(wp_api_hdr_t)];
		for (i=0;i<len;i++) {
			buf[i]=gain[buf[i]];
		}
	} else {
     	return -1;
	}

	return 0;
}

static int wp_tdmapi_read_msg(void *obj, netskb_t **skb_ptr, wp_api_hdr_t *hdr, int count)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	netskb_t *skb=NULL;
	wan_smp_flag_t irq_flags;
	sdla_t *card;
	int rx_q_len=0;
	u8 *buf;

	
	if (tdm_api == NULL || !wan_test_bit(0,&tdm_api->init) || !wan_test_bit(0,&tdm_api->used) || !hdr) {
		if (hdr) {
			hdr->wp_api_hdr_operation_status = SANG_STATUS_GENERAL_ERROR;	
		}
		return -ENODEV;
	}

	if (tdm_api == &tdmapi_ctrl) {
		hdr->wp_api_hdr_operation_status = SANG_STATUS_INVALID_DEVICE;
		return -EINVAL;
	}

	if (wan_test_bit(WP_TDM_DOWN,&tdm_api->critical)) {
		hdr->wp_api_hdr_operation_status = SANG_STATUS_GENERAL_ERROR;
		return -ENODEV;
	}

	card=(sdla_t*)tdm_api->card;

	if (!card) {
		hdr->wp_api_hdr_operation_status = SANG_STATUS_GENERAL_ERROR;
		return -ENODEV;
	}

	irq_flags=0;

	if (tdm_api->buffer_multiplier > 1) {
		netskb_t *nskb;
		netskb_t *skb_to_free;
		char * buf, *u_buf;
		int single_len;
		int multiplier=tdm_api->buffer_multiplier;
		int i;

		wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);

		if (wan_skb_queue_len(&tdm_api->wp_rx_list) < multiplier) {
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
			DEBUG_ERROR("%s: %s:%d: Error: Rx queue len %i: multiplier %i mismatch\n",
				tdm_api->name,__FUNCTION__,__LINE__,wan_skb_queue_len(&tdm_api->wp_rx_list),multiplier);
			hdr->wp_api_hdr_operation_status = SANG_STATUS_NO_DATA_AVAILABLE;
			return -ENOBUFS;
		}

		skb=wan_skb_dequeue(&tdm_api->wp_rx_list);
		if (!skb) {
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
			DEBUG_ERROR("%s: %s:%d: Error rx queue emtpy on first skb: multiplier %i\n",
				tdm_api->name,__FUNCTION__,__LINE__,multiplier);
			hdr->wp_api_hdr_operation_status = SANG_STATUS_NO_DATA_AVAILABLE;
			return -ENOBUFS;
		}

		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);

		/* This section must be unlocked because we are allocating memory */
		single_len = (wan_skb_len(skb)-sizeof(wp_api_hdr_t));
		nskb=wan_skb_kalloc(wan_skb_len(skb)+(single_len*multiplier));
		if (!nskb) {
			hdr->wp_api_hdr_operation_status = SANG_STATUS_NO_DATA_AVAILABLE;
			return -ENOMEM;
		}

		buf=wan_skb_put(nskb,sizeof(wp_api_hdr_t)+single_len);
		u_buf=wan_skb_data(skb);
		memcpy(buf,u_buf,sizeof(wp_api_hdr_t)+single_len);

		wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);

		wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
		skb=NULL;

		/* Count 1 less then multiplier because we already pulled one buffer above */
		for (i=1;i<multiplier;i++) {
			skb=wan_skb_dequeue(&tdm_api->wp_rx_list);
			if (!skb) {
				wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
				DEBUG_ERROR("%s:%d: Error rx queue emtpy on %i skb - multiplier %i\n",
					__FUNCTION__,__LINE__,i+1,multiplier);
				wan_skb_free(nskb);
				wan_skb_free(skb);
				hdr->wp_api_hdr_operation_status = SANG_STATUS_NO_DATA_AVAILABLE;
				return -ENOBUFS;
			}

			u_buf=wan_skb_data(skb);
			u_buf = &u_buf[sizeof(wp_api_hdr_t)];

			buf=wan_skb_put(nskb,single_len);

			memcpy(buf,u_buf,single_len);

			wan_skb_queue_tail(&tdm_api->wp_dealloc_list,skb);
			skb=NULL;
		}

		skb=nskb;
		rx_q_len = wan_skb_queue_len(&tdm_api->wp_rx_list);
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);

		do {

			wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);

			skb_to_free = wan_skb_dequeue(&tdm_api->wp_dealloc_list);

			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);

			if (skb_to_free) {
				wan_skb_free(skb_to_free);
			}

		} while(skb_to_free);

	} else {

		wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
		skb=wan_skb_dequeue(&tdm_api->wp_rx_list);
		rx_q_len = wan_skb_queue_len(&tdm_api->wp_rx_list);
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
	}

	if (!skb){
		hdr->wp_api_hdr_operation_status = SANG_STATUS_NO_DATA_AVAILABLE;
		return -ENOBUFS;
	}
		
	if (tdm_api->rx_gain) {
		wp_tdmapi_set_gain(skb,tdm_api->rx_gain);
	}

#ifdef DEBUG_API_READ
	if (tdm_api->tdm_span == 1 &&
	    tdm_api->tdm_chan == 1) {
	    gread_cnt++;
	    if (gread_cnt &&
	        gread_cnt % 1000 == 0) {
		DEBUG_EVENT("%s: WP_TDM API READ %i Len=%i\n",
				tdm_api->name, gread_cnt,
				wan_skb_len(skb)-sizeof(wp_api_hdr_t));
	    }
	}
#endif

	if (count < (int)(wan_skb_len(skb)) ||
		wan_skb_len(skb) < sizeof(wp_api_hdr_t)){

		DEBUG_ERROR("%s:%d User API Error: User Rx Len=%i < Driver Rx Len=%i (hdr=%i). User API must increase expected rx length!\n",
			__FUNCTION__,__LINE__,count,wan_skb_len(skb),sizeof(wp_api_hdr_t));
		wan_skb_free(skb);
		hdr->wp_api_hdr_operation_status = SANG_STATUS_BUFFER_TOO_SMALL;
		return -EFAULT;
	}

	buf=(u8*)wan_skb_data(skb);

	/* copy header info (such as time stamp) */
	memcpy(hdr, buf, sizeof(wp_api_hdr_t));
		
	if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {

		/* do nothing */
		
	} else {
		netskb_t *tmp_skb;

		/* Allocate another free skb for RX */
		tmp_skb=wan_skb_kalloc(WP_TDM_API_MAX_LEN);
		if (tmp_skb) {
			wan_skb_init(tmp_skb,sizeof(wp_api_hdr_t));
			wan_skb_trim(tmp_skb,0);
			wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
			wan_skb_queue_tail(&tdm_api->wp_rx_free_list,tmp_skb);
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		}
	}

	hdr->wp_api_rx_hdr_number_of_frames_in_queue = (u8)rx_q_len;
	hdr->wp_api_rx_hdr_max_queue_length = (u8)tdm_api->cfg.rx_queue_sz;

	hdr->wp_api_hdr_operation_status = SANG_STATUS_RX_DATA_AVAILABLE;
	hdr->wp_api_hdr_data_length = wan_skb_len(skb)-sizeof(wp_api_hdr_t);

	hdr->wp_api_rx_hdr_errors = card->fe.fe_stats.te_pmon.sync_errors;

	if (wan_skb_len(skb) >= sizeof(wp_api_hdr_t)) {
		/* Copy back the updated header info into the skb header
           section. */
		memcpy(buf,hdr,sizeof(wp_api_hdr_t));
	} else {
		DEBUG_ERROR("%s: Internal Error: Rx Data Invalid %i\n",tdm_api->name,wan_skb_len(skb));
	}

	*skb_ptr = skb;

//NENAD
	DEBUG_TEST("%s: RX Q=%i  RX Q MAX=%i SIZE=%i %p\n",
				tdm_api->name,
				hdr->wp_api_rx_hdr_number_of_frames_in_queue,
				hdr->wp_api_rx_hdr_max_queue_length,
				wan_skb_len(skb), skb);

	return 0;
}


static int wp_tdmapi_tx (wanpipe_tdm_api_dev_t *tdm_api, netskb_t *skb,  wp_api_hdr_t *hdr)
{
	int err=-EINVAL;

	if (wan_test_and_set_bit(WP_TDM_HDLC_TX,&tdm_api->critical)){
		DEBUG_ERROR("%s: Error: %s() WP_TDM_HDLC_TX critical\n",
			tdm_api->name, __FUNCTION__);
		hdr->wp_api_hdr_operation_status = SANG_STATUS_GENERAL_ERROR;
		return -EINVAL;
	}

	if (tdm_api->buffer_multiplier > 1) {

			netskb_t *nskb;
			int len,i;
			char *buf;
			char *u_buf=wan_skb_data(skb);

	
			if (tdm_api->write_hdlc_check) {
				int rc=tdm_api->write_hdlc_check(tdm_api->chan, 0, tdm_api->buffer_multiplier);
				if (rc) {
					DEBUG_ERROR("%s: Error: %s() Write check failed for multiplier %i\n",
						tdm_api->name, __FUNCTION__,tdm_api->buffer_multiplier);
					err=-EINVAL;
					goto wp_tdmapi_tx_exit;
				}
			}

			if ((wan_skb_len(skb) % tdm_api->buffer_multiplier) != 0) {
				DEBUG_ERROR("%s: Error: %s() buf size %i is not divisable by multiplier %i\n",
						tdm_api->name, __FUNCTION__,wan_skb_len(skb),tdm_api->buffer_multiplier);
				err=-EINVAL;
				goto wp_tdmapi_tx_exit;
			}

			len = wan_skb_len(skb) / tdm_api->buffer_multiplier;

			for (i=0;i<(int)tdm_api->buffer_multiplier;i++) {

				nskb=wan_skb_kalloc(len+sizeof(wp_api_hdr_t));
				if (!nskb) {
					err = -ENOMEM;
					break;
				}

				buf=wan_skb_put(nskb,len);
				memcpy(buf,&u_buf[i*len],len);

				if (tdm_api->write_hdlc_frame) {
					err=tdm_api->write_hdlc_frame(tdm_api->chan,nskb,hdr);
					if (err) {
						if (WAN_NET_RATELIMIT()) {
						DEBUG_ERROR("%s: Error: %s() write_hdlc_frame failed on multiple %i/%i len=%i\n",
									tdm_api->name, __FUNCTION__,i+1,tdm_api->buffer_multiplier,len);
						}
						wan_skb_free(nskb);
						break;
					}	
				} else {
					wan_skb_free(nskb);
					DEBUG_ERROR("%s: Error: %s() tdm_api->write_hdlc_frame is NULL: critical\n",
						tdm_api->name, __FUNCTION__);
					err=-EINVAL;
					break;
				}
				nskb=NULL;
			}

			/* Free the orignal copy of the skb */
			if (err==0) {
				wan_skb_free(skb);
				skb=NULL;
			}

	} else {

		if (tdm_api->write_hdlc_frame) {
			err=tdm_api->write_hdlc_frame(tdm_api->chan,skb, hdr);
		} else {
			DEBUG_ERROR("%s: Error: %s() tdm_api->write_hdlc_frame is NULL: critical\n",
				tdm_api->name, __FUNCTION__);
			err=-EINVAL;
		}
	}

wp_tdmapi_tx_exit:

	if (err == -EBUSY) {
		/* Busy condition */
		hdr->wp_api_hdr_operation_status = SANG_STATUS_DEVICE_BUSY;
		wp_tdm_api_stop(tdm_api);
		err=1;
	} else if (err == 1) {
		/* Successful tx but now queue is full.
		 * Note that BRI D-channel will always return 1 after accepting a frame for transmission.
		 */
		wp_tdm_api_stop(tdm_api);
		err=0;
	} else if (err == 0) {
		/* Successful tx queue is still NOT full */
		wp_tdm_api_start(tdm_api);
		err=0;
	} else {
		wp_tdm_api_start(tdm_api);
		hdr->wp_api_hdr_operation_status = SANG_STATUS_IO_ERROR;
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
	}

#if 0
	while ((skb=wan_skb_dequeue(&tdm_api->wp_tx_list)) != NULL) {
		err=tdm_api->write_hdlc_frame(tdm_api->chan,skb);
		if (err) {
			wan_skb_queue_head(&tdm_api->wp_tx_list, skb);
			break;
		}
		tdm_api->cfg.stats.tx_packets++;
		skb=NULL;
	}
#endif

	wan_clear_bit(WP_TDM_HDLC_TX,&tdm_api->critical);
	return err;
}

#ifdef DEBUG_API_WRITE
static int gwrite_cnt=0;
#endif
static int wp_tdmapi_write_msg(void *obj, netskb_t *skb, wp_api_hdr_t *hdr)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	wan_smp_flag_t irq_flags;
	int err=-EINVAL;
	sdla_t *card;

	if (tdm_api == NULL || !wan_test_bit(0,&tdm_api->init) || !wan_test_bit(0,&tdm_api->used)){
		return -ENODEV;
	}

	if (tdm_api == &tdmapi_ctrl) {
		hdr->wp_api_hdr_operation_status = SANG_STATUS_INVALID_DEVICE;
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
		if (WAN_NET_RATELIMIT()) {
			DEBUG_ERROR("Error: Wanpipe API: wp_tdmapi_write_msg() on ctrl device!\n");
		}
		return -EINVAL;
	}

	card=tdm_api->card;
	irq_flags=0;

	if (wan_test_bit(WP_TDM_DOWN,&tdm_api->critical)) {
		hdr->wp_api_hdr_operation_status = SANG_STATUS_GENERAL_ERROR;
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
		if (WAN_NET_RATELIMIT()) {
			DEBUG_ERROR("Error: Wanpipe API: wp_tdmapi_write_msg() WP_TDM_DOWN\n");
		}
		return -ENODEV;
	}

	if (wan_skb_len(skb) <= sizeof(wp_api_hdr_t)) {
		hdr->wp_api_hdr_operation_status = SANG_STATUS_BUFFER_TOO_SMALL;
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
		if (WAN_NET_RATELIMIT()) {
			DEBUG_ERROR("Error: Wanpipe API: wp_tdmapi_write_msg() datalen=%i < hdrlen=%i\n",
				wan_skb_len(skb),sizeof(wp_api_hdr_t));
		}
		return -EINVAL;
	}

	if (tdm_api->buffer_multiplier > 1) {

		if ((wan_skb_len(skb) - sizeof(wp_api_hdr_t)) % tdm_api->buffer_multiplier != 0 ) {
			DEBUG_ERROR("%s: Error: TDM API Tx packet len %i not divisible by multiple %i\n",
					tdm_api->name, wan_skb_len(skb), tdm_api->buffer_multiplier);
			hdr->wp_api_hdr_operation_status =SANG_STATUS_TX_DATA_TOO_LONG;
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
			return -EFBIG;
		}

	} else if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {
		if (wan_skb_len(skb) > (int)(tdm_api->cfg.usr_mtu_mru + sizeof(wp_api_hdr_t))) {
			DEBUG_ERROR("%s: Error: TDM API Tx packet too big %d Max=%d\n",
					tdm_api->name,wan_skb_len(skb), (tdm_api->cfg.usr_mtu_mru + sizeof(wp_api_hdr_t)));
			
			hdr->wp_api_hdr_operation_status =SANG_STATUS_TX_DATA_TOO_LONG;
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
			return -EFBIG;
		}
	} else {
		if (wan_skb_len(skb) > (WP_TDM_API_MAX_LEN+sizeof(wp_api_hdr_t))) {
			if (WAN_NET_RATELIMIT()) {
				DEBUG_ERROR("%s: Error: TDM API Tx packet too big %d\n",
					tdm_api->name, wan_skb_len(skb));
			}
			hdr->wp_api_hdr_operation_status =SANG_STATUS_TX_DATA_TOO_LONG;
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
			return -EFBIG;
		}
	}

	hdr->wp_api_hdr_data_length = wan_skb_len(skb)-sizeof(wp_api_hdr_t);

	if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {

		if (tdm_api->tx_gain && !tdm_api->hdlc_framing) {
			wp_tdmapi_set_gain(skb,tdm_api->tx_gain);
		}
		
		wan_skb_pull(skb,sizeof(wp_api_hdr_t));

		err=wp_tdmapi_tx(tdm_api, skb, hdr);
		if (err) {
			wan_skb_push(skb,sizeof(wp_api_hdr_t));
			goto wp_tdmapi_write_msg_exit;
		} else {
			tdm_api->cfg.stats.tx_packets++;
			hdr->wp_api_hdr_operation_status=SANG_STATUS_SUCCESS;
		}

	} else {

		if (wan_skb_queue_len(&tdm_api->wp_tx_list) >= (int)tdm_api->cfg.tx_queue_sz){
			wp_tdm_api_stop(tdm_api);
            hdr->tx_h.current_number_of_frames_in_tx_queue = (u8)wan_skb_queue_len(&tdm_api->wp_tx_list);
	   		hdr->tx_h.tx_idle_packets = tdm_api->cfg.stats.tx_idle_packets;   
			hdr->tx_h.max_tx_queue_length = (u8)tdm_api->cfg.tx_queue_sz;
			err=1;
			DEBUG_TDMAPI("Error: Wanpipe API: wp_tdmapi_write_msg() TxList=%i >= MaxTxList=%i\n",
				wan_skb_queue_len(&tdm_api->wp_tx_list), tdm_api->cfg.tx_queue_sz);
			goto wp_tdmapi_write_msg_exit;
		}

		/* The code below implements the channelized tx mode */
	
#ifdef DEBUG_API_WRITE
		if (tdm_api->tdm_span == 1 &&
			tdm_api->tdm_chan == 1) {
			gwrite_cnt++;
			if (gwrite_cnt &&
				gwrite_cnt % 1000 == 0) {
				DEBUG_EVENT("%s: WP_TDM API WRITE CODEC %i Len=%i\n",
						tdm_api->name, gwrite_cnt,
						wan_skb_len(skb)-sizeof(wp_api_hdr_t));
			}
		}
#endif
	
		card = tdm_api->card;

		if (tdm_api->tx_gain) {
			wp_tdmapi_set_gain(skb,tdm_api->tx_gain);
		}

		wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
		wan_skb_queue_tail(&tdm_api->wp_tx_list, skb);
		hdr->tx_h.current_number_of_frames_in_tx_queue = (u8)wan_skb_queue_len(&tdm_api->wp_tx_list);
		hdr->tx_h.tx_idle_packets = tdm_api->cfg.stats.tx_idle_packets;
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);

		hdr->tx_h.max_tx_queue_length = (u8)tdm_api->cfg.tx_queue_sz;
		
		
		/* Free up previously used tx buffers */
		skb=NULL;
		if (wan_skb_queue_len(&tdm_api->wp_tx_free_list)) {
			do {
				wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
				skb=wan_skb_dequeue(&tdm_api->wp_tx_free_list);
				wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
				if (skb) {
					wan_skb_free(skb);
					continue;
				}

			}while(skb);
		}

		hdr->wp_api_hdr_operation_status=SANG_STATUS_SUCCESS;
		err=0;
	}

	DEBUG_TEST("%s: TX Q=%i  TX Q MAX=%i\n",
				tdm_api->name,
				hdr->tx_h.current_number_of_frames_in_tx_queue,hdr->tx_h.max_tx_queue_length);

wp_tdmapi_write_msg_exit:

	return err;
}


static int wp_tdmapi_tx_timeout(void *obj)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	int err=-EINVAL;
			
	if (tdm_api->write_hdlc_timeout) {
		/* Call timeout function with lock parameter set */
		err=tdm_api->write_hdlc_timeout(tdm_api->chan, 1);
	}

	DEBUG_TDMAPI("%s(): %s\n", __FUNCTION__, tdm_api->name);

	return err;
}

static int wp_tdmapi_release(void *obj)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	wanpipe_api_cmd_t usr_tdm_api;
	wan_smp_flag_t flag;
	u32 cnt;

	if (tdm_api == NULL || !wan_test_bit(0,&tdm_api->init)) {
		if (tdm_api) {
			wan_clear_bit(0,&tdm_api->used);
		}
		return -ENODEV;
	}

	cnt = wp_tdm_dec_open_cnt(tdm_api);
	if (cnt == 0) {
		wan_clear_bit(0,&tdm_api->used);
		wp_wakeup_rx_tdmapi(tdm_api);

		wan_mutex_lock(&tdm_api->lock,&flag);
	
		tdm_api->cfg.rbs_rx_bits=-1;
		tdm_api->cfg.rbs_tx_bits=-1;
	
		wan_mutex_unlock(&tdm_api->lock,&flag);
	
		if (tdm_api != &tdmapi_ctrl && tdm_api->driver_ctrl) {
			tdm_api->driver_ctrl(tdm_api->chan,WP_API_CMD_FLUSH_BUFFERS,&usr_tdm_api);
		}
	}

	return 0;
}


static int wan_skb_push_to_ctrl_event (netskb_t *skb)
{
	netskb_t *ctrl_skb;
	wan_smp_flag_t flags;

	if (!skb) {
		DEBUG_ERROR("TDMAPI: Error: Null skb argument!\n");
		return -ENODEV;
	}

	if (!wan_test_bit(0,&tdmapi_ctrl.used)) {
		return -EBUSY;
	}
		
	wan_spin_lock_irq(&tdmapi_ctrl.irq_lock, &flags);
	ctrl_skb=wan_skb_dequeue(&tdmapi_ctrl.wp_event_free_list);
	wan_spin_unlock_irq(&tdmapi_ctrl.irq_lock, &flags);

	if (ctrl_skb){

		int len = wan_skb_len(skb);
		u8 *buf;
		if (len > wan_skb_tailroom(ctrl_skb)) {

			DEBUG_ERROR("TDMAPI: Error: TDM API CTRL Event Buffer Overflow Elen=%i Max=%i!\n",
					len,wan_skb_tailroom(ctrl_skb));

			WP_AFT_CHAN_ERROR_STATS(tdmapi_ctrl.cfg.stats, rx_events_dropped);
			return -EFAULT;
		}

		buf=wan_skb_put(ctrl_skb,len);
		memcpy(buf,wan_skb_data(skb),len);

		wan_spin_lock_irq(&tdmapi_ctrl.irq_lock, &flags);
		wanpipe_tdm_api_handle_event(&tdmapi_ctrl, ctrl_skb);
		wan_spin_unlock_irq(&tdmapi_ctrl.irq_lock, &flags);

	} else {
		WP_AFT_CHAN_ERROR_STATS(tdmapi_ctrl.cfg.stats, rx_events_dropped);
	}

	return 0;
}


#ifdef DEBUG_API_POLL
# if defined(__WINDOWS__)
#  pragma message ("POLL Debugging Enabled")
# else
#  warning "POLL Debugging Enabled"
# endif
static int gpoll_cnt=0;
#endif

static unsigned int wp_tdmapi_poll(void *obj)
{
	int ret=0,rc;
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	sdla_t *card;
	wan_smp_flag_t irq_flags;	

	irq_flags=0;

	if (tdm_api == NULL || !wan_test_bit(0,&tdm_api->init) || !wan_test_bit(0,&tdm_api->used)){
		return -ENODEV;
	}

	if (wan_test_bit(WP_TDM_DOWN,&tdm_api->critical)) {
		return -ENODEV;
	}

	if (tdm_api == &tdmapi_ctrl) {
		/* intentionally NOT locking! */
		if (wan_skb_queue_len(&tdm_api->wp_event_list)) {
			/* Indicate an Exception or an Event */
			ret |= POLLPRI;
		}
		return ret;	
	}


	card = tdm_api->card;
	if (!card) {
		return -ENODEV;
	}

#ifdef DEBUG_API_POLL
	if (tdm_api->tdm_span == 1 && tdm_api->tdm_chan == 1) {
		gpoll_cnt++;
		if (gpoll_cnt && gpoll_cnt % 1000 == 0) {
			DEBUG_EVENT("%s: WP_TDM POLL WAKEUP %i RxF=%i TxF=%i TxCE=%i TxE=%i TxP=%i\n",
				tdm_api->name, gpoll_cnt,
				wan_skb_queue_len(&tdm_api->wp_tx_free_list),
				wan_skb_queue_len(&tdm_api->wp_rx_free_list),
				tdm_api->cfg.stats.tx_idle_packets,
				tdm_api->cfg.stats.tx_errors,
				tdm_api->cfg.stats.tx_packets);
		}
	}
#endif

	wan_spin_lock_irq(&card->wandev.lock,&irq_flags);

	/* Tx Poll */
	if (!wan_test_bit(0,&tdm_api->cfg.tx_disable)) {
		if (tdm_api->tx_channelized) {
			if (wan_skb_queue_len(&tdm_api->wp_tx_list) < (int)tdm_api->cfg.tx_queue_sz) {
				wp_tdm_api_start(tdm_api);
				ret |= POLLOUT | POLLWRNORM;
			}
		} else {

			rc=1;
			/* tdm_api->buffer_multiplir is by default 1 so we can use it to check
			   for available buffers */
			if (tdm_api->write_hdlc_check) {
				rc=tdm_api->write_hdlc_check(tdm_api->chan, 0, tdm_api->buffer_multiplier);
			}

			if (rc == 0) {
				/* Wake up the user application tx available */
				wp_tdm_api_start(tdm_api);
				ret |= POLLOUT | POLLWRNORM;
			}
		}
	} 

	/* Rx Poll */
	if (!wan_test_bit(0,&tdm_api->cfg.rx_disable)) {
		/* tdm_api->buffer_multiplir is by default 1 so we can use it to check
		   for available buffers */
		if (wan_skb_queue_len(&tdm_api->wp_rx_list) >= (int)tdm_api->buffer_multiplier) {
			ret |= POLLIN | POLLRDNORM;
		}
	}

	if (wan_skb_queue_len(&tdm_api->wp_event_list)) {
		/* Indicate an exception */
		ret |= POLLPRI;
	}

	wan_spin_unlock_irq(&card->wandev.lock,&irq_flags);

	return ret;
}

static int wanpipe_tdm_api_handle_event(wanpipe_tdm_api_dev_t *tdm_api, netskb_t *skb)
{
	wp_api_event_t  *p_tdmapi_event = NULL;
	wan_time_t sec;	/* windows note: must use wan_time_t when calling wan_get_timestamp()
					 * because it take 64 bit paramter 1. */
	wan_suseconds_t	usec;

	p_tdmapi_event = (wp_api_event_t*)wan_skb_data(skb);

	wan_get_timestamp(&sec, &usec);

	p_tdmapi_event->time_stamp_sec = (u32)sec;
	p_tdmapi_event->time_stamp_usec = usec;

	wan_skb_queue_tail(&tdm_api->wp_event_list,skb);
	tdm_api->cfg.stats.rx_events++;
	wp_wakeup_event_tdmapi(tdm_api);
	return 0;
}

static int wanpipe_tdm_api_ioctl_handle_tdm_api_cmd(wanpipe_tdm_api_dev_t *tdm_api, void *udata)
{
	wanpipe_api_cmd_t usr_tdm_api, *utdmapi;
	int err=0, cmd;
	wanpipe_codec_ops_t *wp_codec_ops;
	netskb_t *skb;
	sdla_fe_t			*fe = NULL;
	sdla_t *card=NULL;
	wanpipe_tdm_api_card_dev_t *tdm_card_dev=NULL;
	wan_smp_flag_t flags,irq_flags;

	int channel=tdm_api->tdm_chan;
	int span=0;

	flags=0;
	irq_flags=0;

	/* This is a klooge because global ctrl device does not have card pointer */
    card = (sdla_t*)tdm_api->card;  
	if (card) {
		tdm_card_dev=card->tdm_api_dev;
		fe = &card->fe;
	}

	utdmapi = (wanpipe_api_cmd_t*)udata;

#if defined(__WINDOWS__)
	/* udata is a pointer to wanpipe_api_cmd_t */
	memcpy(&usr_tdm_api, udata, sizeof(wanpipe_api_cmd_t));
#else
	if (WAN_COPY_FROM_USER(&usr_tdm_api,
			       utdmapi,
			       sizeof(wanpipe_api_cmd_t))){
	       return -EFAULT;
	}
#endif
	cmd=usr_tdm_api.cmd;

	
	if (WPTDM_SPAN_OP_MODE(tdm_api)) {
	
		if (usr_tdm_api.span) {
			span=usr_tdm_api.span;
		}

		if (usr_tdm_api.chan) {
			channel=usr_tdm_api.chan;
		}
	}

	if (channel >= MAX_TDM_API_CHANNELS) {
		DEBUG_EVENT("%s: %s(): Error: Invalid channel selected %i  (Max=%i)\n",
					tdm_api->name, __FUNCTION__, channel, MAX_TDM_API_CHANNELS);
		return -EINVAL;
	}

	usr_tdm_api.result=SANG_STATUS_SUCCESS;

	DEBUG_TDMAPI("%s: TDM API CMD: %i CMD Size=%i HDR Size=%i Even Size=%i\n",
			tdm_api->name,cmd,sizeof(wanpipe_api_cmd_t),sizeof(wp_api_hdr_t), sizeof(wp_api_event_t));

	wan_mutex_lock(&tdm_api->lock,&flags);


	/* Set the span/channel so that user knows which channel its using */
    usr_tdm_api.chan=(unsigned int)channel;

	if (card) {
		/* must NOT do it for ctrl device! */
		usr_tdm_api.span=wp_tdmapi_get_span(card);
	}

	/* Commands for HDLC Device */

	if (tdm_api->hdlc_framing) {
		switch (cmd) {
		case WP_API_CMD_OPEN_CNT:
		case WP_API_CMD_GET_USR_MTU_MRU:
		case WP_API_CMD_GET_STATS:
		case WP_API_CMD_GET_FULL_CFG:
		case WP_API_CMD_GET_FE_STATUS:
		case WP_API_CMD_GET_FE_STATS:
		case WP_API_CMD_RESET_FE_STATS:
		case WP_API_CMD_SET_FE_STATUS:
       	case WP_API_CMD_READ_EVENT:
       	case WP_API_CMD_GET_FE_ALARMS:
		case WP_API_CMD_RESET_STATS:
		case WP_API_CMD_SET_EVENT:
		case WP_API_CMD_SET_RM_RXFLASHTIME:
		case WP_API_CMD_DRIVER_VERSION:
		case WP_API_CMD_FIRMWARE_VERSION:
		case WP_API_CMD_CPLD_VERSION:
		case WP_API_CMD_FLUSH_BUFFERS:
		case WP_API_CMD_FLUSH_RX_BUFFERS:
		case WP_API_CMD_FLUSH_TX_BUFFERS:
		case WP_API_CMD_FLUSH_EVENT_BUFFERS:
		case WP_API_CMD_SET_TX_Q_SIZE:
		case WP_API_CMD_GET_TX_Q_SIZE:
		case WP_API_CMD_GET_RX_Q_SIZE:
		case WP_API_CMD_SET_RX_Q_SIZE:
		case WP_API_CMD_SS7_FORCE_RX:
		case WP_API_CMD_SS7_GET_CFG_STATUS:
			break;
		default:
			DEBUG_WARNING("%s: Warning: Invalid TDM API HDLC CMD %i\n", tdm_api->name,cmd);
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
			goto tdm_api_exit;
		}
	}

	/* Commands for CTRL Device */

	if (tdm_api == &tdmapi_ctrl) {

		wan_smp_flag_t irq_flags;

		/* Note that in case of tdmapi_ctrl the lock has already
		   been taken above so no need to lock here */
		err=-EOPNOTSUPP;

		switch (cmd) {
		case WP_API_CMD_READ_EVENT:
		
			wan_spin_lock_irq(&tdm_api->irq_lock,&irq_flags);
			skb=wan_skb_dequeue(&tdm_api->wp_event_list);
			wan_spin_unlock_irq(&tdm_api->irq_lock,&irq_flags);
			if (!skb){
				usr_tdm_api.result=SANG_STATUS_NO_DATA_AVAILABLE;
				err=-ENOBUFS;
				break;
			}

			memcpy(&usr_tdm_api.event, wan_skb_data(skb), sizeof(wp_api_event_t));

			wan_skb_init(skb,sizeof(wp_api_hdr_t));
			wan_skb_trim(skb,0);

			wan_spin_lock_irq(&tdm_api->irq_lock,&irq_flags);
			wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
			wan_spin_unlock_irq(&tdm_api->irq_lock,&irq_flags);

			err=0;
			break;

		default:
			DEBUG_WARNING("%s: Warning: Invalid TDM API cmd=%d for CTRL DEVICE\n", tdm_api->name, cmd);
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
			break;
		}
	
		goto tdm_api_exit;
	}

	/* at this point 'card' must NOT be null */
	if (!card) {
		DEBUG_ERROR("%s: Error: %s(): 'card' pointer is null!\n", tdm_api->name, __FUNCTION__);
		err=-EOPNOTSUPP;
		goto tdm_api_exit;
	}

	/* Commands for TDM API Device */

	switch (cmd) {

	case WP_API_CMD_OPEN_CNT:
		usr_tdm_api.open_cnt = __wp_tdm_get_open_cnt(tdm_api);
		break;

	case WP_API_CMD_SET_HW_MTU_MRU:
		usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
		err=-EOPNOTSUPP;
		break;

	case WP_API_CMD_GET_HW_MTU_MRU:
		usr_tdm_api.hw_mtu_mru = tdm_api->cfg.hw_mtu_mru;
		break;

	case WP_API_CMD_SET_USR_PERIOD:
		
		if (!WPTDM_CHAN_OP_MODE(tdm_api)) {
			err=-EOPNOTSUPP;
			goto tdm_api_exit;
		}

		if (usr_tdm_api.usr_period >= 10 &&
		    (usr_tdm_api.usr_period % 10) == 0 &&
		    usr_tdm_api.usr_period <= 1000) {

			usr_tdm_api.usr_mtu_mru = usr_tdm_api.usr_period*tdm_api->cfg.hw_mtu_mru*tdm_api->buffer_multiplier*tdm_api->timeslots;

		} else {
			usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
			err = -EINVAL;
			DEBUG_EVENT("%s: TDM API: Invalid HW Period %i!\n",
					tdm_api->name,usr_tdm_api.usr_period);
			goto tdm_api_exit;
		}

		usr_tdm_api.usr_mtu_mru = wanpipe_codec_calc_new_mtu(tdm_api->cfg.tdm_codec, usr_tdm_api.usr_mtu_mru); 

		tdm_api->cfg.usr_period = usr_tdm_api.usr_period;
		tdm_api->cfg.usr_mtu_mru = usr_tdm_api.usr_mtu_mru;

		tdm_api->mtu_mru = tdm_api->cfg.usr_period*tdm_api->cfg.hw_mtu_mru*tdm_api->timeslots+sizeof(wp_api_hdr_t);

		break;

	case WP_API_CMD_GET_USR_PERIOD:

		usr_tdm_api.usr_period = tdm_api->cfg.usr_period;
		break;

	case WP_API_CMD_GET_USR_MTU_MRU:

		usr_tdm_api.usr_mtu_mru = tdm_api->cfg.usr_mtu_mru;
		break;

	case WP_API_CMD_SET_CODEC:

		if (WPTDM_SPAN_OP_MODE(tdm_api)) {
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
			goto tdm_api_exit;
		}

		if (usr_tdm_api.tdm_codec == tdm_api->cfg.tdm_codec){
			err = 0;
			goto tdm_api_exit;
		}

		if (usr_tdm_api.tdm_codec >= WP_TDM_CODEC_MAX){
			usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
			err = -EINVAL;
			goto tdm_api_exit;
		}

		if (usr_tdm_api.tdm_codec == WP_NONE) {

			usr_tdm_api.usr_mtu_mru = tdm_api->cfg.hw_mtu_mru * tdm_api->cfg.usr_period * tdm_api->buffer_multiplier * tdm_api->timeslots;

		} else {
			wp_codec_ops = WANPIPE_CODEC_OPS[tdm_api->cfg.hw_tdm_coding][usr_tdm_api.tdm_codec];
			if (!wp_codec_ops || !wp_codec_ops->init){

				usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
				err = -EINVAL;
				DEBUG_EVENT("%s: TDM API: Codec %i is unsupported!\n",
						tdm_api->name,usr_tdm_api.tdm_codec);
				goto tdm_api_exit;
			}
			usr_tdm_api.usr_mtu_mru = tdm_api->cfg.hw_mtu_mru * tdm_api->cfg.usr_period * tdm_api->buffer_multiplier * tdm_api->timeslots;
			usr_tdm_api.usr_mtu_mru = wanpipe_codec_calc_new_mtu(usr_tdm_api.tdm_codec, tdm_api->cfg.usr_mtu_mru);
		}

		tdm_api->cfg.usr_mtu_mru=usr_tdm_api.usr_mtu_mru;
		tdm_api->cfg.tdm_codec = usr_tdm_api.tdm_codec;
		break;

	case WP_API_CMD_GET_CODEC:
		usr_tdm_api.tdm_codec = tdm_api->cfg.tdm_codec;
		break;

	case WP_API_CMD_SET_POWER_LEVEL:
		tdm_api->cfg.power_level = usr_tdm_api.power_level;
		break;

	case WP_API_CMD_GET_POWER_LEVEL:
		usr_tdm_api.power_level = tdm_api->cfg.power_level;
		break;

	case WP_API_CMD_TOGGLE_RX:
		if (tdm_api->cfg.tx_disable){
			wan_clear_bit(0,&tdm_api->cfg.rx_disable);
		}else{
			wan_set_bit(0,&tdm_api->cfg.rx_disable);
		}
		break;

	case WP_API_CMD_TOGGLE_TX:
		if (tdm_api->cfg.tx_disable){
			wan_clear_bit(0,&tdm_api->cfg.tx_disable);
		}else{
			wan_set_bit(0,&tdm_api->cfg.tx_disable);
		}
		break;

	case WP_API_CMD_SET_EC_TAP:

		switch (usr_tdm_api.ec_tap){
			case 0:
			case 32:
			case 64:
			case 128:
				tdm_api->cfg.ec_tap = usr_tdm_api.ec_tap;
				break;

			default:
				usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
				DEBUG_EVENT("%s: Illegal Echo Cancellation Tap \n",
						tdm_api->name);
				err = -EINVAL;
				goto tdm_api_exit;
		}
		break;

	case WP_API_CMD_GET_EC_TAP:
		usr_tdm_api.ec_tap = tdm_api->cfg.ec_tap;
		break;

	case WP_API_CMD_ENABLE_HWEC:
		if (card && card->wandev.ec_enable) {
			err=card->wandev.ec_enable(card, 1, channel);
		} else{
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err = -EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_DISABLE_HWEC:
		if (card && card->wandev.ec_enable) {
			err=card->wandev.ec_enable(card, 0, channel);
		} else {
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err = -EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_GET_HW_EC:
		if (card && card->wandev.ec_state) {
			wan_hwec_dev_state_t ecdev_state = {0};
			card->wandev.ec_state(card,&ecdev_state);
			if (ecdev_state.ec_state) {
				usr_tdm_api.hw_ec = WANOPT_YES;
			} else {
				usr_tdm_api.hw_ec = WANOPT_NO;
			}
		} else {
			usr_tdm_api.hw_ec = WANOPT_NO;
		}
		break;

	case WP_API_CMD_GET_HW_EC_PERSIST:
		if (card->hwec_conf.persist_disable) {
			usr_tdm_api.hw_ec = WANOPT_NO;
		} else {
			usr_tdm_api.hw_ec = WANOPT_YES;
		}
		break;
		
	case WP_API_CMD_GET_HW_EC_CHAN:
		if (wan_test_bit(channel, &card->wandev.fe_ec_map)) {
			usr_tdm_api.hw_ec = WANOPT_YES;
		} else {
			usr_tdm_api.hw_ec = WANOPT_NO;
		}
		break;

	case WP_API_CMD_GET_HW_DTMF:
		if (card && card->wandev.ec_state) {
			wan_hwec_dev_state_t ecdev_state = {0};
			card->wandev.ec_state(card,&ecdev_state);
			if (wan_test_bit(channel,&ecdev_state.dtmf_map)) {
				usr_tdm_api.hw_dtmf = WANOPT_YES;
			} else {
				usr_tdm_api.hw_dtmf = WANOPT_NO;
			}
		} else {
			usr_tdm_api.hw_dtmf = WANOPT_NO;
		}
		break;

	case WP_API_CMD_GET_HW_FAX_DETECT:
		if (card && card->wandev.ec_state) {
			wan_hwec_dev_state_t ecdev_state = {0};
			card->wandev.ec_state(card,&ecdev_state);
			if (wan_test_bit(channel,&ecdev_state.fax_calling_map)) {
				usr_tdm_api.hw_fax = WANOPT_YES;
			} else {
				usr_tdm_api.hw_fax = WANOPT_NO;
			}
		} else {
			usr_tdm_api.hw_fax = WANOPT_NO;
		}
		break;

	case WP_API_CMD_EC_IOCTL:

		if (!card->wandev.ec_dev || !card->wandev.ec_tdmapi_ioctl) {
			DEBUG_WARNING("%s: Warning: HWEC dev OR ec_tdmapi_ioctl() is NULL!\n",
				card->devname);
			err = -EOPNOTSUPP;
			break;
		}

		err = card->wandev.ec_tdmapi_ioctl(card->wandev.ec_dev, &usr_tdm_api.iovec_list);

		break;

	case WP_API_CMD_GET_FE_STATS:
		if (card && card->wandev.fe_iface.read_pmon){
			wan_smp_flag_t smp_flags1;
			card->hw_iface.hw_lock(card->hw,&smp_flags1);
			card->wandev.fe_iface.read_pmon(&card->fe, 0);
			card->hw_iface.hw_unlock(card->hw,&smp_flags1);
			memcpy(&usr_tdm_api.pmon_stats, &card->fe.fe_stats.te_pmon, sizeof(card->fe.fe_stats.te_pmon));
		}
		break;

	case WP_API_CMD_RESET_FE_STATS:
		if (card && card->wandev.fe_iface.flush_pmon){
			wan_smp_flag_t smp_flags1;
			card->hw_iface.hw_lock(card->hw,&smp_flags1);
			card->wandev.fe_iface.flush_pmon(&card->fe);
			card->hw_iface.hw_unlock(card->hw,&smp_flags1);
			memcpy(&usr_tdm_api.pmon_stats, &card->fe.fe_stats.te_pmon, sizeof(card->fe.fe_stats.te_pmon));
		}
		memset(&card->fe.fe_stats.te_pmon, 0x00, sizeof(card->fe.fe_stats.te_pmon));
		break;

	case WP_API_CMD_GET_STATS:
		
		if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {
			if (tdm_api->driver_ctrl) {
					err = tdm_api->driver_ctrl(tdm_api->chan,usr_tdm_api.cmd,&usr_tdm_api);

					/* Overwrite with TDM API rx/tx queue size.
					   In this case we hide the low level core buffering.
					   FIXME: Create new stats for low level core buffering */
					if (err == 0) {
						usr_tdm_api.stats.max_rx_queue_length =  (u8)tdm_api->cfg.rx_queue_sz;
						wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
						usr_tdm_api.stats.current_number_of_frames_in_rx_queue =  wan_skb_queue_len(&tdm_api->wp_rx_list);
						wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
						
					}
			} else {
				usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
				err = -EOPNOTSUPP;
			}
		} else {
			int rx_fifo=0, rx_over=0, tx_fifo=0;
			if (tdm_api->driver_ctrl) {
				err = tdm_api->driver_ctrl(tdm_api->chan,usr_tdm_api.cmd,&usr_tdm_api);
				if (err == 0) {
					rx_fifo=usr_tdm_api.stats.rx_fifo_errors;
					rx_over=usr_tdm_api.stats.rx_over_errors;
					tx_fifo=usr_tdm_api.stats.tx_fifo_errors;
				} else {
					DEBUG_ERROR("Error: Failed to exec driver_ctrl\n");
				}
			}
			memcpy(&usr_tdm_api.stats,&tdm_api->cfg.stats,sizeof(tdm_api->cfg.stats));
			usr_tdm_api.stats.max_rx_queue_length =  (u8)tdm_api->cfg.rx_queue_sz; 
			usr_tdm_api.stats.max_tx_queue_length = (u8)tdm_api->cfg.tx_queue_sz;
			usr_tdm_api.stats.rx_fifo_errors = rx_fifo;
			usr_tdm_api.stats.rx_over_errors = rx_over;
			usr_tdm_api.stats.tx_fifo_errors = tx_fifo;
			wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
			usr_tdm_api.stats.current_number_of_frames_in_tx_queue = wan_skb_queue_len(&tdm_api->wp_tx_list);
			usr_tdm_api.stats.current_number_of_frames_in_rx_queue = wan_skb_queue_len(&tdm_api->wp_rx_list);
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		}

		if (err == 0) {
			wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
			usr_tdm_api.stats.max_event_queue_length = wan_skb_queue_len(&tdm_api->wp_event_list)+wan_skb_queue_len(&tdm_api->wp_event_free_list);
			usr_tdm_api.stats.current_number_of_events_in_event_queue = wan_skb_queue_len(&tdm_api->wp_event_list);
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);

			usr_tdm_api.stats.rx_events_dropped	= tdm_api->cfg.stats.rx_events_dropped;
			usr_tdm_api.stats.rx_events 		= tdm_api->cfg.stats.rx_events;
			usr_tdm_api.stats.rx_events_tone 	= tdm_api->cfg.stats.rx_events_tone;
		}
		break;

	case WP_API_CMD_RESET_STATS:

		/* Always reset both set of stats for SPAN & CHAN mode, because
  		   chan mode uses fifo errors stats from lower layer */
		if (tdm_api->driver_ctrl) {
				err = tdm_api->driver_ctrl(tdm_api->chan,usr_tdm_api.cmd,&usr_tdm_api);
		} else {
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err = -EOPNOTSUPP;
		}
		/* Alwasy flush the tdm_api stats SPAN or CHAN mode*/
		memset(&tdm_api->cfg.stats, 0x00, sizeof(tdm_api->cfg.stats));
		break;

	case WP_API_CMD_SET_IDLE_FLAG:
		if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {
			if (tdm_api->driver_ctrl) {
				err = tdm_api->driver_ctrl(tdm_api->chan, usr_tdm_api.cmd, &usr_tdm_api);
			} else {
				usr_tdm_api.result = SANG_STATUS_OPTION_NOT_SUPPORTED;
				err = -EOPNOTSUPP;
			}
		}
		/* Set the new idle_flag, for both SPAN or CHAN mode. */
		tdm_api->cfg.idle_flag = usr_tdm_api.idle_flag;
		break;
	
	case WP_API_CMD_SS7_FORCE_RX:
		if (tdm_api->hdlc_framing && tdm_api->driver_ctrl) {
			err = tdm_api->driver_ctrl(tdm_api->chan, usr_tdm_api.cmd, &usr_tdm_api);
		} else {
			usr_tdm_api.result = SANG_STATUS_OPTION_NOT_SUPPORTED;
			err = -EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_GET_FULL_CFG:
		memcpy(&usr_tdm_api.data[0],&tdm_api->cfg,sizeof(wanpipe_api_dev_cfg_t));
		break;

	case WP_API_CMD_ENABLE_RBS_EVENTS:
		/* 'usr_tdm_api.rbs_poll' is the user provided 'number of polls per second' */
		if (usr_tdm_api.rbs_poll < 20 || usr_tdm_api.rbs_poll > 100) {
			DEBUG_ERROR("%s: Error: Invalid RBS Poll Count Min=20 Max=100\n",
					tdm_api->name);
			
			usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
			err=-EINVAL;
		} else {

			err=-EOPNOTSUPP;
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;

			if (tdm_card_dev->rbs_enable_cnt == 0) {
				if (card && card->wandev.fe_iface.set_fe_sigctrl){
						wan_smp_flag_t smp_flags1;
						card->hw_iface.hw_lock(card->hw,&smp_flags1);
						err=card->wandev.fe_iface.set_fe_sigctrl(
								&card->fe,
								WAN_TE_SIG_POLL,
								ENABLE_ALL_CHANNELS,
								WAN_ENABLE);
						card->hw_iface.hw_unlock(card->hw,&smp_flags1);
				}

				if (err == 0) {
					tdm_card_dev->rbs_enable_cnt++;
					tdm_card_dev->rbs_poll_timeout =  (HZ * usr_tdm_api.rbs_poll) / 1000;
					tdm_api->cfg.rbs_poll = usr_tdm_api.rbs_poll;
					usr_tdm_api.result=SANG_STATUS_SUCCESS;
					err=0;
					DEBUG_EVENT("%s: Poll Timeout = %u, RBS POLL = %u and RBS enable Count = %d\n", tdm_api->name, tdm_card_dev->rbs_poll_timeout, tdm_api->cfg.rbs_poll, tdm_card_dev->rbs_enable_cnt);

				}

			} else {
				tdm_card_dev->rbs_enable_cnt++;
				usr_tdm_api.result=SANG_STATUS_SUCCESS;
				err=0;
				DEBUG_EVENT("%s: Poll Timeout = %u, RBS POLL = %u and RBS enable Count = %d\n", tdm_api->name, tdm_card_dev->rbs_poll_timeout, tdm_api->cfg.rbs_poll, tdm_card_dev->rbs_enable_cnt);
			}

		}
		break;

	case WP_API_CMD_DISABLE_RBS_EVENTS:

		if (tdm_card_dev->rbs_enable_cnt == 1) {

			tdm_card_dev->rbs_poll_timeout=0;
			tdm_api->cfg.rbs_poll=0;
	
			if (card && card->wandev.fe_iface.set_fe_sigctrl){
				wan_smp_flag_t smp_flags1;
				card->hw_iface.hw_lock(card->hw,&smp_flags1);
				err=card->wandev.fe_iface.set_fe_sigctrl(
							&card->fe,
							WAN_TE_SIG_POLL,
							ENABLE_ALL_CHANNELS,
							WAN_DISABLE);
				card->hw_iface.hw_unlock(card->hw,&smp_flags1);
			}else{
				usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
				err=-EOPNOTSUPP;
			}
		}

		if (tdm_card_dev->rbs_enable_cnt > 0) {
			tdm_card_dev->rbs_enable_cnt--;
		}

		break;

	case WP_API_CMD_WRITE_RBS_BITS:

		if (tdm_api->write_rbs_bits) {
			err=tdm_api->write_rbs_bits(
							tdm_api->chan,
							usr_tdm_api.chan,
							(u8)usr_tdm_api.rbs_tx_bits);
			if (err) {
				usr_tdm_api.result=SANG_STATUS_IO_ERROR;
				DEBUG_ERROR("%s: WRITE RBS Error (%i)\n",tdm_api->name,err);
			}
		} else {
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_READ_RBS_BITS:

		if (tdm_api->read_rbs_bits) {
			err=tdm_api->read_rbs_bits(
							tdm_api->chan,
							usr_tdm_api.chan,
							(u8*)&usr_tdm_api.rbs_rx_bits);
			if (err) {
				usr_tdm_api.result=SANG_STATUS_IO_ERROR;
				DEBUG_ERROR("%s: READ RBS Error (%d)\n",tdm_api->name,err);
			}
			DEBUG_TEST("%s: READ RBS  (0x%X)\n",tdm_api->name,usr_tdm_api.rbs_rx_bits);
		} else {
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_GET_FE_STATUS:
		if (card && card->wandev.fe_iface.get_fe_status){
			wan_smp_flag_t smp_flags1;
			card->hw_iface.hw_lock(card->hw,&smp_flags1);
			card->wandev.fe_iface.get_fe_status(
					&card->fe, &usr_tdm_api.fe_status,tdm_api->tdm_chan);
			card->hw_iface.hw_unlock(card->hw,&smp_flags1);
		}else{
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
			DEBUG_WARNING("%s: Warning: WP_API_CMD_GET_FE_STATUS request is not supported!\n",
						tdm_api->name);
		}
		break;

	case WP_API_CMD_SET_FE_STATUS:
		if (card && card->wandev.fe_iface.set_fe_status){
			wan_smp_flag_t smp_flags1;
			card->hw_iface.hw_lock(card->hw,&smp_flags1);
                 	card->wandev.fe_iface.set_fe_status(
					&card->fe, usr_tdm_api.fe_status);
			card->hw_iface.hw_unlock(card->hw,&smp_flags1);
		}else{
			usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
			err=-EOPNOTSUPP;
			DEBUG_WARNING("%s: Warning: WP_API_CMD_SET_FE_STATUS request is not supported!\n",
						tdm_api->name);
		}
		break;

	case WP_API_CMD_SET_EVENT:
		err = wanpipe_tdm_api_event_ioctl(tdm_api, &usr_tdm_api);
		if (err) {
			if (err==-EBUSY) {
				usr_tdm_api.result=SANG_STATUS_DEVICE_BUSY;
			} else if (err==-EINVAL) {
				usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
			} else {
				usr_tdm_api.result=SANG_STATUS_IO_ERROR;
			}
		}
		break;

	case WP_API_CMD_READ_EVENT:
		wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
		skb=wan_skb_dequeue(&tdm_api->wp_event_list);
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		if (!skb){
			usr_tdm_api.result=SANG_STATUS_NO_DATA_AVAILABLE;
			err=-ENOBUFS;
			break;
		}

		err = 0;
		memcpy(&usr_tdm_api.event,wan_skb_data(skb),sizeof(wp_api_event_t));

#if DBG_FALSE_RING2
		{
			wp_api_event_t *event_ptr = &usr_tdm_api.event;

			switch(event_ptr->wp_api_event_type)
			{
			case WP_API_EVENT_RING_DETECT:
				DEBUG_FALSE_RING("%s():%s: Received WP_API_EVENT_RING_DETECT at channel: %d!\n",
					__FUNCTION__, tdm_api->name, event_ptr->channel);
				break;
			case WP_API_EVENT_RING_TRIP_DETECT:
				DEBUG_FALSE_RING("%s():%s: Received WP_API_EVENT_RING_TRIP_DETECT at channel: %d!\n",
					__FUNCTION__, tdm_api->name, event_ptr->channel);
				break;
			}
		}
#endif
		wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		break;

	case WP_API_CMD_GET_HW_CODING:
		usr_tdm_api.hw_tdm_coding = tdm_api->cfg.hw_tdm_coding;
		break;

	case WP_API_CMD_SET_RX_GAINS:

		if (usr_tdm_api.data_len && utdmapi->data) {
			uint8_t *rx_gains;
			
			if (usr_tdm_api.data_len != 256) {
				usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
				err=-EINVAL;
				break;
			}

			rx_gains = tdm_api->rx_gain;
					
			wan_mutex_unlock(&tdm_api->lock, &flags); 	

			if (!rx_gains) {
				rx_gains = wan_malloc(usr_tdm_api.data_len);
				if (!rx_gains) {
					usr_tdm_api.result=SANG_STATUS_FAILED_ALLOCATE_MEMORY;
					err=-ENOMEM;
					goto tdm_api_unlocked_exit;
				}
			}

			if (WAN_COPY_FROM_USER(rx_gains,
							utdmapi->data,
							usr_tdm_api.data_len)){
					usr_tdm_api.result=SANG_STATUS_FAILED_TO_LOCK_USER_MEMORY;
	       			err=-EFAULT;
				goto tdm_api_unlocked_exit;
			}

			wan_mutex_lock(&tdm_api->lock,&flags);
			
			tdm_api->rx_gain = rx_gains;

		} else {
			usr_tdm_api.result=SANG_STATUS_FAILED_TO_LOCK_USER_MEMORY;
			err=-EFAULT;
		}

		break;

	case WP_API_CMD_GET_FE_ALARMS:
		usr_tdm_api.fe_alarms = tdm_api->cfg.fe_alarms;
		break;

	case WP_API_CMD_SET_TX_GAINS:

		if (usr_tdm_api.data_len && utdmapi->data) {
			uint8_t *tx_gains;
			
			if (usr_tdm_api.data_len != 256) {
				usr_tdm_api.result=SANG_STATUS_INVALID_PARAMETER;
				err=-EINVAL;
				break;
			}

			tx_gains = tdm_api->tx_gain;
			
			wan_mutex_unlock(&tdm_api->lock, &flags);

			if (!tx_gains) {
				tx_gains = wan_malloc(usr_tdm_api.data_len);
				if (!tx_gains) {
					usr_tdm_api.result=SANG_STATUS_FAILED_ALLOCATE_MEMORY;
					err=-ENOMEM;
					goto tdm_api_unlocked_exit;
				}
			}

			if (WAN_COPY_FROM_USER(tx_gains,
							utdmapi->data,
							usr_tdm_api.data_len)){
							usr_tdm_api.result=SANG_STATUS_FAILED_TO_LOCK_USER_MEMORY;
				err=-EFAULT;
				goto tdm_api_unlocked_exit;
			}
			wan_mutex_lock(&tdm_api->lock,&flags);
			
			tdm_api->tx_gain = tx_gains;

		} else {
				usr_tdm_api.result=SANG_STATUS_FAILED_TO_LOCK_USER_MEMORY;
				err=-EFAULT;
		}

		break;



	case WP_API_CMD_CLEAR_RX_GAINS:
		if (tdm_api->rx_gain) {
			int i;
			for (i=0;i<256;i++) {
				tdm_api->rx_gain[i]=(u8)i;
			}
		}
		break;

	case WP_API_CMD_CLEAR_TX_GAINS:
		if (tdm_api->tx_gain) {
			int i;
			for (i=0;i<256;i++) {
				tdm_api->tx_gain[i]=(u8)i;
			}
		}
		break;

	case WP_API_CMD_FLUSH_BUFFERS:
		wp_tdmapi_init_buffs(tdm_api, WP_API_FLUSH_ALL);
		break;

	case WP_API_CMD_FLUSH_RX_BUFFERS:
		wp_tdmapi_init_buffs(tdm_api, WP_API_FLUSH_RX);
		break;

	case WP_API_CMD_FLUSH_TX_BUFFERS:
		wp_tdmapi_init_buffs(tdm_api, WP_API_FLUSH_TX);
		break;

	case WP_API_CMD_FLUSH_EVENT_BUFFERS:
		wp_tdmapi_init_buffs(tdm_api, WP_API_FLUSH_EVENT);
		break;

	case WP_API_CMD_SET_TX_Q_SIZE:

		if (usr_tdm_api.tx_queue_sz) {
			if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {
				if (tdm_api->driver_ctrl) {
					err = tdm_api->driver_ctrl(tdm_api->chan,usr_tdm_api.cmd,&usr_tdm_api);
				} else {
					err=-EFAULT;
				}
			} else {
				tdm_api->cfg.tx_queue_sz =usr_tdm_api.tx_queue_sz;
			}
		} else {
			err=-EINVAL;
		}

		break;

	case WP_API_CMD_GET_TX_Q_SIZE:

		if (WPTDM_SPAN_OP_MODE(tdm_api) || tdm_api->hdlc_framing) {
			if (tdm_api->driver_ctrl) {
				err = tdm_api->driver_ctrl(tdm_api->chan,usr_tdm_api.cmd,&usr_tdm_api);
			} else {
				err=-EFAULT;
			}
		} else {
			usr_tdm_api.tx_queue_sz = tdm_api->cfg.tx_queue_sz;
		}

		break;

	case WP_API_CMD_SET_RX_Q_SIZE:

		if (usr_tdm_api.rx_queue_sz) {
			tdm_api->cfg.rx_queue_sz =usr_tdm_api.rx_queue_sz;
		} else {
			err=-EINVAL;
		}
		break;

	case WP_API_CMD_GET_RX_Q_SIZE:
		usr_tdm_api.rx_queue_sz = tdm_api->cfg.rx_queue_sz;
		break;

	case WP_API_CMD_SET_RM_RXFLASHTIME:
		if (card->wandev.config_id == WANCONFIG_AFT_ANALOG) {
			if (fe->rm_param.mod[(tdm_api->tdm_chan) - 1].type == MOD_TYPE_FXS) {
				DEBUG_EVENT("%s: Module %d: rxflashtime changed (%d -> %d)\n",
					fe->name,
					tdm_api->tdm_chan,
					fe->rm_param.mod[(tdm_api->tdm_chan) - 1].u.fxs.rxflashtime,
					usr_tdm_api.rxflashtime);
				fe->rm_param.mod[(tdm_api->tdm_chan) - 1].u.fxs.rxflashtime=usr_tdm_api.rxflashtime;						
			} else {
				DEBUG_WARNING("Warning %s: Module %d: WP_API_CMD_SET_RM_RXFLASHTIME is only valid for FXS module!\n", 
					fe->name,tdm_api->tdm_chan);
				err=-EOPNOTSUPP;
			}
		} else {
				DEBUG_WARNING("Warning %s : Unsupported Protocol/Card %s WP_API_CMD_SET_RM_RXFLASHTIME is only valid for analog!\n", 
					fe->name,SDLA_DECODE_PROTOCOL(card->wandev.config_id));
				err=-EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_ENABLE_LOOP:

		if (WPTDM_CHAN_OP_MODE(tdm_api)) {
			if (wan_test_bit(channel, &card->wandev.fe_ec_map)) {				
				if (card && card->wandev.ec_enable) {
					int terr=card->wandev.ec_enable(card, 0, channel);
					if (terr == 0) {
						tdm_api->loop_reenable_hwec=1;
					}
				}
			}

			tdm_api->cfg.loop=1;

		} else {
			err=-EOPNOTSUPP;
		}
			
		
		break;

	case WP_API_CMD_DISABLE_LOOP:

		if (WPTDM_CHAN_OP_MODE(tdm_api)) {
			if (tdm_api->loop_reenable_hwec) {
				if (card && card->wandev.ec_enable) {
					card->wandev.ec_enable(card, 1, channel);
				}
			}

			tdm_api->cfg.loop=0;
		} else {
			err=-EOPNOTSUPP;
		}
		break;

	case WP_API_CMD_DRIVER_VERSION:
	case WP_API_CMD_FIRMWARE_VERSION:
	case WP_API_CMD_CPLD_VERSION:
	case WP_API_CMD_GEN_FIFO_ERR_TX:
	case WP_API_CMD_GEN_FIFO_ERR_RX:
	case WP_API_CMD_SS7_GET_CFG_STATUS:
		if (tdm_api->driver_ctrl) {
			err = tdm_api->driver_ctrl(tdm_api->chan,usr_tdm_api.cmd,&usr_tdm_api);
		} else {
			err=-EFAULT;
		}
		break;

	case WP_API_CMD_BUFFER_MULTIPLIER:

		if (WPTDM_SPAN_OP_MODE(tdm_api) && !tdm_api->hdlc_framing) {
			int mult = *(uint32_t*)&usr_tdm_api.data[0];
			if (mult > TDMAPI_MAX_BUFFER_MULTIPLIER) {
				DEBUG_ERROR("%s: Error: Inavlid buffer multiplier %i\n",tdm_api->name,mult);
				err=-EINVAL;
				goto tdm_api_exit;
			}
			if (mult < 2) {
				tdm_api->buffer_multiplier = 1;
			} else {
				tdm_api->buffer_multiplier = mult;
			}
			tdm_api->cfg.usr_mtu_mru = tdm_api->cfg.usr_period*tdm_api->cfg.hw_mtu_mru*tdm_api->buffer_multiplier *tdm_api->timeslots;
			DEBUG_TDMAPI("%s: Setting Multiplier %i, New MTU %i (p=%i,hwmtu=%i)\n",
				tdm_api->name,tdm_api->buffer_multiplier,tdm_api->cfg.usr_mtu_mru,tdm_api->cfg.usr_period,tdm_api->cfg.hw_mtu_mru);

		} else {
			DEBUG_WARNING("Warning: %s: WP_API_CMD_BUFFER_MULTIPLIER not supported for device mode\n", tdm_api->name);
			err=-EOPNOTSUPP;
		}

		break;

	default:
		DEBUG_EVENT("%s: Invalid TDM API CMD %i\n", tdm_api->name,cmd);
		usr_tdm_api.result=SANG_STATUS_OPTION_NOT_SUPPORTED;
		err=-EOPNOTSUPP;
		break;
	}

tdm_api_exit:
	wan_mutex_unlock(&tdm_api->lock, &flags);

tdm_api_unlocked_exit:

	usr_tdm_api.result = err;


#if defined(__WINDOWS__)
	/* udata is a pointer to wanpipe_api_cmd_t */
	memcpy(udata, &usr_tdm_api, sizeof(wanpipe_api_cmd_t));
#else
	if (WAN_COPY_TO_USER(udata,
			     &usr_tdm_api,
			     sizeof(wanpipe_api_cmd_t))){
		usr_tdm_api.result=SANG_STATUS_FAILED_TO_LOCK_USER_MEMORY;
	    return -EFAULT;
	}
#endif

	return err;

}

static int wanpipe_tdm_api_ioctl(void *obj, int cmd, void *udata)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)obj;
	int err=0;

	if (!tdm_api->chan && tdm_api != &tdmapi_ctrl){
		DEBUG_ERROR("%s:%d Error: TDM API Not initialized! chan=NULL!\n",
			__FUNCTION__,__LINE__);
		return -EFAULT;
	}

	if (!udata){
		return -EINVAL;
	}

	switch (cmd) {

#if defined (__LINUX__)
	case SIOC_WANPIPE_TDM_API:
#endif
	case WANPIPE_IOCTL_API_CMD:
		err=wanpipe_tdm_api_ioctl_handle_tdm_api_cmd(tdm_api,udata);
		break;

	case WANPIPE_IOCTL_PIPEMON:
		err=-EINVAL;
		if (tdm_api->pipemon) {
			wan_smp_flag_t flag;
			wan_mutex_lock(&tdm_api->lock,&flag);
			err=tdm_api->pipemon(tdm_api->card,tdm_api->chan,udata);
			wan_mutex_unlock(&tdm_api->lock,&flag);
		}
		break;

	default:
		err=-EINVAL;
		break;
	}	

	return err;
}

static int
wanpipe_tdm_api_event_ioctl(wanpipe_tdm_api_dev_t *tdm_api, wanpipe_api_cmd_t *tdm_cmd)
{
	wp_api_event_t	*tdm_event;
	wan_event_ctrl_t	event_ctrl;
	sdla_t *card=tdm_api->card;
	int channel = tdm_api->tdm_chan;

	if (WPTDM_SPAN_OP_MODE(tdm_api)) {
		if (tdm_cmd->chan) {
			channel = tdm_cmd->chan;
		}
	}

	if (channel >= MAX_TDM_API_CHANNELS) {
		DEBUG_ERROR("%s: %s(): Error: Invalid channel selected %i  (Max=%i)\n",
					tdm_api->name, __FUNCTION__, channel, MAX_TDM_API_CHANNELS);
		return -EINVAL;
	}

	if (tdm_api->event_ctrl == NULL){
		DEBUG_ERROR("%s: Error: Event control interface doesn't initialized!\n",
				tdm_api->name);
		return -EINVAL;
	}

	tdm_event = &tdm_cmd->event;
	memset(&event_ctrl, 0, sizeof(wan_event_ctrl_t));

	switch(tdm_event->wp_api_event_type){


	case WP_API_EVENT_DTMF:

		event_ctrl.type = WAN_EVENT_EC_DTMF;

		if (card && card->wandev.ec_state) {
			wan_hwec_dev_state_t ecdev_state = {0};
			card->wandev.ec_state(card,&ecdev_state);
			if (ecdev_state.ec_state == 0) {
				return -EINVAL;
			}
		} else {
			return -EINVAL;
		}

		DEBUG_TEST("%s: %s HW EC DTMF event %X %p !\n",
					tdm_api->name,
					WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode),
					tdm_api->active_ch,card->wandev.ec_enable);

		// Octasic DTMF event
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
				event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
				event_ctrl.mode = WAN_EVENT_DISABLE;
		}

		event_ctrl.channel      = channel;
		break;

	case WP_API_EVENT_FAX_DETECT:

		event_ctrl.type = WAN_EVENT_EC_FAX_DETECT;

		if (card && card->wandev.ec_state) {
			wan_hwec_dev_state_t ecdev_state = {0};
			card->wandev.ec_state(card,&ecdev_state);
			if (ecdev_state.ec_state == 0) {
				return -EINVAL;
			}
		} else {
			return -EINVAL;
		}

		DEBUG_TEST("%s: %s HW EC FAX Detect event %X %p !\n",
					tdm_api->name,
					WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode),
					tdm_api->active_ch,card->wandev.ec_enable);

		// Octasic DTMF event
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
				event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
				event_ctrl.mode = WAN_EVENT_DISABLE;
		}

		event_ctrl.channel      = channel;

		break;

	case WP_API_EVENT_RM_DTMF:
		// A200-Remora DTMF event
		DEBUG_TDMAPI("%s: %s A200-Remora DTMF event!\n",
			tdm_api->name,
			WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode));
		event_ctrl.type		= WAN_EVENT_RM_DTMF;
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_RXHOOK:
		DEBUG_TDMAPI("%s: %s A200-Remora Loop Closure event!\n",
			tdm_api->name,
			WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode));
		event_ctrl.type		= WAN_EVENT_RM_LC;
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_RING:
		DEBUG_TDMAPI("%s: %s Ring Event on module %d!\n",
			tdm_api->name,
			WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode),
						channel);
		event_ctrl.type   	= WAN_EVENT_RM_RING;
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_RING_DETECT:
		DEBUG_TDMAPI("%s: %s Ring Detection Event on module %d!\n",
			tdm_api->name,
			WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode),
					channel);
		event_ctrl.type   	= WAN_EVENT_RM_RING_DETECT;
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_RING_TRIP_DETECT:
		DEBUG_TDMAPI("%s: %s Ring Trip Detection Event on module %d!\n",
			tdm_api->name,
			WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode),
						channel);
		event_ctrl.type   	= WAN_EVENT_RM_RING_TRIP;
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_TONE:

		DEBUG_TDMAPI("%s: %s Tone Event (%d)on module %d!\n",
			tdm_api->name,
			WP_API_EVENT_MODE_DECODE(tdm_event->wp_api_event_mode),
			tdm_event->wp_api_event_tone_type,
						channel);
		event_ctrl.type   	= WAN_EVENT_RM_TONE;
		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
			switch(tdm_event->wp_api_event_tone_type){
			case WP_API_EVENT_TONE_DIAL:
				event_ctrl.tone	= WAN_EVENT_RM_TONE_TYPE_DIAL;
				break;
			case WP_API_EVENT_TONE_BUSY:
				event_ctrl.tone	= WAN_EVENT_RM_TONE_TYPE_BUSY;
				break;
			case WP_API_EVENT_TONE_RING:
				event_ctrl.tone	= WAN_EVENT_RM_TONE_TYPE_RING;
				break;
			case WP_API_EVENT_TONE_CONGESTION:
				event_ctrl.tone	= WAN_EVENT_RM_TONE_TYPE_CONGESTION;
				break;
			default:
				DEBUG_EVENT("%s: Unsupported TDM API Tone Type  %d!\n",
						tdm_api->name,
						tdm_event->wp_api_event_tone_type);
				return -EINVAL;
			}
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_TXSIG_KEWL:
		DEBUG_TDMAPI("%s: TX Signalling KEWL on module %d!\n",
				tdm_api->name, channel);
		event_ctrl.type   	= WAN_EVENT_RM_TXSIG_KEWL;
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_TXSIG_START:
		DEBUG_TDMAPI("%s: TX Signalling START for module %d!\n",
				tdm_api->name, channel);
		event_ctrl.type		= WAN_EVENT_RM_TXSIG_START;
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_TXSIG_OFFHOOK:
		DEBUG_TDMAPI("%s: TX Signalling OFFHOOK for module %d!\n",
				tdm_api->name, channel);
		event_ctrl.type		= WAN_EVENT_RM_TXSIG_OFFHOOK;
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_TXSIG_ONHOOK:
		DEBUG_TDMAPI("%s: TX Signalling ONHOOK for module %d!\n",
				tdm_api->name, channel);
		event_ctrl.type		= WAN_EVENT_RM_TXSIG_ONHOOK;
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_ONHOOKTRANSFER:
		DEBUG_TDMAPI("%s: RM ONHOOKTRANSFER for module %d!\n",
				tdm_api->name, channel);
		event_ctrl.type		= WAN_EVENT_RM_ONHOOKTRANSFER;
		event_ctrl.mod_no	= channel;
		event_ctrl.ohttimer	= tdm_event->wp_api_event_ohttimer;
		break;

	case WP_API_EVENT_SETPOLARITY:
		DEBUG_TDMAPI("%s: RM SETPOLARITY(%s) for module %d!\n",
				tdm_api->name, tdm_event->wp_api_event_polarity == 0 ? "Forward" : "Reverse" ,channel);
		event_ctrl.type		= WAN_EVENT_RM_SETPOLARITY;
		event_ctrl.mod_no	= channel;
		event_ctrl.polarity	= tdm_event->wp_api_event_polarity;
		break;

	case WP_API_EVENT_BRI_CHAN_LOOPBACK:
		event_ctrl.type		= WAN_EVENT_BRI_CHAN_LOOPBACK;
		event_ctrl.channel	= tdm_event->channel;

		if (tdm_event->wp_api_event_mode == WP_API_EVENT_ENABLE){
			event_ctrl.mode = WAN_EVENT_ENABLE;
		}else{
			event_ctrl.mode = WAN_EVENT_DISABLE;
		}

		DEBUG_TDMAPI("%s: BRI_BCHAN_LOOPBACK: %s for channel %d!\n",
			tdm_api->name,
			(event_ctrl.mode == WAN_EVENT_ENABLE ? "Enable" : "Disable"),
			event_ctrl.channel);
		break;

	case WP_API_EVENT_SET_RM_TX_GAIN:
		DEBUG_TDMAPI("%s: Setting TX gain %d on  %d!\n",
				tdm_api->name, tdm_event->wp_api_event_gain_value ,channel);
		event_ctrl.type		= WAN_EVENT_RM_SET_TX_GAIN;
		event_ctrl.rm_gain	= (int)tdm_event->wp_api_event_gain_value;
		event_ctrl.mod_no	= channel;
		break;

	case WP_API_EVENT_SET_RM_RX_GAIN:
		DEBUG_TDMAPI("%s: Setting RX gains %d on  %d!\n",
				tdm_api->name, tdm_event->wp_api_event_gain_value,channel);
		event_ctrl.type		= WAN_EVENT_RM_SET_RX_GAIN;
		event_ctrl.rm_gain	= tdm_event->wp_api_event_gain_value;
		event_ctrl.mod_no	= channel;
		break;
		
	default:
		DEBUG_EVENT("%s: Unknown TDM API Event Type %02X!\n",
				tdm_api->name,
				tdm_event->wp_api_event_type);
		return -EINVAL;
	}

	return tdm_api->event_ctrl(tdm_api->chan, &event_ctrl);
}

static int wanpipe_tdm_api_channelized_tx (wanpipe_tdm_api_dev_t *tdm_api, u8 *tx_data, int len)
{
	u8 *buf;
	wp_api_hdr_t *tx_hdr;

	if (wan_test_bit(0,&tdm_api->cfg.tx_disable)){
		return 0;
	}

	if (!tdm_api->tx_skb) {
		tdm_api->tx_skb=wan_skb_dequeue(&tdm_api->wp_tx_list);
		if (!tdm_api->tx_skb){
			memset(tx_data,tdm_api->cfg.idle_flag,len);
			tdm_api->cfg.stats.tx_carrier_errors++;
			tdm_api->cfg.stats.tx_idle_packets++;
			return -ENOBUFS;
		}

		wp_wakeup_tx_tdmapi(tdm_api);
		buf=wan_skb_pull(tdm_api->tx_skb,sizeof(wp_api_hdr_t));
		memcpy(&tdm_api->tx_hdr, buf, sizeof(wp_api_hdr_t));
		tdm_api->cfg.stats.tx_bytes+=wan_skb_len(tdm_api->tx_skb);

		if (wan_skb_len(tdm_api->tx_skb) % len) {
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
			goto wanpipe_tdm_api_tx_error;
		}
	}

	tx_hdr=&tdm_api->tx_hdr;
	
	if (wan_skb_len(tdm_api->tx_skb) < len) {
#if 0
			if (WAN_NET_RATELIMIT()) {
				DEBUG_EVENT("%s: TX LEN %i Less then Expecting %i\n",
						tdm_api->name, wan_skb_len(tdm_api->tx_skb) , len);
			}
#endif
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,tx_errors);
		goto wanpipe_tdm_api_tx_error;
	}

	buf=(u8*)wan_skb_data(tdm_api->tx_skb);
	memcpy(tx_data,buf,len);
	wan_skb_pull(tdm_api->tx_skb,len);

	if (wan_skb_len(tdm_api->tx_skb) <= 0) {
		tdm_api->cfg.stats.tx_packets++;
		wan_skb_init(tdm_api->tx_skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(tdm_api->tx_skb,0);
		wan_skb_queue_tail(&tdm_api->wp_tx_free_list,tdm_api->tx_skb);
		tdm_api->tx_skb=NULL;
	}

	return 0;

wanpipe_tdm_api_tx_error:
			
	memset(tx_data,tdm_api->cfg.idle_flag,len);
	tdm_api->cfg.stats.tx_carrier_errors++;
	tdm_api->cfg.stats.tx_idle_packets++;

	if (tdm_api->tx_skb) {
		wan_skb_init(tdm_api->tx_skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(tdm_api->tx_skb,0);
		wan_skb_queue_tail(&tdm_api->wp_tx_free_list,tdm_api->tx_skb);
		tdm_api->tx_skb=NULL;
	}

	tdm_api->tx_skb=NULL;
	return -1;

}

static void wanpipe_tdm_timestamp_hdr(wp_api_hdr_t *hdr)
{
	wan_time_t sec;
	wan_suseconds_t usec;
	
	wan_get_timestamp(&sec, &usec);

	hdr->wp_api_hdr_time_stamp_sec = (u32)sec;
	hdr->wp_api_hdr_time_stamp_use = usec;
	return;
}

static int wanpipe_tdm_api_channelized_rx (wanpipe_tdm_api_dev_t *tdm_api, u8 *rx_data, u8 *tx_data, int len)
{
	u8 *data_ptr;
	wp_api_hdr_t *rx_hdr;
	int err=-EINVAL;
	int skblen;
	int hdrsize = sizeof(wp_api_hdr_t);
	sdla_t  *card = (sdla_t*)tdm_api->card;

	if (wan_test_bit(0,&tdm_api->cfg.rx_disable)) {
		err=0;
		goto wanpipe_tdm_api_rx_error;
	}

	if (card && (WANCONFIG_AFT_ANALOG == card->wandev.config_id)) {
		/*Only for Analog card - check and report fake polarity event(If applicable)*/
		wp_tdmapi_check_fakepolarity(rx_data,len,tdm_api);
	}

	if (wan_skb_queue_len(&tdm_api->wp_rx_list) >= (int)tdm_api->cfg.rx_queue_sz) {
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_dropped);
		wp_wakeup_rx_tdmapi(tdm_api);
		err=-EBUSY;
		goto wanpipe_tdm_api_rx_error;
	}

	if (!tdm_api->rx_skb) {
		tdm_api->rx_skb=wan_skb_dequeue(&tdm_api->wp_rx_free_list);
		if (!tdm_api->rx_skb) {
			err=-ENOMEM;
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_errors);
			goto wanpipe_tdm_api_rx_error;
		}

#if 0
		if (wan_skb_headroom(tdm_api->rx_skb) != sizeof(wp_api_hdr_t)) {
#if 0
			if (WAN_NET_RATELIMIT()) {
				DEBUG_EVENT("%s: RX HEAD ROOM =%i Expecting %i\n",
						tdm_api->name, wan_skb_headroom(tdm_api->rx_skb), sizeof(wp_api_hdr_t));
			}
#endif
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_errors);
			goto wanpipe_tdm_api_rx_error;
		}
#endif

		data_ptr=wan_skb_put(tdm_api->rx_skb,hdrsize);
		memset(data_ptr,0,hdrsize);
	}

#if 0
	if (len > wan_skb_tailroom(tdm_api->rx_skb)) {
		err=-EINVAL;
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_errors);
#if 0
		if (WAN_NET_RATELIMIT()) {
			DEBUG_EVENT("%s: RX TAIL ROOM =%i Less than Len %i\n",
						tdm_api->name, wan_skb_tailroom(tdm_api->rx_skb), len);
		}
#endif
		goto wanpipe_tdm_api_rx_error;
	}
#endif

	rx_hdr=(wp_api_hdr_t*)wan_skb_data(tdm_api->rx_skb);

	data_ptr=wan_skb_put(tdm_api->rx_skb,len);
	memcpy((u8*)data_ptr,rx_data,len);

	skblen = wan_skb_len(tdm_api->rx_skb);
	if (skblen >= (int)tdm_api->mtu_mru) {

		wanpipe_tdm_timestamp_hdr(rx_hdr);

		tdm_api->cfg.stats.rx_bytes+=skblen-hdrsize;
		wan_skb_queue_tail(&tdm_api->wp_rx_list,tdm_api->rx_skb);
		tdm_api->rx_skb=NULL;
		wp_wakeup_rx_tdmapi(tdm_api);
		tdm_api->cfg.stats.rx_packets++;
		
	}

	return 0;

wanpipe_tdm_api_rx_error:

	if (tdm_api->rx_skb) {
		netskb_t *skb = tdm_api->rx_skb;
		tdm_api->rx_skb = NULL;

		wan_skb_init(skb,hdrsize);
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_rx_free_list,skb);
	}

	tdm_api->rx_skb=NULL;

	return err;
}

static __inline int wp_tdmapi_check_fakepolarity(u8 *buf, int len, wanpipe_tdm_api_dev_t* tdm_api)
{
	sdla_fe_t        *fe    	= NULL;
	wp_remora_fxo_t  *fxo   	= NULL;
	int              channo 	= 0;
	sdla_t          *card   	= NULL;
	int             upper_thres_val = 0;
	int             lower_thres_val = 0;
	int 		i		= 0;
	int 		linear_sample	= 0;
	int             fake_polarity_thres = 0;

	if(!buf || !tdm_api || !len || !tdm_api->card) {
		return 0 ;
	}

	card 	= (sdla_t*)tdm_api->card;
	fe 	= &card->fe;
	channo 	= tdm_api->tdm_chan;
	fxo 	= &fe->rm_param.mod[channo].u.fxo;

	if (WANOPT_NO == card->fe.fe_cfg.cfg.remora.fake_polarity) {
		/*RM_FAKE_POLARITY disabled */
		return 0;
	}

	/* only look for sound on the line if its an fxo card and state is onhook*/
	if (MOD_TYPE_FXO != fe->rm_param.mod[channo].type) {
			return 0;
	}

	if (fxo->offhook) {
		fxo->cidtimer = fe->rm_param.intcount;
		return 0;
	}

	/* If no possible CID has been reported yet, scan the signal looking for samples bigger than the user-specified threshold */
	if (!fxo->readcid && !fxo->wasringing && fe->rm_param.intcount > fxo->cidtimer + 400) {

		fake_polarity_thres = card->fe.fe_cfg.cfg.remora.fake_polarity_thres;

		if(0 == fake_polarity_thres) {
			/*threshold value not configured...but RM_FAKE_POLARITY enable , 
			 * hence considering default threshold value*/
			fake_polarity_thres = WP_TDM_DEF_FAKE_POLARITY_THRESHOLD;
		}
		upper_thres_val = fake_polarity_thres;
		lower_thres_val = -1 * fake_polarity_thres;

		for (i=0;i<len;i++) {
			linear_sample = wanpipe_codec_convert_to_linear(buf[i],(u8)tdm_api->cfg.hw_tdm_coding);

			if (linear_sample > upper_thres_val || linear_sample < lower_thres_val) {
				if (WAN_NET_RATELIMIT()) {
					DEBUG_EVENT("%s: Possible CID signal detected, faking polarity reverse event on module %d\n", card->devname, channo);
				}
				wp_tdmapi_report_fakepolarityreverse(card,(u8)channo);
				fxo->readcid = 1;
				fxo->cidtimer = fe->rm_param.intcount;
				return 1;
			}
		}
	} else if (fxo->readcid && fe->rm_param.intcount > fxo->cidtimer + 2000) {
		fxo->cidtimer = fe->rm_param.intcount;
		fxo->readcid = 0;
	}

	return 0;
}

static void wp_tdmapi_report_fakepolarityreverse(void* card_id,u_int8_t channel)
{
	wan_event_t event;

	memset(&event,0,sizeof(event));
	event.channel 		= channel;
	event.type      	= WAN_EVENT_RM_POLARITY_REVERSE;
	event.polarity_reverse 	= WAN_EVENT_POLARITY_REV_POSITIVE_NEGATIVE;

	wp_tdmapi_polarityreverse(card_id, &event);
	return;
}

#undef AFT_TDM_ROTATE_DEBUG
#ifdef AFT_TDM_ROTATE_DEBUG
#warning "AFT_TDM_ROTATE_DEBUG Enabled"
#include "aft_core.h"
static int rotate_debug[32];
#endif
int wanpipe_tdm_api_span_rx_tx(sdla_t *card, wanpipe_tdm_api_span_t *tdm_span, u32 buf_sz, unsigned long mask, int circ_buf_len)
{
	int i,l_ch,tidx;
	wanpipe_tdm_api_dev_t *tdm_api;
	unsigned int rx_offset, tx_offset;
	void *ptr;

	if (!tdm_span || !card) {
		return -ENODEV;
	}

#ifdef AFT_TDM_ROTATE_DEBUG
	rotate_debug[card->tdmv_conf.span_no]++;
#endif

	for (i=0;i<32;i++) {
		if (wan_test_bit(i,&tdm_span->timeslot_map)) {
			tdm_api = &tdm_span->chans[i];

			if (IS_E1_CARD(card)) {
				tidx=i;
			} else {
				tidx=i-1;
			}
			
			l_ch=i-1;

#ifdef AFT_TDM_ROTATE_DEBUG
			if (1) { 
				u32 dma_descr, lo_reg, lo_reg_tx, lch;

				lch=i-1;

				dma_descr=(lch<<4) +
							AFT_PORT_REG(card,AFT_RX_DMA_LO_DESCR_BASE_REG);
				card->hw_iface.bus_read_4(card->hw,dma_descr,&lo_reg);
			
				dma_descr=(lch<<4) +
							AFT_PORT_REG(card,AFT_TX_DMA_LO_DESCR_BASE_REG);
				card->hw_iface.bus_read_4(card->hw,dma_descr,&lo_reg_tx);
			
				lo_reg=(lo_reg&AFT_TDMV_BUF_MASK)/AFT_TDMV_CIRC_BUF;
				lo_reg_tx=(lo_reg_tx&AFT_TDMV_BUF_MASK)/AFT_TDMV_CIRC_BUF;

				if (rotate_debug[card->tdmv_conf.span_no] < 5 ||
					abs(card->u.aft.tdm_rx_dma_toggle[tidx] - lo_reg) != 2 ||
					abs(card->u.aft.tdm_tx_dma_toggle[tidx] - lo_reg_tx) != 2) {
					DEBUG_EVENT("%s: Rotating buffer lcn=%i tdx=%i  rxdma=%i (hw=%i), txdma=%i (hw=%i)\n",
						card->devname, lch, tidx, card->u.aft.tdm_rx_dma_toggle[tidx], lo_reg,card->u.aft.tdm_tx_dma_toggle[tidx],lo_reg_tx);
				}
			}
#endif
			if (buf_sz) {
				rx_offset = buf_sz * card->u.aft.tdm_rx_dma_toggle[tidx];
				tx_offset = buf_sz * card->u.aft.tdm_tx_dma_toggle[tidx];
				card->u.aft.tdm_rx_dma_toggle[tidx]++;
				if (card->u.aft.tdm_rx_dma_toggle[tidx] >= circ_buf_len) {
					card->u.aft.tdm_rx_dma_toggle[tidx]=0;
				}
				card->u.aft.tdm_tx_dma_toggle[tidx]++;
				if (card->u.aft.tdm_tx_dma_toggle[tidx] >= circ_buf_len) {
					card->u.aft.tdm_tx_dma_toggle[tidx]=0;
				}

				ptr=(void*)((((ulong_ptr_t)tdm_api->rx_buf) & ~((ulong_ptr_t)mask)) + rx_offset);
				tdm_api->rx_buf = ptr;

				ptr=(void*)((((ulong_ptr_t)tdm_api->tx_buf) & ~((ulong_ptr_t)mask)) + tx_offset);
				tdm_api->tx_buf = ptr;
			}

			if (!tdm_api || !tdm_api->chan || !wan_test_bit(0,&tdm_api->init)){
				continue;
			}


#if 0
			if (tdm_api->tdm_span == 5 && tdm_api->tdm_chan == 15) {
				static unsigned char sync=0;
				int x;
				for (x=0;x<tdm_api->rx_buf_len;x++) {
					sync++;
					if (sync != tdm_api->rx_buf[x]) {
						DEBUG_ERROR("Error: RX Span 5 chan 15 expecting %02X got %02X diff=%i rlen=%i tlen=%i rxoff=%i tx_off=%i\n",
								sync,tdm_api->rx_buf[x],abs(sync-tdm_api->rx_buf[x]), tdm_api->rx_buf_len, tdm_api->tx_buf_len,
								card->u.aft.tdm_rx_dma_toggle[i],card->u.aft.tdm_tx_dma_toggle[i]);
						sync=tdm_api->rx_buf[x];
					}
				}
			}			
#endif

#if defined(__LINUX__)

			if (card->u.aft.tdm_rx_dma[l_ch]) {			
				wan_dma_descr_t *dma_descr = card->u.aft.tdm_rx_dma[l_ch]; 
				card->hw_iface.busdma_sync(card->hw, dma_descr, 1, 1, PCI_DMA_FROMDEVICE);
			}

			if (card->u.aft.tdm_tx_dma[l_ch]) {
				wan_dma_descr_t *dma_descr = card->u.aft.tdm_tx_dma[l_ch]; 
				card->hw_iface.busdma_sync(card->hw, dma_descr, 1, 1, PCI_DMA_TODEVICE);
			}
 
			prefetch(tdm_api->rx_buf);
			prefetch(tdm_api->tx_buf);
#endif
  
			if (!wan_test_bit(0,&tdm_api->used)) {
				/* Always use 'cfg.idle_flag' because it may be it was set by WP_API_CMD_SET_IDLE_FLAG */
				memset(tdm_api->tx_buf,tdm_api->cfg.idle_flag,tdm_api->tx_buf_len);
				continue;
			}

#if 0
			/* NC: Enable rtp tap for api */
			if (card->wandev.rtp_len && card->wandev.rtp_tap) {
				card->wandev.rtp_tap(card,tidx,
						     tdm_api->rx_buf,
						     tdm_api->tx_buf,
						     tdm_api->rx_buf_len);
			}
#endif

			wanpipe_tdm_api_channelized_rx (tdm_api, tdm_api->rx_buf, tdm_api->tx_buf, tdm_api->rx_buf_len);
			wanpipe_tdm_api_channelized_tx (tdm_api, tdm_api->tx_buf, tdm_api->tx_buf_len);


			if (tdm_api->cfg.loop) {
				memcpy(tdm_api->tx_buf, tdm_api->rx_buf, tdm_api->rx_buf_len);
			}

#if 0
			if (tdm_api->tdm_span == 5 && tdm_api->tdm_chan == 15) {
				static unsigned char tsync=0;
				int x;
				for (x=0;x<tdm_api->tx_buf_len;x++) {
					tsync++;
					if (tsync != tdm_api->tx_buf[x]) {
						DEBUG_ERROR("Error: TX Span 5 chan 15 expecting %02X got %02X diff=%i tx_offset\n",
								tsync,tdm_api->tx_buf[x],abs(tsync-tdm_api->tx_buf[x]));
						tsync=tdm_api->tx_buf[x];
					}
				}
			}
#endif
		}
	}
	
	return 0;
}


int wanpipe_tdm_api_rx_tx (wanpipe_tdm_api_dev_t *tdm_api, u8 *rx_data, u8 *tx_data, int len)
{

	if (!tdm_api || !rx_data || !tx_data || len == 0) {
		if (WAN_NET_RATELIMIT()) {
			DEBUG_ERROR("%s:%d Internal Error: tdm_api=%p rx_data=%p tx_data=%p len=%i\n",
					__FUNCTION__,__LINE__,tdm_api,rx_data,tx_data,len);
		}
		return 0;
	}

	if (!tdm_api->chan || !wan_test_bit(0,&tdm_api->init)){
		return 0;
	}

	tdm_api->rx_buf=rx_data;
	tdm_api->rx_buf_len=len;
	tdm_api->tx_buf=tx_data;
	tdm_api->tx_buf_len=len;

	if (!wan_test_bit(0,&tdm_api->used)) {
		/* Always use 'cfg.idle_flag' because it may be it was set by WP_API_CMD_SET_IDLE_FLAG */
		memset(tdm_api->tx_buf,tdm_api->cfg.idle_flag,len);
		return 0;
	}

	wanpipe_tdm_api_channelized_rx (tdm_api, tdm_api->rx_buf, tdm_api->tx_buf, tdm_api->rx_buf_len);
	wanpipe_tdm_api_channelized_tx (tdm_api, tdm_api->tx_buf, tdm_api->tx_buf_len);

	if (tdm_api->cfg.loop) {
		memcpy(tdm_api->tx_buf, tdm_api->rx_buf, tdm_api->rx_buf_len);
	}

	return 0;
}

int wanpipe_tdm_api_span_rx (wanpipe_tdm_api_dev_t *tdm_api, netskb_t *skb)
{
	sdla_t *card;
	wan_smp_flag_t flag;
	wp_api_hdr_t *rx_hdr;

	flag=0;
	
	if (!tdm_api->chan || !wan_test_bit(0,&tdm_api->init) || !tdm_api->card){
		return -ENODEV;
	}

	if (!wan_test_bit(0,&tdm_api->used)) {
		return -ENODEV;
	}

	card=tdm_api->card;

	if (wan_skb_len(skb) <= sizeof(wp_api_hdr_t)) {
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_errors);
		if (WAN_NET_RATELIMIT()) {
			DEBUG_ERROR("%s: Internal Error RX Data has no rx header!\n",
					__FUNCTION__);	
		}
		return -EINVAL;
	}

	wptdm_os_lock_irq(&card->wandev.lock,&flag);
	if (wan_skb_queue_len(&tdm_api->wp_rx_list) >= (int)tdm_api->cfg.rx_queue_sz) {
		wptdm_os_unlock_irq(&card->wandev.lock,&flag);
		wp_wakeup_rx_tdmapi(tdm_api);
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_dropped);
		DEBUG_TEST("%s: Error RX Buffer Overrun!\n",__FUNCTION__);
		return -EBUSY;
	}

	rx_hdr=(wp_api_hdr_t*)wan_skb_data(skb);

	wanpipe_tdm_timestamp_hdr(rx_hdr);

	wan_skb_queue_tail(&tdm_api->wp_rx_list,skb);
	tdm_api->cfg.stats.rx_packets++;
	wptdm_os_unlock_irq(&card->wandev.lock,&flag);


	/* Buffer multiplier is by default 1 so we can use it to check if there
	   is something in the rx_list */
	if (wan_skb_queue_len(&tdm_api->wp_rx_list) >= (int)tdm_api->buffer_multiplier) {
		wp_wakeup_rx_tdmapi(tdm_api);
	}

	return 0;
}


static wanpipe_tdm_api_dev_t *wp_tdmapi_search(sdla_t *card, int fe_chan)
{
	wanpipe_tdm_api_dev_t	*tdm_api;

	if(fe_chan < 0 || fe_chan >= MAX_TDM_API_CHANNELS){
		DEBUG_ERROR("%s(): TDM API Error: Invalid Channel Number=%i!\n",
			__FUNCTION__, fe_chan);
		return NULL;
	}

	tdm_api = card->wp_tdmapi_hash[fe_chan];
	if(tdm_api == NULL){
		DEBUG_TEST("%s(): TDM API Warning: No TDM API registered for Channel Number=%i!\n",
			__FUNCTION__, fe_chan);
		return NULL;
	}


	if (!wan_test_bit(0,&tdm_api->init)) {
		return NULL;
	}

	return tdm_api;
}

static void wp_tdmapi_report_rbsbits(void* card_id, int channel, unsigned char rbsbits)
{
	netskb_t                *skb;
	wanpipe_tdm_api_dev_t   *tdm_api = NULL;
	sdla_t                  *card = (sdla_t*)card_id;
	wp_api_event_t  *p_tdmapi_event = NULL;
	wan_smp_flag_t irq_flags;

	irq_flags=0;

	tdm_api = wp_tdmapi_search(card, channel);
	if (tdm_api == NULL){
		return;
	}

	if (tdm_api->rbs_rx_bits[channel] == rbsbits) {
		return;
	}

	DEBUG_TEST("%s: Received RBS Bits Event at TDM_API channel=%d rbs=0x%02X!\n",
					card->devname,
					channel,rbsbits);

	tdm_api->rbs_rx_bits[channel] = rbsbits;

	wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);

	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
			return;
	}

	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));



	p_tdmapi_event->wp_api_event_type = WP_API_EVENT_RBS;
	p_tdmapi_event->wp_api_event_channel = (u8)channel;
	p_tdmapi_event->wp_api_event_span = wp_tdmapi_get_span(card);

	p_tdmapi_event->wp_api_event_rbs_bits	= (u8)rbsbits;

	wan_skb_push_to_ctrl_event(skb);
	
	if (!wan_test_bit(0,&tdm_api->used)) {
			wan_skb_init(skb,sizeof(wp_api_hdr_t));
			wan_skb_trim(skb,0);
			wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
			wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
			return;
	}

	wanpipe_tdm_api_handle_event(tdm_api,skb);

	wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);


	return;

}


static void wp_tdmapi_alarms(void* card_id, wan_event_t *event)
{
	netskb_t		*skb;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;
	wan_smp_flag_t irq_flags;

	irq_flags=0;


	DEBUG_TEST("%s: Received ALARM Event at TDM_API channel=%d alarm=0x%X!\n",
			card->devname,
			event->channel,
			event->alarms);

	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		return;
	}

	wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
		return;
	}	

	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));

	p_tdmapi_event->wp_api_event_type = WP_API_EVENT_ALARM;
	p_tdmapi_event->wp_api_event_channel = event->channel;
	p_tdmapi_event->wp_api_event_span = wp_tdmapi_get_span(card);

	p_tdmapi_event->wp_api_event_alarm = event->alarms;			
	tdm_api->cfg.fe_alarms = event->alarms;

	wan_skb_push_to_ctrl_event(skb);

	if (!wan_test_bit(0,&tdm_api->used)) {
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		return;
	}

	wanpipe_tdm_api_handle_event(tdm_api,skb);

	wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);


	return;
}

static void wp_tdmapi_report_alarms(void* card_id, uint32_t te_alarm)
{
	wan_event_t event;
	u8 i;

	memset(&event,0,sizeof(event));

	event.alarms=te_alarm;

	for (i=0; i<NUM_OF_E1_CHANNELS; i++) {
		event.channel=i;
		wp_tdmapi_alarms(card_id, &event);
	}

	return;
}


static void wp_tdmapi_tone (void* card_id, wan_event_t *event)
{
	netskb_t		*skb = NULL;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;
	wan_smp_flag_t irq_flags;
	int 			lock=1;

	irq_flags=0;

	switch(event->type) {
     	case WAN_EVENT_EC_DTMF:
		case WAN_EVENT_EC_FAX_1100:
		case WAN_EVENT_EC_FAX_2100:
		case WAN_EVENT_EC_FAX_2100_WSPR:
		case WAN_EVENT_RM_DTMF:
			break;
		default:
             DEBUG_ERROR("%s: %s() Error Invalid event type %X (%s)!\n",
				card->devname, __FUNCTION__, event->type, WAN_EVENT_TYPE_DECODE(event->type));       
			return;
	}

	if (event->type == WAN_EVENT_RM_DTMF){
		DEBUG_TDMAPI("%s: Received DTMF Event at TDM API (%d:%c)!\n",
			card->devname,
			event->channel,
			event->digit);
			lock=0;
			/* FIXME: Updated locking architecture */
	}else{
		DEBUG_TDMAPI("%s: Received Tone Event %s at TDM API (%d:%c:%s:%s)!\n",
			WAN_EVENT_TYPE_DECODE(event->type),
			card->devname,
			event->channel,
			event->digit,
			(event->tone_port == WAN_EC_CHANNEL_PORT_ROUT)?"ROUT":"SOUT",
			(event->tone_type == WAN_EC_TONE_PRESENT)?"PRESENT":"STOP");
	}

	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		return;
	}

	if (lock) wptdm_os_lock_irq(&card->wandev.lock,&irq_flags);
	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
		if (lock) wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
		return;
	}
	
	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event,0,sizeof(wp_api_event_t));

    switch(event->type) {
     	case WAN_EVENT_EC_DTMF:
		case WAN_EVENT_RM_DTMF:
		  	 p_tdmapi_event->wp_api_event_type	= WP_API_EVENT_DTMF;
			 break;
		case WAN_EVENT_EC_FAX_1100:
		  	 p_tdmapi_event->wp_api_event_type	= WP_API_EVENT_FAX_1100;
			 break;
		case WAN_EVENT_EC_FAX_2100:
		  	 p_tdmapi_event->wp_api_event_type	= WP_API_EVENT_FAX_2100;
			 break;
		case WAN_EVENT_EC_FAX_2100_WSPR:
		  	 p_tdmapi_event->wp_api_event_type	= WP_API_EVENT_FAX_2100_WSPR;
			 break;
	}

	p_tdmapi_event->wp_api_event_dtmf_digit	= event->digit;
	p_tdmapi_event->wp_api_event_dtmf_type	= event->tone_type;
	p_tdmapi_event->wp_api_event_dtmf_port	= event->tone_port;

#ifdef WAN_TDMAPI_DTMF_TEST
#ifdef __LINUX__
#warning "Enabling DTMF TEST - Should only be done on debugging"
#endif
	{
		if (event->tone_type == WAN_EC_TONE_PRESENT) {
			u8 ntone=tdm_api->dtmf_check[event->channel].dtmf_tone_present;
			if (ntone != 0) {
				ntone = tdm_api->dtmf_check[event->channel].dtmf_tone_present + 1;
			
				if (ntone != event->digit) {
					DEBUG_ERROR("%s: Error: Digit mismatch Ch=%i Expecting %c  Received %c\n",
						tdm_api->name,event->channel, ntone, event->digit);
				}
			}
			tdm_api->dtmf_check[event->channel].dtmf_tone_present = event->digit;
		} else {
			u8 ntone=tdm_api->dtmf_check[event->channel].dtmf_tone_stop;

			if (ntone != 0) {
				ntone = tdm_api->dtmf_check[event->channel].dtmf_tone_stop + 1;
				
				if (ntone != event->digit) {
					DEBUG_ERROR("%s: Error: Digit mismatch Ch=%i Expecting %c  Received %c\n",
						tdm_api->name,event->channel, ntone, event->digit);
				}
			}
			tdm_api->dtmf_check[event->channel].dtmf_tone_stop = event->digit;
		}
	}
#endif

	p_tdmapi_event->channel = event->channel;
	p_tdmapi_event->span = wp_tdmapi_get_span(card);

#if 0
	rx_hdr->event_time_stamp = gettimeofday();
#endif

	wan_skb_push_to_ctrl_event(skb);

	if (!wan_test_bit(0,&tdm_api->used)) {
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		if (lock)  wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);
		return;
	}

	tdm_api->cfg.stats.rx_events_tone++;
	wanpipe_tdm_api_handle_event(tdm_api,skb);

	if (lock) wptdm_os_unlock_irq(&card->wandev.lock,&irq_flags);


	return;
}


static void wp_tdmapi_hook (void* card_id, wan_event_t *event)
{
	netskb_t		*skb;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;

	DEBUG_TDMAPI("%s: Received RM LC Event at TDM_API (%d:%s)!\n",
			card->devname,
			event->channel,
			(event->rxhook==WAN_EVENT_RXHOOK_OFF)?"OFF-HOOK":"ON-HOOK");

	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		DEBUG_ERROR("%s: Error: failed to find API device. Discarding RM LC Event at TDM_API (%d:%s)!\n",
			card->devname,
			event->channel,
			(event->rxhook==WAN_EVENT_RXHOOK_OFF)?"OFF-HOOK":"ON-HOOK");
		return;
	}

	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
		DEBUG_ERROR("%s: Error: no free buffers for API event. Discarding RM LC Event at TDM_API (%d:%s)!\n",
			card->devname,
			event->channel,
			(event->rxhook==WAN_EVENT_RXHOOK_OFF)?"OFF-HOOK":"ON-HOOK");
		return;
	}

	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));

	p_tdmapi_event->wp_api_event_type 		= WP_API_EVENT_RXHOOK;
	p_tdmapi_event->wp_api_event_channel	= event->channel;
	p_tdmapi_event->wp_api_event_span		= wp_tdmapi_get_span(card);

	switch(event->rxhook){
	case WAN_EVENT_RXHOOK_ON:
		p_tdmapi_event->wp_api_event_hook_state =
					WP_API_EVENT_RXHOOK_ON;
		break;
	case WAN_EVENT_RXHOOK_OFF:
		p_tdmapi_event->wp_api_event_hook_state =
					WP_API_EVENT_RXHOOK_OFF;
		break;
	case WAN_EVENT_RXHOOK_FLASH:
		p_tdmapi_event->wp_api_event_hook_state =
				WP_API_EVENT_RXHOOK_FLASH;
		break;
		}

#if 0
	rx_hdr->event_time_stamp = gettimeofday();
#endif

	wan_skb_push_to_ctrl_event(skb);

	if (!wan_test_bit(0,&tdm_api->used)) {
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		return;
	}

	wanpipe_tdm_api_handle_event(tdm_api,skb);

	return;
}



static void __wp_tdmapi_linkstatus (void* card_id, wan_event_t *event, int lock)
{
#define wptdm_queue_lock_irq(card,flags)    if (lock) wptdm_os_lock_irq(card,flags)
#define wptdm_queue_unlock_irq(card,flags)  if (lock) wptdm_os_unlock_irq(card,flags)

	netskb_t		*skb;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;
	wan_smp_flag_t 	flags;
	u8 i;

	flags=0;

	DEBUG_TEST("%s: Received Link Status Event at TDM_API (%d:%s)!\n",
			card->devname,
			event->channel,
			(event->link_status==WAN_EVENT_LINK_STATUS_DISCONNECTED)?"Disconnected":"Connected");

	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		return;
	}

	if (tdm_api->state == event->link_status) {
		return;
	}

   	wptdm_queue_lock_irq(&card->wandev.lock,&flags);
	tdm_api->state = event->link_status;

	if (event->link_status  == WAN_EVENT_LINK_STATUS_CONNECTED) {
		wp_tdm_api_start(tdm_api);		
	} else {
		wp_tdm_api_stop(tdm_api);
	}
	wanpipe_tdm_api_kick(tdm_api);

	for (i=0;i<NUM_OF_E1_CHANNELS;i++) {

		if (!wan_test_bit(i,&tdm_api->active_ch)) {
			continue;
		}

		skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
		if (skb == NULL) {
			WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
			break;
		}
	
		p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));
	
		memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));
		p_tdmapi_event->wp_api_event_type		= WP_API_EVENT_LINK_STATUS;
		p_tdmapi_event->wp_api_event_channel	= i;
		p_tdmapi_event->wp_api_event_span		= wp_tdmapi_get_span(card);
	
		if (event->link_status  == WAN_EVENT_LINK_STATUS_CONNECTED) {
			p_tdmapi_event->wp_api_event_link_status =
						WP_API_EVENT_LINK_STATUS_CONNECTED;
		} else {
		
			p_tdmapi_event->wp_api_event_link_status =
						WP_API_EVENT_LINK_STATUS_DISCONNECTED;
		}
	
		wan_skb_push_to_ctrl_event(skb);
	
		if (!wan_test_bit(0,&tdm_api->used)) {
			wan_skb_init(skb,sizeof(wp_api_hdr_t));
			wan_skb_trim(skb,0);
			wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
			continue;
		}
	
		wanpipe_tdm_api_handle_event(tdm_api,skb);
	}

	wptdm_queue_unlock_irq(&card->wandev.lock,&flags);

#if 0
	rx_hdr->event_time_stamp = gettimeofday();
#endif

	return;
#undef wptdm_queue_lock_irq
#undef wptdm_queue_unlock_irq
}

static void wp_tdmapi_linkstatus (void* card_id, wan_event_t *event)
{
	u8 i;
	sdla_t *card = (sdla_t*)card_id;
	
	if (card->wandev.config_id == WANCONFIG_AFT_ANALOG) {
		__wp_tdmapi_linkstatus(card_id, event, 0);
		return;
	}

	for (i=0; i<NUM_OF_E1_CHANNELS; i++) {
		event->channel=i;
		__wp_tdmapi_linkstatus(card_id, event, 1);
	}

	return;	

}

/*FXO
	Some analog line use reverse polarity to indicate line hangup 
*/


static void wp_tdmapi_polarityreverse (void* card_id, wan_event_t *event)
{
	netskb_t		*skb;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;

	DEBUG_TDMAPI("%s: Received Polarity Reverse Event at TDM_API (%d: %s)!\n",
			card->devname,
			event->channel,(event->polarity_reverse==WAN_EVENT_POLARITY_REV_POSITIVE_NEGATIVE)?"+ve  to -ve":"-ve to +ve");

	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		return;
	}

	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
		return;
	}

	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));
	p_tdmapi_event->wp_api_event_type			= WP_API_EVENT_POLARITY_REVERSE;
	p_tdmapi_event->wp_api_event_channel		= event->channel;
	p_tdmapi_event->wp_api_event_span			= wp_tdmapi_get_span(card);
	
	switch(event->polarity_reverse){
	case WAN_EVENT_POLARITY_REV_POSITIVE_NEGATIVE:
		p_tdmapi_event->wp_api_event_polarity_reverse =
					WP_API_EVENT_POL_REV_POS_TO_NEG;
		wp_tdm_api_start(tdm_api);		

		break;
	case WAN_EVENT_POLARITY_REV_NEGATIVE_POSITIVE	:
		p_tdmapi_event->wp_api_event_polarity_reverse =
					WP_API_EVENT__POL_REV_NEG_TO_POS;
		wp_tdm_api_stop(tdm_api);
		break;
	}

	wan_skb_push_to_ctrl_event(skb);

	if (!wan_test_bit(0,&tdm_api->used)) {
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		return;
	}

#if 0
	rx_hdr->event_time_stamp = gettimeofday();
#endif

	wanpipe_tdm_api_handle_event(tdm_api,skb);

	return;
}

/*
FXS:
A ring trip event signals that the terminal equipment has gone
off-hook during the ringing state.
At this point the application should stop the ring because the
call was answered.
*/
static void wp_tdmapi_ringtrip (void* card_id, wan_event_t *event)
{
	netskb_t		*skb;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;

	DEBUG_TDMAPI("%s: Received RM RING TRIP Event at TDM_API (%d:%s)!\n",
			card->devname,
			event->channel,
			WAN_EVENT_RING_TRIP_DECODE(event->ring_mode));


	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		return;
	}

	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
		return;
	}

	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));
	
	p_tdmapi_event->wp_api_event_type = WP_API_EVENT_RING_TRIP_DETECT;
	p_tdmapi_event->wp_api_event_channel	= event->channel;
	p_tdmapi_event->wp_api_event_span = wp_tdmapi_get_span(card);

	
	if (event->ring_mode == WAN_EVENT_RING_TRIP_STOP){
		p_tdmapi_event->wp_api_event_ring_state =
				WP_API_EVENT_RING_TRIP_STOP;
	}else if (event->ring_mode == WAN_EVENT_RING_TRIP_PRESENT){
		p_tdmapi_event->wp_api_event_ring_state =
				WP_API_EVENT_RING_TRIP_PRESENT;
	}

#if 0
	rx_hdr->event_time_stamp = gettimeofday();
#endif

	wan_skb_push_to_ctrl_event(skb);

	if (!wan_test_bit(0,&tdm_api->used)) {
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		return;
	}

	wanpipe_tdm_api_handle_event(tdm_api,skb);

	return;
}

/*
FXO:
received a ring Start/Stop from the other side.
*/
static void wp_tdmapi_ringdetect (void* card_id, wan_event_t *event)
{
	netskb_t		*skb;
	wanpipe_tdm_api_dev_t	*tdm_api = NULL;
	sdla_t			*card = (sdla_t*)card_id;
	wp_api_event_t	*p_tdmapi_event = NULL;

	DEBUG_TDMAPI("%s: Received RM RING DETECT Event at TDM_API (%d:%s)!\n",
			card->devname,
			event->channel,
			WAN_EVENT_RING_DECODE(event->ring_mode));


	tdm_api = wp_tdmapi_search(card, event->channel);
	if (tdm_api == NULL){
		return;
	}

	skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
	if (skb == NULL) {
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_events_dropped);
		return;
	}	

	p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));

	memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));

	p_tdmapi_event->wp_api_event_type = WP_API_EVENT_RING_DETECT;
	p_tdmapi_event->wp_api_event_channel	= event->channel;
	p_tdmapi_event->wp_api_event_span = wp_tdmapi_get_span(card);

	switch(event->ring_mode){
	case WAN_EVENT_RING_PRESENT:
		p_tdmapi_event->wp_api_event_ring_state =
					WP_API_EVENT_RING_PRESENT;
		break;
	case WAN_EVENT_RING_STOP:
		p_tdmapi_event->wp_api_event_ring_state =
					WP_API_EVENT_RING_STOP;
		break;
	}

#if 0
	rx_hdr->event_time_stamp = gettimeofday();
#endif

 	wan_skb_push_to_ctrl_event(skb);

	if (!wan_test_bit(0,&tdm_api->used)) {
		wan_skb_init(skb,sizeof(wp_api_hdr_t));
		wan_skb_trim(skb,0);
		wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
		return;
	}

	wanpipe_tdm_api_handle_event(tdm_api,skb);

	return;
}


static int __store_tdm_api_pointer_in_card(sdla_t *card, wanpipe_tdm_api_dev_t *tdm_api, int fe_chan)
{

	if(fe_chan >= MAX_TDM_API_CHANNELS){
		DEBUG_ERROR("%s(): TDM API Error (TE1): Invalid Channel Number=%i (Span=%d)!\n",
				__FUNCTION__, fe_chan, tdm_api->tdm_span);
		return 1;
	}

	if(card->wp_tdmapi_hash[fe_chan] != NULL){
		DEBUG_ERROR("%s(): TDM API Error: device SPAN=%i CHAN=%i already in use!\n",
			__FUNCTION__, tdm_api->tdm_span, fe_chan);
		return 1;
	}

	card->wp_tdmapi_hash[fe_chan] = tdm_api;

#if 0
	DEBUG_TDMAPI("%s(): card->wp_tdmapi_hash[%d]==0x%p\n", __FUNCTION__, fe_chan, tdm_api);
#endif
	return 0;
}

static int store_tdm_api_pointer_in_card(sdla_t *card, wanpipe_tdm_api_dev_t *tdm_api)
{	
	int fe_chan=0, err;
#if 0
	DEBUG_TDMAPI("%s(): tdm_api ptr==0x%p, tdm_api->active_ch: 0x%X\n", __FUNCTION__, tdm_api, tdm_api->active_ch);
#endif

	/* Go through all timeslots belonging to this interface. VERY important for SPAN based API! */
	for(fe_chan = 0; fe_chan < NUM_OF_E1_CHANNELS; fe_chan++){

		/*	Note: 'tdm_api->active_ch' bitmap is 1-based so 'fe_chan' will start from 1.
			It is important because 'channo' in 'read_rbsbits(sdla_fe_t* fe, int channo, int mode)'
			is 1-based. */
		if (!wan_test_bit(fe_chan, &tdm_api->active_ch)) {
			/* timeslot does NOT belong to this interface */
			continue;
		}

		err=__store_tdm_api_pointer_in_card(card, tdm_api, fe_chan);
		if (err) {
			return err;
		}
	}

	return 0;
}


static int __remove_tdm_api_pointer_from_card(wanpipe_tdm_api_dev_t *tdm_api,int fe_chan)
{
	sdla_t	*card = NULL;

	WAN_ASSERT(tdm_api == NULL);
	card = (sdla_t*)tdm_api->card;

#if 1
	DEBUG_TDMAPI("%s(): tdm_api ptr==0x%p, fe_chan: %d\n", __FUNCTION__, tdm_api, fe_chan);
#endif

	if(card == NULL){
		DEBUG_ERROR("%s(): TDM API Error: Invalid 'card' pointer!\n", __FUNCTION__);
		return 1;
	}


	if(fe_chan >= MAX_TDM_API_CHANNELS){
		DEBUG_ERROR("%s(): TDM API Error (TE1): Invalid Channel Number=%i (Span=%d)!\n",
			__FUNCTION__, fe_chan, tdm_api->tdm_span);
		return 1;
	}

	if(card->wp_tdmapi_hash[fe_chan] == NULL){
		DEBUG_WARNING("%s: TDM API Warning: device SPAN=%i CHAN=%i was NOT initialized!\n",
			__FUNCTION__, tdm_api->tdm_span, fe_chan);
	}

	card->wp_tdmapi_hash[fe_chan] = NULL;

	return 0;
}


static int remove_tdm_api_pointer_from_card(wanpipe_tdm_api_dev_t *tdm_api)
{
	sdla_t	*card = NULL;
	int fe_chan=0, err;

	WAN_ASSERT(tdm_api == NULL);

	card = (sdla_t*)tdm_api->card;
	
	/* Go through all timeslots belonging to this interface. VERY important for SPAN based API! */
	for(fe_chan = 0; fe_chan < NUM_OF_E1_CHANNELS; fe_chan++){

		/*	Note: 'tdm_api->active_ch' bitmap is 1-based so 'fe_chan' will start from 1.
			It is important because 'channo' in 'read_rbsbits(sdla_fe_t* fe, int channo, int mode)'
			is 1-based. */
		if (!wan_test_bit(fe_chan, &tdm_api->active_ch)) {
			/* timeslot does NOT belong to this interface */
			continue;
		}

		err=__remove_tdm_api_pointer_from_card(tdm_api, fe_chan);
		if (err) {
			return err;
		}

	}

	return 0;
}

/* When a Port is unloaded tell ctrldev that the Port is 'disconnected' */
static int wp_tdmapi_unreg_notify_ctrl(wanpipe_tdm_api_dev_t *tdm_api)
{
	sdla_t *card = tdm_api->card;

	if (!card) {
		return 0;
	}

	if (wan_test_bit(0,&tdmapi_ctrl.used)) {

		wan_smp_flag_t flags;
		netskb_t *skb;
		u8 i;
		flags=0;

		for (i=0;i<NUM_OF_E1_CHANNELS;i++) {

			if (!wan_test_bit(i,&tdm_api->active_ch)) {
				continue;
			}

			wptdm_os_lock_irq(&card->wandev.lock,&flags);
			skb=wan_skb_dequeue(&tdm_api->wp_event_free_list);
			wptdm_os_unlock_irq(&card->wandev.lock,&flags);
	
			if (skb) {
				wp_api_event_t *	p_tdmapi_event;
				p_tdmapi_event = (wp_api_event_t*)wan_skb_put(skb,sizeof(wp_api_event_t));
		
				memset(p_tdmapi_event, 0, sizeof(wp_api_event_t));
				p_tdmapi_event->wp_api_event_type		= WP_API_EVENT_LINK_STATUS;
				p_tdmapi_event->wp_api_event_channel	= i;
				p_tdmapi_event->wp_api_event_span		= wp_tdmapi_get_span(card);
				p_tdmapi_event->wp_api_event_link_status =	WP_API_EVENT_LINK_STATUS_DISCONNECTED;
	
				wan_skb_push_to_ctrl_event(skb);
	
				wan_skb_init(skb,sizeof(wp_api_hdr_t));
				wan_skb_trim(skb,0);
	
				wptdm_os_lock_irq(&card->wandev.lock,&flags);
				wan_skb_queue_tail(&tdm_api->wp_event_free_list,skb);
				wptdm_os_unlock_irq(&card->wandev.lock,&flags);
			}
		}
	}

	return 0;
}


/* The api task funciton is not used right now. we are leaving
   the infrastructure here for future use */

#if defined(__LINUX__)
# if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
static void wp_api_task_func (void * tdmapi_ptr)
# else
static void wp_api_task_func (struct work_struct *work)
# endif
#elif defined(__WINDOWS__)
static void wp_api_task_func (IN PKDPC Dpc, IN PVOID tdmapi_ptr, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
#else
static void wp_api_task_func (void * tdmapi_ptr, int arg)
#endif
{
#if defined(__LINUX__)
# if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20))
	wanpipe_tdm_api_dev_t 		*tdm_api = (wanpipe_tdm_api_dev_t *)container_of(work, wanpipe_tdm_api_dev_t, wp_api_task);
# else
	wanpipe_tdm_api_dev_t 		*tdm_api = (wanpipe_tdm_api_dev_t *)tdmapi_ptr;
# endif
#else
	wanpipe_tdm_api_dev_t 		*tdm_api = (wanpipe_tdm_api_dev_t *)tdmapi_ptr;
#endif
	sdla_t *card = tdm_api->card;

	if (!card) {
		return;
	}

	if (!wan_test_bit(0,&tdm_api->init)) {
		return;
	}

	return;
}

#if 0
static int wp_tdm_api_mtp1_rx_data (void *priv_ptr, u8 *rx_data, int rx_len)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)priv_ptr;
	sdla_t *card;
	netskb_t *skb;
	u8 *data;
	
	if (!tdm_api->chan || !wan_test_bit(0,&tdm_api->init) || !tdm_api->card){
		return -ENODEV;
	}

	if (!wan_test_bit(0,&tdm_api->used)) {
		return -ENODEV;
	}

	card=tdm_api->card;
	
	skb=wan_skb_alloc(sizeof(wp_api_hdr_t)+rx_len);
	if (!skb) {
		return -ENOBUFS;	
	}

	wan_skb_put(skb,sizeof(wp_api_hdr_t));
	data=wan_skb_put(skb,rx_len);
	memcpy(data,rx_data,rx_len);

	wptdm_os_lock_irq(&card->wandev.lock,&flag);
	if (wan_skb_queue_len(&tdm_api->wp_rx_list) >= (int)tdm_api->cfg.rx_queue_sz) {
		wptdm_os_unlock_irq(&card->wandev.lock,&flag);
		wp_wakeup_rx_tdmapi(tdm_api);
		WP_AFT_CHAN_ERROR_STATS(tdm_api->cfg.stats,rx_dropped);
		if (WAN_NET_RATELIMIT()) {
			DEBUG_TEST("%s: Error RX Buffer Overrun!\n",
					   __FUNCTION__);	
		}
		return -EBUSY;
	}

	wan_skb_queue_tail(&tdm_api->wp_rx_list,skb);
	tdm_api->cfg.stats.rx_packets++;
	wptdm_os_unlock_irq(&card->wandev.lock,&flag);

	wp_wakeup_rx_tdmapi(tdm_api);

	return 0;
}

static int wp_tdm_api_mtp1_rx_reject (void *priv_prt, char *reason)
{
	/* Do nothing */
	return 0;
	
}

static int wp_tdm_api_mtp1_rx_suerm (void *priv_ptr)
{
	/* Pass up suerm event */
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)priv_ptr;
	wp_wakeup_rx_tdmapi(tdm_api);
	return 0;
}

static int wp_tdm_api_tmp1_wakup(void *priv_ptr)
{
	wanpipe_tdm_api_dev_t *tdm_api = (wanpipe_tdm_api_dev_t*)priv_ptr;
	wp_wakeup_rx_tdmapi(tdm_api);
	return 0;
}    
#endif

#else

int wanpipe_tdm_api_reg(wanpipe_tdm_api_dev_t *tdm_api)
{
	return -EINVAL;
}

int wanpipe_tdm_api_unreg(wanpipe_tdm_api_dev_t *tdm_api)
{
	return -EINVAL;
}

int wanpipe_tdm_api_kick(wanpipe_tdm_api_dev_t *tdm_api)
{
	return -EINVAL;
}

int wanpipe_tdm_api_rx_tx (wanpipe_tdm_api_dev_t *tdm_api, u8 *rx_data, u8 *tx_data, int len)
{
	return -EINVAL;
}


int wanpipe_tdm_api_span_rx (wanpipe_tdm_api_dev_t *tdm_api, netskb_t *skb)
{
	return -EINVAL;
}

#endif /* #if defined(BUILD_TDMV_API) */


