/* 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"); /*[]*/