2015-12-15 01:18:06 +00:00
|
|
|
/*
|
|
|
|
* 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];
|
|
|
|
|
2015-12-15 02:33:19 +00:00
|
|
|
if (!strcmp(file->f_path.dentry->d_iname, entry->name)) {
|
2015-12-15 01:18:06 +00:00
|
|
|
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");
|