greybus: es2: clean up cport-reset handling
[cascardo/linux.git] / drivers / staging / greybus / es2.c
index 4bb500f..f06a322 100644 (file)
 #include "connection.h"
 #include "greybus_trace.h"
 
+
+/* Fixed CPort numbers */
+#define ES2_CPORT_CDSI0                16
+#define ES2_CPORT_CDSI1                17
+
 /* Memory sizes for the buffers sent to/from the ES2 controller */
 #define ES2_GBUF_MSG_SIZE_MAX  2048
 
 static const struct usb_device_id id_table[] = {
-       { USB_DEVICE(0xffff, 0x0002) }, /* Made up number, delete once firmware is fixed to use real number */
        { USB_DEVICE(0x18d1, 0x1eaf) },
        { },
 };
@@ -45,22 +49,6 @@ MODULE_DEVICE_TABLE(usb, id_table);
  */
 #define NUM_CPORT_OUT_URB      (8 * NUM_BULKS)
 
-/* vendor request APB1 log */
-#define REQUEST_LOG            0x02
-
-/* vendor request to map a cport to bulk in and bulk out endpoints */
-#define REQUEST_EP_MAPPING     0x03
-
-/* vendor request to get the number of cports available */
-#define REQUEST_CPORT_COUNT    0x04
-
-/* vendor request to reset a cport state */
-#define REQUEST_RESET_CPORT    0x05
-
-/* vendor request to time the latency of messages on a given cport */
-#define REQUEST_LATENCY_TAG_EN 0x06
-#define REQUEST_LATENCY_TAG_DIS        0x07
-
 /*
  * @endpoint: bulk in endpoint for CPort data
  * @urb: array of urbs for the CPort in messages
@@ -189,7 +177,7 @@ static int map_cport_to_ep(struct es2_ap_dev *es2,
 
        retval = usb_control_msg(es2->usb_dev,
                                 usb_sndctrlpipe(es2->usb_dev, 0),
-                                REQUEST_EP_MAPPING,
+                                GB_APB_REQUEST_EP_MAPPING,
                                 USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
                                 0x00, 0x00,
                                 (char *)cport_to_ep,
@@ -209,6 +197,129 @@ static int unmap_cport(struct es2_ap_dev *es2, u16 cport_id)
 }
 #endif
 
+static int output_sync(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
+{
+       struct usb_device *udev = es2->usb_dev;
+       u8 *data;
+       int retval;
+
+       data = kmalloc(size, GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+       memcpy(data, req, size);
+
+       retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                                cmd,
+                                USB_DIR_OUT | USB_TYPE_VENDOR |
+                                USB_RECIP_INTERFACE,
+                                0, 0, data, size, ES2_TIMEOUT);
+       if (retval < 0)
+               dev_err(&udev->dev, "%s: return error %d\n", __func__, retval);
+       else
+               retval = 0;
+
+       kfree(data);
+       return retval;
+}
+
+static void ap_urb_complete(struct urb *urb)
+{
+       struct usb_ctrlrequest *dr = urb->context;
+
+       kfree(dr);
+       usb_free_urb(urb);
+}
+
+static int output_async(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
+{
+       struct usb_device *udev = es2->usb_dev;
+       struct urb *urb;
+       struct usb_ctrlrequest *dr;
+       u8 *buf;
+       int retval;
+
+       urb = usb_alloc_urb(0, GFP_ATOMIC);
+       if (!urb)
+               return -ENOMEM;
+
+       dr = kmalloc(sizeof(*dr) + size, GFP_ATOMIC);
+       if (!dr) {
+               usb_free_urb(urb);
+               return -ENOMEM;
+       }
+
+       buf = (u8 *)dr + sizeof(*dr);
+       memcpy(buf, req, size);
+
+       dr->bRequest = cmd;
+       dr->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
+       dr->wValue = 0;
+       dr->wIndex = 0;
+       dr->wLength = cpu_to_le16(size);
+
+       usb_fill_control_urb(urb, udev, usb_sndctrlpipe(udev, 0),
+                            (unsigned char *)dr, buf, size,
+                            ap_urb_complete, dr);
+       retval = usb_submit_urb(urb, GFP_ATOMIC);
+       if (retval) {
+               usb_free_urb(urb);
+               kfree(dr);
+       }
+       return retval;
+}
+
+static int output(struct gb_host_device *hd, void *req, u16 size, u8 cmd,
+                    bool async)
+{
+       struct es2_ap_dev *es2 = hd_to_es2(hd);
+
+       if (async)
+               return output_async(es2, req, size, cmd);
+
+       return output_sync(es2, req, size, cmd);
+}
+
+static int es2_cport_in_enable(struct es2_ap_dev *es2,
+                               struct es2_cport_in *cport_in)
+{
+       struct urb *urb;
+       int ret;
+       int i;
+
+       for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
+               urb = cport_in->urb[i];
+
+               ret = usb_submit_urb(urb, GFP_KERNEL);
+               if (ret) {
+                       dev_err(&es2->usb_dev->dev,
+                                       "failed to submit in-urb: %d\n", ret);
+                       goto err_kill_urbs;
+               }
+       }
+
+       return 0;
+
+err_kill_urbs:
+       for (--i; i >= 0; --i) {
+               urb = cport_in->urb[i];
+               usb_kill_urb(urb);
+       }
+
+       return ret;
+}
+
+static void es2_cport_in_disable(struct es2_ap_dev *es2,
+                               struct es2_cport_in *cport_in)
+{
+       struct urb *urb;
+       int i;
+
+       for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
+               urb = cport_in->urb[i];
+               usb_kill_urb(urb);
+       }
+}
+
 static struct urb *next_free_urb(struct es2_ap_dev *es2, gfp_t gfp_mask)
 {
        struct urb *urb = NULL;
@@ -234,7 +345,7 @@ static struct urb *next_free_urb(struct es2_ap_dev *es2, gfp_t gfp_mask)
         * Crap, pool is empty, complain to the syslog and go allocate one
         * dynamically as we have to succeed.
         */
-       dev_err(&es2->usb_dev->dev,
+       dev_dbg(&es2->usb_dev->dev,
                "No free CPort OUT urbs, having to dynamically allocate one!\n");
        return usb_alloc_urb(0, gfp_mask);
 }
@@ -308,8 +419,7 @@ static int message_send(struct gb_host_device *hd, u16 cport_id,
         * the target CPort id before filling it in.
         */
        if (!cport_id_valid(hd, cport_id)) {
-               dev_err(&udev->dev, "invalid destination cport 0x%02x\n",
-                               cport_id);
+               dev_err(&udev->dev, "invalid cport %u\n", cport_id);
                return -EINVAL;
        }
 
@@ -396,13 +506,18 @@ static int cport_reset(struct gb_host_device *hd, u16 cport_id)
        struct usb_device *udev = es2->usb_dev;
        int retval;
 
+       switch (cport_id) {
+       case GB_SVC_CPORT_ID:
+               return 0;
+       }
+
        retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
-                                REQUEST_RESET_CPORT,
+                                GB_APB_REQUEST_RESET_CPORT,
                                 USB_DIR_OUT | USB_TYPE_VENDOR |
-                                USB_RECIP_INTERFACE, 0, cport_id,
+                                USB_RECIP_INTERFACE, cport_id, 0,
                                 NULL, 0, ES2_TIMEOUT);
        if (retval < 0) {
-               dev_err(&udev->dev, "failed to reset cport %hu: %d\n", cport_id,
+               dev_err(&udev->dev, "failed to reset cport %u: %d\n", cport_id,
                        retval);
                return retval;
        }
@@ -414,11 +529,9 @@ static int cport_enable(struct gb_host_device *hd, u16 cport_id)
 {
        int retval;
 
-       if (cport_id != GB_SVC_CPORT_ID) {
-               retval = cport_reset(hd, cport_id);
-               if (retval)
-                       return retval;
-       }
+       retval = cport_reset(hd, cport_id);
+       if (retval)
+               return retval;
 
        return 0;
 }
@@ -430,13 +543,12 @@ static int latency_tag_enable(struct gb_host_device *hd, u16 cport_id)
        struct usb_device *udev = es2->usb_dev;
 
        if (!cport_id_valid(hd, cport_id)) {
-               dev_err(&udev->dev, "invalid destination cport 0x%02x\n",
-                       cport_id);
+               dev_err(&udev->dev, "invalid cport %u\n", cport_id);
                return -EINVAL;
        }
 
        retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
-                                REQUEST_LATENCY_TAG_EN,
+                                GB_APB_REQUEST_LATENCY_TAG_EN,
                                 USB_DIR_OUT | USB_TYPE_VENDOR |
                                 USB_RECIP_INTERFACE, cport_id, 0, NULL,
                                 0, ES2_TIMEOUT);
@@ -454,13 +566,12 @@ static int latency_tag_disable(struct gb_host_device *hd, u16 cport_id)
        struct usb_device *udev = es2->usb_dev;
 
        if (!cport_id_valid(hd, cport_id)) {
-               dev_err(&udev->dev, "invalid destination cport 0x%02x\n",
-                       cport_id);
+               dev_err(&udev->dev, "invalid cport %u\n", cport_id);
                return -EINVAL;
        }
 
        retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
-                                REQUEST_LATENCY_TAG_DIS,
+                                GB_APB_REQUEST_LATENCY_TAG_DIS,
                                 USB_DIR_OUT | USB_TYPE_VENDOR |
                                 USB_RECIP_INTERFACE, cport_id, 0, NULL,
                                 0, ES2_TIMEOUT);
@@ -471,6 +582,41 @@ static int latency_tag_disable(struct gb_host_device *hd, u16 cport_id)
        return retval;
 }
 
+static int cport_features_enable(struct gb_host_device *hd, u16 cport_id)
+{
+       int retval;
+       struct es2_ap_dev *es2 = hd_to_es2(hd);
+       struct usb_device *udev = es2->usb_dev;
+
+       retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                                GB_APB_REQUEST_CPORT_FEAT_EN,
+                                USB_DIR_OUT | USB_TYPE_VENDOR |
+                                USB_RECIP_INTERFACE, cport_id, 0, NULL,
+                                0, ES2_TIMEOUT);
+       if (retval < 0)
+               dev_err(&udev->dev, "Cannot enable CPort features for cport %u: %d\n",
+                       cport_id, retval);
+       return retval;
+}
+
+static int cport_features_disable(struct gb_host_device *hd, u16 cport_id)
+{
+       int retval;
+       struct es2_ap_dev *es2 = hd_to_es2(hd);
+       struct usb_device *udev = es2->usb_dev;
+
+       retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                                GB_APB_REQUEST_CPORT_FEAT_DIS,
+                                USB_DIR_OUT | USB_TYPE_VENDOR |
+                                USB_RECIP_INTERFACE, cport_id, 0, NULL,
+                                0, ES2_TIMEOUT);
+       if (retval < 0)
+               dev_err(&udev->dev,
+                       "Cannot disable CPort features for cport %u: %d\n",
+                       cport_id, retval);
+       return retval;
+}
+
 static struct gb_hd_driver es2_driver = {
        .hd_priv_size           = sizeof(struct es2_ap_dev),
        .message_send           = message_send,
@@ -478,6 +624,9 @@ static struct gb_hd_driver es2_driver = {
        .cport_enable           = cport_enable,
        .latency_tag_enable     = latency_tag_enable,
        .latency_tag_disable    = latency_tag_disable,
+       .output                 = output,
+       .cport_features_enable  = cport_features_enable,
+       .cport_features_disable = cport_features_disable,
 };
 
 /* Common function to report consistent warnings based on URB status */
@@ -506,18 +655,13 @@ static int check_urb_status(struct urb *urb)
        return -EAGAIN;
 }
 
-static void ap_disconnect(struct usb_interface *interface)
+static void es2_destroy(struct es2_ap_dev *es2)
 {
-       struct es2_ap_dev *es2;
        struct usb_device *udev;
-       int *cport_to_ep;
        int bulk_in;
        int i;
 
-       es2 = usb_get_intfdata(interface);
-       if (!es2)
-               return;
-
+       debugfs_remove(es2->apb_log_enable_dentry);
        usb_log_disable(es2);
 
        /* Tear down everything! */
@@ -540,19 +684,17 @@ static void ap_disconnect(struct usb_interface *interface)
 
                        if (!urb)
                                break;
-                       usb_kill_urb(urb);
                        usb_free_urb(urb);
                        kfree(cport_in->buffer[i]);
                        cport_in->buffer[i] = NULL;
                }
        }
 
-       usb_set_intfdata(interface, NULL);
+       kfree(es2->cport_to_ep);
+
        udev = es2->usb_dev;
-       cport_to_ep = es2->cport_to_ep;
-       gb_hd_remove(es2->hd);
+       gb_hd_put(es2->hd);
 
-       kfree(cport_to_ep);
        usb_put_dev(udev);
 }
 
@@ -568,6 +710,11 @@ static void cport_in_callback(struct urb *urb)
        if (status) {
                if ((status == -EAGAIN) || (status == -EPROTO))
                        goto exit;
+
+               /* The urb is being unlinked */
+               if (status == -ENOENT || status == -ESHUTDOWN)
+                       return;
+
                dev_err(dev, "urb cport in error %d (dropped)\n", status);
                return;
        }
@@ -586,7 +733,7 @@ static void cport_in_callback(struct urb *urb)
                greybus_data_rcvd(hd, cport_id, urb->transfer_buffer,
                                                        urb->actual_length);
        } else {
-               dev_err(dev, "invalid cport id 0x%02x received\n", cport_id);
+               dev_err(dev, "invalid cport id %u received\n", cport_id);
        }
 exit:
        /* put our urb back in the request pool */
@@ -627,7 +774,7 @@ static void apb_log_get(struct es2_ap_dev *es2, char *buf)
        do {
                retval = usb_control_msg(es2->usb_dev,
                                        usb_rcvctrlpipe(es2->usb_dev, 0),
-                                       REQUEST_LOG,
+                                       GB_APB_REQUEST_LOG,
                                        USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
                                        0x00, 0x00,
                                        buf,
@@ -695,7 +842,7 @@ static void usb_log_enable(struct es2_ap_dev *es2)
                return;
        /* XXX We will need to rename this per APB */
        es2->apb_log_dentry = debugfs_create_file("apb_log", S_IRUGO,
-                                               gb_debugfs_get(), NULL,
+                                               gb_debugfs_get(), es2,
                                                &apb_log_fops);
 }
 
@@ -751,18 +898,22 @@ static int apb_get_cport_count(struct usb_device *udev)
        int retval;
        __le16 *cport_count;
 
-       cport_count = kmalloc(sizeof(*cport_count), GFP_KERNEL);
+       cport_count = kzalloc(sizeof(*cport_count), GFP_KERNEL);
        if (!cport_count)
                return -ENOMEM;
 
        retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
-                                REQUEST_CPORT_COUNT,
+                                GB_APB_REQUEST_CPORT_COUNT,
                                 USB_DIR_IN | USB_TYPE_VENDOR |
                                 USB_RECIP_INTERFACE, 0, 0, cport_count,
                                 sizeof(*cport_count), ES2_TIMEOUT);
-       if (retval < 0) {
+       if (retval != sizeof(*cport_count)) {
                dev_err(&udev->dev, "Cannot retrieve CPort count: %d\n",
                        retval);
+
+               if (retval >= 0)
+                       retval = -EIO;
+
                goto out;
        }
 
@@ -795,7 +946,7 @@ static int ap_probe(struct usb_interface *interface,
        struct usb_endpoint_descriptor *endpoint;
        int bulk_in = 0;
        int bulk_out = 0;
-       int retval = -ENOMEM;
+       int retval;
        int i;
        int num_cports;
 
@@ -824,6 +975,17 @@ static int ap_probe(struct usb_interface *interface,
        INIT_KFIFO(es2->apb_log_fifo);
        usb_set_intfdata(interface, es2);
 
+       /*
+        * Reserve the CDSI0 and CDSI1 CPorts so they won't be allocated
+        * dynamically.
+        */
+       retval = gb_hd_cport_reserve(hd, ES2_CPORT_CDSI0);
+       if (retval)
+               goto error;
+       retval = gb_hd_cport_reserve(hd, ES2_CPORT_CDSI1);
+       if (retval)
+               goto error;
+
        es2->cport_to_ep = kcalloc(hd->num_cports, sizeof(*es2->cport_to_ep),
                                   GFP_KERNEL);
        if (!es2->cport_to_ep) {
@@ -844,16 +1006,17 @@ static int ap_probe(struct usb_interface *interface,
                                endpoint->bEndpointAddress;
                } else {
                        dev_err(&udev->dev,
-                               "Unknown endpoint type found, address %x\n",
+                               "Unknown endpoint type found, address 0x%02x\n",
                                endpoint->bEndpointAddress);
                }
        }
        if (bulk_in != NUM_BULKS || bulk_out != NUM_BULKS) {
                dev_err(&udev->dev, "Not enough endpoints found in device, aborting!\n");
+               retval = -ENODEV;
                goto error;
        }
 
-       /* Allocate buffers for our cport in messages and start them up */
+       /* Allocate buffers for our cport in messages */
        for (bulk_in = 0; bulk_in < NUM_BULKS; bulk_in++) {
                struct es2_cport_in *cport_in = &es2->cport_in[bulk_in];
 
@@ -862,11 +1025,15 @@ static int ap_probe(struct usb_interface *interface,
                        u8 *buffer;
 
                        urb = usb_alloc_urb(0, GFP_KERNEL);
-                       if (!urb)
+                       if (!urb) {
+                               retval = -ENOMEM;
                                goto error;
+                       }
                        buffer = kmalloc(ES2_GBUF_MSG_SIZE_MAX, GFP_KERNEL);
-                       if (!buffer)
+                       if (!buffer) {
+                               retval = -ENOMEM;
                                goto error;
+                       }
 
                        usb_fill_bulk_urb(urb, udev,
                                          usb_rcvbulkpipe(udev,
@@ -875,9 +1042,6 @@ static int ap_probe(struct usb_interface *interface,
                                          cport_in_callback, hd);
                        cport_in->urb[i] = urb;
                        cport_in->buffer[i] = buffer;
-                       retval = usb_submit_urb(urb, GFP_KERNEL);
-                       if (retval)
-                               goto error;
                }
        }
 
@@ -886,8 +1050,10 @@ static int ap_probe(struct usb_interface *interface,
                struct urb *urb;
 
                urb = usb_alloc_urb(0, GFP_KERNEL);
-               if (!urb)
+               if (!urb) {
+                       retval = -ENOMEM;
                        goto error;
+               }
 
                es2->cport_out_urb[i] = urb;
                es2->cport_out_urb_busy[i] = false;     /* just to be anal */
@@ -898,18 +1064,48 @@ static int ap_probe(struct usb_interface *interface,
                                                        (S_IWUSR | S_IRUGO),
                                                        gb_debugfs_get(), es2,
                                                        &apb_log_enable_fops);
+
+       retval = gb_hd_add(hd);
+       if (retval)
+               goto error;
+
+       for (i = 0; i < NUM_BULKS; ++i) {
+               retval = es2_cport_in_enable(es2, &es2->cport_in[i]);
+               if (retval)
+                       goto err_disable_cport_in;
+       }
+
        return 0;
+
+err_disable_cport_in:
+       for (--i; i >= 0; --i)
+               es2_cport_in_disable(es2, &es2->cport_in[i]);
+       gb_hd_del(hd);
 error:
-       ap_disconnect(interface);
+       es2_destroy(es2);
 
        return retval;
 }
 
+static void ap_disconnect(struct usb_interface *interface)
+{
+       struct es2_ap_dev *es2 = usb_get_intfdata(interface);
+       int i;
+
+       gb_hd_del(es2->hd);
+
+       for (i = 0; i < NUM_BULKS; ++i)
+               es2_cport_in_disable(es2, &es2->cport_in[i]);
+
+       es2_destroy(es2);
+}
+
 static struct usb_driver es2_ap_driver = {
        .name =         "es2_ap_driver",
        .probe =        ap_probe,
        .disconnect =   ap_disconnect,
        .id_table =     id_table,
+       .soft_unbind =  1,
 };
 
 module_usb_driver(es2_ap_driver);