/* 
 * tcs.c -- Implementation of Bluetooth Telephony Control Protocol 
 *          Specification, TCS Binary
 *
 * Copyright (C) 2000  Axis Communications AB
 *
 * Mattias Agren <mattias.agren@axis.com>
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: tcs.c,v 1.10 2000/10/24 08:47:52 matsf Exp $
 *
 */

#define __NO_VERSION__ /* don't define kernel_version in module.h */

#ifdef __KERNEL__
#include <linux/malloc.h>
#include <linux/bluetooth/btdebug.h>
#include <linux/bluetooth/btcommon.h>
#include <linux/bluetooth/btmem.h>
#include <linux/bluetooth/tcs.h>
#include <linux/bluetooth/l2cap.h>
#include <linux/bluetooth/hci.h>
#else /* user mode */
#include <string.h>
#include "include/btdebug.h"
#include "include/btcommon.h"
#include "include/btmem.h"
#include "include/tcs.h"
#include "include/l2cap.h"
#include "include/hci.h"

#endif


/****************** DEBUG CONSTANT AND MACRO SECTION ************************/

#define TCS_DBG_STR "           TCS "

#if TCS_DEBUG_XMIT
#define D_XMIT(fmt, args...) printk(TCS_DBG_STR fmt, ## args)
#else
#define D_XMIT(fmt, args...)
#endif

#if TCS_DEBUG_REC
#define D_REC(fmt, args...) printk(TCS_DBG_STR fmt, ## args)
#else
#define D_REC(fmt, args...)
#endif

#if TCS_DEBUG_MISC
#define D_MISC(fmt, args...) printk(TCS_DBG_STR fmt, ## args)
#else
#define D_MISC(fmt, args...)
#endif

#if TCS_PRINT_DATA
#define PRINTPKT(str, data, len) print_data(str, data, len)
#else
#define PRINTPKT(str, data, len)
#endif


/****************** CONSTANT AND MACRO SECTION ******************************/

#define TCS_NBR_OF_LINES 1

/* Values used for the L2CAP configuration process */
#define TCS_MTU 672

/* Default, why should we be different */
#define TCS_QOS 0x01 

/* Best effort */
#define TCS_FLUSH_TIMEOUT 0xffff 

/* Reliable channel, infinite amout of retransmissions */

/* Call Control messages */

/* Call Establishment */
#define CALL_CONTROL 0x0
#define ALERTING 0x0
#define CALL_PROCEEDING 0x1
#define CONNECT 0x2
#define CONNECT_ACKNOWLEDGE 0x3
#define PROGRESS 0x4
#define SETUP 0x5
#define SETUP_ACKNOWLEDGE 0x6

/* Call clearing */
#define DISCONNECT 0x7
#define RELEASE 0x8
#define RELEASE_COMPLETE 0x9

/* Miscellaneous */
#define INFORMATION 0xa
#define START_DTMF 0x10
#define START_DTMF_ACKNOWLEDGE 0x11
#define START_DTMF_REJECT 0x12
#define STOP_DTMF 0x13
#define STOP_DTMF_ACKNOWLEDGE 0x14

/* Group management messages */
#define GROUP_MANAGEMENT 0x1
#define INFO_SUGGEST 0x0
#define INFO_ACCEPT 0x1
#define LISTEN_REQUEST 0x2
#define LISTEN_ACCEPT 0x3
#define LISTEN_SUGGEST 0x4
#define LISTEN_REJECT 0x5
#define ACCESS_RIGHTS_REQUEST 0x6
#define ACCESS_RIGHTS_ACCEPT 0x7
#define ACCESS_RIGHTS_REJECT 0x8

/* Connectionless messages */
#define CONNECTIONLESS 0x2
#define CL_INFO 0x0

/* Single octet information elements */
#define SENDING_COMPLETE 0xa1

/* Double octet information elements */
#define CALL_CLASS 0xc0
#define CAUSE 0xc1
#define PROGRESS_INDICATOR 0xc2 
#define SIGNAL 0xc3
#define KEYPAD_FACILITY 0xc4 
#define SCO_HANDLE 0xc5

/* Variable length information elements */
#define CLOCK_OFFSET 0x0
#define CONFIGURATION_DATA 0x1 
#define BEARER_CAPABILITY 0x2
#define DESTINATION_CID 0x3
#define CALLING_PARTY_NUMBER 0x4 
#define CALLED_PARTY_NUMBER 0x5
#define AUDIO_CONTROL 0x6
#define COMPANY_SPECIFIC 0x7

/* Other information parameters */

/* Bearer capability parameters */
#define SCO_LINK 0x0
#define ACL_LINK 0x1
#define HV1_PACKET 0x5
#define HV2_PACKET 0x6
#define HV3_PACKET 0x7
#define DV_PACKET 0x8
#define CVSD 0x1
#define PCM_A 0x2
#define PCM_U 0x3

/* Call class parameters */
#define EXTERNAL_CALL 0x0
#define INTERCOM_CALL 0x1
#define SERVICE_CALL 0x2
#define EMERGENCY_CALL 0x3

/* Cause parameters */
#define NORMAL_CALL_CLEARING 0x10


/* Signal parameters */
#define EXTERNAL_CALL_SIGNAL 0x40
#define INTERNAL_CALL_SIGNAL 0x41
#define CALL_BACK_SIGNAL 0x42



/****************** TYPE DEFINITION SECTION *********************************/

typedef struct tcs_message {
	s32 type:5;
	s32 prot_disc:3;
	u8 info[0];
} tcs_message;


/****************** LOCAL FUNCTION DECLARATION SECTION **********************/

static void process_cc_msg(l2cap_con *l2cap, u8* data, u32 len);
static void process_gw_msg(l2cap_con *l2cap, u8* data, u32 len);
static void process_cl_msg(l2cap_con *l2cap, u8* data, u32 len);
static s32 send_single_octet_msg(l2cap_con *l2cap, u8 type, u8 prot_disc);
static s32 send_tcs_msg(l2cap_con *l2cap, u8 type, u8 prot_disc, u8 *nfo, u32 nfo_len);

/****************** GLOBAL VARIABLE DECLARATION SECTION *********************/

/****************** LOCAL VARIABLE DECLARATION SECTION **********************/

tcs_con tcs[TCS_NBR_OF_LINES];

/****************** FUNCTION DEFINITION SECTION *****************************/

void 
tcs_init(void)
{
	s32 i;
	protocol_layer this_layer;

	DSYS("Initialising TCS\n");

	for (i = 0; i < TCS_NBR_OF_LINES; i++) {
		tcs[i].tcs_cur_state = STATE_NULL;
	}
	
	/* Set the confirm and indication functions for the L2CAP-layer */
	this_layer.con_ind = tcs_connect_ind;
	this_layer.conf_ind = tcs_config_ind;
	this_layer.disc_ind = tcs_disconnect_ind;
	this_layer.con_cfm = tcs_connect_cfm;
	this_layer.conf_cfm = tcs_config_cfm;
	this_layer.disc_cfm = tcs_disconnect_cfm;
	this_layer.receive_data = tcs_receive_data;

	/* Register TCS in the L2CAP layer*/
	l2cap_register_upper(TCS_LAYER, &this_layer);
}

void 
tcs_shutdown(void)
{
	DSYS("Shutting down TCS\n");
	tcs[0].tcs_cur_state = STATE_NULL;	
}

s32
tcs_connect_req(u8 *bd)
{
	if (l2ca_connect_req(bd, TCS_LAYER)) {
		D_ERR(" tcs_connect_req: l2ca_connect_req failed\n");
		return -1; 
	}
	return 0;
}

void 
tcs_connect_ind(l2cap_con *l2cap) 
{
	D_MISC("tcs_connect_ind : remote cid : %d\n", l2cap->remote_cid);

	tcs[0].l2cap = l2cap;
	
	if(l2ca_connect_rsp(l2cap, RES_SUCCESS, STAT_NOINFO)){
		D_ERR("tcs_connect_ind: l2ca_connect_rsp failed\n"); 
	}
}

void 
tcs_connect_cfm(l2cap_con *l2cap, s32 status)
{
	D_MISC("tcs_connect_cfm : remote cid : %d\n", l2cap->remote_cid);
	
	if ( !l2ca_local_conf_done(l2cap) )
	{
		/* still haven't sent config request yet */
		if(l2ca_config_req(l2cap, TCS_MTU, NULL, 
				   TCS_FLUSH_TIMEOUT, 0)){
			D_ERR("tcs_connect_cfm : Conf req failed\n");
		}

		/* store connection */
		tcs[0].l2cap = l2cap;
	} else 	       
		D_REC("tcs_config_cfm : already have sent config request\n");
}

void 
tcs_config_ind(l2cap_con* l2cap)
{
	D_MISC("tcs_config_ind : remote cid : %d\n", l2cap->remote_cid);


	/* check if we have sent a pos response yet */
	if (!l2ca_remote_conf_done(l2cap)){
		/* still haven't sent a pos configure response*/
		
		if (l2ca_config_rsp(l2cap, l2cap->remote_mtu, NULL, 1)){
			D_ERR("tcs_config_ind : Conf rsp failed\n");
		}    
		
	} else 
		printk("tcs_config_ind : already sent back a pos response\n");
	

	/* check if we received a pos response on a previous config req */
	if (!l2ca_local_conf_done(l2cap))
	{
		if (!l2cap->initiator) {

			DSYS("tcs_config_ind : set local l2cap mtu to %d\n", 
			     l2cap->local_mtu);
			l2cap->local_mtu=l2cap->remote_mtu;
			
			/* up3 -- fixme, use real options not static values */
			if(l2ca_config_req(l2cap, TCS_MTU, NULL, 
					   TCS_FLUSH_TIMEOUT, 0)){
				D_ERR("tcs_config_ind : Conf request failed\n");
			}
		}
	} else 
		DSYS("tcs_config_ind : already ready with config req\n");
}

void 
tcs_config_cfm(l2cap_con *l2cap, s32 status)
{
	D_MISC("tcs_config_cfm :  remote cid : %d\n", l2cap->remote_cid);

	DSYS("Now we have an open l2cap channel for TCS\n");

	/* tcs negotiation */
	if (!l2cap->initiator){
		/* Server case */
		tcs[0].acl_hdl = l2cap->hci_con_handle;
	} else {
		tcs[0].acl_hdl = l2cap->hci_con_handle;
		/* Client case */
	}
}

void 
tcs_disconnect_req(u32 tcs_hdl)
{
	D_ERR("tcs_disconnect_req : NOT DONE\n");
}  

void 
tcs_disconnect_ind(l2cap_con *l2cap) 
{
	D_MISC("tcs_disconnect_ind : remote cid : %d\n", l2cap->remote_cid);
	l2ca_disconnect_rsp(l2cap);

}

void 
tcs_disconnect_cfm(l2cap_con *l2cap)
{
	D_MISC("tcs_disconnect_cfm : remote cid : %d\n",l2cap->remote_cid);
}

s32
tcs_add_sco_link(u8 err_code, u32 hci_hdl)
{
	D_MISC("tcs_add_sco_link: Added SCO handle %d\n",hci_hdl);
	if (err_code) {
		D_ERR("Failure establishing SCO link: %s\n",
		      get_err_msg(err_code));
		
		return -1;
	} else {
		tcs[0].sco_hdl = hci_hdl;
		send_single_octet_msg(tcs[0].l2cap, CONNECT_ACKNOWLEDGE,
				      CALL_CONTROL);
		tcs[0].tcs_cur_state = ACTIVE;
		return 0;
	}
	return 0;
}


void 
tcs_receive_data(l2cap_con *l2cap, u8* data, u32 len)
{
#define FNC "tcs_receive_data: "
	tcs_message *tcs_msg;

	tcs_msg = (tcs_message*) data;

	PRINTPKT(FNC, data, len);
	
	switch (tcs_msg->prot_disc) {
	case CALL_CONTROL:
		D_REC(FNC"Received CALL_CONTROL message\n");
		process_cc_msg(l2cap, data, len);
		break;
		
	case GROUP_MANAGEMENT:
		D_REC(FNC"Received GROUP_MANAGEMENT message\n");
		process_gw_msg(l2cap, data, len);
		break;
		
	case CONNECTIONLESS:
		D_REC(FNC"Received CONNECTIONLESS message\n");
		process_cl_msg(l2cap, data, len);
		break;

	default:
		D_ERR(FNC"Unknown protocol discriminator (0x%x)\n",
		      tcs_msg->prot_disc);
	}
#undef FNC	
}

void
process_cc_msg(l2cap_con *l2cap, u8* data, u32 len)
{
#define FNC "process_cc_msg: "
	
	tcs_message *tcs_msg;

	tcs_msg = (tcs_message*) data;
	
	switch (tcs_msg->type) {
	case ALERTING:
		D_REC(FNC"Received ALERTING message\n");
		tcs[0].tcs_cur_state = CALL_DELIVERED;
		break;
		
	case CALL_PROCEEDING:
		D_REC(FNC"Received CALL_PROCEEDING message\n");
		break;

	case CONNECT:
		D_REC(FNC"Received CONNECT message\n");
		tcs[0].tcs_cur_state = ACTIVE;
		hci_add_sco_connection(tcs[0].acl_hdl);
		break;
		
	case CONNECT_ACKNOWLEDGE:
		D_REC(FNC"Received CONNECT_ACKNOWLEDGE message\n");
		tcs[0].tcs_cur_state = ACTIVE;
		break;
		
	case PROGRESS:
		D_REC(FNC"Received PROGRESS message\n");
		break;
	
	case SETUP:
		if (tcs[0].tcs_cur_state != STATE_NULL) {
			DSYS(FNC"Received SETUP in wrong state (%d)\n",
			     tcs[0].tcs_cur_state);
		} else {
			u8 alert_nfo[4];
			
			D_REC(FNC"Received SETUP\n");
			tcs[0].tcs_cur_state = CALL_PRESENT;

			alert_nfo[0] = BEARER_CAPABILITY;
			alert_nfo[1] = 2;
			alert_nfo[2] = SCO_LINK;
			alert_nfo[3] = (CVSD << 5) | (HV3_PACKET & 0x1f);

			send_tcs_msg(l2cap, ALERTING, CALL_CONTROL, alert_nfo, 4);
			tcs[0].tcs_cur_state = CALL_RECEIVED;
		}
		break;
	
	case SETUP_ACKNOWLEDGE:
		D_REC(FNC"Received SETUP_ACKNOWLEDGE message\n");
		break;
	
	case DISCONNECT:
		D_REC(FNC"Received DISCONNECT message\n");
		tcs[0].tcs_cur_state = DISCONNECT_INDICATION;
		send_single_octet_msg(l2cap, RELEASE, CALL_CONTROL);
		tcs[0].tcs_cur_state = RELEASE_REQUEST;
		break;
	
	case RELEASE:
		D_REC(FNC"Received RELEASE message\n");
		if (lp_disconnect(tcs[0].sco_hdl)) {
			D_ERR("lp_disconnect failed\n");
		} else {
			send_single_octet_msg(l2cap, RELEASE_COMPLETE, CALL_CONTROL);
		}
		break;
	
	case RELEASE_COMPLETE:
		D_REC(FNC"Received RELEASE_COMPLETE message\n");
		tcs[0].tcs_cur_state = STATE_NULL;
		break;
	
	case INFORMATION:
		D_REC(FNC"Received INFORMATION message %c\n",tcs_msg->info[1]);
		break;
	
	case START_DTMF:
		D_REC(FNC"Received START_DTMF message\n");
		break;
	
	case START_DTMF_ACKNOWLEDGE:
		D_REC(FNC"Received START_DTMF_ACKNOWLEDGE message\n");
		break;
	
	case START_DTMF_REJECT:
		D_REC(FNC"Received START_DTMF_REJECT message\n");
		break;
	
	case STOP_DTMF:
		D_REC(FNC"Received STOP_DTMF message\n");
		break;
	
	case STOP_DTMF_ACKNOWLEDGE:
		D_REC(FNC"Received STOP_DTMF_ACKNOWLEDGE message\n");
		break;

	default:
		D_ERR(FNC"Unknown message type (0x%x)\n", tcs_msg->type);
		break;
	}
#undef FNC
}


void
process_gw_msg(l2cap_con *l2cap, u8* data, u32 len)
{
#define FNC "process_gw_msg: "
	tcs_message *tcs_msg;

	tcs_msg = (tcs_message*) data;
	
	switch (tcs_msg->type) {
	
	case INFO_SUGGEST:
		D_REC(FNC"Received INFO_SUGGEST message\n");
		break;
	
	case INFO_ACCEPT:
		D_REC(FNC"Received INFO_ACCEPT message\n");
		break;
	
	case LISTEN_REQUEST:
		D_REC(FNC"Received LISTEN_REQUEST message\n");
		break;
	
	case LISTEN_ACCEPT:
		D_REC(FNC"Received LISTEN_ACCEPT message\n");
		break;
	
	case LISTEN_SUGGEST:
		D_REC(FNC"Received LISTEN_SUGGEST message\n");
		break;
	
	case LISTEN_REJECT:
		D_REC(FNC"Received LISTEN_REJECT message\n");
		break;
	
	case ACCESS_RIGHTS_REQUEST:
		D_REC(FNC"Received ACCESS_RIGHTS_REQUEST message\n");
		break;
	
	case ACCESS_RIGHTS_ACCEPT:
		D_REC(FNC"Received ACCESS_RIGHTS_ACCEPT message\n");
		break;
	
	case ACCESS_RIGHTS_REJECT:
		D_REC(FNC"Received ACCESS_RIGHTS_REJECT message\n");
		break;

	default:
		D_ERR(FNC"Unknown message type (0x%x)\n", tcs_msg->type);
		break;	
	}
#undef FNC
}

void
process_cl_msg(l2cap_con *l2cap, u8* data, u32 len)
{
#define FNC "process_cl_msg: "
	tcs_message *tcs_msg;

	tcs_msg = (tcs_message*) data;
	
	switch (tcs_msg->type) {
	case CL_INFO:
		D_REC(FNC"Received CL_INFO message\n");
		break;

	default:
		D_ERR(FNC"Unknown message type (0x%x)\n", tcs_msg->type);
		break;
	}
#undef FNC
}

s32
send_single_octet_msg(l2cap_con *l2cap, u8 type, u8 prot_disc)
{
	bt_tx_buf *tx_buf;
	tcs_message *msg;
	u32 tcs_frame_size;
  
	
	tcs_frame_size = 1;
	tx_buf = subscribe_bt_buf(sizeof(tcs_tx_buf) + tcs_frame_size);

	if (!tx_buf) {
		D_ERR("send_single_octet_msg: didn't get a valid tx_buf\n");
		return -1;
	}

	tx_buf->cur_len = tcs_frame_size;

	msg = (tcs_message*) (tx_buf->data + sizeof(tcs_tx_buf));

	msg->type = type & 0x1f;
	msg->prot_disc = prot_disc & 0x7;

	printk("send_single_octet_msg :  prot_disc 0x%x, type : 0x%x \n", 
		 prot_disc, type);
	
	return l2cap_send_data(tx_buf, l2cap);
}

s32
tcs_connect(l2cap_con *l2cap)
{
	return send_single_octet_msg(l2cap, CONNECT, CALL_CONTROL);
}

s32
tcs_disconnect(l2cap_con *l2cap)
{
	u8 disconnect_nfo[2];

	disconnect_nfo[0] = CAUSE;
	disconnect_nfo[1] = NORMAL_CALL_CLEARING;

	return send_tcs_msg(l2cap, DISCONNECT, CALL_CONTROL, disconnect_nfo,2);
}

s32
tcs_setup(l2cap_con *l2cap, u8 *nbr, u8 nbr_len)
{
	u8 setup_nfo[11 + nbr_len];

	tcs[0].tcs_cur_state = CALL_INITIATED;

	setup_nfo[0] = CALL_CLASS;
	setup_nfo[1] = INTERCOM_CALL;
	
	setup_nfo[2] = BEARER_CAPABILITY;
	setup_nfo[3] = 2;
	setup_nfo[4] = SCO_LINK;
	setup_nfo[5] = (CVSD << 5) | (HV3_PACKET & 0x1f);

	setup_nfo[6] = SIGNAL;
	setup_nfo[7] = INTERNAL_CALL_SIGNAL;

	setup_nfo[8] = CALLED_PARTY_NUMBER;
	setup_nfo[9] = nbr_len;
	setup_nfo[10] = 0x10;
	memcpy(&setup_nfo[11], nbr, nbr_len);

	if (nbr_len) {
		return send_tcs_msg(l2cap, SETUP, CALL_CONTROL, setup_nfo, 11 + nbr_len);
	} else {
		return send_tcs_msg(l2cap, SETUP, CALL_CONTROL, setup_nfo, 8);
	}
	
	/* If using telephone number change length to nbr_len+11 */
}

s32
tcs_send_information(l2cap_con *l2cap, u8 ch)
{
	u8 nfo[2];

	if (tcs[0].tcs_cur_state != ACTIVE) {
		D_ERR("tcs_send_information: Not in state ACTIVE!\n");
	}
	
	nfo[0] = KEYPAD_FACILITY;
	nfo[1] = ch;

	return send_tcs_msg(l2cap, INFORMATION, CALL_CONTROL, nfo, 2);
}

s32
send_tcs_msg(l2cap_con *l2cap, u8 type, u8 prot_disc, u8 *nfo, u32 nfo_len)
{
#define FNC "send_tcs_msg: "
bt_tx_buf *tx_buf;
	tcs_message *msg;
	u32 tcs_frame_size;
  
	tcs_frame_size = 1 + nfo_len;
	tx_buf = subscribe_bt_buf(sizeof(tcs_tx_buf) + tcs_frame_size);

	if (!tx_buf) {
		D_ERR(FNC"didn't get a valid tx_buf\n");
		return -1;
	}

	tx_buf->cur_len = tcs_frame_size;

	msg = (tcs_message*) (tx_buf->data + sizeof(tcs_tx_buf));

	msg->type = type;
	msg->prot_disc = prot_disc;
	if (nfo_len) {
		memcpy(msg->info, nfo, nfo_len);
	}

	PRINTPKT(FNC,(u8*)msg, tcs_frame_size);
	
	return l2cap_send_data(tx_buf, l2cap);
#undef FNC
}

/****************** END OF FILE tcs.c ***************************************/
