@@ -17,6 +17,36 @@
#include "gadget-export.h"
#include "gadget.h"
+/**
+ * cdns3_handshake - spin reading until handshake completes or fails
+ * @ptr: address of device controller register to be read
+ * @mask: bits to look at in result of read
+ * @done: value of those bits when handshake succeeds
+ * @usec: timeout in microseconds
+ *
+ * Returns negative errno, or zero on success
+ *
+ * Success happens when the "mask" bits have the specified value (hardware
+ * handshake done). There are two failure modes: "usec" have passed (major
+ * hardware flakeout), or the register reads as all-ones (hardware removed).
+ */
+int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
+{
+ u32 result;
+
+ do {
+ result = readl(ptr);
+ if (result == ~(u32)0) /* card removed */
+ return -ENODEV;
+ result &= mask;
+ if (result == done)
+ return 0;
+ udelay(1);
+ usec--;
+ } while (usec > 0);
+ return -ETIMEDOUT;
+}
+
/**
* cdns3_set_register_bit - set bit in given register.
* @ptr: address of device controller register to be read and changed
@@ -45,6 +75,20 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
wmb();
}
+static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
+{
+ struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+
+ dma_free_coherent(priv_dev->sysdev,
+ TRB_SIZE * TRBS_PER_SEGMENT,
+ priv_ep->trb_pool, priv_ep->trb_pool_dma);
+ priv_ep->trb_pool = NULL;
+
+ dma_free_coherent(priv_dev->sysdev, CDNS3_UNALIGNED_BUF_SIZE,
+ priv_ep->aligned_buff, priv_ep->aligned_dma_addr);
+ priv_ep->aligned_buff = NULL;
+}
+
/**
* cdns3_irq_handler - irq line interrupt handler
* @cdns: cdns3 instance
@@ -60,6 +104,114 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
return ret;
}
+/* Find correct direction for HW endpoint according to description */
+static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
+ struct cdns3_endpoint *priv_ep)
+{
+ return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) ||
+ (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc));
+}
+
+static struct cdns3_endpoint *cdns3_find_available_ss_ep(struct cdns3_device *priv_dev,
+ struct usb_endpoint_descriptor *desc)
+{
+ struct usb_ep *ep;
+ struct cdns3_endpoint *priv_ep;
+
+ list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
+ unsigned long num;
+ int ret;
+ /* ep name pattern likes epXin or epXout */
+ char c[2] = {ep->name[2], '\0'};
+
+ ret = kstrtoul(c, 10, &num);
+ if (ret)
+ return ERR_PTR(ret);
+
+ priv_ep = ep_to_cdns3_ep(ep);
+ if (cdns3_ep_dir_is_correct(desc, priv_ep)) {
+ if (!(priv_ep->flags & EP_USED)) {
+ priv_ep->num = num;
+ priv_ep->flags |= EP_USED;
+ return priv_ep;
+ }
+ }
+ }
+ return ERR_PTR(-ENOENT);
+}
+
+static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
+ struct usb_endpoint_descriptor *desc,
+ struct usb_ss_ep_comp_descriptor *comp_desc)
+{
+ struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+ struct cdns3_endpoint *priv_ep;
+ unsigned long flags;
+
+ priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
+ if (IS_ERR(priv_ep)) {
+ dev_err(&priv_dev->dev, "no available ep\n");
+ return NULL;
+ }
+
+ dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
+
+ spin_lock_irqsave(&priv_dev->lock, flags);
+ priv_ep->endpoint.desc = desc;
+ priv_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
+ priv_ep->type = usb_endpoint_type(desc);
+
+ list_add_tail(&priv_ep->ep_match_pending_list,
+ &priv_dev->ep_match_list);
+ spin_unlock_irqrestore(&priv_dev->lock, flags);
+ return &priv_ep->endpoint;
+}
+
+/**
+ * cdns3_gadget_get_frame Returns number of actual ITP frame
+ * @gadget: gadget object
+ *
+ * Returns number of actual ITP frame
+ */
+static int cdns3_gadget_get_frame(struct usb_gadget *gadget)
+{
+ struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+
+ return readl(&priv_dev->regs->usb_iptn);
+}
+
+static int cdns3_gadget_wakeup(struct usb_gadget *gadget)
+{
+ return 0;
+}
+
+static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget,
+ int is_selfpowered)
+{
+ struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv_dev->lock, flags);
+ gadget->is_selfpowered = !!is_selfpowered;
+ spin_unlock_irqrestore(&priv_dev->lock, flags);
+ return 0;
+}
+
+static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+
+ if (!priv_dev->start_gadget)
+ return 0;
+
+ if (is_on)
+ writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf);
+ else
+ writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
+
+ return 0;
+}
+
static void cdns3_gadget_config(struct cdns3_device *priv_dev)
{
struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
@@ -77,6 +229,95 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev)
writel(USB_CONF_DEVEN, ®s->usb_conf);
}
+/**
+ * cdns3_gadget_udc_start Gadget start
+ * @gadget: gadget object
+ * @driver: driver which operates on this gadget
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int cdns3_gadget_udc_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+ unsigned long flags;
+
+ if (priv_dev->gadget_driver) {
+ dev_err(&priv_dev->dev, "%s is already bound to %s\n",
+ priv_dev->gadget.name,
+ priv_dev->gadget_driver->driver.name);
+ return -EBUSY;
+ }
+
+ spin_lock_irqsave(&priv_dev->lock, flags);
+ priv_dev->gadget_driver = driver;
+ if (!priv_dev->start_gadget)
+ goto unlock;
+
+ cdns3_gadget_config(priv_dev);
+unlock:
+ spin_unlock_irqrestore(&priv_dev->lock, flags);
+ return 0;
+}
+
+/**
+ * cdns3_gadget_udc_stop Stops gadget
+ * @gadget: gadget object
+ *
+ * Returns 0
+ */
+static int cdns3_gadget_udc_stop(struct usb_gadget *gadget)
+{
+ struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+ struct cdns3_endpoint *priv_ep, *temp_ep;
+ u32 bEndpointAddress;
+ struct usb_ep *ep;
+ int ret = 0;
+ int i;
+
+ priv_dev->gadget_driver = NULL;
+ list_for_each_entry_safe(priv_ep, temp_ep, &priv_dev->ep_match_list,
+ ep_match_pending_list) {
+ list_del(&priv_ep->ep_match_pending_list);
+ priv_ep->flags &= ~EP_USED;
+ }
+
+ priv_dev->onchip_mem_allocated_size = 0;
+ priv_dev->out_mem_is_allocated = 0;
+ priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
+
+ for (i = 0; i < priv_dev->ep_nums ; i++)
+ cdns3_free_trb_pool(priv_dev->eps[i]);
+
+ if (!priv_dev->start_gadget)
+ return 0;
+
+ list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
+ priv_ep = ep_to_cdns3_ep(ep);
+ bEndpointAddress = priv_ep->num | priv_ep->dir;
+ cdns3_select_ep(priv_dev, bEndpointAddress);
+ writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
+ ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
+ EP_CMD_EPRST, 0, 100);
+ }
+
+ /* disable interrupt for device */
+ writel(0, &priv_dev->regs->usb_ien);
+ writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
+
+ return ret;
+}
+
+static const struct usb_gadget_ops cdns3_gadget_ops = {
+ .get_frame = cdns3_gadget_get_frame,
+ .wakeup = cdns3_gadget_wakeup,
+ .set_selfpowered = cdns3_gadget_set_selfpowered,
+ .pullup = cdns3_gadget_pullup,
+ .udc_start = cdns3_gadget_udc_start,
+ .udc_stop = cdns3_gadget_udc_stop,
+ .match_ep = cdns3_gadget_match_ep,
+};
+
/**
* cdns3_init_ep Initializes software endpoints of gadget
* @cdns3: extended gadget object
@@ -185,8 +426,7 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
/* fill gadget fields */
priv_dev->gadget.max_speed = USB_SPEED_SUPER;
priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
- //TODO: Add implementation of cdns3_gadget_ops
- //priv_dev->gadget.ops = &cdns3_gadget_ops;
+ priv_dev->gadget.ops = &cdns3_gadget_ops;
priv_dev->gadget.name = "usb-ss-gadget";
priv_dev->gadget.sg_supported = 1;
priv_dev->is_connected = 0;
Patch adds implementation callback function defined in usb_gadget_ops object. Signed-off-by: Pawel Laszczak <pawell@cadence.com> --- drivers/usb/cdns3/gadget.c | 244 ++++++++++++++++++++++++++++++++++++- 1 file changed, 242 insertions(+), 2 deletions(-)