--- /dev/null
+/*
+ * Greybus Camera protocol driver.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include "greybus.h"
+#include "greybus_protocols.h"
+
+enum gb_camera_debugs_buffer_id {
+       GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES,
+       GB_CAMERA_DEBUGFS_BUFFER_STREAMS,
+       GB_CAMERA_DEBUGFS_BUFFER_CAPTURE,
+       GB_CAMERA_DEBUGFS_BUFFER_FLUSH,
+       GB_CAMERA_DEBUGFS_BUFFER_MAX,
+};
+
+struct gb_camera_debugfs_buffer {
+       char data[PAGE_SIZE];
+       size_t length;
+};
+
+/**
+ * struct gb_camera - A Greybus Camera Device
+ * @connection: the greybus connection for camera control
+ * @data_connected: whether the data connection has been established
+ * @debugfs: debugfs entries for camera protocol operations testing
+ */
+struct gb_camera {
+       struct gb_connection *connection;
+       bool data_connected;
+
+       struct {
+               struct dentry *root;
+               struct gb_camera_debugfs_buffer *buffers;
+       } debugfs;
+};
+
+struct gb_camera_stream_config {
+       unsigned int width;
+       unsigned int height;
+       unsigned int format;
+       unsigned int vc;
+       unsigned int dt[2];
+       unsigned int max_size;
+};
+
+#define ES2_APB_CDSI0_CPORT            16
+#define ES2_APB_CDSI1_CPORT            17
+
+#define GB_CAMERA_MAX_SETTINGS_SIZE    8192
+
+#define gcam_dbg(gcam, format...) \
+       dev_dbg(&gcam->connection->bundle->dev, format)
+#define gcam_info(gcam, format...) \
+       dev_info(&gcam->connection->bundle->dev, format)
+#define gcam_err(gcam, format...) \
+       dev_err(&gcam->connection->bundle->dev, format)
+
+/* -----------------------------------------------------------------------------
+ * Camera Protocol Operations
+ */
+
+static int gb_camera_configure_streams(struct gb_camera *gcam,
+                                      unsigned int nstreams,
+                                      struct gb_camera_stream_config *streams)
+{
+       struct gb_camera_configure_streams_request *req;
+       struct gb_camera_configure_streams_response *resp;
+       unsigned int i;
+       size_t req_size;
+       size_t resp_size;
+       int ret;
+
+       if (nstreams > GB_CAMERA_MAX_STREAMS)
+               return -EINVAL;
+
+       req_size = sizeof(*req) + nstreams * sizeof(req->config[0]);
+       resp_size = sizeof(*resp) + nstreams * sizeof(resp->config[0]);
+
+       req = kmalloc(req_size, GFP_KERNEL);
+       resp = kmalloc(resp_size, GFP_KERNEL);
+       if (!req || !resp) {
+               ret = -ENOMEM;
+               goto done;
+       }
+
+       req->num_streams = nstreams;
+       req->padding = 0;
+
+       for (i = 0; i < nstreams; ++i) {
+               struct gb_camera_stream_config_request *cfg = &req->config[i];
+
+               cfg->width = streams[i].width;
+               cfg->height = streams[i].height;
+               cfg->format = streams[i].format;
+               cfg->padding = 0;
+       }
+
+       ret = gb_operation_sync(gcam->connection,
+                               GB_CAMERA_TYPE_CONFIGURE_STREAMS,
+                               req, req_size, resp, resp_size);
+       if (ret < 0)
+               return ret;
+
+       if (resp->num_streams > nstreams) {
+               gcam_dbg(gcam, "got #streams %u > request %u\n",
+                        resp->num_streams, nstreams);
+               ret = -EIO;
+               goto done;
+       }
+
+       if (resp->padding != 0) {
+               gcam_dbg(gcam, "response padding != 0");
+               ret = -EIO;
+               goto done;
+       }
+
+       for (i = 0; i < nstreams; ++i) {
+               struct gb_camera_stream_config_response *cfg = &resp->config[i];
+
+               streams[i].width = cfg->width;
+               streams[i].height = cfg->height;
+               streams[i].format = cfg->format;
+               streams[i].vc = cfg->virtual_channel;
+               streams[i].dt[0] = cfg->data_type[0];
+               streams[i].dt[1] = cfg->data_type[1];
+               streams[i].max_size = cfg->max_size;
+
+               if (cfg->padding[0] || cfg->padding[1] || cfg->padding[2]) {
+                       gcam_dbg(gcam, "stream #%u padding != 0", i);
+                       ret = -EIO;
+                       goto done;
+               }
+       }
+
+       ret = resp->num_streams;
+
+done:
+       kfree(req);
+       kfree(resp);
+       return ret;
+}
+
+static int gb_camera_capture(struct gb_camera *gcam, u32 request_id,
+                            unsigned int streams, unsigned int num_frames,
+                            size_t settings_size, const void *settings)
+{
+       struct gb_camera_capture_request *req;
+       size_t req_size;
+
+       if (settings_size > GB_CAMERA_MAX_SETTINGS_SIZE)
+               return -EINVAL;
+
+       req_size = sizeof(*req) + settings_size;
+       req = kmalloc(req_size, GFP_KERNEL);
+       if (!req)
+               return -ENOMEM;
+
+       req->request_id = request_id;
+       req->streams = streams;
+       req->padding = 0;
+       req->num_frames = num_frames;
+       memcpy(req->settings, settings, settings_size);
+
+       return gb_operation_sync(gcam->connection, GB_CAMERA_TYPE_CAPTURE,
+                                req, req_size, NULL, 0);
+}
+
+static int gb_camera_flush(struct gb_camera *gcam, u32 *request_id)
+{
+       struct gb_camera_flush_response resp;
+       int ret;
+
+       ret = gb_operation_sync(gcam->connection, GB_CAMERA_TYPE_FLUSH, NULL, 0,
+                               &resp, sizeof(resp));
+       if (ret < 0)
+               return ret;
+
+       if (request_id)
+               *request_id = resp.request_id;
+
+       return 0;
+}
+
+static int gb_camera_event_recv(u8 type, struct gb_operation *op)
+{
+       struct gb_camera *gcam = op->connection->private;
+       struct gb_camera_metadata_request *payload;
+       struct gb_message *request;
+
+       if (type != GB_CAMERA_TYPE_METADATA) {
+               gcam_err(gcam, "Unsupported unsolicited event: %u\n", type);
+               return -EINVAL;
+       }
+
+       request = op->request;
+
+       if (request->payload_size < sizeof(*payload)) {
+               gcam_err(gcam, "Wrong event size received (%zu < %zu)\n",
+                        request->payload_size, sizeof(*payload));
+               return -EINVAL;
+       }
+
+       payload = request->payload;
+
+       gcam_dbg(gcam, "received metadata for request %u, frame %u, stream %u\n",
+                payload->request_id, payload->frame_number, payload->stream);
+
+       return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * DebugFS
+ */
+static ssize_t gb_camera_debugfs_capabilities(struct gb_camera *gcam,
+               char *buf, size_t len)
+{
+       return len;
+}
+
+static ssize_t gb_camera_debugfs_configure_streams(struct gb_camera *gcam,
+               char *buf, size_t len)
+{
+       struct gb_camera_debugfs_buffer *buffer =
+               &gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_STREAMS];
+       struct gb_camera_stream_config *streams;
+       unsigned int nstreams;
+       const char *sep = ";";
+       unsigned int i;
+       char *token;
+       int ret;
+
+       /* Retrieve number of streams to configure */
+       token = strsep(&buf, sep);
+       if (token == NULL)
+               return -EINVAL;
+
+       ret = kstrtouint(token, 10, &nstreams);
+       if (ret < 0)
+               return ret;
+
+       if (nstreams > GB_CAMERA_MAX_STREAMS)
+               return -EINVAL;
+
+       /* For each stream to configure parse width, height and format */
+       streams = kzalloc(nstreams * sizeof(*streams), GFP_KERNEL);
+       if (!streams)
+               return -ENOMEM;
+
+       for (i = 0; i < nstreams; ++i) {
+               struct gb_camera_stream_config *stream = &streams[i];
+
+               /* width */
+               token = strsep(&buf, ";");
+               if (token == NULL) {
+                       ret = -EINVAL;
+                       goto done;
+               }
+               ret = kstrtouint(token, 10, &stream->width);
+               if (ret < 0)
+                       goto done;
+
+               /* height */
+               token = strsep(&buf, ";");
+               if (token == NULL)
+                       goto done;
+
+               ret = kstrtouint(token, 10, &stream->height);
+               if (ret < 0)
+                       goto done;
+
+               /* Image format code */
+               token = strsep(&buf, ";");
+               if (token == NULL)
+                       goto done;
+
+               ret = kstrtouint(token, 16, &stream->format);
+               if (ret < 0)
+                       goto done;
+       }
+
+       ret = gb_camera_configure_streams(gcam, nstreams, streams);
+       if (ret < 0)
+               goto done;
+
+       nstreams = ret;
+       buffer->length = sprintf(buffer->data, "%u;", nstreams);
+
+       for (i = 0; i < nstreams; ++i) {
+               struct gb_camera_stream_config *stream = &streams[i];
+
+               buffer->length += sprintf(buffer->data + buffer->length,
+                                         "%u;%u;%u;%u;%u;%u;%u;",
+                                         stream->width, stream->height,
+                                         stream->format, stream->vc,
+                                         stream->dt[0], stream->dt[1],
+                                         stream->max_size);
+       }
+
+       ret = len;
+
+done:
+       kfree(streams);
+       return ret;
+};
+
+static ssize_t gb_camera_debugfs_capture(struct gb_camera *gcam,
+               char *buf, size_t len)
+{
+       unsigned int request_id;
+       unsigned int streams_mask;
+       unsigned int num_frames;
+       char *token;
+       int ret;
+
+       /* Request id */
+       token = strsep(&buf, ";");
+       if (token == NULL)
+               return -EINVAL;
+       ret = kstrtouint(token, 10, &request_id);
+       if (ret < 0)
+               return ret;
+
+       /* Stream mask */
+       token = strsep(&buf, ";");
+       if (token == NULL)
+               return -EINVAL;
+       ret = kstrtouint(token, 16, &streams_mask);
+       if (ret < 0)
+               return ret;
+
+       /* number of frames */
+       token = strsep(&buf, ";");
+       if (token == NULL)
+               return -EINVAL;
+       ret = kstrtouint(token, 10, &num_frames);
+       if (ret < 0)
+               return ret;
+
+       ret = gb_camera_capture(gcam, request_id, streams_mask, num_frames, 0,
+                               NULL);
+       if (ret < 0)
+               return ret;
+
+       return len;
+}
+
+static ssize_t gb_camera_debugfs_flush(struct gb_camera *gcam,
+               char *buf, size_t len)
+{
+       struct gb_camera_debugfs_buffer *buffer =
+               &gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_FLUSH];
+       unsigned int req_id;
+       int ret;
+
+       ret = gb_camera_flush(gcam, &req_id);
+       if (ret < 0)
+               return ret;
+
+       buffer->length = sprintf(buffer->data, "%u", req_id);
+
+       return len;
+}
+
+struct gb_camera_debugfs_entry {
+       const char *name;
+       unsigned int mask;
+       unsigned int buffer;
+       ssize_t (*execute)(struct gb_camera *gcam, char *buf, size_t len);
+};
+
+static const struct gb_camera_debugfs_entry gb_camera_debugfs_entries[] = {
+       {
+               .name = "capabilities",
+               .mask = S_IFREG | S_IRUGO,
+               .buffer = GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES,
+               .execute = gb_camera_debugfs_capabilities,
+       }, {
+               .name = "configure_streams",
+               .mask = S_IFREG | S_IRUGO | S_IWUGO,
+               .buffer = GB_CAMERA_DEBUGFS_BUFFER_STREAMS,
+               .execute = gb_camera_debugfs_configure_streams,
+       }, {
+               .name = "capture",
+               .mask = S_IFREG | S_IRUGO | S_IWUGO,
+               .buffer = GB_CAMERA_DEBUGFS_BUFFER_CAPTURE,
+               .execute = gb_camera_debugfs_capture,
+       }, {
+               .name = "flush",
+               .mask = S_IFREG | S_IRUGO | S_IWUGO,
+               .buffer = GB_CAMERA_DEBUGFS_BUFFER_FLUSH,
+               .execute = gb_camera_debugfs_flush,
+       },
+};
+
+static ssize_t gb_camera_debugfs_read(struct file *file, char __user *buf,
+                                     size_t len, loff_t *offset)
+{
+       const struct gb_camera_debugfs_entry *op = file->private_data;
+       struct gb_camera *gcam = file->f_inode->i_private;
+       struct gb_camera_debugfs_buffer *buffer;
+       ssize_t ret;
+
+       /* For read-only entries the operation is triggered by a read. */
+       if (!(op->mask & S_IWUGO)) {
+               ret = op->execute(gcam, NULL, 0);
+               if (ret < 0)
+                       return ret;
+       }
+
+       buffer = &gcam->debugfs.buffers[op->buffer];
+
+       return simple_read_from_buffer(buf, len, offset, buffer->data,
+                                      buffer->length);
+}
+
+static ssize_t gb_camera_debugfs_write(struct file *file,
+                                      const char __user *buf, size_t len,
+                                      loff_t *offset)
+{
+       const struct gb_camera_debugfs_entry *op = file->private_data;
+       struct gb_camera *gcam = file->f_inode->i_private;
+       ssize_t ret;
+       char *kbuf;
+
+       if (len > 1024)
+              return -EINVAL;
+
+       kbuf = kmalloc(len + 1, GFP_KERNEL);
+       if (kbuf == NULL)
+               return -ENOMEM;
+
+       if (copy_from_user(kbuf, buf, len)) {
+               ret = -EFAULT;
+               goto done;
+       }
+
+       kbuf[len] = '\0';
+
+       ret = op->execute(gcam, kbuf, len);
+
+done:
+       kfree(kbuf);
+       return ret;
+}
+
+static int gb_camera_debugfs_open(struct inode *inode, struct file *file)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(gb_camera_debugfs_entries); ++i) {
+               const struct gb_camera_debugfs_entry *entry =
+                       &gb_camera_debugfs_entries[i];
+
+               if (!strcmp(file->f_dentry->d_iname, entry->name)) {
+                       file->private_data = (void *)entry;
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static const struct file_operations gb_camera_debugfs_ops = {
+       .open = gb_camera_debugfs_open,
+       .read = gb_camera_debugfs_read,
+       .write = gb_camera_debugfs_write,
+};
+
+static int gb_camera_debugfs_init(struct gb_camera *gcam)
+{
+       struct gb_connection *connection = gcam->connection;
+       char dirname[27];
+       unsigned int i;
+
+       /*
+        * Create root debugfs entry and a file entry for each camera operation.
+        */
+       snprintf(dirname, 27, "camera-%u.%u", connection->intf->interface_id,
+                connection->bundle->id);
+
+       gcam->debugfs.root = debugfs_create_dir(dirname, gb_debugfs_get());
+       if (IS_ERR(gcam->debugfs.root)) {
+               gcam_err(gcam, "debugfs root create failed (%ld)\n",
+                        PTR_ERR(gcam->debugfs.root));
+               return PTR_ERR(gcam->debugfs.root);
+       }
+
+       gcam->debugfs.buffers = vmalloc(sizeof(*gcam->debugfs.buffers) *
+                                       GB_CAMERA_DEBUGFS_BUFFER_MAX);
+       if (!gcam->debugfs.buffers)
+               return -ENOMEM;
+
+       for (i = 0; i < ARRAY_SIZE(gb_camera_debugfs_entries); ++i) {
+               const struct gb_camera_debugfs_entry *entry =
+                       &gb_camera_debugfs_entries[i];
+               struct dentry *dentry;
+
+               gcam->debugfs.buffers[i].length = 0;
+
+               dentry = debugfs_create_file(entry->name, entry->mask,
+                                            gcam->debugfs.root, gcam,
+                                            &gb_camera_debugfs_ops);
+               if (IS_ERR(dentry)) {
+                       gcam_err(gcam,
+                                "debugfs operation %s create failed (%ld)\n",
+                                entry->name, PTR_ERR(gcam->debugfs.root));
+                       return PTR_ERR(dentry);
+               }
+       }
+
+       return 0;
+}
+
+static void gb_camera_debugfs_cleanup(struct gb_camera *gcam)
+{
+       if (gcam->debugfs.root)
+               debugfs_remove_recursive(gcam->debugfs.root);
+
+       vfree(gcam->debugfs.buffers);
+}
+
+/* -----------------------------------------------------------------------------
+ * Init & Cleanup
+ */
+
+static void gb_camera_cleanup(struct gb_camera *gcam)
+{
+       gb_camera_debugfs_cleanup(gcam);
+
+       if (gcam->data_connected) {
+               struct gb_interface *intf = gcam->connection->intf;
+               struct gb_svc *svc = gcam->connection->hd->svc;
+
+               gb_svc_connection_destroy(svc, intf->interface_id,
+                                         ES2_APB_CDSI0_CPORT, svc->ap_intf_id,
+                                         ES2_APB_CDSI1_CPORT);
+       }
+
+       kfree(gcam);
+}
+
+static int gb_camera_connection_init(struct gb_connection *connection)
+{
+       struct gb_svc *svc = connection->hd->svc;
+       struct gb_camera *gcam;
+       int ret;
+
+       gcam = kzalloc(sizeof(*gcam), GFP_KERNEL);
+       if (!gcam)
+               return -ENOMEM;
+
+       gcam->connection = connection;
+       connection->private = gcam;
+
+       /*
+        * Create the data connection between camera module CDSI0 and APB CDS1.
+        * The CPort IDs are hardcoded by the ES2 bridges.
+        */
+       ret = gb_svc_connection_create(svc, connection->intf->interface_id,
+                                      ES2_APB_CDSI0_CPORT, svc->ap_intf_id,
+                                      ES2_APB_CDSI1_CPORT, false);
+       if (ret < 0)
+               goto error;
+
+       gcam->data_connected = true;
+
+       ret = gb_camera_debugfs_init(gcam);
+       if (ret < 0)
+               goto error;
+
+       return 0;
+
+error:
+       gb_camera_cleanup(gcam);
+       return ret;
+}
+
+static void gb_camera_connection_exit(struct gb_connection *connection)
+{
+       struct gb_camera *gcam = connection->private;
+
+       gb_camera_cleanup(gcam);
+}
+
+static struct gb_protocol camera_protocol = {
+       .name                   = "camera",
+       .id                     = GREYBUS_PROTOCOL_CAMERA_MGMT,
+       .major                  = GB_CAMERA_VERSION_MAJOR,
+       .minor                  = GB_CAMERA_VERSION_MINOR,
+       .connection_init        = gb_camera_connection_init,
+       .connection_exit        = gb_camera_connection_exit,
+       .request_recv           = gb_camera_event_recv,
+};
+
+gb_protocol_driver(&camera_protocol);
+
+MODULE_LICENSE("GPL v2");