blob: defa05cd5e50fff3d7ca94ad6b6125bdea41f1b5 [file] [log] [blame]
/* cyangadget.c - Linux USB Gadget driver file for the Cypress West Bridge
## ===========================
## Copyright (C) 2010 Cypress Semiconductor
##
## 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.
## ===========================
*/
/*
* Cypress West Bridge high/full speed usb device controller code
* Based on the Netchip 2280 device controller by David Brownell
* in the linux 2.6.10 kernel
*
* linux/drivers/usb/gadget/net2280.c
*/
/*
* Copyright (C) 2002 NetChip Technology, Inc. (http://www.netchip.com)
* Copyright (C) 2003 David Brownell
*
* 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., 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*/
#include "cyasgadget.h"
#define CY_AS_DRIVER_DESC "cypress west bridge usb gadget"
#define CY_AS_DRIVER_VERSION "REV B"
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
static const char cy_as_driver_name[] = "cy_astoria_gadget";
static const char cy_as_driver_desc[] = CY_AS_DRIVER_DESC;
static const char cy_as_ep0name[] = "EP0";
static const char *cy_as_ep_names[] = {
cy_as_ep0name, "EP1",
"EP2", "EP3", "EP4", "EP5", "EP6", "EP7", "EP8",
"EP9", "EP10", "EP11", "EP12", "EP13", "EP14", "EP15"
};
/* forward declarations */
static void
cyas_ep_reset(
struct cyasgadget_ep *an_ep);
static int
cyasgadget_fifo_status(
struct usb_ep *_ep);
static void
cyasgadget_stallcallback(
cy_as_device_handle h,
cy_as_return_status_t status,
uint32_t tag,
cy_as_funct_c_b_type cbtype,
void *cbdata);
/* variables */
static cyasgadget *cy_as_gadget_controller;
static int append_mtp;
module_param(append_mtp, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(append_mtp,
"west bridge to append descriptors for mtp 0=no 1=yes");
static int msc_enum_bus_0;
module_param(msc_enum_bus_0, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msc_enum_bus_0,
"west bridge to enumerate bus 0 as msc 0=no 1=yes");
static int msc_enum_bus_1;
module_param(msc_enum_bus_1, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msc_enum_bus_1,
"west bridge to enumerate bus 1 as msc 0=no 1=yes");
/* all Callbacks are placed in this subsection*/
static void cy_as_gadget_usb_event_callback(
cy_as_device_handle h,
cy_as_usb_event ev,
void *evdata
)
{
cyasgadget *cy_as_dev;
#ifndef WESTBRIDGE_NDEBUG
struct usb_ctrlrequest *ctrlreq;
#endif
/* cy_as_dev = container_of(h, cyasgadget, dev_handle); */
cy_as_dev = cy_as_gadget_controller;
switch (ev) {
case cy_as_event_usb_suspend:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_suspend received\n");
#endif
cy_as_dev->driver->suspend(&cy_as_dev->gadget);
break;
case cy_as_event_usb_resume:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_resume received\n");
#endif
cy_as_dev->driver->resume(&cy_as_dev->gadget);
break;
case cy_as_event_usb_reset:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_reset received\n");
#endif
break;
case cy_as_event_usb_speed_change:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_speed_change received\n");
#endif
break;
case cy_as_event_usb_set_config:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_set_config received\n");
#endif
break;
case cy_as_event_usb_setup_packet:
#ifndef WESTBRIDGE_NDEBUG
ctrlreq = (struct usb_ctrlrequest *)evdata;
cy_as_hal_print_message("<1>_cy_as_event_usb_setup_packet "
"received"
"bRequestType=0x%x,"
"bRequest=0x%x,"
"wValue=x%x,"
"wIndex=0x%x,"
"wLength=0x%x,",
ctrlreq->bRequestType,
ctrlreq->bRequest,
ctrlreq->wValue,
ctrlreq->wIndex,
ctrlreq->wLength
);
#endif
cy_as_dev->outsetupreq = 0;
if ((((uint8_t *)evdata)[0] & USB_DIR_IN) == USB_DIR_OUT)
cy_as_dev->outsetupreq = 1;
cy_as_dev->driver->setup(&cy_as_dev->gadget,
(struct usb_ctrlrequest *)evdata);
break;
case cy_as_event_usb_status_packet:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_status_packet received\n");
#endif
break;
case cy_as_event_usb_inquiry_before:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_inquiry_before received\n");
#endif
break;
case cy_as_event_usb_inquiry_after:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_inquiry_after received\n");
#endif
break;
case cy_as_event_usb_start_stop:
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<1>_cy_as_event_usb_start_stop received\n");
#endif
break;
default:
break;
}
}
static void cy_as_gadget_mtp_event_callback(
cy_as_device_handle handle,
cy_as_mtp_event evtype,
void *evdata
)
{
cyasgadget *dev = cy_as_gadget_controller;
(void) handle;
switch (evtype) {
case cy_as_mtp_send_object_complete:
{
cy_as_mtp_send_object_complete_data *send_obj_data =
(cy_as_mtp_send_object_complete_data *) evdata;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<6>MTP EVENT: send_object_complete\n");
cy_as_hal_print_message(
"<6>_bytes sent = %d\n_send status = %d",
send_obj_data->byte_count,
send_obj_data->status);
#endif
dev->tmtp_send_complete_data.byte_count =
send_obj_data->byte_count;
dev->tmtp_send_complete_data.status =
send_obj_data->status;
dev->tmtp_send_complete_data.transaction_id =
send_obj_data->transaction_id;
dev->tmtp_send_complete = cy_true;
break;
}
case cy_as_mtp_get_object_complete:
{
cy_as_mtp_get_object_complete_data *get_obj_data =
(cy_as_mtp_get_object_complete_data *) evdata;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<6>MTP EVENT: get_object_complete\n");
cy_as_hal_print_message(
"<6>_bytes got = %d\n_get status = %d",
get_obj_data->byte_count, get_obj_data->status);
#endif
dev->tmtp_get_complete_data.byte_count =
get_obj_data->byte_count;
dev->tmtp_get_complete_data.status =
get_obj_data->status;
dev->tmtp_get_complete = cy_true;
break;
}
case cy_as_mtp_block_table_needed:
{
dev->tmtp_need_new_blk_tbl = cy_true;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<6>MTP EVENT: cy_as_mtp_block_table_needed\n");
#endif
break;
}
default:
break;
}
}
static void
cyasgadget_setupreadcallback(
cy_as_device_handle h,
cy_as_end_point_number_t ep,
uint32_t count,
void *buf,
cy_as_return_status_t status)
{
cyasgadget_ep *an_ep;
cyasgadget_req *an_req;
cyasgadget *cy_as_dev;
unsigned stopped;
unsigned long flags;
(void)buf;
cy_as_dev = cy_as_gadget_controller;
if (cy_as_dev->driver == NULL)
return;
an_ep = &cy_as_dev->an_gadget_ep[ep];
spin_lock_irqsave(&cy_as_dev->lock, flags);
stopped = an_ep->stopped;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: ep=%d, count=%d, "
"status=%d\n", __func__, ep, count, status);
#endif
an_req = list_entry(an_ep->queue.next,
cyasgadget_req, queue);
list_del_init(&an_req->queue);
if (status == CY_AS_ERROR_SUCCESS)
an_req->req.status = 0;
else
an_req->req.status = -status;
an_req->req.actual = count;
an_ep->stopped = 1;
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
an_ep->stopped = stopped;
}
/*called when the write of a setup packet has been completed*/
static void cyasgadget_setupwritecallback(
cy_as_device_handle h,
cy_as_end_point_number_t ep,
uint32_t count,
void *buf,
cy_as_return_status_t status
)
{
cyasgadget_ep *an_ep;
cyasgadget_req *an_req;
cyasgadget *cy_as_dev;
unsigned stopped;
unsigned long flags;
(void)buf;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called status=0x%x\n",
__func__, status);
#endif
cy_as_dev = cy_as_gadget_controller;
if (cy_as_dev->driver == NULL)
return;
an_ep = &cy_as_dev->an_gadget_ep[ep];
spin_lock_irqsave(&cy_as_dev->lock, flags);
stopped = an_ep->stopped;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("setup_write_callback: ep=%d, "
"count=%d, status=%d\n", ep, count, status);
#endif
an_req = list_entry(an_ep->queue.next, cyasgadget_req, queue);
list_del_init(&an_req->queue);
an_req->req.actual = count;
an_req->req.status = 0;
an_ep->stopped = 1;
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
an_ep->stopped = stopped;
}
/* called when a read operation has completed.*/
static void cyasgadget_readcallback(
cy_as_device_handle h,
cy_as_end_point_number_t ep,
uint32_t count,
void *buf,
cy_as_return_status_t status
)
{
cyasgadget_ep *an_ep;
cyasgadget_req *an_req;
cyasgadget *cy_as_dev;
unsigned stopped;
cy_as_return_status_t ret;
unsigned long flags;
(void)h;
(void)buf;
cy_as_dev = cy_as_gadget_controller;
if (cy_as_dev->driver == NULL)
return;
an_ep = &cy_as_dev->an_gadget_ep[ep];
stopped = an_ep->stopped;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: ep=%d, count=%d, status=%d\n",
__func__, ep, count, status);
#endif
if (status == CY_AS_ERROR_CANCELED)
return;
spin_lock_irqsave(&cy_as_dev->lock, flags);
an_req = list_entry(an_ep->queue.next, cyasgadget_req, queue);
list_del_init(&an_req->queue);
if (status == CY_AS_ERROR_SUCCESS)
an_req->req.status = 0;
else
an_req->req.status = -status;
an_req->complete = 1;
an_req->req.actual = count;
an_ep->stopped = 1;
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
an_ep->stopped = stopped;
/* We need to call ReadAsync on this end-point
* again, so as to not miss any data packets. */
if (!an_ep->stopped) {
spin_lock_irqsave(&cy_as_dev->lock, flags);
an_req = 0;
if (!list_empty(&an_ep->queue))
an_req = list_entry(an_ep->queue.next,
cyasgadget_req, queue);
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
if ((an_req) && (an_req->req.status == -EINPROGRESS)) {
ret = cy_as_usb_read_data_async(cy_as_dev->dev_handle,
an_ep->num, cy_false, an_req->req.length,
an_req->req.buf, cyasgadget_readcallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_read_data_async failed "
"with error code %d\n", ret);
else
an_req->req.status = -EALREADY;
}
}
}
/* function is called when a usb write operation has completed*/
static void cyasgadget_writecallback(
cy_as_device_handle h,
cy_as_end_point_number_t ep,
uint32_t count,
void *buf,
cy_as_return_status_t status
)
{
cyasgadget_ep *an_ep;
cyasgadget_req *an_req;
cyasgadget *cy_as_dev;
unsigned stopped = 0;
cy_as_return_status_t ret;
unsigned long flags;
(void)h;
(void)buf;
cy_as_dev = cy_as_gadget_controller;
if (cy_as_dev->driver == NULL)
return;
an_ep = &cy_as_dev->an_gadget_ep[ep];
if (status == CY_AS_ERROR_CANCELED)
return;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: ep=%d, count=%d, status=%d\n",
__func__, ep, count, status);
#endif
spin_lock_irqsave(&cy_as_dev->lock, flags);
an_req = list_entry(an_ep->queue.next, cyasgadget_req, queue);
list_del_init(&an_req->queue);
an_req->req.actual = count;
/* Verify the status value before setting req.status to zero */
if (status == CY_AS_ERROR_SUCCESS)
an_req->req.status = 0;
else
an_req->req.status = -status;
an_ep->stopped = 1;
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
an_ep->stopped = stopped;
/* We need to call WriteAsync on this end-point again, so as to not
miss any data packets. */
if (!an_ep->stopped) {
spin_lock_irqsave(&cy_as_dev->lock, flags);
an_req = 0;
if (!list_empty(&an_ep->queue))
an_req = list_entry(an_ep->queue.next,
cyasgadget_req, queue);
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
if ((an_req) && (an_req->req.status == -EINPROGRESS)) {
ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
an_ep->num, an_req->req.length, an_req->req.buf,
cy_false, cyasgadget_writecallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_write_data_async "
"failed with error code %d\n", ret);
else
an_req->req.status = -EALREADY;
}
}
}
static void cyasgadget_stallcallback(
cy_as_device_handle h,
cy_as_return_status_t status,
uint32_t tag,
cy_as_funct_c_b_type cbtype,
void *cbdata
)
{
#ifndef WESTBRIDGE_NDEBUG
if (status != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_set/_clear stall "
"failed with status %d\n", status);
#endif
}
/*******************************************************************/
/* All usb_ep_ops (cyasgadget_ep_ops) are placed in this subsection*/
/*******************************************************************/
static int cyasgadget_enable(
struct usb_ep *_ep,
const struct usb_endpoint_descriptor *desc
)
{
cyasgadget *an_dev;
cyasgadget_ep *an_ep;
u32 max, tmp;
unsigned long flags;
an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
if (!_ep || !desc || an_ep->desc || _ep->name == cy_as_ep0name
|| desc->bDescriptorType != USB_DT_ENDPOINT)
return -EINVAL;
an_dev = an_ep->dev;
if (!an_dev->driver || an_dev->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN;
max = le16_to_cpu(desc->wMaxPacketSize) & 0x1fff;
spin_lock_irqsave(&an_dev->lock, flags);
_ep->maxpacket = max & 0x7ff;
an_ep->desc = desc;
/* ep_reset() has already been called */
an_ep->stopped = 0;
an_ep->out_overflow = 0;
if (an_ep->cyepconfig.enabled != cy_true) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_end_point_config EP %s mismatch "
"on enabled\n", an_ep->usb_ep_inst.name);
#endif
return -EINVAL;
}
tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK);
an_ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC) ? 1 : 0;
spin_unlock_irqrestore(&an_dev->lock, flags);
switch (tmp) {
case USB_ENDPOINT_XFER_ISOC:
if (an_ep->cyepconfig.type != cy_as_usb_iso) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_end_point_config EP %s mismatch "
"on type %d %d\n", an_ep->usb_ep_inst.name,
an_ep->cyepconfig.type, cy_as_usb_iso);
#endif
return -EINVAL;
}
break;
case USB_ENDPOINT_XFER_INT:
if (an_ep->cyepconfig.type != cy_as_usb_int) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_end_point_config EP %s mismatch "
"on type %d %d\n", an_ep->usb_ep_inst.name,
an_ep->cyepconfig.type, cy_as_usb_int);
#endif
return -EINVAL;
}
break;
default:
if (an_ep->cyepconfig.type != cy_as_usb_bulk) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_end_point_config EP %s mismatch "
"on type %d %d\n", an_ep->usb_ep_inst.name,
an_ep->cyepconfig.type, cy_as_usb_bulk);
#endif
return -EINVAL;
}
break;
}
tmp = desc->bEndpointAddress;
an_ep->is_in = (tmp & USB_DIR_IN) != 0;
if ((an_ep->cyepconfig.dir == cy_as_usb_in) &&
(!an_ep->is_in)) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_end_point_config EP %s mismatch "
"on dir %d %d\n", an_ep->usb_ep_inst.name,
an_ep->cyepconfig.dir, cy_as_usb_in);
#endif
return -EINVAL;
} else if ((an_ep->cyepconfig.dir == cy_as_usb_out) &&
(an_ep->is_in)) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_end_point_config EP %s mismatch "
"on dir %d %d\n", an_ep->usb_ep_inst.name,
an_ep->cyepconfig.dir, cy_as_usb_out);
#endif
return -EINVAL;
}
cy_as_usb_clear_stall(an_dev->dev_handle, an_ep->num,
cyasgadget_stallcallback, 0);
cy_as_hal_print_message("%s enabled %s (ep%d-%d) max %04x\n",
__func__, _ep->name, an_ep->num, tmp, max);
return 0;
}
static int cyasgadget_disable(
struct usb_ep *_ep
)
{
cyasgadget_ep *an_ep;
unsigned long flags;
an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
if (!_ep || !an_ep->desc || _ep->name == cy_as_ep0name)
return -EINVAL;
spin_lock_irqsave(&an_ep->dev->lock, flags);
cyas_ep_reset(an_ep);
spin_unlock_irqrestore(&an_ep->dev->lock, flags);
return 0;
}
static struct usb_request *cyasgadget_alloc_request(
struct usb_ep *_ep, gfp_t gfp_flags
)
{
cyasgadget_ep *an_ep;
cyasgadget_req *an_req;
if (!_ep)
return NULL;
an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
an_req = kzalloc(sizeof(cyasgadget_req), gfp_flags);
if (!an_req)
return NULL;
an_req->req.dma = DMA_ADDR_INVALID;
INIT_LIST_HEAD(&an_req->queue);
return &an_req->req;
}
static void cyasgadget_free_request(
struct usb_ep *_ep,
struct usb_request *_req
)
{
cyasgadget_req *an_req;
if (!_ep || !_req)
return;
an_req = container_of(_req, cyasgadget_req, req);
kfree(an_req);
}
/* Load a packet into the fifo we use for usb IN transfers.
* works for all endpoints. */
static int cyasgadget_queue(
struct usb_ep *_ep,
struct usb_request *_req,
gfp_t gfp_flags
)
{
cyasgadget_req *as_req;
cyasgadget_ep *as_ep;
cyasgadget *cy_as_dev;
unsigned long flags;
cy_as_return_status_t ret = 0;
as_req = container_of(_req, cyasgadget_req, req);
if (!_req || !_req->complete || !_req->buf
|| !list_empty(&as_req->queue))
return -EINVAL;
as_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
if (!_ep || (!as_ep->desc && (as_ep->num != 0)))
return -EINVAL;
cy_as_dev = as_ep->dev;
if (!cy_as_dev->driver ||
cy_as_dev->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN;
spin_lock_irqsave(&cy_as_dev->lock, flags);
_req->status = -EINPROGRESS;
_req->actual = 0;
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
/* Call Async functions */
if (as_ep->is_in) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_write_data_async being called "
"on ep %d\n", as_ep->num);
#endif
ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
as_ep->num, _req->length, _req->buf,
cy_false, cyasgadget_writecallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_write_data_async failed with "
"error code %d\n", ret);
else
_req->status = -EALREADY;
} else if (as_ep->num == 0) {
/*
ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
as_ep->num, _req->length, _req->buf, cy_false,
cyasgadget_setupwritecallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_write_data_async failed with error "
"code %d\n", ret);
*/
if ((cy_as_dev->outsetupreq) && (_req->length)) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_read_data_async "
"being called on ep %d\n",
as_ep->num);
#endif
ret = cy_as_usb_read_data_async (
cy_as_dev->dev_handle, as_ep->num,
cy_true, _req->length, _req->buf,
cyasgadget_setupreadcallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_read_data_async failed with "
"error code %d\n", ret);
} else {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_write_data_async "
"being called on ep %d\n",
as_ep->num);
#endif
ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
as_ep->num, _req->length, _req->buf, cy_false,
cyasgadget_setupwritecallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_write_data_async failed with "
"error code %d\n", ret);
}
} else if (list_empty(&as_ep->queue)) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_read_data_async being called since "
"ep queue empty%d\n", ret);
#endif
ret = cy_as_usb_read_data_async(cy_as_dev->dev_handle,
as_ep->num, cy_false, _req->length, _req->buf,
cyasgadget_readcallback);
if (ret != CY_AS_ERROR_SUCCESS)
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_read_data_async failed with error "
"code %d\n", ret);
else
_req->status = -EALREADY;
}
spin_lock_irqsave(&cy_as_dev->lock, flags);
if (as_req)
list_add_tail(&as_req->queue, &as_ep->queue);
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
return 0;
}
/* dequeue request */
static int cyasgadget_dequeue(
struct usb_ep *_ep,
struct usb_request *_req
)
{
cyasgadget_ep *an_ep;
cyasgadget *dev;
an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
dev = an_ep->dev;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
cy_as_usb_cancel_async(dev->dev_handle, an_ep->num);
return 0;
}
static int cyasgadget_set_halt(
struct usb_ep *_ep,
int value
)
{
cyasgadget_ep *an_ep;
int retval = 0;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
if (!_ep || (!an_ep->desc && an_ep->num != 0))
return -EINVAL;
if (!an_ep->dev->driver || an_ep->dev->gadget.speed ==
USB_SPEED_UNKNOWN)
return -ESHUTDOWN;
if (an_ep->desc /* not ep0 */ &&
(an_ep->desc->bmAttributes & 0x03) == USB_ENDPOINT_XFER_ISOC)
return -EINVAL;
if (!list_empty(&an_ep->queue))
retval = -EAGAIN;
else if (an_ep->is_in && value &&
cyasgadget_fifo_status(_ep) != 0)
retval = -EAGAIN;
else {
if (value) {
cy_as_usb_set_stall(an_ep->dev->dev_handle,
an_ep->num, cyasgadget_stallcallback, 0);
} else {
cy_as_usb_clear_stall(an_ep->dev->dev_handle,
an_ep->num, cyasgadget_stallcallback, 0);
}
}
return retval;
}
static int cyasgadget_fifo_status(
struct usb_ep *_ep
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
return 0;
}
static void cyasgadget_fifo_flush(
struct usb_ep *_ep
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
}
static struct usb_ep_ops cyasgadget_ep_ops = {
.enable = cyasgadget_enable,
.disable = cyasgadget_disable,
.alloc_request = cyasgadget_alloc_request,
.free_request = cyasgadget_free_request,
.queue = cyasgadget_queue,
.dequeue = cyasgadget_dequeue,
.set_halt = cyasgadget_set_halt,
.fifo_status = cyasgadget_fifo_status,
.fifo_flush = cyasgadget_fifo_flush,
};
/*************************************************************/
/*This subsection contains all usb_gadget_ops cyasgadget_ops */
/*************************************************************/
static int cyasgadget_get_frame(
struct usb_gadget *_gadget
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
return 0;
}
static int cyasgadget_wakeup(
struct usb_gadget *_gadget
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
return 0;
}
static int cyasgadget_set_selfpowered(
struct usb_gadget *_gadget,
int value
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
return 0;
}
static int cyasgadget_pullup(
struct usb_gadget *_gadget,
int is_on
)
{
struct cyasgadget *cy_as_dev;
unsigned long flags;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
if (!_gadget)
return -ENODEV;
cy_as_dev = container_of(_gadget, cyasgadget, gadget);
spin_lock_irqsave(&cy_as_dev->lock, flags);
cy_as_dev->softconnect = (is_on != 0);
if (is_on)
cy_as_usb_connect(cy_as_dev->dev_handle, 0, 0);
else
cy_as_usb_disconnect(cy_as_dev->dev_handle, 0, 0);
spin_unlock_irqrestore(&cy_as_dev->lock, flags);
return 0;
}
static int cyasgadget_ioctl(
struct usb_gadget *_gadget,
unsigned code,
unsigned long param
)
{
int err = 0;
int retval = 0;
int ret_stat = 0;
cyasgadget *dev = cy_as_gadget_controller;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called, code=%d, param=%ld\n",
__func__, code, param);
#endif
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(code) != CYASGADGET_IOC_MAGIC) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s, bad magic number = 0x%x\n",
__func__, _IOC_TYPE(code));
#endif
return -ENOTTY;
}
if (_IOC_NR(code) > CYASGADGET_IOC_MAXNR) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s, bad ioctl code = 0x%x\n",
__func__, _IOC_NR(code));
#endif
return -ENOTTY;
}
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(code) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,
(void __user *)param, _IOC_SIZE(code));
else if (_IOC_DIR(code) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,
(void __user *)param, _IOC_SIZE(code));
if (err) {
cy_as_hal_print_message("%s, bad ioctl dir = 0x%x\n",
__func__, _IOC_DIR(code));
return -EFAULT;
}
switch (code) {
case CYASGADGET_GETMTPSTATUS:
{
cy_as_gadget_ioctl_tmtp_status *usr_d =
(cy_as_gadget_ioctl_tmtp_status *)param;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: got CYASGADGET_GETMTPSTATUS\n",
__func__);
#endif
retval = __put_user(dev->tmtp_send_complete,
(uint32_t __user *)(&(usr_d->tmtp_send_complete)));
retval = __put_user(dev->tmtp_get_complete,
(uint32_t __user *)(&(usr_d->tmtp_get_complete)));
retval = __put_user(dev->tmtp_need_new_blk_tbl,
(uint32_t __user *)(&(usr_d->tmtp_need_new_blk_tbl)));
if (copy_to_user((&(usr_d->tmtp_send_complete_data)),
(&(dev->tmtp_send_complete_data)),
sizeof(cy_as_gadget_ioctl_send_object)))
return -EFAULT;
if (copy_to_user((&(usr_d->tmtp_get_complete_data)),
(&(dev->tmtp_get_complete_data)),
sizeof(cy_as_gadget_ioctl_get_object)))
return -EFAULT;
break;
}
case CYASGADGET_CLEARTMTPSTATUS:
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s got CYASGADGET_CLEARTMTPSTATUS\n",
__func__);
#endif
dev->tmtp_send_complete = 0;
dev->tmtp_get_complete = 0;
dev->tmtp_need_new_blk_tbl = 0;
break;
}
case CYASGADGET_INITSOJ:
{
cy_as_gadget_ioctl_i_s_o_j_d k_d;
cy_as_gadget_ioctl_i_s_o_j_d *usr_d =
(cy_as_gadget_ioctl_i_s_o_j_d *)param;
cy_as_mtp_block_table blk_table;
struct scatterlist sg;
char *alloc_filename;
struct file *file_to_allocate;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s got CYASGADGET_INITSOJ\n",
__func__);
#endif
memset(&blk_table, 0, sizeof(blk_table));
/* Get user argument structure */
if (copy_from_user(&k_d, usr_d,
sizeof(cy_as_gadget_ioctl_i_s_o_j_d)))
return -EFAULT;
/* better use fixed size buff*/
alloc_filename = kmalloc(k_d.name_length + 1, GFP_KERNEL);
if (alloc_filename == NULL)
return -ENOMEM;
/* get the filename */
if (copy_from_user(alloc_filename, k_d.file_name,
k_d.name_length + 1)) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: CYASGADGET_INITSOJ, "
"copy file name from user space failed\n",
__func__);
#endif
kfree(alloc_filename);
return -EFAULT;
}
file_to_allocate = filp_open(alloc_filename, O_RDWR, 0);
if (!IS_ERR(file_to_allocate)) {
struct address_space *mapping =
file_to_allocate->f_mapping;
const struct address_space_operations *a_ops =
mapping->a_ops;
struct inode *inode = mapping->host;
struct inode *alloc_inode =
file_to_allocate->f_path.dentry->d_inode;
uint32_t num_clusters = 0;
struct buffer_head bh;
struct kstat stat;
int nr_pages = 0;
int ret_stat = 0;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: fhandle is OK, "
"calling vfs_getattr\n", __func__);
#endif
ret_stat = vfs_getattr(file_to_allocate->f_path.mnt,
file_to_allocate->f_path.dentry, &stat);
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: returned from "
"vfs_getattr() stat->blksize=0x%lx\n",
__func__, stat.blksize);
#endif
/* TODO: get this from disk properties
* (from blockdevice)*/
#define SECTOR_SIZE 512
if (stat.blksize != 0) {
num_clusters = (k_d.num_bytes) / SECTOR_SIZE;
if (((k_d.num_bytes) % SECTOR_SIZE) != 0)
num_clusters++;
} else {
goto initsoj_safe_exit;
}
bh.b_state = 0;
bh.b_blocknr = 0;
/* block size is arbitrary , we'll use sector size*/
bh.b_size = SECTOR_SIZE;
/* clear dirty pages in page cache
* (if were any allocated) */
nr_pages = (k_d.num_bytes) / (PAGE_CACHE_SIZE);
if (((k_d.num_bytes) % (PAGE_CACHE_SIZE)) != 0)
nr_pages++;
#ifndef WESTBRIDGE_NDEBUG
/*check out how many pages where actually allocated */
if (mapping->nrpages != nr_pages)
cy_as_hal_print_message("%s mpage_cleardirty "
"mapping->nrpages %d != num_pages %d\n",
__func__, (int) mapping->nrpages,
nr_pages);
cy_as_hal_print_message("%s: calling "
"mpage_cleardirty() "
"for %d pages\n", __func__, nr_pages);
#endif
ret_stat = mpage_cleardirty(mapping, nr_pages);
/*fill up the the block table from the addr mapping */
if (a_ops->bmap) {
int8_t blk_table_idx = -1;
uint32_t file_block_idx = 0;
uint32_t last_blk_addr_map = 0,
curr_blk_addr_map = 0;
#ifndef WESTBRIDGE_NDEBUG
if (alloc_inode->i_bytes == 0)
cy_as_hal_print_message(
"%s: alloc_inode->ibytes =0\n",
__func__);
#endif
/* iterate through the list of
* blocks (not clusters)*/
for (file_block_idx = 0;
file_block_idx < num_clusters
/*inode->i_bytes*/; file_block_idx++) {
/* returns starting sector number */
curr_blk_addr_map =
a_ops->bmap(mapping,
file_block_idx);
/*no valid mapping*/
if (curr_blk_addr_map == 0) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s:hit invalid "
"mapping\n", __func__);
#endif
break;
} else if (curr_blk_addr_map !=
(last_blk_addr_map + 1) ||
(blk_table.num_blocks
[blk_table_idx] == 65535)) {
/* next table entry */
blk_table_idx++;
/* starting sector of a
* scattered cluster*/
blk_table.start_blocks
[blk_table_idx] =
curr_blk_addr_map;
/* ++ num of blocks in cur
* table entry*/
blk_table.
num_blocks[blk_table_idx]++;
#ifndef WESTBRIDGE_NDEBUG
if (file_block_idx != 0)
cy_as_hal_print_message(
"<*> next table "
"entry:%d required\n",
blk_table_idx);
#endif
} else {
/*add contiguous block*/
blk_table.num_blocks
[blk_table_idx]++;
} /*if (curr_blk_addr_map == 0)*/
last_blk_addr_map = curr_blk_addr_map;
} /* end for (file_block_idx = 0; file_block_idx
< inode->i_bytes;) */
#ifndef WESTBRIDGE_NDEBUG
/*print result for verification*/
{
int i;
cy_as_hal_print_message(
"%s: print block table "
"mapping:\n",
__func__);
for (i = 0; i <= blk_table_idx; i++) {
cy_as_hal_print_message(
"<1> %d 0x%x 0x%x\n", i,
blk_table.start_blocks[i],
blk_table.num_blocks[i]);
}
}
#endif
/* copy the block table to user
* space (for debug purposes) */
retval = __put_user(
blk_table.start_blocks[blk_table_idx],
(uint32_t __user *)
(&(usr_d->blk_addr_p)));
retval = __put_user(
blk_table.num_blocks[blk_table_idx],
(uint32_t __user *)
(&(usr_d->blk_count_p)));
blk_table_idx++;
retval = __put_user(blk_table_idx,
(uint32_t __user *)
(&(usr_d->item_count)));
} /*end if (a_ops->bmap)*/
filp_close(file_to_allocate, NULL);
dev->tmtp_send_complete = 0;
dev->tmtp_need_new_blk_tbl = 0;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: calling cy_as_mtp_init_send_object()\n",
__func__);
#endif
sg_init_one(&sg, &blk_table, sizeof(blk_table));
ret_stat = cy_as_mtp_init_send_object(dev->dev_handle,
(cy_as_mtp_block_table *)&sg,
k_d.num_bytes, 0, 0);
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: returned from "
"cy_as_mtp_init_send_object()\n", __func__);
#endif
}
#ifndef WESTBRIDGE_NDEBUG
else {
cy_as_hal_print_message(
"%s: failed to allocate the file %s\n",
__func__, alloc_filename);
} /* end if (file_to_allocate)*/
#endif
kfree(alloc_filename);
initsoj_safe_exit:
ret_stat = 0;
retval = __put_user(ret_stat,
(uint32_t __user *)(&(usr_d->ret_val)));
break;
}
case CYASGADGET_INITGOJ:
{
cy_as_gadget_ioctl_i_g_o_j_d k_d;
cy_as_gadget_ioctl_i_g_o_j_d *usr_d =
(cy_as_gadget_ioctl_i_g_o_j_d *)param;
cy_as_mtp_block_table blk_table;
struct scatterlist sg;
char *map_filename;
struct file *file_to_map;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: got CYASGADGET_INITGOJ\n",
__func__);
#endif
memset(&blk_table, 0, sizeof(blk_table));
/* Get user argument sturcutre */
if (copy_from_user(&k_d, usr_d,
sizeof(cy_as_gadget_ioctl_i_g_o_j_d)))
return -EFAULT;
map_filename = kmalloc(k_d.name_length + 1, GFP_KERNEL);
if (map_filename == NULL)
return -ENOMEM;
if (copy_from_user(map_filename, k_d.file_name,
k_d.name_length + 1)) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: copy file name from "
"user space failed\n", __func__);
#endif
kfree(map_filename);
return -EFAULT;
}
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<*>%s: opening %s for kernel "
"mode access map\n", __func__, map_filename);
#endif
file_to_map = filp_open(map_filename, O_RDWR, 0);
if (file_to_map) {
struct address_space *mapping = file_to_map->f_mapping;
const struct address_space_operations
*a_ops = mapping->a_ops;
struct inode *inode = mapping->host;
int8_t blk_table_idx = -1;
uint32_t file_block_idx = 0;
uint32_t last_blk_addr_map = 0, curr_blk_addr_map = 0;
/*verify operation exists*/
if (a_ops->bmap) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<*>%s: bmap found, i_bytes=0x%x, "
"i_size=0x%x, i_blocks=0x%x\n",
__func__, inode->i_bytes,
(unsigned int) inode->i_size,
(unsigned int) inode->i_blocks);
#endif
k_d.num_bytes = inode->i_size;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"<*>%s: k_d.num_bytes=0x%x\n",
__func__, k_d.num_bytes);
#endif
for (file_block_idx = 0;
file_block_idx < inode->i_size;
file_block_idx++) {
curr_blk_addr_map =
a_ops->bmap(mapping,
file_block_idx);
if (curr_blk_addr_map == 0) {
/*no valid mapping*/
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: no valid "
"mapping\n", __func__);
#endif
break;
} else if (curr_blk_addr_map !=
(last_blk_addr_map + 1)) {
/*non-contiguous break*/
blk_table_idx++;
blk_table.start_blocks
[blk_table_idx] =
curr_blk_addr_map;
blk_table.num_blocks
[blk_table_idx]++;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: found non-"
"contiguous break",
__func__);
#endif
} else {
/*add contiguous block*/
blk_table.num_blocks
[blk_table_idx]++;
}
last_blk_addr_map = curr_blk_addr_map;
}
/*print result for verification*/
#ifndef WESTBRIDGE_NDEBUG
{
int i = 0;
for (i = 0; i <= blk_table_idx; i++) {
cy_as_hal_print_message(
"%s %d 0x%x 0x%x\n",
__func__, i,
blk_table.start_blocks[i],
blk_table.num_blocks[i]);
}
}
#endif
} else {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: could not find "
"a_ops->bmap\n", __func__);
#endif
return -EFAULT;
}
filp_close(file_to_map, NULL);
dev->tmtp_get_complete = 0;
dev->tmtp_need_new_blk_tbl = 0;
ret_stat = __put_user(
blk_table.start_blocks[blk_table_idx],
(uint32_t __user *)(&(usr_d->blk_addr_p)));
ret_stat = __put_user(
blk_table.num_blocks[blk_table_idx],
(uint32_t __user *)(&(usr_d->blk_count_p)));
sg_init_one(&sg, &blk_table, sizeof(blk_table));
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: calling cy_as_mtp_init_get_object() "
"start=0x%x, num =0x%x, tid=0x%x, "
"num_bytes=0x%x\n",
__func__,
blk_table.start_blocks[0],
blk_table.num_blocks[0],
k_d.tid,
k_d.num_bytes);
#endif
ret_stat = cy_as_mtp_init_get_object(
dev->dev_handle,
(cy_as_mtp_block_table *)&sg,
k_d.num_bytes, k_d.tid, 0, 0);
if (ret_stat != CY_AS_ERROR_SUCCESS) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: cy_as_mtp_init_get_object "
"failed ret_stat=0x%x\n",
__func__, ret_stat);
#endif
}
}
#ifndef WESTBRIDGE_NDEBUG
else {
cy_as_hal_print_message(
"%s: failed to open file %s\n",
__func__, map_filename);
}
#endif
kfree(map_filename);
ret_stat = 0;
retval = __put_user(ret_stat, (uint32_t __user *)
(&(usr_d->ret_val)));
break;
}
case CYASGADGET_CANCELSOJ:
{
cy_as_gadget_ioctl_cancel *usr_d =
(cy_as_gadget_ioctl_cancel *)param;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message(
"%s: got CYASGADGET_CANCELSOJ\n",
__func__);
#endif
ret_stat = cy_as_mtp_cancel_send_object(dev->dev_handle, 0, 0);
retval = __put_user(ret_stat, (uint32_t __user *)
(&(usr_d->ret_val)));
break;
}
case CYASGADGET_CANCELGOJ:
{
cy_as_gadget_ioctl_cancel *usr_d =
(cy_as_gadget_ioctl_cancel *)param;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: got CYASGADGET_CANCELGOJ\n",
__func__);
#endif
ret_stat = cy_as_mtp_cancel_get_object(dev->dev_handle, 0, 0);
retval = __put_user(ret_stat,
(uint32_t __user *)(&(usr_d->ret_val)));
break;
}
default:
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: unknown ioctl received: %d\n",
__func__, code);
cy_as_hal_print_message("%s: known codes:\n"
"CYASGADGET_GETMTPSTATUS=%d\n"
"CYASGADGET_CLEARTMTPSTATUS=%d\n"
"CYASGADGET_INITSOJ=%d\n"
"CYASGADGET_INITGOJ=%d\n"
"CYASGADGET_CANCELSOJ=%d\n"
"CYASGADGET_CANCELGOJ=%d\n",
__func__,
CYASGADGET_GETMTPSTATUS,
CYASGADGET_CLEARTMTPSTATUS,
CYASGADGET_INITSOJ,
CYASGADGET_INITGOJ,
CYASGADGET_CANCELSOJ,
CYASGADGET_CANCELGOJ);
#endif
break;
}
}
return 0;
}
static const struct usb_gadget_ops cyasgadget_ops = {
.get_frame = cyasgadget_get_frame,
.wakeup = cyasgadget_wakeup,
.set_selfpowered = cyasgadget_set_selfpowered,
.pullup = cyasgadget_pullup,
.ioctl = cyasgadget_ioctl,
};
/* keeping it simple:
* - one bus driver, initted first;
* - one function driver, initted second
*
* most of the work to support multiple controllers would
* be to associate this gadget driver with all of them, or
* perhaps to bind specific drivers to specific devices.
*/
static void cyas_ep_reset(
cyasgadget_ep *an_ep
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
an_ep->desc = NULL;
INIT_LIST_HEAD(&an_ep->queue);
an_ep->stopped = 0;
an_ep->is_in = 0;
an_ep->is_iso = 0;
an_ep->usb_ep_inst.maxpacket = ~0;
an_ep->usb_ep_inst.ops = &cyasgadget_ep_ops;
}
static void cyas_usb_reset(
cyasgadget *cy_as_dev
)
{
cy_as_return_status_t ret;
cy_as_usb_enum_control config;
#ifndef WESTBRIDGE_NDEBUG
cy_as_device *dev_p = (cy_as_device *)cy_as_dev->dev_handle;
cy_as_hal_print_message("<1>%s called mtp_firmware=0x%x\n",
__func__, dev_p->is_mtp_firmware);
#endif
ret = cy_as_misc_release_resource(cy_as_dev->dev_handle,
cy_as_bus_u_s_b);
if (ret != CY_AS_ERROR_SUCCESS && ret !=
CY_AS_ERROR_RESOURCE_NOT_OWNED) {
cy_as_hal_print_message("<1>_cy_as_gadget: cannot "
"release usb resource: failed with error code %d\n",
ret);
return;
}
cy_as_dev->gadget.speed = USB_SPEED_HIGH;
ret = cy_as_usb_start(cy_as_dev->dev_handle, 0, 0);
if (ret != CY_AS_ERROR_SUCCESS) {
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_start failed with error code %d\n",
ret);
return;
}
/* P port will do enumeration, not West Bridge */
config.antioch_enumeration = cy_false;
/* 1 2 : 1-BUS_NUM , 2:Storage_device number, SD - is bus 1*/
/* TODO: add module param to enumerate mass storage */
config.mass_storage_interface = 0;
if (append_mtp) {
ret = cy_as_mtp_start(cy_as_dev->dev_handle,
cy_as_gadget_mtp_event_callback, 0, 0);
if (ret == CY_AS_ERROR_SUCCESS) {
cy_as_hal_print_message("MTP start passed, enumerating "
"MTP interface\n");
config.mtp_interface = append_mtp;
/*Do not enumerate NAND storage*/
config.devices_to_enumerate[0][0] = cy_false;
/*enumerate SD storage as MTP*/
config.devices_to_enumerate[1][0] = cy_true;
}
} else {
cy_as_hal_print_message("MTP start not attempted, not "
"enumerating MTP interface\n");
config.mtp_interface = 0;
/* enumerate mass storage based on module parameters */
config.devices_to_enumerate[0][0] = msc_enum_bus_0;
config.devices_to_enumerate[1][0] = msc_enum_bus_1;
}
ret = cy_as_usb_set_enum_config(cy_as_dev->dev_handle,
&config, 0, 0);
if (ret != CY_AS_ERROR_SUCCESS) {
cy_as_hal_print_message("<1>_cy_as_gadget: "
"cy_as_usb_set_enum_config failed with error "
"code %d\n", ret);
return;
}
cy_as_usb_set_physical_configuration(cy_as_dev->dev_handle, 1);
}
static void cyas_usb_reinit(
cyasgadget *cy_as_dev
)
{
int index = 0;
cyasgadget_ep *an_ep_p;
cy_as_return_status_t ret;
cy_as_device *dev_p = (cy_as_device *)cy_as_dev->dev_handle;
INIT_LIST_HEAD(&cy_as_dev->gadget.ep_list);
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called, is_mtp_firmware = "
"0x%x\n", __func__, dev_p->is_mtp_firmware);
#endif
/* Init the end points */
for (index = 1; index <= 15; index++) {
an_ep_p = &cy_as_dev->an_gadget_ep[index];
cyas_ep_reset(an_ep_p);
an_ep_p->usb_ep_inst.name = cy_as_ep_names[index];
an_ep_p->dev = cy_as_dev;
an_ep_p->num = index;
memset(&an_ep_p->cyepconfig, 0, sizeof(an_ep_p->cyepconfig));
/* EP0, EPs 2,4,6,8 need not be added */
if ((index <= 8) && (index % 2 == 0) &&
(!dev_p->is_mtp_firmware)) {
/* EP0 is 64 and EPs 2,4,6,8 not allowed */
cy_as_dev->an_gadget_ep[index].fifo_size = 0;
} else {
if (index == 1)
an_ep_p->fifo_size = 64;
else
an_ep_p->fifo_size = 512;
list_add_tail(&an_ep_p->usb_ep_inst.ep_list,
&cy_as_dev->gadget.ep_list);
}
}
/* need to setendpointconfig before usb connect, this is not
* quite compatible with gadget methodology (ep_enable called
* by gadget after connect), therefore need to set config in
* initialization and verify compatibility in ep_enable,
* kick up error otherwise*/
an_ep_p = &cy_as_dev->an_gadget_ep[3];
an_ep_p->cyepconfig.enabled = cy_true;
an_ep_p->cyepconfig.dir = cy_as_usb_out;
an_ep_p->cyepconfig.type = cy_as_usb_bulk;
an_ep_p->cyepconfig.size = 0;
an_ep_p->cyepconfig.physical = 1;
ret = cy_as_usb_set_end_point_config(an_ep_p->dev->dev_handle,
3, &an_ep_p->cyepconfig);
if (ret != CY_AS_ERROR_SUCCESS) {
cy_as_hal_print_message("cy_as_usb_set_end_point_config "
"failed with error code %d\n", ret);
}
cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 3, 0, 0);
an_ep_p = &cy_as_dev->an_gadget_ep[5];
an_ep_p->cyepconfig.enabled = cy_true;
an_ep_p->cyepconfig.dir = cy_as_usb_in;
an_ep_p->cyepconfig.type = cy_as_usb_bulk;
an_ep_p->cyepconfig.size = 0;
an_ep_p->cyepconfig.physical = 2;
ret = cy_as_usb_set_end_point_config(an_ep_p->dev->dev_handle,
5, &an_ep_p->cyepconfig);
if (ret != CY_AS_ERROR_SUCCESS) {
cy_as_hal_print_message("cy_as_usb_set_end_point_config "
"failed with error code %d\n", ret);
}
cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 5, 0, 0);
an_ep_p = &cy_as_dev->an_gadget_ep[9];
an_ep_p->cyepconfig.enabled = cy_true;
an_ep_p->cyepconfig.dir = cy_as_usb_in;
an_ep_p->cyepconfig.type = cy_as_usb_bulk;
an_ep_p->cyepconfig.size = 0;
an_ep_p->cyepconfig.physical = 4;
ret = cy_as_usb_set_end_point_config(an_ep_p->dev->dev_handle,
9, &an_ep_p->cyepconfig);
if (ret != CY_AS_ERROR_SUCCESS) {
cy_as_hal_print_message("cy_as_usb_set_end_point_config "
"failed with error code %d\n", ret);
}
cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 9, 0, 0);
if (dev_p->mtp_count != 0) {
/* these need to be set for compatibility with
* the gadget_enable logic */
an_ep_p = &cy_as_dev->an_gadget_ep[2];
an_ep_p->cyepconfig.enabled = cy_true;
an_ep_p->cyepconfig.dir = cy_as_usb_out;
an_ep_p->cyepconfig.type = cy_as_usb_bulk;
an_ep_p->cyepconfig.size = 0;
an_ep_p->cyepconfig.physical = 0;
cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 2, 0, 0);
an_ep_p = &cy_as_dev->an_gadget_ep[6];
an_ep_p->cyepconfig.enabled = cy_true;
an_ep_p->cyepconfig.dir = cy_as_usb_in;
an_ep_p->cyepconfig.type = cy_as_usb_bulk;
an_ep_p->cyepconfig.size = 0;
an_ep_p->cyepconfig.physical = 0;
cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 6, 0, 0);
}
cyas_ep_reset(&cy_as_dev->an_gadget_ep[0]);
cy_as_dev->an_gadget_ep[0].usb_ep_inst.name = cy_as_ep_names[0];
cy_as_dev->an_gadget_ep[0].dev = cy_as_dev;
cy_as_dev->an_gadget_ep[0].num = 0;
cy_as_dev->an_gadget_ep[0].fifo_size = 64;
cy_as_dev->an_gadget_ep[0].usb_ep_inst.maxpacket = 64;
cy_as_dev->gadget.ep0 = &cy_as_dev->an_gadget_ep[0].usb_ep_inst;
cy_as_dev->an_gadget_ep[0].stopped = 0;
INIT_LIST_HEAD(&cy_as_dev->gadget.ep0->ep_list);
}
static void cyas_ep0_start(
cyasgadget *dev
)
{
cy_as_return_status_t ret;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
ret = cy_as_usb_register_callback(dev->dev_handle,
cy_as_gadget_usb_event_callback);
if (ret != CY_AS_ERROR_SUCCESS) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: cy_as_usb_register_callback "
"failed with error code %d\n", __func__, ret);
#endif
return;
}
ret = cy_as_usb_commit_config(dev->dev_handle, 0, 0);
if (ret != CY_AS_ERROR_SUCCESS) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: cy_as_usb_commit_config "
"failed with error code %d\n", __func__, ret);
#endif
return;
}
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: cy_as_usb_commit_config "
"message sent\n", __func__);
#endif
ret = cy_as_usb_connect(dev->dev_handle, 0, 0);
if (ret != CY_AS_ERROR_SUCCESS) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: cy_as_usb_connect failed "
"with error code %d\n", __func__, ret);
#endif
return;
}
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s: cy_as_usb_connect message "
"sent\n", __func__);
#endif
}
/*
* When a driver is successfully registered, it will receive
* control requests including set_configuration(), which enables
* non-control requests. then usb traffic follows until a
* disconnect is reported. then a host may connect again, or
* the driver might get unbound.
*/
int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
int (*bind)(struct usb_gadget *))
{
cyasgadget *dev = cy_as_gadget_controller;
int retval;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called driver=0x%x\n",
__func__, (unsigned int) driver);
#endif
/* insist on high speed support from the driver, since
* "must not be used in normal operation"
*/
if (!driver
|| !bind
|| !driver->unbind
|| !driver->setup)
return -EINVAL;
if (!dev)
return -ENODEV;
if (dev->driver)
return -EBUSY;
/* hook up the driver ... */
dev->softconnect = 1;
driver->driver.bus = NULL;
dev->driver = driver;
dev->gadget.dev.driver = &driver->driver;
/* Do the needful */
cyas_usb_reset(dev); /* External usb */
cyas_usb_reinit(dev); /* Internal */
retval = bind(&dev->gadget);
if (retval) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("%s bind to driver %s --> %d\n",
__func__, driver->driver.name, retval);
#endif
dev->driver = NULL;
dev->gadget.dev.driver = NULL;
return retval;
}
/* ... then enable host detection and ep0; and we're ready
* for set_configuration as well as eventual disconnect.
*/
cyas_ep0_start(dev);
return 0;
}
EXPORT_SYMBOL(usb_gadget_probe_driver);
static void cyasgadget_nuke(
cyasgadget_ep *an_ep
)
{
cyasgadget *dev = cy_as_gadget_controller;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
cy_as_usb_cancel_async(dev->dev_handle, an_ep->num);
an_ep->stopped = 1;
while (!list_empty(&an_ep->queue)) {
cyasgadget_req *an_req = list_entry
(an_ep->queue.next, cyasgadget_req, queue);
list_del_init(&an_req->queue);
an_req->req.status = -ESHUTDOWN;
an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
}
}
static void cyasgadget_stop_activity(
cyasgadget *dev,
struct usb_gadget_driver *driver
)
{
int index;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
/* don't disconnect if it's not connected */
if (dev->gadget.speed == USB_SPEED_UNKNOWN)
driver = NULL;
if (spin_is_locked(&dev->lock))
spin_unlock(&dev->lock);
/* Stop hardware; prevent new request submissions;
* and kill any outstanding requests.
*/
cy_as_usb_disconnect(dev->dev_handle, 0, 0);
for (index = 3; index <= 7; index += 2) {
cyasgadget_ep *an_ep_p = &dev->an_gadget_ep[index];
cyasgadget_nuke(an_ep_p);
}
for (index = 9; index <= 15; index++) {
cyasgadget_ep *an_ep_p = &dev->an_gadget_ep[index];
cyasgadget_nuke(an_ep_p);
}
/* report disconnect; the driver is already quiesced */
if (driver)
driver->disconnect(&dev->gadget);
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("cy_as_usb_disconnect returned success");
#endif
/* Stop Usb */
cy_as_usb_stop(dev->dev_handle, 0, 0);
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("cy_as_usb_stop returned success");
#endif
}
int usb_gadget_unregister_driver(
struct usb_gadget_driver *driver
)
{
cyasgadget *dev = cy_as_gadget_controller;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
if (!dev)
return -ENODEV;
if (!driver || driver != dev->driver)
return -EINVAL;
cyasgadget_stop_activity(dev, driver);
driver->unbind(&dev->gadget);
dev->gadget.dev.driver = NULL;
dev->driver = NULL;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("unregistered driver '%s'\n",
driver->driver.name);
#endif
return 0;
}
EXPORT_SYMBOL(usb_gadget_unregister_driver);
static void cyas_gadget_release(
struct device *_dev
)
{
cyasgadget *dev = dev_get_drvdata(_dev);
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>%s called\n", __func__);
#endif
kfree(dev);
}
/* DeInitialize gadget driver */
static void cyasgadget_deinit(
cyasgadget *cy_as_dev
)
{
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget deinitialize called\n");
#endif
if (!cy_as_dev) {
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget_deinit: "
"invalid cyasgadget device\n");
#endif
return;
}
if (cy_as_dev->driver) {
/* should have been done already by driver model core */
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1> cy_as_gadget: '%s' "
"is still registered\n",
cy_as_dev->driver->driver.name);
#endif
usb_gadget_unregister_driver(cy_as_dev->driver);
}
kfree(cy_as_dev);
cy_as_gadget_controller = NULL;
}
/* Initialize gadget driver */
static int cyasgadget_initialize(void)
{
cyasgadget *cy_as_dev = 0;
int retval = 0;
#ifndef WESTBRIDGE_NDEBUG
cy_as_hal_print_message("<1>_cy_as_gadget [V1.1] initialize called\n");
#endif
if (cy_as_gadget_controller != 0) {
cy_as_hal_print_message("<1> cy_as_gadget: the device has "
"already been initilaized. ignoring\n");
return -EBUSY;
}
cy_as_dev = kzalloc(sizeof(cyasgadget), GFP_ATOMIC);
if (cy_as_dev == NULL) {
cy_as_hal_print_message("<1> cy_as_gadget: memory "
"allocation failed\n");
return -ENOMEM;
}
spin_lock_init(&cy_as_dev->lock);
cy_as_dev->gadget.ops = &cyasgadget_ops;
cy_as_dev->gadget.is_dualspeed = 1;
/* the "gadget" abstracts/virtualizes the controller */
/*strcpy(cy_as_dev->gadget.dev.bus_id, "cyasgadget");*/
cy_as_dev->gadget.dev.release = cyas_gadget_release;
cy_as_dev->gadget.name = cy_as_driver_name;
/* Get the device handle */
cy_as_dev->dev_handle = cyasdevice_getdevhandle();
if (0 == cy_as_dev->dev_handle) {
#ifndef NDEBUG
cy_as_hal_print_message("<1> cy_as_gadget: "
"no west bridge device\n");
#endif
retval = -EFAULT;
goto done;
}
/* We are done now */
cy_as_gadget_controller = cy_as_dev;
return 0;
/*
* in case of an error
*/
done:
if (cy_as_dev)
cyasgadget_deinit(cy_as_dev);
return retval;
}
static int __init cyas_init(void)
{
int init_res = 0;
init_res = cyasgadget_initialize();
if (init_res != 0) {
printk(KERN_WARNING "<1> gadget ctl instance "
"init error:%d\n", init_res);
if (init_res > 0) {
/* force -E/0 linux convention */
init_res = init_res * -1;
}
}
return init_res;
}
module_init(cyas_init);
static void __exit cyas_cleanup(void)
{
if (cy_as_gadget_controller != NULL)
cyasgadget_deinit(cy_as_gadget_controller);
}
module_exit(cyas_cleanup);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(CY_AS_DRIVER_DESC);
MODULE_AUTHOR("cypress semiconductor");
/*[]*/