/*
* hcd_queue.c - DesignWare HS OTG Controller host queuing routines
*
* Copyright (C) 2004-2013 Synopsys, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The names of the above-listed copyright holders may not be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* ALTERNATIVELY, this software may be distributed under the terms of the
* GNU General Public License ("GPL") as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any
* later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This file contains the functions to manage Queue Heads and Queue
* Transfer Descriptors for Host mode
*/
#include <linux/gcd.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/ch11.h>
#include "core.h"
#include "hcd.h"
/* Wait this long before releasing periodic reservation */
#define DWC2_UNRESERVE_DELAY (msecs_to_jiffies(5))
/**
* dwc2_periodic_channel_available() - Checks that a channel is available for a
* periodic transfer
*
* @hsotg: The HCD state structure for the DWC OTG controller
*
* Return: 0 if successful, negative error code otherwise
*/
static int dwc2_periodic_channel_available(struct dwc2_hsotg *hsotg)
{
/*
* Currently assuming that there is a dedicated host channel for
* each periodic transaction plus at least one host channel for
* non-periodic transactions
*/
int status;
int num_channels;
num_channels = hsotg->core_params->host_channels;
if (hsotg->periodic_channels + hsotg->non_periodic_channels <
num_channels
&& hsotg->periodic_channels < num_channels - 1) {
status = 0;
} else {
dev_dbg(hsotg->dev,
"%s: Total channels: %d, Periodic: %d, "
"Non-periodic: %d\n", __func__, num_channels,
hsotg->periodic_channels, hsotg->non_periodic_channels);
status = -ENOSPC;
}
return status;
}
/**
* dwc2_check_periodic_bandwidth() - Checks that there is sufficient bandwidth
* for the specified QH in the periodic schedule
*
* @hsotg: The HCD state structure for the DWC OTG controller
* @qh: QH containing periodic bandwidth required
*
* Return: 0 if successful, negative error code otherwise
*
* For simplicity, this calculation assumes that all the transfers in the
* periodic schedule may occur in the same (micro)frame
*/
static int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg,
struct dwc2_qh *qh)
{
int status;
s16 max_claimed_usecs;
status = 0;
if (qh->dev_speed == USB_SPEED_HIGH || qh->do_split) {
/*
* High speed mode
* Max periodic usecs is 80% x 125 usec = 100 usec
*/
max_claimed_usecs = 100 - qh->host_us;
} else {
/*
* Full speed mode
* Max periodic usecs is 90% x 1000 usec = 900 usec
*/
max_claimed_usecs = 900 - qh->host_us;
}
if (hsotg->periodic_usecs > max_claimed_usecs) {
dev_err(hsotg->dev,
"%s: already claimed usecs %d, required usecs %d\n",
__func__, hsotg->periodic_usecs, qh->host_us);
status = -ENOSPC;
}
return status;
}
/**
* Microframe scheduler
* track the total use in hsotg->frame_usecs
* keep each qh use in qh->frame_usecs
* when surrendering the qh then donate the time back
*/
static const unsigned short max_uframe_usecs[] = {
100, 100, 100, 100, 100, 100, 30, 0
};
void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg)
{
int i;
for (i = 0; i < 8; i++)
hsotg->frame_usecs[i] = max_uframe_usecs[i];
}
static int dwc2_find_single_uframe(struct dwc2_hsotg *hsot