[PATCH 07/15] Introduce Cadence USBSSP DRD Driver - added gadget-if.c Driver implements interface between gadget drivers and USBSSP device driver.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Laszczak Pawel <pawell@xxxxxxxxxxx>
---
 drivers/usb/usbssp/gadget-if.c | 572 +++++++++++++++++++++++++++++++++
 1 file changed, 572 insertions(+)
 create mode 100644 drivers/usb/usbssp/gadget-if.c

diff --git a/drivers/usb/usbssp/gadget-if.c b/drivers/usb/usbssp/gadget-if.c
new file mode 100644
index 000000000000..00391560e921
--- /dev/null
+++ b/drivers/usb/usbssp/gadget-if.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USBSSP device controller driver
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak
+ * Some code borrowed from the Linux XHCI driver.
+ */
+
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+#include "gadget.h"
+
+#define usbssp_g_lock(flag, save_flags) { \
+	if (in_interrupt()) {\
+		spin_lock_irqsave(&usbssp_data->lock, save_flags); \
+	} else { \
+		if (!irqs_disabled()) { \
+			spin_lock_irqsave(&usbssp_data->irq_thread_lock,\
+					usbssp_data->irq_thread_flag);\
+			flag = 1; \
+		} else \
+			spin_lock(&usbssp_data->irq_thread_lock); \
+	} }
+
+
+#define usbssp_g_unlock(flag, save_flags) { \
+	if (in_interrupt()) \
+		spin_unlock_irqrestore(&usbssp_data->lock, save_flags); \
+	else  { \
+		if (flag) \
+			spin_unlock_irqrestore(&usbssp_data->irq_thread_lock,\
+					usbssp_data->irq_thread_flag);\
+		else \
+			spin_unlock(&usbssp_data->irq_thread_lock); \
+	} }
+
+static int usbssp_gadget_ep_enable(struct usb_ep *ep,
+		const struct usb_endpoint_descriptor *desc)
+{
+	struct usbssp_ep *ep_priv;
+	struct usbssp_udc *usbssp_data;
+	int ret = 0;
+	int irq_disabled_locally = 0;
+	unsigned long flags = 0;
+
+	if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) {
+		pr_err("invalid parameters\n");
+		return -EINVAL;
+	}
+
+	ep_priv = to_usbssp_ep(ep);
+	usbssp_data = ep_priv->usbssp_data;
+
+	if (!desc->wMaxPacketSize) {
+		usbssp_dbg(usbssp_data, "missing wMaxPacketSize\n");
+		return -EINVAL;
+	}
+
+
+
+
+	if (ep_priv->ep_state & USBSSP_EP_ENABLED) {
+		usbssp_dbg(usbssp_data, "%s is already enabled\n",
+			   ep_priv->name);
+		return -EINVAL;
+	}
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+	ret = usbssp_add_endpoint(usbssp_data,  ep_priv);
+	if (ret < 0)
+		goto finish;
+
+	ep_priv->ep_state |= USBSSP_EP_ENABLED;
+
+	/*Update bandwidth information*/
+	ret = usbssp_check_bandwidth(usbssp_data, &usbssp_data->gadget);
+
+	if (ret < 0)
+		ep_priv->ep_state &= ~USBSSP_EP_ENABLED;
+
+finish:
+	usbssp_dbg(usbssp_data, "%s enable endpoint %s\n", ep_priv->name,
+			(ret == 0) ? "success" : "failed");
+	usbssp_g_unlock(irq_disabled_locally, flags);
+	return ret;
+}
+
+int usbssp_gadget_ep_disable(struct usb_ep *ep)
+{
+	struct usbssp_ep *ep_priv;
+	struct usbssp_udc *usbssp_data;
+	int ep_index = 0;
+	int ret;
+	int irq_disabled_locally = 0;
+	unsigned long flags = 0;
+	struct usbssp_request  *req_priv;
+
+	ep_priv = to_usbssp_ep(ep);
+	usbssp_data = ep_priv->usbssp_data;
+	ep_index = usbssp_get_endpoint_index(ep_priv->endpoint.desc);
+
+	if (!(ep_priv->ep_state & USBSSP_EP_ENABLED)) {
+		usbssp_dbg(usbssp_data, "%s is already disabled\n",
+				ep_priv->name);
+		return -EINVAL;
+	}
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+
+	ep_priv->ep_state |= USBSSP_EP_DISABLE_PENDING;
+
+	/*dequeue all USB request from endpoint*/
+	list_for_each_entry(req_priv, &ep_priv->pending_list, list) {
+		usbssp_dequeue(ep_priv, req_priv);
+	}
+
+	ret = usbssp_drop_endpoint(usbssp_data, &usbssp_data->gadget, ep_priv);
+	if (ret)
+		goto finish;
+
+	ret = usbssp_check_bandwidth(usbssp_data, &usbssp_data->gadget);
+	if (ret)
+		goto finish;
+
+	ep_priv->ep_state &= ~USBSSP_EP_ENABLED;
+
+finish:
+	ep_priv->ep_state &= ~USBSSP_EP_DISABLE_PENDING;
+	usbssp_dbg(usbssp_data, "%s disable endpoint %s\n", ep_priv->name,
+			(ret == 0) ? "success" : "failed");
+
+	usbssp_g_unlock(irq_disabled_locally, flags);
+	return ret;
+}
+
+static struct usb_request *usbssp_gadget_ep_alloc_request(struct usb_ep *ep,
+							  gfp_t gfp_flags)
+{
+	struct usbssp_request *req_priv;
+	struct usbssp_ep *ep_priv = to_usbssp_ep(ep);
+
+	req_priv = kzalloc(sizeof(*req_priv), gfp_flags);
+	if (!req_priv)
+		return NULL;
+
+	req_priv->epnum = ep_priv->number;
+	req_priv->dep  = ep_priv;
+
+	trace_usbssp_alloc_request(&req_priv->request);
+	return &req_priv->request;
+}
+
+static void usbssp_gadget_ep_free_request(struct usb_ep *ep,
+		struct usb_request *request)
+{
+	struct usbssp_request *req_priv = to_usbssp_request(request);
+
+	trace_usbssp_free_request(&req_priv->request);
+	kfree(req_priv);
+}
+
+static int  usbssp_gadget_ep_queue(struct usb_ep *ep,
+				   struct usb_request *request,
+				   gfp_t gfp_flags)
+{
+	struct usbssp_ep *ep_priv = to_usbssp_ep(ep);
+	struct usbssp_request *req_priv = to_usbssp_request(request);
+	struct usbssp_udc *usbssp_data = ep_priv->usbssp_data;
+	unsigned long flags = 0;
+	int irq_disabled_locally = 0;
+	int ret;
+
+	if (!ep_priv->endpoint.desc) {
+		usbssp_err(usbssp_data,
+				"%s: can't queue to disabled endpoint\n",
+				ep_priv->name);
+		return -ESHUTDOWN;
+	}
+
+	if ((ep_priv->ep_state & USBSSP_EP_DISABLE_PENDING ||
+			!(ep_priv->ep_state & USBSSP_EP_ENABLED))) {
+		dev_err(usbssp_data->dev,
+				"%s: can't queue to disabled endpoint\n",
+				ep_priv->name);
+		ret = -ESHUTDOWN;
+		goto out;
+	}
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+	ret =  usbssp_enqueue(ep_priv, req_priv);
+	usbssp_g_unlock(irq_disabled_locally, flags);
+out:
+	return ret;
+}
+
+static int usbssp_gadget_ep_dequeue(struct usb_ep *ep,
+				    struct usb_request *request)
+{
+	struct usbssp_ep *ep_priv = to_usbssp_ep(ep);
+	struct usbssp_request *req_priv = to_usbssp_request(request);
+	struct usbssp_udc *usbssp_data = ep_priv->usbssp_data;
+	unsigned long flags = 0;
+	int ret;
+	int irq_disabled_locally = 0;
+
+	if (!ep_priv->endpoint.desc) {
+		usbssp_err(usbssp_data,
+				"%s: can't queue to disabled endpoint\n",
+				ep_priv->name);
+		return -ESHUTDOWN;
+	}
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+	ret =  usbssp_dequeue(ep_priv, req_priv);
+	usbssp_g_unlock(irq_disabled_locally, flags);
+
+	return ret;
+}
+
+static int usbssp_gadget_ep_set_halt(struct usb_ep *ep, int value)
+{
+	struct usbssp_ep *ep_priv = to_usbssp_ep(ep);
+	struct usbssp_udc *usbssp_data = ep_priv->usbssp_data;
+	int ret;
+	int irq_disabled_locally = 0;
+	unsigned long flags = 0;
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+	ret =  usbssp_halt_endpoint(usbssp_data, ep_priv, value);
+	usbssp_g_unlock(irq_disabled_locally, flags);
+	return ret;
+}
+
+static int usbssp_gadget_ep_set_wedge(struct usb_ep *ep)
+{
+	struct usbssp_ep *ep_priv = to_usbssp_ep(ep);
+	struct usbssp_udc *usbssp_data = ep_priv->usbssp_data;
+	unsigned long flags = 0;
+	int ret;
+	int irq_disabled_locally = 0;
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+	ep_priv->ep_state |= USBSSP_EP_WEDGE;
+	ret =  usbssp_halt_endpoint(usbssp_data, ep_priv, 1);
+	usbssp_g_unlock(irq_disabled_locally, flags);
+
+	return ret;
+}
+
+static const struct usb_ep_ops usbssp_gadget_ep0_ops = {
+	.enable		= usbssp_gadget_ep_enable,
+	.disable	= usbssp_gadget_ep_disable,
+	.alloc_request	= usbssp_gadget_ep_alloc_request,
+	.free_request	= usbssp_gadget_ep_free_request,
+	.queue		= usbssp_gadget_ep_queue,
+	.dequeue	= usbssp_gadget_ep_dequeue,
+	.set_halt	= usbssp_gadget_ep_set_halt,
+	.set_wedge	= usbssp_gadget_ep_set_wedge,
+};
+
+static const struct usb_ep_ops usbssp_gadget_ep_ops = {
+	.enable		= usbssp_gadget_ep_enable,
+	.disable	= usbssp_gadget_ep_disable,
+	.alloc_request	= usbssp_gadget_ep_alloc_request,
+	.free_request	= usbssp_gadget_ep_free_request,
+	.queue		= usbssp_gadget_ep_queue,
+	.dequeue	= usbssp_gadget_ep_dequeue,
+	.set_halt	= usbssp_gadget_ep_set_halt,
+	.set_wedge	= usbssp_gadget_ep_set_wedge,
+};
+
+void usbssp_gadget_giveback(struct usbssp_ep *ep_priv,
+			    struct usbssp_request *req_priv, int status)
+{
+	struct usbssp_udc *usbssp_data = ep_priv->usbssp_data;
+
+	list_del(&req_priv->list);
+
+	if (req_priv->request.status == -EINPROGRESS)
+		req_priv->request.status = status;
+
+	usb_gadget_unmap_request_by_dev(usbssp_data->dev,
+			&req_priv->request, req_priv->direction);
+
+	trace_usbssp_request_giveback(&req_priv->request);
+
+	if (in_interrupt())
+		spin_unlock(&usbssp_data->lock);
+	else
+		spin_unlock(&usbssp_data->irq_thread_lock);
+
+	if (req_priv != &usbssp_data->usb_req_ep0_in) {
+		usb_gadget_giveback_request(&ep_priv->endpoint,
+				&req_priv->request);
+	}
+
+	if (in_interrupt())
+		spin_lock(&usbssp_data->lock);
+	else
+		spin_lock(&usbssp_data->irq_thread_lock);
+
+}
+
+static struct usb_endpoint_descriptor usbssp_gadget_ep0_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_CONTROL,
+};
+
+static int usbssp_gadget_start(struct usb_gadget *g,
+			       struct usb_gadget_driver *driver)
+{
+	struct usbssp_udc *usbssp_data = gadget_to_usbssp(g);
+	int ret = 0;
+
+	if (usbssp_data->gadget_driver) {
+		usbssp_err(usbssp_data, "%s is already bound to %s\n",
+				usbssp_data->gadget.name,
+				usbssp_data->gadget_driver->driver.name);
+		ret = -EBUSY;
+		goto err1;
+	}
+
+	usbssp_data->gadget_driver = driver;
+
+	if (pm_runtime_active(usbssp_data->dev)) {
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+		usbssp_data->ep0state = USBSSP_EP0_UNCONNECTED;
+		ret = usbssp_run(usbssp_data);
+		if (ret < 0)
+			goto err1;
+	}
+	return 0;
+err1:
+	return ret;
+}
+
+static int usbssp_gadget_stop(struct usb_gadget *g)
+{
+	unsigned long flags = 0;
+	int irq_disabled_locally = 0;
+	struct usbssp_udc *usbssp_data  = gadget_to_usbssp(g);
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+	if (pm_runtime_suspended(usbssp_data->dev))
+		goto out;
+
+	usbssp_free_dev(usbssp_data);
+	usbssp_stop(usbssp_data);
+out:
+	usbssp_data->gadget_driver = NULL;
+	usbssp_g_unlock(irq_disabled_locally, flags);
+
+	return 0;
+}
+
+static int usbssp_gadget_get_frame(struct usb_gadget *g)
+{
+	struct usbssp_udc *usbssp_data = gadget_to_usbssp(g);
+
+	return usbssp_get_frame(usbssp_data);
+}
+
+static int usbssp_gadget_wakeup(struct usb_gadget *g)
+{
+	struct usbssp_udc *usbssp_data = gadget_to_usbssp(g);
+	unsigned long flags = 0;
+	int irq_disabled_locally = 0;
+	__le32 __iomem *port_regs;
+	u32 temp;
+
+	if (!usbssp_data->port_remote_wakeup)
+		return -EINVAL;
+
+	if (!usbssp_data->port_suspended)
+		return -EINVAL;
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+
+	port_regs = usbssp_get_port_io_addr(usbssp_data);
+	temp = readl(port_regs+PORTPMSC);
+
+	if (!(temp & PORT_RWE))
+		return 0;
+
+	temp = readl(port_regs + PORTSC);
+
+	temp &= ~PORT_PLS_MASK;
+	writel(temp, port_regs + PORTPMSC);
+	usbssp_g_unlock(irq_disabled_locally, flags);
+	return 0;
+}
+
+static int usbssp_gadget_set_selfpowered(struct usb_gadget *g,
+		int is_selfpowered)
+{
+	unsigned long flags = 0;
+	int irq_disabled_locally = 0;
+	struct usbssp_udc *usbssp_data = gadget_to_usbssp(g);
+
+	usbssp_g_lock(irq_disabled_locally, flags);
+
+	g->is_selfpowered = !!is_selfpowered;
+	usbssp_g_unlock(irq_disabled_locally, flags);
+
+	return 0;
+}
+
+static const struct usb_gadget_ops usbssp_gadget_ops = {
+	.get_frame		= usbssp_gadget_get_frame,
+	.wakeup			= usbssp_gadget_wakeup,
+	.set_selfpowered	= usbssp_gadget_set_selfpowered,
+	.udc_start		= usbssp_gadget_start,
+	.udc_stop		= usbssp_gadget_stop,
+};
+
+int usbssp_gadget_init_endpoint(struct usbssp_udc *usbssp_data)
+{
+	int i = 0;
+	struct usbssp_ep *ep_priv;
+
+	usbssp_data->num_endpoints = USBSSP_ENDPOINTS_NUM;
+	INIT_LIST_HEAD(&usbssp_data->gadget.ep_list);
+
+	for (i = 1; i < usbssp_data->num_endpoints; i++) {
+		bool direction = i & 1;  /*start from OUT endpoint*/
+		u8 epnum = (i >>  1);
+
+		ep_priv = &usbssp_data->devs.eps[i-1];
+		ep_priv->usbssp_data = usbssp_data;
+		ep_priv->number = epnum;
+		ep_priv->direction = direction;  /*0 for OUT, 1 for IN*/
+
+		snprintf(ep_priv->name, sizeof(ep_priv->name), "ep%d%s", epnum,
+				(ep_priv->direction) ? "in"  : "out");
+
+		ep_priv->endpoint.name = ep_priv->name;
+
+		if (ep_priv->number < 2) {
+			ep_priv->endpoint.desc = &usbssp_gadget_ep0_desc;
+			ep_priv->endpoint.comp_desc = NULL;
+		}
+
+		if (epnum == 0) {  //EP0 is  bidirectional endpoint
+			usb_ep_set_maxpacket_limit(&ep_priv->endpoint, 512);
+			usbssp_dbg(usbssp_data,
+				"Initializing %s, MaxPack: %04x Type: Ctrl\n",
+				ep_priv->name, 512);
+			ep_priv->endpoint.maxburst = 1;
+			ep_priv->endpoint.ops = &usbssp_gadget_ep0_ops;
+			ep_priv->endpoint.caps.type_control = true;
+
+			usbssp_data->usb_req_ep0_in.epnum = ep_priv->number;
+			usbssp_data->usb_req_ep0_in.dep  = ep_priv;
+
+			if (!epnum)
+				usbssp_data->gadget.ep0 = &ep_priv->endpoint;
+		} else {
+			usb_ep_set_maxpacket_limit(&ep_priv->endpoint, 1024);
+			ep_priv->endpoint.maxburst = 15;
+			ep_priv->endpoint.ops = &usbssp_gadget_ep_ops;
+			list_add_tail(&ep_priv->endpoint.ep_list,
+					&usbssp_data->gadget.ep_list);
+			ep_priv->endpoint.caps.type_iso = true;
+			ep_priv->endpoint.caps.type_bulk = true;
+			ep_priv->endpoint.caps.type_int = true;
+
+		}
+
+		ep_priv->endpoint.caps.dir_in = direction;
+		ep_priv->endpoint.caps.dir_out = !direction;
+
+		usbssp_dbg(usbssp_data, "Init %s, MaxPack: %04x SupType:"
+				" INT/BULK/ISOC , SupDir %s\n",
+				ep_priv->name, 1024,
+				(ep_priv->endpoint.caps.dir_in) ? "IN" : "OUT");
+
+		INIT_LIST_HEAD(&ep_priv->pending_list);
+	}
+	return 0;
+}
+
+void usbssp_gadget_free_endpoint(struct usbssp_udc *usbssp_data)
+{
+	int i;
+	struct usbssp_ep *ep_priv;
+
+	for (i = 0; i < usbssp_data->num_endpoints; i++) {
+		ep_priv = &usbssp_data->devs.eps[i];
+
+		if (ep_priv->number != 0)
+			list_del(&ep_priv->endpoint.ep_list);
+	}
+}
+
+static void usbssp_disconnect_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (usbssp_data->gadget_driver &&
+	    usbssp_data->gadget_driver->disconnect) {
+		spin_unlock(&usbssp_data->irq_thread_lock);
+		usbssp_data->gadget_driver->disconnect(&usbssp_data->gadget);
+		spin_lock(&usbssp_data->irq_thread_lock);
+	}
+}
+
+void usbssp_suspend_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (usbssp_data->gadget_driver && usbssp_data->gadget_driver->suspend) {
+		spin_unlock(&usbssp_data->lock);
+		usbssp_data->gadget_driver->suspend(&usbssp_data->gadget);
+		spin_lock(&usbssp_data->lock);
+	}
+}
+
+void usbssp_resume_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (usbssp_data->gadget_driver && usbssp_data->gadget_driver->resume) {
+		spin_unlock(&usbssp_data->lock);
+		usbssp_data->gadget_driver->resume(&usbssp_data->gadget);
+		spin_lock(&usbssp_data->lock);
+	}
+}
+
+static void usbssp_reset_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (!usbssp_data->gadget_driver)
+		return;
+
+	if (usbssp_data->gadget.speed != USB_SPEED_UNKNOWN) {
+		spin_unlock(&usbssp_data->lock);
+		usb_gadget_udc_reset(&usbssp_data->gadget,
+				usbssp_data->gadget_driver);
+		spin_lock(&usbssp_data->lock);
+	}
+}
+
+void usbssp_gadget_disconnect_interrupt(struct usbssp_udc *usbssp_data)
+{
+	usbssp_disconnect_gadget(usbssp_data);
+}
+
+
+void usbssp_gadget_reset_interrupt(struct usbssp_udc *usbssp_data)
+{
+	usbssp_reset_gadget(usbssp_data);
+	switch (usbssp_data->gadget.speed) {
+	case USB_SPEED_SUPER_PLUS:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+		usbssp_data->gadget.ep0->maxpacket = 512;
+		break;
+	case USB_SPEED_SUPER:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+		usbssp_data->gadget.ep0->maxpacket = 512;
+		break;
+	case USB_SPEED_HIGH:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+		usbssp_data->gadget.ep0->maxpacket = 64;
+		break;
+	case USB_SPEED_FULL:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+		usbssp_data->gadget.ep0->maxpacket = 64;
+		break;
+	case USB_SPEED_LOW:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8);
+		usbssp_data->gadget.ep0->maxpacket = 8;
+		break;
+	default:
+		break;
+	}
+}
-- 
2.17.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux