//------------------------------------------------------------------------------
// Copyright (c) 2004-2010 Atheros Communications Inc.
// All rights reserved.
//
// 
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
//
//
// Author(s): ="Atheros"
//------------------------------------------------------------------------------

#include "ar6000_drv.h"
#ifdef AR6K_ENABLE_HCI_PAL
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <ar6k_pal.h>

extern unsigned int setupbtdev;
#define bt_check_bit(val, bit) (val & bit)
#define bt_set_bit(val, bit) (val |= bit)
#define bt_clear_bit(val, bit) (val &= ~bit)

/* export ATH_AR6K_DEBUG_HCI_PAL=yes in host/localmake.linux.inc
 * to enable debug information */
#ifdef HCIPAL_DEBUG
#define PRIN_LOG(format, args...) printk(KERN_ALERT "%s:%d - %s Msg:" format "\n",__FUNCTION__, __LINE__, __FILE__, ## args) 
#else
#define PRIN_LOG(format, args...)
#endif

/**********************************
 * HCI PAL private info structure 
 *********************************/
typedef struct ar6k_hci_pal_info_s{

	unsigned long ulFlags;
#define HCI_NORMAL_MODE (1)
#define HCI_REGISTERED (1<<1)
	struct hci_dev *hdev;            /* BT Stack HCI dev */
	AR_SOFTC_T *ar;

}ar6k_hci_pal_info_t;

/*** BT Stack Entrypoints *******/
/***************************************
 * bt_open - open a handle to the device
 ***************************************/
static int bt_open(struct hci_dev *hdev)
{
	PRIN_LOG("HCI PAL: bt_open - enter - x\n");
	set_bit(HCI_RUNNING, &hdev->flags);
	set_bit(HCI_UP, &hdev->flags);
	set_bit(HCI_INIT, &hdev->flags);         
	return 0;
}

/***************************************
 * bt_close - close handle to the device
 ***************************************/
static int bt_close(struct hci_dev *hdev)
{
	PRIN_LOG("HCI PAL: bt_close - enter\n");
	clear_bit(HCI_RUNNING, &hdev->flags);
	return 0;
}

/*****************************
 * bt_ioctl - ioctl processing
 *****************************/
static int bt_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg)
{
	PRIN_LOG("HCI PAL: bt_ioctl - enter\n");
	return -ENOIOCTLCMD;
}

/**************************************
 * bt_flush - flush outstanding packets
 **************************************/
static int bt_flush(struct hci_dev *hdev)
{
	PRIN_LOG("HCI PAL: bt_flush - enter\n");
	return 0;
}

/***************
 * bt_destruct  
 ***************/
static void bt_destruct(struct hci_dev *hdev)
{
	PRIN_LOG("HCI PAL: bt_destruct - enter\n");
	/* nothing to do here */
}

/****************************************************
 * Invoked from bluetooth stack via hdev->send()
 * to send the packet out via ar6k to PAL firmware.
 *
 * For HCI command packet wmi_send_hci_cmd() is invoked.
 * wmi_send_hci_cmd adds WMI_CMD_HDR and sends the packet
 * to PAL firmware.
 *
 * For HCI ACL data packet wmi_data_hdr_add is invoked 
 * to add WMI_DATA_HDR to the packet.  ar6000_acl_data_tx 
 * is then invoked to send the packet to PAL firmware.
 ******************************************************/
static int btpal_send_frame(struct sk_buff *skb)
{
	struct hci_dev *hdev = (struct hci_dev *)skb->dev;
	HCI_TRANSPORT_PACKET_TYPE type;
	ar6k_hci_pal_info_t *pHciPalInfo;
	A_STATUS status = A_OK;
	struct sk_buff *txSkb = NULL;
	AR_SOFTC_T *ar;

	if (!hdev) {
		PRIN_LOG("HCI PAL: btpal_send_frame - no device\n");
		return -ENODEV;
	}

	if (!test_bit(HCI_RUNNING, &hdev->flags)) {
		PRIN_LOG("HCI PAL: btpal_send_frame - not open\n");
		return -EBUSY;
	}

	pHciPalInfo = (ar6k_hci_pal_info_t *)hdev->driver_data;   
	A_ASSERT(pHciPalInfo != NULL);
	ar = pHciPalInfo->ar;

	PRIN_LOG("+btpal_send_frame type: %d \n",bt_cb(skb)->pkt_type);
	type = HCI_COMMAND_TYPE;

	switch (bt_cb(skb)->pkt_type) {
		case HCI_COMMAND_PKT:
			type = HCI_COMMAND_TYPE;
			hdev->stat.cmd_tx++;
			break;

		case HCI_ACLDATA_PKT:
			type = HCI_ACL_TYPE;
			hdev->stat.acl_tx++;
			break;

		case HCI_SCODATA_PKT:
			/* we don't support SCO over the pal */
			kfree_skb(skb);
			return 0;
		default:
			A_ASSERT(FALSE);
			kfree_skb(skb);
			return 0;
	} 

	if (AR_DEBUG_LVL_CHECK(ATH_DEBUG_HCI_DUMP)) {
		A_PRINTF(">>> Send HCI %s packet len: %d\n",
				(type == HCI_COMMAND_TYPE) ? "COMMAND" : "ACL",
				skb->len);
		if (type == HCI_COMMAND_TYPE) {
			PRIN_LOG("    HCI Command: OGF:0x%X OCF:0x%X \r\n", 
					HCI_GET_OP_CODE(skb-data) >> 10, HCI_GET_OP_CODE(skb-data) & 0x3FF);
		}
		AR_DEBUG_PRINTBUF(skb->data,skb->len,"BT HCI SEND Packet Dump");
	}

	do {
		if(type == HCI_COMMAND_TYPE)
		{
			PRIN_LOG("HCI command");

			if (ar->arWmiReady == FALSE)
			{
				PRIN_LOG("WMI not ready ");
				break;
			}

			if (wmi_send_hci_cmd(ar->arWmi, skb->data, skb->len) != A_OK)
			{
				PRIN_LOG("send hci cmd error");
				break;
			}
		}
		else if(type == HCI_ACL_TYPE)
		{
			void *osbuf;

			PRIN_LOG("ACL data");
			if (ar->arWmiReady == FALSE)
			{
				PRIN_LOG("WMI not ready");
				break;
			}

			/* need to add WMI header so allocate a skb with more space */
			txSkb = bt_skb_alloc(TX_PACKET_RSV_OFFSET + WMI_MAX_TX_META_SZ + 
					sizeof(WMI_DATA_HDR) + skb->len, 
					GFP_ATOMIC);

			if (txSkb == NULL) {
				status = A_NO_MEMORY;
				PRIN_LOG("No memory");
				break;
			}

			bt_cb(txSkb)->pkt_type = bt_cb(skb)->pkt_type;
			txSkb->dev = (void *)pHciPalInfo->hdev;
			skb_reserve(txSkb, TX_PACKET_RSV_OFFSET + WMI_MAX_TX_META_SZ + sizeof(WMI_DATA_HDR));
			A_MEMCPY(txSkb->data, skb->data, skb->len);
			skb_put(txSkb,skb->len);
			/* Add WMI packet type */
			osbuf = (void *)txSkb;

			if (wmi_data_hdr_add(ar->arWmi, osbuf, DATA_MSGTYPE, 0, WMI_DATA_HDR_DATA_TYPE_ACL,0,NULL) != A_OK) {
				PRIN_LOG("XIOCTL_ACL_DATA - wmi_data_hdr_add failed\n");
			} else {
				/* Send data buffer over HTC */
				PRIN_LOG("acl data tx");
				ar6000_acl_data_tx(osbuf, ar->arNetDev);
			}
			txSkb = NULL;
		}
	} while (FALSE);

	if (txSkb != NULL) {
		PRIN_LOG("Free skb");
		kfree_skb(txSkb);    
	}
	kfree_skb(skb);        
	return 0;
}


/***********************************************
 * Unregister HCI device and free HCI device info
 ***********************************************/
static void bt_cleanup_hci_pal(ar6k_hci_pal_info_t *pHciPalInfo)
{   
	int err;      

	if (bt_check_bit(pHciPalInfo->ulFlags, HCI_REGISTERED)) {
		bt_clear_bit(pHciPalInfo->ulFlags, HCI_REGISTERED);
		clear_bit(HCI_RUNNING, &pHciPalInfo->hdev->flags);
		clear_bit(HCI_UP, &pHciPalInfo->hdev->flags);
		clear_bit(HCI_INIT, &pHciPalInfo->hdev->flags);   
		A_ASSERT(pHciPalInfo->hdev != NULL);
		/* unregister */
		PRIN_LOG("Unregister PAL device");
		if ((err = hci_unregister_dev(pHciPalInfo->hdev)) < 0) {
			PRIN_LOG("HCI PAL: failed to unregister with bluetooth %d\n",err);
		}          
	}

	if (pHciPalInfo->hdev != NULL) {
		kfree(pHciPalInfo->hdev);
		pHciPalInfo->hdev = NULL;
	}
}

/*********************************************************
 * Allocate HCI device and store in PAL private info structure.
 *********************************************************/
static A_STATUS bt_setup_hci_pal(ar6k_hci_pal_info_t *pHciPalInfo)
{
	A_STATUS status = A_OK;
	struct hci_dev *pHciDev = NULL;

	if (!setupbtdev) {
		return A_OK;    
	} 

	do {
		/* allocate a BT HCI struct for this device */
		pHciDev = hci_alloc_dev();
		if (NULL == pHciDev) {
			PRIN_LOG("HCI PAL driver - failed to allocate BT HCI struct \n");
			status = A_NO_MEMORY;
			break;
		}    

		/* save the device, we'll register this later */
		pHciPalInfo->hdev = pHciDev;       
                SET_HCI_BUS_TYPE(pHciDev, HCI_VIRTUAL, HCI_80211);
		pHciDev->driver_data = pHciPalInfo;
		pHciDev->open     = bt_open;
		pHciDev->close    = bt_close;
		pHciDev->send     = btpal_send_frame;
		pHciDev->ioctl    = bt_ioctl;
		pHciDev->flush    = bt_flush;
		pHciDev->destruct = bt_destruct;
		pHciDev->owner = THIS_MODULE; 
		/* driver is running in normal BT mode */
		PRIN_LOG("Normal mode enabled");
		bt_set_bit(pHciPalInfo->ulFlags, HCI_NORMAL_MODE);

	} while (FALSE);

	if (A_FAILED(status)) {
		bt_cleanup_hci_pal(pHciPalInfo);    
	}
	return status;
}

/**********************************************
 * Cleanup HCI device and free HCI PAL private info
 *********************************************/
void ar6k_cleanup_hci_pal(void *ar_p)
{
	AR_SOFTC_T *ar = (AR_SOFTC_T *)ar_p;
	ar6k_hci_pal_info_t *pHciPalInfo = (ar6k_hci_pal_info_t *)ar->hcipal_info;

	if (pHciPalInfo != NULL) {
		bt_cleanup_hci_pal(pHciPalInfo);   
		A_FREE(pHciPalInfo);
		ar->hcipal_info = NULL;
	}
}

/****************************
 *  Register HCI device
 ****************************/
static A_BOOL ar6k_pal_transport_ready(void *pHciPal)
{
	ar6k_hci_pal_info_t *pHciPalInfo = (ar6k_hci_pal_info_t *)pHciPal;

	PRIN_LOG("HCI device transport ready");
	if(pHciPalInfo == NULL)
		return FALSE;

	if (hci_register_dev(pHciPalInfo->hdev) < 0) {
		PRIN_LOG("Can't register HCI device");
		hci_free_dev(pHciPalInfo->hdev);
		return FALSE;
	}
	PRIN_LOG("HCI device registered");
	pHciPalInfo->ulFlags |= HCI_REGISTERED;
	return TRUE;
}

/**************************************************
 * Called from ar6k driver when command or ACL data 
 * packet is received. Pass the packet to bluetooth
 * stack via hci_recv_frame.
 **************************************************/
A_BOOL ar6k_pal_recv_pkt(void *pHciPal, void *osbuf)
{
	struct sk_buff *skb = (struct sk_buff *)osbuf;
	ar6k_hci_pal_info_t *pHciPalInfo;
	A_BOOL success = FALSE;
	A_UINT8 btType = 0;
	pHciPalInfo = (ar6k_hci_pal_info_t *)pHciPal;

	do {

		/* if normal mode is not enabled pass on to the stack
		 * by returning failure */
		if(!(pHciPalInfo->ulFlags & HCI_NORMAL_MODE))
		{
			PRIN_LOG("Normal mode not enabled");
			break;
		}

		if (!test_bit(HCI_RUNNING, &pHciPalInfo->hdev->flags)) {
			PRIN_LOG("HCI PAL: HCI - not running\n");
			break;
		}

		if(*((short *)A_NETBUF_DATA(skb)) == WMI_ACL_DATA_EVENTID)
			btType = HCI_ACLDATA_PKT;
		else
			btType = HCI_EVENT_PKT;
		/* pull 4 bytes which contains WMI packet type */
		A_NETBUF_PULL(skb, sizeof(int));
		bt_cb(skb)->pkt_type = btType;
		skb->dev = (void *)pHciPalInfo->hdev;

		/* pass the received event packet up the stack */    
		if (hci_recv_frame(skb) != 0) {
			PRIN_LOG("HCI PAL: hci_recv_frame failed \n");
			break;
		} else {
			PRIN_LOG("HCI PAL: Indicated RCV of type:%d, Length:%d \n",HCI_EVENT_PKT, skb->len);
		}
		PRIN_LOG("hci recv success");
		success = TRUE;
	}while(FALSE);
	return success;
}

/**********************************************************
 * HCI PAL init function called from ar6k when it is loaded..
 * Allocates PAL private info, stores the same in ar6k private info.
 * Registers a HCI device.
 * Registers packet receive callback function with ar6k 
 **********************************************************/
A_STATUS ar6k_setup_hci_pal(void *ar_p)
{
	A_STATUS status = A_OK;
	ar6k_hci_pal_info_t *pHciPalInfo;
	ar6k_pal_config_t ar6k_pal_config;
	AR_SOFTC_T *ar = (AR_SOFTC_T *)ar_p;

	do {

		pHciPalInfo = (ar6k_hci_pal_info_t *)A_MALLOC(sizeof(ar6k_hci_pal_info_t));

		if (NULL == pHciPalInfo) {
			status = A_NO_MEMORY;
			break;    
		}

		A_MEMZERO(pHciPalInfo, sizeof(ar6k_hci_pal_info_t));
		ar->hcipal_info = pHciPalInfo;
		pHciPalInfo->ar = ar;

		status = bt_setup_hci_pal(pHciPalInfo);
		if (A_FAILED(status)) {
			break;    
		}

		if(bt_check_bit(pHciPalInfo->ulFlags, HCI_NORMAL_MODE))
			PRIN_LOG("HCI PAL: running in normal mode... \n");    
		else 
			PRIN_LOG("HCI PAL: running in test mode... \n");     

		ar6k_pal_config.fpar6k_pal_recv_pkt = ar6k_pal_recv_pkt;
		register_pal_cb(&ar6k_pal_config);
		ar6k_pal_transport_ready(ar->hcipal_info);
	} while (FALSE);

	if (A_FAILED(status)) {
		ar6k_cleanup_hci_pal(ar);    
	}
	return status;
}
#else  /* AR6K_ENABLE_HCI_PAL */
A_STATUS ar6k_setup_hci_pal(void *ar_p)
{
	return A_OK;
}
void ar6k_cleanup_hci_pal(void *ar_p)
{
}
#endif /* AR6K_ENABLE_HCI_PAL */

#ifdef EXPORT_HCI_PAL_INTERFACE
/*****************************************************
 * Register init and callback function with ar6k
 * when PAL driver is a separate kernel module.
 ****************************************************/
A_STATUS ar6k_register_hci_pal(HCI_TRANSPORT_CALLBACKS *hciTransCallbacks);
static int __init pal_init_module(void)
{
	HCI_TRANSPORT_CALLBACKS hciTransCallbacks;

	hciTransCallbacks.setupTransport = ar6k_setup_hci_pal;
	hciTransCallbacks.cleanupTransport = ar6k_cleanup_hci_pal;

	if(ar6k_register_hci_pal(&hciTransCallbacks) != A_OK)
		return -ENODEV;

	return 0;
}

static void __exit pal_cleanup_module(void)
{
}

module_init(pal_init_module);
module_exit(pal_cleanup_module);
MODULE_LICENSE("Dual BSD/GPL");
#endif
