/*
 ***************************************************************************
 * MediaTek Inc.
 * 4F, No. 2 Technology 5th Rd.
 * Science-based Industrial Park
 * Hsin-chu, Taiwan, R.O.C.
 *
 * (c) Copyright 1997-2012, MediaTek, Inc.
 *
 * All rights reserved. MediaTek source code is an unpublished work and the
 * use of a copyright notice does not imply otherwise. This source code
 * contains confidential trade secret material of MediaTek. Any attemp
 * or participation in deciphering, decoding, reverse engineering or in any
 * way altering the source code is stricitly prohibited, unless the prior
 * written consent of MediaTek Technology, Inc. is obtained.
 ***************************************************************************

*/

#ifdef RTMP_RBUS_SUPPORT

#define RTMP_MODULE_OS

#include "rt_config.h"
#include "os/wbsys_res.h"

#if defined(CONFIG_RA_CLASSIFIER) && (!defined(CONFIG_RA_CLASSIFIER_MODULE))
extern int (*ra_classifier_init_func)(void);
extern void (*ra_classifier_release_func)(void);
extern struct proc_dir_entry *proc_ptr, *proc_ralink_wl_video;
#endif

/*
*
*/
#ifdef MEM_ALLOC_INFO_SUPPORT

extern MEM_INFO_LIST MemInfoList;
extern MEM_INFO_LIST PktInfoList;

static void free_meminfo(void)
{
	UINT32 memalctotal, pktalctotal;

	memalctotal = ShowMemAllocInfo();
	pktalctotal = ShowPktAllocInfo();

	if ((memalctotal != 0) || (pktalctotal != 0)) {
		MTWF_LOG(DBG_CAT_INIT, DBG_SUBCAT_ALL, DBG_LVL_ERROR,
				 ("Error: Memory leak!!\n"));
		ASSERT(0);
	}

	MIListExit(&MemInfoList);
	MIListExit(&PktInfoList);
}
#endif /* MEM_ALLOC_INFO_SUPPORT */

/*
*
*/
static int wbsys_probe(struct platform_device *pdev)
{
	struct resource *res;
	unsigned long base_addr;
	unsigned int dev_irq;
	struct net_device *net_dev;
	int rv;
	void *handle = NULL;
	RTMP_ADAPTER *pAd;
	RTMP_OS_NETDEV_OP_HOOK netDevHook;
	unsigned int Value;

	/*resource allocate*/
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	dev_irq = platform_get_irq(pdev, 0);
	base_addr = (unsigned long)devm_ioremap(&pdev->dev, res->start, resource_size(res));
	MTWF_LOG(DBG_CAT_INIT, DBG_SUBCAT_ALL, DBG_LVL_ERROR,
			 ("%s(): irq=%d,base_addr=%lx\n", __func__, dev_irq, base_addr));
	/*other global resource allocation*/
#ifdef MEM_ALLOC_INFO_SUPPORT
	MemInfoListInital();
#endif /* MEM_ALLOC_INFO_SUPPORT */
	/* RtmpDevInit */
	/* Allocate RTMP_ADAPTER adapter structure */
	os_alloc_mem(NULL, (UCHAR **)&handle, sizeof(struct os_cookie));

	if (!handle) {
		MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("Allocate memory for os_cookie failed!\n"));
		goto err_out;
	}

	os_zero_mem(handle, sizeof(struct os_cookie));
#ifdef OS_ABL_FUNC_SUPPORT
	/* get DRIVER operations */
	RTMP_DRV_OPS_FUNCTION(pRtmpDrvOps, NULL, NULL, NULL);
#endif /* OS_ABL_FUNC_SUPPORT */
	rv = RTMPAllocAdapterBlock(handle, (VOID **)&pAd);

	if (rv != NDIS_STATUS_SUCCESS) {
		MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, (" RTMPAllocAdapterBlock !=  NDIS_STATUS_SUCCESS\n"));
		os_free_mem(handle);
		goto err_out;
	}

	/* Here are the RTMP_ADAPTER structure with rbus-bus specific parameters. */
	pAd->PciHif.CSRBaseAddress = (PUCHAR)base_addr;
	RTMP_IO_READ32(pAd, TOP_HCR, &Value);
	pAd->ChipID = Value;
	/*link platform dev to pAd*/
	((POS_COOKIE)handle)->pci_dev = (void *)pdev;
	/*link platform dev to pAd*/
	((POS_COOKIE)handle)->pDev = &pdev->dev;
	RtmpRaDevCtrlInit(pAd, RTMP_DEV_INF_RBUS);
	/*Prepare netdev for wifi dev, NetDevInit */
	net_dev = RtmpPhyNetDevInit(pAd, &netDevHook);

	if (net_dev == NULL)
		goto err_out_free_radev;

	/* Interrupt IRQ number */
	net_dev->irq = dev_irq;
	/* Save CSR virtual address and irq to device structure */
	net_dev->base_addr = base_addr;
	/*link net_dev to platform_dev*/
	platform_set_drvdata(pdev, net_dev);
	/*link platform_dev to net_dev*/
	SET_NETDEV_DEV(net_dev, &pdev->dev);
	/*All done, it's time to register the net device to kernel. */
	rv = RtmpOSNetDevAttach(pAd->OpMode, net_dev, &netDevHook);

	if (rv) {
		MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("failed to call RtmpOSNetDevAttach(), rv=%d!\n", rv));
		goto err_out_free_netdev;
	}

	wl_proc_init();
	MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_TRACE, ("%s: at CSR addr 0x%lx, IRQ %ld.\n",
			 net_dev->name, (ULONG)base_addr, (long int)net_dev->irq));
	MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_TRACE, ("<=== wifi probe\n"));
#if defined(CONFIG_RA_CLASSIFIER) && (!defined(CONFIG_RA_CLASSIFIER_MODULE))
	proc_ptr = proc_ralink_wl_video;

	if (ra_classifier_init_func != NULL)
		ra_classifier_init_func();

#endif
	return 0;
err_out_free_netdev:
	RtmpOSNetDevFree(net_dev);
#ifdef MEM_ALLOC_INFO_SUPPORT
	free_meminfo();
#endif /* MEM_ALLOC_INFO_SUPPORT */
err_out_free_radev:
	/* free RTMP_ADAPTER strcuture and os_cookie*/
	RTMPFreeAdapter(pAd);
err_out:
	return -ENODEV;
}

/*
*
*/
static int wbsys_remove(struct platform_device *pdev)
{
	struct net_device *net_dev = platform_get_drvdata(pdev);
	RTMP_ADAPTER *pAd;

	if (net_dev == NULL)
		return -ENODEV;

	/* pAd = net_dev->priv*/
	GET_PAD_FROM_NET_DEV(pAd, net_dev);

	if (pAd != NULL) {
		RtmpPhyNetDevExit(pAd, net_dev);
		RtmpRaDevCtrlExit(pAd);
	} else
		RtmpOSNetDevDetach(net_dev);

	/* Free the root net_device. */
	RtmpOSNetDevFree(net_dev);
#if defined(CONFIG_RA_CLASSIFIER) && (!defined(CONFIG_RA_CLASSIFIER_MODULE))
	proc_ptr = proc_ralink_wl_video;

	if (ra_classifier_release_func != NULL)
		ra_classifier_release_func();

#endif
	wl_proc_exit();
#ifdef MEM_ALLOC_INFO_SUPPORT
	free_meminfo();
#endif /* MEM_ALLOC_INFO_SUPPORT */
	return 0;
}

static int wbsys_suspend(struct platform_device *pdev, pm_message_t state)
{
	INT32 retval = 0;
	struct net_device *net_dev = platform_get_drvdata(pdev);
	VOID *pAd = NULL;

	MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("===>%s()\n", __func__));

	if (net_dev == NULL)
		MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("net_dev == NULL!\n"));
	else {
		UINT32 IfNum;

		GET_PAD_FROM_NET_DEV(pAd, net_dev);
		/* we can not use IFF_UP because ra0 down but ra1 up */
		/* and 1 suspend/resume function for 1 module, not for each interface */
		/* so Linux will call suspend/resume function once */
		RTMP_DRIVER_VIRTUAL_INF_NUM_GET(pAd, &IfNum);

		MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("%s::IfNum=%d\n", __func__, IfNum));

		if (IfNum > 0) {
			/* avoid users do suspend after interface is down */
			/* stop interface */
			netif_carrier_off(net_dev);
			netif_stop_queue(net_dev);
			/* mark device as removed from system and therefore no longer available */
			netif_device_detach(net_dev);
			RTMP_DRIVER_RBUS_SUSPEND(pAd);
			RT_MOD_HNAT_DEREG(net_dev);
			RT_MOD_DEC_USE_COUNT();
		}
	}

	MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("<===%s()\n", __func__));
	return retval;
}

static int wbsys_resume(struct platform_device *pdev)
{
	struct net_device *net_dev = platform_get_drvdata(pdev);
	VOID *pAd = NULL;

	MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("===>%s()\n", __func__));

	if (net_dev == NULL)
		MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("net_dev == NULL!\n"));
	else
		GET_PAD_FROM_NET_DEV(pAd, net_dev);

	if (pAd != NULL) {
		UINT32 IfNum;
		/*
		 *	we can not use IFF_UP because ra0 down but ra1 up
		 *	and 1 suspend/resume function for 1 module, not for each interface
		 *	so Linux will call suspend/resume function once
		 */
		RTMP_DRIVER_VIRTUAL_INF_NUM_GET(pAd, &IfNum);

		if (IfNum > 0) {
			/* mark device as attached from system and restart if needed */
			netif_device_attach(net_dev);
			/* increase MODULE use count */
			RT_MOD_INC_USE_COUNT();
			RT_MOD_HNAT_REG(net_dev);
			RTMP_DRIVER_RBUS_RESUME(pAd);
			netif_start_queue(net_dev);
			netif_carrier_on(net_dev);
			netif_wake_queue(net_dev);
		}
	}

	MTWF_LOG(DBG_CAT_HIF, CATHIF_PCI, DBG_LVL_ERROR, ("<=== %s()\n", __func__));
	return 0;
}

/*
* global resource preparing
*/
static struct platform_driver wbsys_driver = {
	.probe  = wbsys_probe,
	.remove = wbsys_remove,
#ifdef CONFIG_PM
	.suspend = wbsys_suspend,
	.resume = wbsys_resume,
#endif /* CONFIG_PM */
	.driver = {
		.name   = wbsys_string,
		.owner  = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = wbsys_of_ids,
#endif /*CONFIG_OF*/
	},
};

/*
*
 */
int __init wbsys_module_init(void)
{
	int ret;

	wbsys_dev_alloc(&wbsys_dev);
	ret = platform_driver_register(&wbsys_driver);
	return ret;
}


VOID __exit wbsys_module_exit(void)
{
	wbsys_dev_release(&wbsys_dev);
	platform_driver_unregister(&wbsys_driver);
}

#ifndef MULTI_INF_SUPPORT
module_init(wbsys_module_init);
module_exit(wbsys_module_exit);

MODULE_DESCRIPTION("Register for MTK WIFI driver HIF is internal bus");
MODULE_SUPPORTED_DEVICE("mtk wbsys platform driver");
#endif

#endif /* RTMP_RBUS_SUPPORT */

