/*-----------------------------------------------------------------------------
 * drivers/usb/mcd/usb_mcd_device.c
 * 
 * USB controller driver
 * 
 * Copyright 2009 Sony Corporation
 *
 * This file is part of the Inventra Controller Driver for Linux.
 * 
 * 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., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *---------------------------------------------------------------------------*/

#include <linux/module.h>
#include <linux/device.h>
#include <linux/memory.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>
#include <linux/usb_gadget.h>
#include <linux/usb/mcd/usb_otg_control.h>
#include "../core/hub.h"
#include "../core/hcd.h"

#include "usb_mcd_version.h"
#include "usb_mcd_cnf.h"
#include "usb_mcd_debug.h"
#include "usb_mcd_dma.h"
#include "usb_mcd_otg.h"
#include "usb_mcd_gadget.h"
#include "usb_mcd_host.h"
#include "usb_mcd_device.h"
#include "usb_mcd_access.h"
#include "usb_mcd_musbhdrc.h"

static struct list_head mdev_list = LIST_HEAD_INIT(mdev_list);
static const struct mcd_host_ops *mcd_attached_host_ops = NULL;
static const u8 get_desc_req_data[]= {
	0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 000 };

static irqreturn_t mcd_device_srp_irq(int irq, void *data)
{
	struct mcd_device *mdev = (struct mcd_device *)data;
	void *regs = mdev_link_reg(mdev);
	union usb_otg_event otg_event;
	irqreturn_t status = IRQ_NONE;
	u8 intrusb;

	intrusb = MCD_READ8(regs, MCD_O_HDRC_INTRUSB);
	intrusb &= MCD_READ8(regs, MCD_O_HDRC_INTRUSBE);

	if (intrusb & MCD_M_INTR_SESSREQ) {
		otg_event.type = USB_OTG_EVENT_TYPE_SRP;
		mcd_otg_control_signal_interrupt(&mdev->otgi.otgc, &otg_event);
		status = IRQ_HANDLED;
	}

	return status;
}

inline int device_request_srp_irq(struct mcd_device *mdev)
{
	return request_irq(mdev->irqs[MCD_LINK_IRQ], mcd_device_srp_irq,
			   IRQF_DISABLED, "mcd_srp", (void*)mdev);
}

inline void device_free_srp_irq(struct mcd_device *mdev)
{
	free_irq(mdev->irqs[MCD_LINK_IRQ], (void*)mdev);
}

void mcd_device_init(struct mcd_device *mdev)
{
	void *regs;
	u8 power;

	regs = mdev_link_reg(mdev);

	MCD_WRITE8(regs, MCD_O_HDRC_FADDR, 0);
	power = MCD_M_POWER_ENSUSPEND;
	if (mdev->attribute & MCD_DEVICE_ATTR_HS_ENABLE) {
		power |= MCD_M_POWER_HSENAB;
	}
	MCD_WRITE8(regs, MCD_O_HDRC_POWER, power);
	MCD_WRITE8(regs, MCD_O_HDRC_INTRTXE, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_INTRRXE, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_INTRUSBE, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_FRAME, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_TESTMODE, 0);
}

struct mcd_device *mcd_device_probe(struct device *dev, void **regs, int *irqs)
{
	struct mcd_device *mdev;
	int status;
	u8 i;

	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
	if (!mdev) {
		return NULL;
	}

	spin_lock_init(&mdev->lock);

	memcpy(mdev->regs, regs, sizeof(mdev->regs));
	memcpy(mdev->irqs, irqs, sizeof(mdev->irqs));

	mdev->dmac = mcd_dma_new_controller((u8 *)regs[MCD_LINK_REG],
					    MCD_NUM_DMACH, irqs[0]);
	if (!mdev->dmac) {
		kfree(mdev);
		return NULL;
	}

	mdev->attribute = MCD_DEVICE_ATTR_HS_ENABLE;

	if (mcd_attached_host_ops) {
		mdev->hcd = mcd_attached_host_ops->create_hcd(mdev);
		if (!mdev->hcd) {
			goto probe_error;
		}
		hcd_to_mhost(mdev->hcd)->mdev = mdev;
	}

	status = mcd_board_map(mdev);
	if (status < 0) {
		goto probe_error;
	}
	mcd_proc_create_fs(mdev, "musbhdrc0");

	INIT_LIST_HEAD(&mdev->mdev_list);
	list_add_tail(&mdev->mdev_list, &mdev_list);

	mcd_first_gadget = &mdev->gadget;

	for (i = 0; i < MCD_NUM_EPS; i++) {
		spin_lock_init(&mdev->gadget.ep[i].Lock);
	}

	return mdev;

probe_error:
	mcd_dma_destroy_controller(mdev->dmac);
	kfree(mdev);
	return NULL;
}

void mcd_device_remove(struct mcd_device *mdev)
{
	list_del(&mdev->mdev_list);

	mcd_proc_delete_fs(mdev);

	mcd_board_unmap(mdev);

	if (mdev->hcd) {
		mcd_attached_host_ops->put_hcd(mdev->hcd);
	}

	mcd_dma_destroy_controller(mdev->dmac);

	mcd_first_gadget = NULL;

	kfree(mdev);
}

int mcd_device_shutdown(struct mcd_device *mdev)
{
	if (mdev->hcd) {
		struct usb_hcd *hcd = mdev->hcd;
		if (hcd->driver->shutdown) {
			hcd->driver->shutdown(hcd);
		}
	}

	mcd_otg_control_shutdown(&mdev->otgi.otgc);

	return 0;
}

int mcd_device_suspend(struct mcd_device *mdev, pm_message_t message)
{
	int ret = 0;
	struct mcd_otg_intdat *otgc_intdat = &mdev->otgi;

	if (mdev->hcd && mdev->hcd->state != HC_STATE_HALT) {
		struct usb_hcd *hcd = mdev->hcd;

		if (hcd->self.root_hub->dev.power.power_state.event
		    == PM_EVENT_ON) {
			ret = -EBUSY;
			goto done_suspend;
		}

		if (hcd->driver->suspend) {
			ret = hcd->driver->suspend(hcd, message);
			suspend_report_result(hcd->driver->suspend, ret);
			if (ret < 0) {
				goto done_suspend;
			}
		}
	}

	mdev->dev->power.power_state = PMSG_SUSPEND;

	if (otgc_intdat->state != MCD_OTGC_S_STOP) {
		ret = mcd_board_exit_timer(mdev);
		if (ret < 0) {
			goto done_suspend;
		}
	}

	ret = mcd_otg_control_suspend(&mdev->otgi.otgc, message);

done_suspend:
	return ret;
}

int mcd_device_resume(struct mcd_device *mdev)
{
	int ret = 0;
	struct mcd_otg_intdat *otgc_intdat = &mdev->otgi;

	mcd_otg_control_resume(&mdev->otgi.otgc);

	mdev->dev->power.power_state = PMSG_ON;

	if (otgc_intdat->state != MCD_OTGC_S_STOP) {
		ret = mcd_board_init_timer(mdev);
		if (ret < 0) {
			goto done_resume;
		}
	}

	if (mdev->hcd) {
		struct usb_hcd *hcd = mdev->hcd;
		if (hcd->state != HC_STATE_SUSPENDED) {
			dev_dbg (hcd->self.controller, 
				 "can't resume, not suspended!\n");
			goto done_resume;
		}
		if (hcd->driver->resume) {
			ret = hcd->driver->resume(hcd);
			if (ret < 0) {
				dev_err (hcd->self.controller,
					 "usb_mcd post-resume error %d!\n", ret);
				goto done_resume;
			}
		}
	}

done_resume:
	/* irq is disabled after resume. so... */
	if (otgc_intdat->state == MCD_OTGC_S_STOP) {
		;
	} else if (otgc_intdat->state == MCD_OTGC_S_AB_IDLE) {
		device_free_srp_irq(mdev);
		device_request_srp_irq(mdev);
	} else {
		mcd_free_irq(mdev);
		mcd_request_irq(mdev);
	}

	return ret;
}

int mcd_device_request_session(struct mcd_device *mdev)
{
	void *regs = mdev_link_reg(mdev);

	MCD_WRITE8(regs, MCD_O_HDRC_POWER, MCD_M_POWER_ENSUSPEND);
	MCD_WRITE8(regs, MCD_O_HDRC_INTRUSBE, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_INTRTXE, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_INTRRXE, 0);
	MCD_WRITE8(regs, MCD_O_HDRC_DEVCTL, MCD_M_DEVCTL_SESSION);

	return 0;
}

int mcd_device_end_dpulse(struct mcd_device *mdev)
{
	void *regs = mdev_link_reg(mdev);

	MCD_WRITE8(regs, MCD_O_HDRC_POWER, MCD_M_POWER_ENSUSPEND);

	return 0;
}

int mcd_device_start(struct mcd_device *mdev)
{
	device_request_srp_irq(mdev);

	return mcd_board_init_timer(mdev);
}

int mcd_device_stop(struct mcd_device *mdev)
{
	device_free_srp_irq(mdev);

	return mcd_board_exit_timer(mdev);
}

int mcd_device_attach_host(const struct mcd_host_ops *ops)
{
	struct mcd_device *mdev;

	if (mcd_attached_host_ops) {
		return -EBUSY;
	}

	if (!ops) {
		return -EINVAL;
	}

	list_for_each_entry(mdev, &mdev_list, mdev_list) {
		mdev->hcd = ops->create_hcd(mdev);
		/* mcd_pci_probeもしくはmcd_platform_probeにて設定した */
		/* drvdataがusb_create_hcd内にてdrvdataが書き換わってしまう */
		/* そのため、ここで再設定する */
		dev_set_drvdata(mdev->dev, mdev);
		if (!mdev->hcd) {
			list_for_each_entry(mdev, &mdev_list, mdev_list) {
				if (mdev->hcd) {
					ops->put_hcd(mdev->hcd);
					mdev->hcd = NULL;
				}
			}
			return -ENOMEM;
		}
	}

	mcd_attached_host_ops = ops;

	return 0;
}
EXPORT_SYMBOL(mcd_device_attach_host);

void mcd_device_detach_host(void)
{
	struct mcd_device *mdev;

	list_for_each_entry(mdev, &mdev_list, mdev_list) {
		if (mdev->hcd) {
			if (mdev->hcd->state != HC_STATE_HALT) {
				/* Hostモード時rmmod処理 */
				mcd_attached_host_ops->remove_hcd(mdev->hcd);
				mcd_free_irq(mdev);
				device_request_srp_irq(mdev);
			}
			mcd_attached_host_ops->put_hcd(mdev->hcd);
			mdev->hcd = NULL;
		}
	}

	mcd_attached_host_ops = NULL;
}
EXPORT_SYMBOL(mcd_device_detach_host);

static irqreturn_t mcd_device_dmac_irq(int irq, void *data)
{
	struct mcd_device *mdev = (struct mcd_device *)data;

	return mdev->dmac->pfDmaControllerIsr(irq, mdev->dmac);
}

int mcd_device_start_gadget(struct mcd_device *mdev)
{
	int status;

	device_free_srp_irq(mdev);

	mcd_gadget_start(&mdev->gadget);

	mdev->irq_hdlrs[MCD_LINK_IRQ] = mcd_gadget_link_irq;
	mdev->irq_hdlrs[MCD_DMAC_IRQ] = mcd_device_dmac_irq;
	status = mcd_request_irq(mdev);
	if (status < 0) {
		return status;
	}

	return 0;
}

int mcd_device_stop_gadget(struct mcd_device *mdev)
{
	mcd_free_irq(mdev);
	mcd_gadget_stop(&mdev->gadget);

	device_request_srp_irq(mdev);

	return 0;
}

int mcd_device_start_host(struct mcd_device *mdev)
{
	int status;

	if (!mdev->hcd) {
		return -ENODEV;
	}

	device_free_srp_irq(mdev);

	mdev->hcd->driver->reset(mdev->hcd);

	mdev->irq_hdlrs[MCD_LINK_IRQ] = mcd_attached_host_ops->link_irq;
	mdev->irq_hdlrs[MCD_DMAC_IRQ] = mcd_device_dmac_irq;
	status = mcd_request_irq(mdev);
	if (status < 0) {
		device_request_srp_irq(mdev);
		return status;
	}

	status = mcd_attached_host_ops->add_hcd(mdev->hcd, 0, 0);
	if (status < 0) {
		mcd_free_irq(mdev);
		device_request_srp_irq(mdev);
		return status;
	}

	return 0;
}

int mcd_device_stop_host(struct mcd_device *mdev)
{
	if (mdev->hcd && mdev->hcd->state != HC_STATE_HALT) {
		/* 既にrmmodされていない場合 */
		mcd_attached_host_ops->remove_hcd(mdev->hcd);
		mcd_free_irq(mdev);
		device_request_srp_irq(mdev);
	}

	return 0;
}

int mcd_device_notify_vbus_change(struct mcd_device *mdev,
				  u8 old_vbus, u8 new_vbus)
{
	void *regs = mdev_link_reg(mdev);
	u8 power = MCD_READ8(regs, MCD_O_HDRC_POWER);

	if (old_vbus > new_vbus &&
	    new_vbus == USB_OTG_VBUS_STAT_LOW &&
	    power & MCD_M_POWER_SOFTCONN &&
	    (mdev->otgi.state == MCD_OTGC_S_B_PERIPH ||
	     mdev->otgi.state == MCD_OTGC_S_A_PERIPH)) {
		mcd_gadget_disconnect(&mdev->gadget);
	}

	return 0;
}

enum usb_otg_vbus_stat mcd_device_check_vbus(struct mcd_device *mdev)
{
	void *regs = mdev_link_reg(mdev);
	enum usb_otg_vbus_stat vbus_stat;
	uint8_t devctl = MCD_READ8(regs, MCD_O_HDRC_DEVCTL);

	switch ((devctl & MCD_M_DEVCTL_VBUS) >> MCD_S_DEVCTL_VBUS) {
	case 0x1:
		vbus_stat = USB_OTG_VBUS_STAT_LOW;
		break;
	case 0x2:
		vbus_stat = USB_OTG_VBUS_STAT_SESS;
		break;
	case 0x3:
		vbus_stat = USB_OTG_VBUS_STAT_VALID;
		break;
	default:		/* 0x0 */
		vbus_stat = USB_OTG_VBUS_STAT_OFF;
		break;
	}

	return vbus_stat;
}

int mcd_device_set_speed(struct mcd_device *mdev, unsigned int speed)
{
	if (speed == USB_OTG_HS_ENABLE) {
		DBG(2, "HS_ENABLE\n");
		mdev->attribute |= MCD_DEVICE_ATTR_HS_ENABLE;
	} else {
		DBG(2, "HS_DISABLE\n");
		mdev->attribute &= ~MCD_DEVICE_ATTR_HS_ENABLE;
	}

	return 0;
}

enum usb_otg_control_speed mcd_device_get_speed(struct mcd_device *mdev)
{
	enum usb_otg_control_speed speed;
	void *regs = mdev_link_reg(mdev);
	uint8_t power = MCD_READ8(regs, MCD_O_HDRC_POWER);
	uint8_t devctl = MCD_READ8(regs, MCD_O_HDRC_DEVCTL);

	if (power & MCD_M_POWER_HSMODE) {
		speed = USB_OTG_SPEED_HS;
	} else if (devctl & MCD_M_DEVCTL_HM) {
		/* Host時にはLowSpeedもサポート */
		if (devctl & MCD_M_DEVCTL_FSDEV) {
			speed = USB_OTG_SPEED_FS;
		} else {
			speed = USB_OTG_SPEED_LS;
		}
	} else {
		/* Gadget時にはHS出ない場合は必ずFS */
		speed = USB_OTG_SPEED_FS;
	}

	return speed;
}

int mcd_device_set_test_mode(struct mcd_device *mdev,
			     enum usb_otg_control_test_mode test_mode)
{
	uint8_t power;
	void *regs = mdev_link_reg(mdev);
	uint8_t testreg = MCD_READ8(regs, MCD_O_HDRC_TESTMODE);

	switch (test_mode) {
	case USB_OTG_TEST_MODE_NORMAL:
		testreg = 0;
		break;
	case USB_OTG_TEST_MODE_G_FORCE_PACKET:
		power = MCD_READ8(regs, MCD_O_HDRC_POWER);
		power &= ~MCD_M_POWER_ENSUSPEND;
		MCD_WRITE8(regs, MCD_O_HDRC_POWER, power);
		mdelay(50);	/* PHY再起動, Chirp待ち */
	case USB_OTG_TEST_MODE_G_PACKET:
	case USB_OTG_TEST_MODE_H_PACKET:
		mcd_select_end(regs, MCD_HEP_EP0_IDX);
		mcd_device_load_fifo(regs, MCD_HEP_EP0_IDX,
				     sizeof(mcd_test_packet),
				     (u8 *)mcd_test_packet);
		testreg |= MCD_M_TEST_PACKET;
		MCD_WRITE8(regs, MCD_O_HDRC_TESTMODE, testreg);
		MCD_WRITE_CSR16(regs, MCD_O_HDRC_CSR0, MCD_HEP_EP0_IDX,
				MCD_M_CSR0_TXPKTRDY);
		goto no_set;
	case USB_OTG_TEST_MODE_G_K:
	case USB_OTG_TEST_MODE_H_K:
		testreg |= MCD_M_TEST_K;
		break;
	case USB_OTG_TEST_MODE_G_J:
	case USB_OTG_TEST_MODE_H_J:
		testreg |= MCD_M_TEST_J;
		break;
	case USB_OTG_TEST_MODE_G_SE0_NAK:
	case USB_OTG_TEST_MODE_H_SE0_NAK:
		testreg |= MCD_M_TEST_SE0_NAK;
		break;
	case USB_OTG_TEST_MODE_H_SETUP:
		mcd_select_end(regs, MCD_HEP_EP0_IDX);
		mcd_device_load_fifo(regs, MCD_HEP_EP0_IDX,
				     sizeof(get_desc_req_data),
				     (u8 *)get_desc_req_data);
		MCD_WRITE_CSR16(regs, MCD_O_HDRC_CSR0, MCD_HEP_EP0_IDX,
				MCD_M_CSR0_H_SETUPPKT | MCD_M_CSR0_TXPKTRDY);
		goto no_set;
	case USB_OTG_TEST_MODE_H_IN:
		mcd_select_end(regs, MCD_HEP_EP0_IDX);
		MCD_WRITE_CSR16(regs, MCD_O_HDRC_CSR0, MCD_HEP_EP0_IDX,
				MCD_M_CSR0_H_DISPING | MCD_M_CSR0_H_REQPKT);
		goto no_set;
	case USB_OTG_TEST_MODE_H_AS:
		goto no_set;
	case USB_OTG_TEST_MODE_H_LS:
		testreg |= MCD_M_TEST_FORCE_HOST;
		testreg &= ~(MCD_M_TEST_FORCE_HS | MCD_M_TEST_FORCE_FS);
		break;
	case USB_OTG_TEST_MODE_H_FS:
		testreg |= MCD_M_TEST_FORCE_HOST | MCD_M_TEST_FORCE_FS;
		testreg &= ~(MCD_M_TEST_FORCE_HS);
		break;
	case USB_OTG_TEST_MODE_H_HS:
		testreg |= MCD_M_TEST_FORCE_HOST | MCD_M_TEST_FORCE_HS;
		testreg &= ~(MCD_M_TEST_FORCE_FS);
		break;
	default:
		return -EINVAL;
	}

	MCD_WRITE8(regs, MCD_O_HDRC_TESTMODE, testreg);

      no_set:
	return 0;
}

enum usb_otg_control_test_mode mcd_device_get_test_mode(struct mcd_device *mdev)
{
	enum usb_otg_control_test_mode test_mode;
	void *regs = mdev_link_reg(mdev);
	uint8_t testreg = MCD_READ8(regs, MCD_O_HDRC_TESTMODE);

	if (testreg == MCD_M_TEST_PACKET) {
		test_mode = USB_OTG_TEST_MODE_G_PACKET;
	} else if (testreg == MCD_M_TEST_K) {
		test_mode = USB_OTG_TEST_MODE_G_K;
	} else if (testreg == MCD_M_TEST_J) {
		test_mode = USB_OTG_TEST_MODE_G_J;
	} else if (testreg == MCD_M_TEST_SE0_NAK) {
		test_mode = USB_OTG_TEST_MODE_G_SE0_NAK;
	} else if (testreg == (MCD_M_TEST_FORCE_HS | MCD_M_TEST_FORCE_HOST)) {
		test_mode = USB_OTG_TEST_MODE_H_HS;
	} else if (testreg == (MCD_M_TEST_FORCE_FS | MCD_M_TEST_FORCE_HOST)) {
		test_mode = USB_OTG_TEST_MODE_H_FS;
	} else if (testreg == MCD_M_TEST_FORCE_HOST) {
		test_mode = USB_OTG_TEST_MODE_H_LS;
	} else if (testreg) {
		/* error */
		test_mode = 0;
	} else {
		test_mode = USB_OTG_TEST_MODE_NORMAL;
	}

	return test_mode;
}

/**
 * Load an HDRC FIFO
 *
 * @param regs base address of HDRC
 * @param index local endpoint
 * @param len how many bytes to load
 * @param src data source buffer
 */
void mcd_device_load_fifo(void *regs, u8 index, u32 len, u8* src)
{
	u16 fidx, fidx32;
	u16 len32 = len >> 2;
	u8 offset = MCD_FIFO_OFFSET(index);

	/* doublewords when possible */
	for (fidx = fidx32 = 0; fidx32 < len32; fidx32++, fidx += 4) {
		MCD_WRITE32(regs, offset, *((u32 *)&src[fidx]));
	}
	for (; fidx < len; fidx++) {
		MCD_WRITE8(regs, offset, src[fidx]);
	}
}
EXPORT_SYMBOL(mcd_device_load_fifo);

/**
 * Unload an HDRC FIFO
 *
 * @param regs base address of HDRC
 * @param index local endpoint
 * @param len how many bytes to unload
 * @param dest data buffer
 */
void mcd_device_unload_fifo(void *regs, u8 index, u32 len, u8* dst)
{
	u16 fidx = 0, fidx32;
	u16 len32 = len >> 2;
	u8 offset = MCD_FIFO_OFFSET(index);

	/* doublewords when possible */
	for (fidx = fidx32 = 0; fidx32 < len32; fidx32++, fidx += 4) {
		*((u32 *)&dst[fidx]) = MCD_READ32(regs, offset);
	}

	while (fidx < len) {
		dst[fidx++] = MCD_READ8(regs, offset);
	}
}
EXPORT_SYMBOL(mcd_device_unload_fifo);

#if defined(MCD_DEFAULT_TIMER)

/*
 *	Timer
 */
struct mcd_default_timer {
	struct timer_list timer;
	struct mcd_device *mdev;
	void (*func)(struct mcd_device *, void *);
	void *data;
};

static void timer_function(unsigned long ptr)
{
	struct mcd_default_timer *mdev_timer;

	mdev_timer = (struct mcd_default_timer *)ptr;
	mdev_timer->func(mdev_timer->mdev, mdev_timer->data);
}

int mcd_board_init_timer(struct mcd_device *mdev)
{
	struct mcd_default_timer *mdev_timer;

	mdev->timer.data = (void *)kmalloc(sizeof(*mdev_timer), GFP_KERNEL);
	if (!mdev->timer.data) {
		return -ENOMEM;
	}

	mdev_timer = (struct mcd_default_timer *)mdev->timer.data;
	init_timer(&mdev_timer->timer);
	mdev_timer->mdev = mdev;
	mdev_timer->timer.function = timer_function;
	mdev_timer->timer.data = (unsigned long)mdev_timer;

	return 0;
}

int mcd_board_exit_timer(struct mcd_device *mdev)
{
	struct mcd_default_timer *mdev_timer;

	mdev_timer = (struct mcd_default_timer *)mdev->timer.data;
	del_timer(&mdev_timer->timer);

	kfree(mdev_timer);
	mdev->timer.data = NULL;

	return 0;
}

/* 1[mcd_time_t] is 1[us] */
u32 mcd_board_time2usec(mcd_time_t time)
{
	return (u32)time;
}
EXPORT_SYMBOL(mcd_board_time2usec);

mcd_time_t mcd_board_usec2time(u32 usec)
{
	return (mcd_time_t)usec;
}
EXPORT_SYMBOL(mcd_board_usec2time);

mcd_time_t mcd_board_get_time(struct mcd_device *mdev)
{
	return (mcd_time_t)jiffies_to_usecs(jiffies);
}
EXPORT_SYMBOL(mcd_board_get_time);

int mcd_board_start_timer(struct mcd_device *mdev, mcd_time_t expire,
			  void (*func)(struct mcd_device *, void *),
			  void *data)
{
	struct mcd_default_timer *mdev_timer;

	if (func) {
		mdev_timer = (struct mcd_default_timer *)mdev->timer.data;
		mdev_timer->func = func;
		mdev_timer->data = data;
		mod_timer(&mdev_timer->timer,
			  jiffies +
			  usecs_to_jiffies(mcd_board_time2usec(expire)) + 1);
	}

	return 0;
}
EXPORT_SYMBOL(mcd_board_start_timer);

void mcd_board_stop_timer(struct mcd_device *mdev, void *data)
{
	struct mcd_default_timer *mdev_timer;

	mdev_timer = (struct mcd_default_timer *)mdev->timer.data;
	del_timer(&mdev_timer->timer);
}
EXPORT_SYMBOL(mcd_board_stop_timer);

#endif				/* defined(MCD_DEFAULT_TIMER) */

MODULE_DESCRIPTION(DRIVER_GADGET_INFO);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_LICENSE("GPL");
