dm: usb: Add support for interrupt queues to the dm usb code
[platform/kernel/u-boot.git] / drivers / usb / host / ehci-hcd.c
index c6696aa..1e5a6e2 100644 (file)
@@ -21,6 +21,7 @@
  * MA 02111-1307 USA
  */
 #include <common.h>
+#include <dm.h>
 #include <errno.h>
 #include <asm/byteorder.h>
 #include <asm/unaligned.h>
@@ -42,7 +43,9 @@
  */
 #define HCHALT_TIMEOUT (8 * 1000)
 
+#ifndef CONFIG_DM_USB
 static struct ehci_ctrl ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT];
+#endif
 
 #define ALIGN_END_ADDR(type, ptr, size)                        \
        ((unsigned long)(ptr) + roundup((size) * sizeof(type), USB_DMA_MINALIGN))
@@ -121,7 +124,11 @@ static struct descriptor {
 
 static struct ehci_ctrl *ehci_get_ctrl(struct usb_device *udev)
 {
+#ifdef CONFIG_DM_USB
+       return dev_get_priv(usb_get_bus(udev->dev));
+#else
        return udev->controller;
+#endif
 }
 
 static int ehci_get_port_speed(struct ehci_ctrl *ctrl, uint32_t reg)
@@ -176,16 +183,15 @@ static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec)
        return -1;
 }
 
-static int ehci_reset(int index)
+static int ehci_reset(struct ehci_ctrl *ctrl)
 {
-       struct ehci_ctrl *ctrl = &ehcic[index];
        uint32_t cmd;
        int ret = 0;
 
-       cmd = ehci_readl(&ehcic[index].hcor->or_usbcmd);
+       cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
        cmd = (cmd & ~CMD_RUN) | CMD_RESET;
-       ehci_writel(&ehcic[index].hcor->or_usbcmd, cmd);
-       ret = handshake((uint32_t *)&ehcic[index].hcor->or_usbcmd,
+       ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
+       ret = handshake((uint32_t *)&ctrl->hcor->or_usbcmd,
                        CMD_RESET, 0, 250 * 1000);
        if (ret < 0) {
                printf("EHCI fail to reset\n");
@@ -193,13 +199,13 @@ static int ehci_reset(int index)
        }
 
        if (ehci_is_TDI())
-               ctrl->ops.set_usb_mode(&ehcic[index]);
+               ctrl->ops.set_usb_mode(ctrl);
 
 #ifdef CONFIG_USB_EHCI_TXFIFO_THRESH
-       cmd = ehci_readl(&ehcic[index].hcor->or_txfilltuning);
+       cmd = ehci_readl(&ctrl->hcor->or_txfilltuning);
        cmd &= ~TXFIFO_THRESH_MASK;
        cmd |= TXFIFO_THRESH(CONFIG_USB_EHCI_TXFIFO_THRESH);
-       ehci_writel(&ehcic[index].hcor->or_txfilltuning, cmd);
+       ehci_writel(&ctrl->hcor->or_txfilltuning, cmd);
 #endif
 out:
        return ret;
@@ -282,12 +288,13 @@ static inline u8 ehci_encode_speed(enum usb_device_speed speed)
        return QH_FULL_SPEED;
 }
 
-static void ehci_update_endpt2_dev_n_port(struct usb_device *dev,
+static void ehci_update_endpt2_dev_n_port(struct usb_device *udev,
                                          struct QH *qh)
 {
        struct usb_device *ttdev;
+       int parent_devnum;
 
-       if (dev->speed != USB_SPEED_LOW && dev->speed != USB_SPEED_FULL)
+       if (udev->speed != USB_SPEED_LOW && udev->speed != USB_SPEED_FULL)
                return;
 
        /*
@@ -295,14 +302,45 @@ static void ehci_update_endpt2_dev_n_port(struct usb_device *dev,
         * the tt, so of the first upstream usb-2 hub, there may be usb-1 hubs
         * in the tree before that one!
         */
-       ttdev = dev;
+#ifdef CONFIG_DM_USB
+       /*
+        * When called from usb-uclass.c: usb_scan_device() udev->dev points
+        * to the parent udevice, not the actual udevice belonging to the
+        * udev as the device is not instantiated yet. So when searching
+        * for the first usb-2 parent start with udev->dev not
+        * udev->dev->parent .
+        */
+       struct udevice *parent;
+       struct usb_device *uparent;
+
+       ttdev = udev;
+       parent = udev->dev;
+       uparent = dev_get_parentdata(parent);
+
+       while (uparent->speed != USB_SPEED_HIGH) {
+               struct udevice *dev = parent;
+
+               if (device_get_uclass_id(dev->parent) != UCLASS_USB_HUB) {
+                       printf("ehci: Error cannot find high speed parent of usb-1 device\n");
+                       return;
+               }
+
+               ttdev = dev_get_parentdata(dev);
+               parent = dev->parent;
+               uparent = dev_get_parentdata(parent);
+       }
+       parent_devnum = uparent->devnum;
+#else
+       ttdev = udev;
        while (ttdev->parent && ttdev->parent->speed != USB_SPEED_HIGH)
                ttdev = ttdev->parent;
        if (!ttdev->parent)
                return;
+       parent_devnum = ttdev->parent->devnum;
+#endif
 
        qh->qh_endpt2 |= cpu_to_hc32(QH_ENDPT2_PORTNUM(ttdev->portnr) |
-                                    QH_ENDPT2_HUBADDR(ttdev->parent->devnum));
+                                    QH_ENDPT2_HUBADDR(parent_devnum));
 }
 
 static int
@@ -837,7 +875,7 @@ static int ehci_submit_root(struct usb_device *dev, unsigned long pipe,
                                      port - 1);
                                reg |= EHCI_PS_PO;
                                ehci_writel(status_reg, reg);
-                               break;
+                               return -ENXIO;
                        } else {
                                int ret;
 
@@ -859,11 +897,22 @@ static int ehci_submit_root(struct usb_device *dev, unsigned long pipe,
                                 */
                                ret = handshake(status_reg, EHCI_PS_PR, 0,
                                                2 * 1000);
-                               if (!ret)
-                                       ctrl->portreset |= 1 << port;
-                               else
+                               if (!ret) {
+                                       reg = ehci_readl(status_reg);
+                                       if ((reg & (EHCI_PS_PE | EHCI_PS_CS))
+                                           == EHCI_PS_CS && !ehci_is_TDI()) {
+                                               debug("port %d full speed --> companion\n", port - 1);
+                                               reg &= ~EHCI_PS_CLEAR;
+                                               reg |= EHCI_PS_PO;
+                                               ehci_writel(status_reg, reg);
+                                               return -ENXIO;
+                                       } else {
+                                               ctrl->portreset |= 1 << port;
+                                       }
+                               } else {
                                        printf("port(%d) reset error\n",
                                               port - 1);
+                               }
                        }
                        break;
                case USB_PORT_FEAT_TEST:
@@ -961,6 +1010,7 @@ static void ehci_setup_ops(struct ehci_ctrl *ctrl, const struct ehci_ops *ops)
        }
 }
 
+#ifndef CONFIG_DM_USB
 void ehci_set_controller_priv(int index, void *priv, const struct ehci_ops *ops)
 {
        struct ehci_ctrl *ctrl = &ehcic[index];
@@ -973,6 +1023,7 @@ void *ehci_get_controller_priv(int index)
 {
        return ehcic[index].priv;
 }
+#endif
 
 static int ehci_common_init(struct ehci_ctrl *ctrl, uint tweaks)
 {
@@ -1083,6 +1134,7 @@ static int ehci_common_init(struct ehci_ctrl *ctrl, uint tweaks)
        return 0;
 }
 
+#ifndef CONFIG_DM_USB
 int usb_lowlevel_stop(int index)
 {
        ehci_shutdown(&ehcic[index]);
@@ -1108,7 +1160,7 @@ int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
                goto done;
 
        /* EHCI spec section 4.1 */
-       if (ehci_reset(index))
+       if (ehci_reset(ctrl))
                return -1;
 
 #if defined(CONFIG_EHCI_HCD_INIT_AFTER_RESET)
@@ -1128,6 +1180,7 @@ done:
        *controller = &ehcic[index];
        return 0;
 }
+#endif
 
 static int _ehci_submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
                                 void *buffer, int length)
@@ -1210,9 +1263,9 @@ disable_periodic(struct ehci_ctrl *ctrl)
        return 0;
 }
 
-struct int_queue *
-create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize,
-                int elementsize, void *buffer, int interval)
+static struct int_queue *_ehci_create_int_queue(struct usb_device *dev,
+                       unsigned long pipe, int queuesize, int elementsize,
+                       void *buffer, int interval)
 {
        struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
        struct int_queue *result = NULL;
@@ -1368,7 +1421,8 @@ fail1:
        return NULL;
 }
 
-void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
+static void *_ehci_poll_int_queue(struct usb_device *dev,
+                                 struct int_queue *queue)
 {
        struct QH *cur = queue->current;
        struct qTD *cur_td;
@@ -1403,8 +1457,8 @@ void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
 }
 
 /* Do not free buffers associated with QHs, they're owned by someone else */
-int
-destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
+static int _ehci_destroy_int_queue(struct usb_device *dev,
+                                  struct int_queue *queue)
 {
        struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
        int result = -1;
@@ -1461,12 +1515,12 @@ static int _ehci_submit_int_msg(struct usb_device *dev, unsigned long pipe,
        debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
              dev, pipe, buffer, length, interval);
 
-       queue = create_int_queue(dev, pipe, 1, length, buffer, interval);
+       queue = _ehci_create_int_queue(dev, pipe, 1, length, buffer, interval);
        if (!queue)
                return -1;
 
        timeout = get_timer(0) + USB_TIMEOUT_MS(pipe);
-       while ((backbuffer = poll_int_queue(dev, queue)) == NULL)
+       while ((backbuffer = _ehci_poll_int_queue(dev, queue)) == NULL)
                if (get_timer(0) > timeout) {
                        printf("Timeout poll on interrupt endpoint\n");
                        result = -ETIMEDOUT;
@@ -1479,7 +1533,7 @@ static int _ehci_submit_int_msg(struct usb_device *dev, unsigned long pipe,
                return -EINVAL;
        }
 
-       ret = destroy_int_queue(dev, queue);
+       ret = _ehci_destroy_int_queue(dev, queue);
        if (ret < 0)
                return ret;
 
@@ -1487,6 +1541,7 @@ static int _ehci_submit_int_msg(struct usb_device *dev, unsigned long pipe,
        return result;
 }
 
+#ifndef CONFIG_DM_USB
 int submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
                            void *buffer, int length)
 {
@@ -1504,3 +1559,126 @@ int submit_int_msg(struct usb_device *dev, unsigned long pipe,
 {
        return _ehci_submit_int_msg(dev, pipe, buffer, length, interval);
 }
+
+struct int_queue *create_int_queue(struct usb_device *dev,
+               unsigned long pipe, int queuesize, int elementsize,
+               void *buffer, int interval)
+{
+       return _ehci_create_int_queue(dev, pipe, queuesize, elementsize,
+                                     buffer, interval);
+}
+
+void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
+{
+       return _ehci_poll_int_queue(dev, queue);
+}
+
+int destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
+{
+       return _ehci_destroy_int_queue(dev, queue);
+}
+#endif
+
+#ifdef CONFIG_DM_USB
+static int ehci_submit_control_msg(struct udevice *dev, struct usb_device *udev,
+                                  unsigned long pipe, void *buffer, int length,
+                                  struct devrequest *setup)
+{
+       debug("%s: dev='%s', udev=%p, udev->dev='%s', portnr=%d\n", __func__,
+             dev->name, udev, udev->dev->name, udev->portnr);
+
+       return _ehci_submit_control_msg(udev, pipe, buffer, length, setup);
+}
+
+static int ehci_submit_bulk_msg(struct udevice *dev, struct usb_device *udev,
+                               unsigned long pipe, void *buffer, int length)
+{
+       debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
+       return _ehci_submit_bulk_msg(udev, pipe, buffer, length);
+}
+
+static int ehci_submit_int_msg(struct udevice *dev, struct usb_device *udev,
+                              unsigned long pipe, void *buffer, int length,
+                              int interval)
+{
+       debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
+       return _ehci_submit_int_msg(udev, pipe, buffer, length, interval);
+}
+
+static struct int_queue *ehci_create_int_queue(struct udevice *dev,
+               struct usb_device *udev, unsigned long pipe, int queuesize,
+               int elementsize, void *buffer, int interval)
+{
+       debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
+       return _ehci_create_int_queue(udev, pipe, queuesize, elementsize,
+                                     buffer, interval);
+}
+
+static void *ehci_poll_int_queue(struct udevice *dev, struct usb_device *udev,
+                                struct int_queue *queue)
+{
+       debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
+       return _ehci_poll_int_queue(udev, queue);
+}
+
+static int ehci_destroy_int_queue(struct udevice *dev, struct usb_device *udev,
+                                 struct int_queue *queue)
+{
+       debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
+       return _ehci_destroy_int_queue(udev, queue);
+}
+
+int ehci_register(struct udevice *dev, struct ehci_hccr *hccr,
+                 struct ehci_hcor *hcor, const struct ehci_ops *ops,
+                 uint tweaks, enum usb_init_type init)
+{
+       struct usb_bus_priv *priv = dev_get_uclass_priv(dev);
+       struct ehci_ctrl *ctrl = dev_get_priv(dev);
+       int ret;
+
+       debug("%s: dev='%s', ctrl=%p, hccr=%p, hcor=%p, init=%d\n", __func__,
+             dev->name, ctrl, hccr, hcor, init);
+
+       priv->desc_before_addr = true;
+
+       ehci_setup_ops(ctrl, ops);
+       ctrl->hccr = hccr;
+       ctrl->hcor = hcor;
+       ctrl->priv = ctrl;
+
+       if (init == USB_INIT_DEVICE)
+               goto done;
+       ret = ehci_reset(ctrl);
+       if (ret)
+               goto err;
+
+       ret = ehci_common_init(ctrl, tweaks);
+       if (ret)
+               goto err;
+done:
+       return 0;
+err:
+       free(ctrl);
+       debug("%s: failed, ret=%d\n", __func__, ret);
+       return ret;
+}
+
+int ehci_deregister(struct udevice *dev)
+{
+       struct ehci_ctrl *ctrl = dev_get_priv(dev);
+
+       ehci_shutdown(ctrl);
+
+       return 0;
+}
+
+struct dm_usb_ops ehci_usb_ops = {
+       .control = ehci_submit_control_msg,
+       .bulk = ehci_submit_bulk_msg,
+       .interrupt = ehci_submit_int_msg,
+       .create_int_queue = ehci_create_int_queue,
+       .poll_int_queue = ehci_poll_int_queue,
+       .destroy_int_queue = ehci_destroy_int_queue,
+};
+
+#endif