/*--------------------------------------------------------------------------*
 * drivers/usb/mcd/usb_mcd_xgadget.c
 * 
 * USB controller driver
 * 
 * Copyright 2005,2006 Sony Corporation
 * 
 * This code is based on musb_xgadget.c,
 * Copyright 2005 Mentor Graphics Corporation
 *
 * This file is part of the Inventra Controller Driver for Linux.
 *
 * The Inventra Controller Driver for Linux is free software; you
 * can redistribute it and/or modify it under the terms of the GNU
 * General Public License version 2 as published by the Free Software
 * Foundation.
 *
 * The Inventra Controller Driver for Linux 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 The Inventra Controller Driver for Linux ; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA  02111-1307  USA
 *
 * ANY DOWNLOAD, USE, REPRODUCTION, MODIFICATION OR DISTRIBUTION
 * OF THIS DRIVER INDICATES YOUR COMPLETE AND UNCONDITIONAL ACCEPTANCE
 * OF THOSE TERMS.THIS DRIVER IS PROVIDED "AS IS" AND MENTOR GRAPHICS
 * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, RELATED TO THIS DRIVER.
 * MENTOR GRAPHICS SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY; FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  MENTOR GRAPHICS DOES NOT PROVIDE SUPPORT
 * SERVICES OR UPDATES FOR THIS DRIVER, EVEN IF YOU ARE A MENTOR
 * GRAPHICS SUPPORT CUSTOMER.
 *---------------------------------------------------------------------------*/

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/init.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_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"

/**********************************************************************
 *
 *
 */

/*
 */
int mcd_gadget_queue_length(struct list_head *lh)
{
	int count = 0;
	struct list_head *p = lh;

	while (p && (p->next != lh)) {
		count++;
		p = p->next;
	}

	return count;
}

/**********************************************************************
 *
 *
 */

/* 
 * @pre pThis!=NULL
 */
void mcd_gadget_resume(struct mcd_gadget *mgadget)
{
	struct usb_otg_control *otg_control;
	bool suspended;

	DBG(2, "<==\n");

	otg_control = &mgadget_to_mdev(mgadget)->otgi.otgc;
	suspended = mcd_otg_control_check_suspend(otg_control, false);
	if (suspended) {
		DBG(2, "Resume ignored\n");
		return;
	}

	mgadget->bSuspended = false;
	DBG(2, "Resumed\n");

	if (mgadget->pGadgetDriver && mgadget->pGadgetDriver->resume) {
		mgadget->pGadgetDriver->resume(mgadget->pGadget);
	}

	DBG(2, "==>\n");
}

/* 
 * @pre pThis!=NULL
 */
void mcd_gadget_suspend(struct mcd_gadget *mgadget)
{
	struct usb_otg_control *otg_control;
	bool suspended;
	DBG(2, "<==\n");

	otg_control = &mgadget_to_mdev(mgadget)->otgi.otgc;
	suspended = mcd_otg_control_check_suspend(otg_control, true);
	if (!suspended) {
		DBG(2, "suspend ignored\n");
		return;
	}

	mgadget->bSuspended = true;
	DBG(2, "Suspended\n");

	if (mgadget->pGadgetDriver && mgadget->pGadgetDriver->suspend) {
		mgadget->pGadgetDriver->suspend(mgadget->pGadget);
	}

	DBG(2, "==>\n");
}

/* 
 * @pre pThis!=NULL
 */
void mcd_gadget_disconnect(struct mcd_gadget *mgadget)
{
	uint8_t bEnd;

	DBG(2, "<==\n");
	if (mgadget->pGadgetDriver) {
		/* 全EP全リクエストの完了 */
		for (bEnd = 0; bEnd < MCD_NUM_EPS; bEnd++) {
			mcd_gadget_flush_ep_requests(&mgadget->ep[bEnd],
						     -ESHUTDOWN);
		}
		/* Disconnectの通知 */
		if (mgadget->pGadgetDriver->disconnect) {
			mgadget->pGadgetDriver->disconnect(mgadget->pGadget);
		}
	}
	DBG(2, "==>\n");
}

/* 
 * @pre pThis!=NULL
 */
void mcd_gadget_reset(struct mcd_gadget *mgadget)
{
	struct mcd_device* mdev = mgadget_to_mdev(mgadget);
	void *regs = mdev_link_reg(mdev);
	uint8_t devctl = MCD_READ8(regs, MCD_O_HDRC_DEVCTL);

	DBG(2, "<==\n");

#ifdef MCD_PARANOID
	ASSERT_SPINLOCK_UNLOCKED(&pThis->Lock);
	ASSERT_SPINLOCK_UNLOCKED(&mcd_gadget_local_end[0]);
#endif

	DBG(1, "peripheral mode\n");
	INFO("mode=%s, addr=%x\n",
	     (devctl & MCD_M_DEVCTL_HM) ? "host" : "function",
             MCD_READ8(regs, MCD_O_HDRC_FADDR));

	/* HR does NOT clear itself */
	if (devctl & MCD_M_DEVCTL_HR) {
		MCD_WRITE8(regs, MCD_O_HDRC_DEVCTL, MCD_M_DEVCTL_SESSION);
	}

	/* unconfigured */
	mgadget->bSuspended = false;
	mgadget->bEnd0Stage = MCD_END0_STAGE_SETUP;
	DBG(2, "Resumed\n");

	if (mgadget->pGadgetDriver) {
		uint8_t power = MCD_READ8(regs, MCD_O_HDRC_POWER);
		mgadget->pGadget->speed = (power & MCD_M_POWER_HSMODE)
			? USB_SPEED_HIGH : USB_SPEED_FULL;

		mcd_gadget_disconnect(mgadget);
	}
#ifdef MCD_MONITOR_DATA
	DBG(-3, "Clearing debug level\n");
	monitor_loglevel = mcd_debug_level;
	mcd_debug_level = 0;
#endif

	DBG(2, "==>\n");
}

/**********************************************************************
 * DEBUG Functions
 *
 */

#if MCD_DEBUG>0

void dump_ep_status(struct mcd_gadget *mgadget)
{
	uint8_t bEnd = 0;
	struct mcd_gep *pEnd;
	struct usb_request *req;

	for (bEnd = 0; bEnd < MCD_NUM_EPS; bEnd++) {
		pEnd = &mgadget->ep[bEnd];
		req = mcd_get_current_request(pEnd);
		INFO("End-%d (%s): qlenght=%d, %s\n", bEnd,
		     (pEnd->bIsTx) ? "tx" : "rx",
		     mcd_gadget_queue_length(&pEnd->req_list),
		     (req) ? dump_usb_request(req) : "none");
	}

}

uint8_t is_ep_corrupted(void *ep)
{

	if (!ep) {
		ERR("null pointer in is_ep_corrupted()");
		return -EINVAL;
	}

	if (mcd_gadget_find_end(ep) < 0) {
		ERR("ep=%p is not an endpoint()", ep);
		return -EINVAL;
	}

	if (((struct mcd_gep *)ep)->wPadFront != MCD_LOCAL_PAD) {
		ERR("ep=%p pad corrupted!()", ep);
		return -EINVAL;
	}

	return 0;		/* not corrupted */
}

int dump_req_list(struct list_head *lh, int verbose)
{
	int count = 0;
	struct list_head *p = lh;

	while (p && p->next != lh) {
		count++;
		p = p->next;
		if (verbose) {
			printk(dump_node(p));
		} else {
			printk("[%p]", list_entry(p, struct usb_request, list));
		}
	}

	return count;
}

#endif
