greybus: vibrator-gb: add vibrator driver

This driver implements the Greybus vibrator protocol, as defined in the
Greybus protocol specification.  It interacts to userspace with a single
sysfs file, "timeout", and a separate "class" called "vibrator".  That
interface can/should be changed in the future depending on what Android
wants for its HAL, but for now should be good enough to test with.

There are some changes needed to kernel_ver.h to support some
sysfs/driver core changes that happened after the 3.10 kernel was
released to try to make the code simpler.  Even with those changes,
there are #ifdefs in the code to do different things depending on the
kernel version to implement the same userspace api.

Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
diff --git a/drivers/staging/greybus/vibrator-gb.c b/drivers/staging/greybus/vibrator-gb.c
new file mode 100644
index 0000000..2fcb2a4
--- /dev/null
+++ b/drivers/staging/greybus/vibrator-gb.c
@@ -0,0 +1,298 @@
+/*
+ * I2C bridge driver for the Greybus "generic" I2C module.
+ *
+ * Copyright 2014 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+#include "greybus.h"
+
+struct gb_vibrator_device {
+	struct gb_connection	*connection;
+	struct device		*dev;
+	u8			version_major;
+	u8			version_minor;
+};
+
+/* Version of the Greybus i2c protocol we support */
+#define	GB_VIBRATOR_VERSION_MAJOR		0x00
+#define	GB_VIBRATOR_VERSION_MINOR		0x01
+
+/* Greybus Vibrator request types */
+#define	GB_VIBRATOR_TYPE_INVALID		0x00
+#define	GB_VIBRATOR_TYPE_PROTOCOL_VERSION	0x01
+#define	GB_VIBRATOR_TYPE_ON			0x02
+#define	GB_VIBRATOR_TYPE_OFF			0x03
+#define	GB_VIBRATOR_TYPE_RESPONSE		0x80	/* OR'd with rest */
+
+struct gb_vibrator_proto_version_response {
+	__u8	status;
+	__u8	major;
+	__u8	minor;
+};
+
+struct gb_vibrator_on_request {
+	__le16	timeout_ms;
+};
+
+struct gb_vibrator_simple_response {
+	__u8	status;
+};
+
+static int request_operation(struct gb_connection *connection, int type,
+			     void *response, int response_size)
+{
+	struct gb_operation *operation;
+	struct gb_vibrator_simple_response *fake_request;
+	u8 *local_response;
+	int ret;
+
+	local_response = kmalloc(response_size, GFP_KERNEL);
+	if (!local_response)
+		return -ENOMEM;
+
+	operation = gb_operation_create(connection, type, 0, response_size);
+	if (!operation) {
+		kfree(local_response);
+		return -ENOMEM;
+	}
+
+	/* Synchronous operation--no callback */
+	ret = gb_operation_request_send(operation, NULL);
+	if (ret) {
+		pr_err("version operation failed (%d)\n", ret);
+		goto out;
+	}
+
+	/*
+	 * We only want to look at the status, and all requests have the same
+	 * layout for where the status is, so cast this to a random request so
+	 * we can see the status easier.
+	 */
+	fake_request = (struct gb_vibrator_simple_response *)local_response;
+	if (fake_request->status) {
+		gb_connection_err(connection, "response %hhu",
+			fake_request->status);
+		ret = -EIO;
+	} else {
+		/* Good request, so copy to the caller's buffer */
+		if (response_size && response)
+			memcpy(response, local_response, response_size);
+	}
+out:
+	gb_operation_destroy(operation);
+	kfree(local_response);
+
+	return ret;
+}
+
+/*
+ * This request only uses the connection field, and if successful,
+ * fills in the major and minor protocol version of the target.
+ */
+static int get_version(struct gb_vibrator_device *vib)
+{
+	struct gb_connection *connection = vib->connection;
+	struct gb_vibrator_proto_version_response version_request;
+	int retval;
+
+	retval = request_operation(connection,
+				   GB_VIBRATOR_TYPE_PROTOCOL_VERSION,
+				   &version_request, sizeof(version_request));
+	if (retval)
+		return retval;
+
+	if (version_request.major > GB_VIBRATOR_VERSION_MAJOR) {
+		dev_err(&connection->dev,
+			"unsupported major version (%hhu > %hhu)\n",
+			version_request.major, GB_VIBRATOR_VERSION_MAJOR);
+		return -ENOTSUPP;
+	}
+
+	vib->version_major = version_request.major;
+	vib->version_minor = version_request.minor;
+	return 0;
+}
+
+static int turn_on(struct gb_vibrator_device *vib, u16 timeout_ms)
+{
+	struct gb_connection *connection = vib->connection;
+	struct gb_operation *operation;
+	struct gb_vibrator_on_request *request;
+	struct gb_vibrator_simple_response *response;
+	int retval;
+
+	operation = gb_operation_create(connection, GB_VIBRATOR_TYPE_ON,
+					sizeof(*request), sizeof(*response));
+	if (!operation)
+		return -ENOMEM;
+	request = operation->request_payload;
+	request->timeout_ms = cpu_to_le16(timeout_ms);
+
+	/* Synchronous operation--no callback */
+	retval = gb_operation_request_send(operation, NULL);
+	if (retval) {
+		dev_err(&connection->dev,
+			"send data operation failed (%d)\n", retval);
+		goto out;
+	}
+
+	response = operation->response_payload;
+	if (response->status) {
+		gb_connection_err(connection, "send data response %hhu",
+				  response->status);
+		retval = -EIO;
+	}
+out:
+	gb_operation_destroy(operation);
+
+	return retval;
+
+	return 0;
+}
+
+static int turn_off(struct gb_vibrator_device *vib)
+{
+	struct gb_connection *connection = vib->connection;
+	int retval;
+
+	retval = request_operation(connection, GB_VIBRATOR_TYPE_OFF, NULL, 0);
+	if (retval)
+		return retval;
+
+	return 0;
+}
+
+static ssize_t timeout_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct gb_vibrator_device *vib = dev_get_drvdata(dev);
+	unsigned long val;
+	int retval;
+
+	retval = kstrtoul(buf, 10, &val);
+	if (retval < 0) {
+		dev_err(dev, "could not parse timeout value %d\n", retval);
+		return retval;
+	}
+
+	if (val < 0)
+		return -EINVAL;
+	if (val)
+		retval = turn_on(vib, (u16)val);
+	else
+		retval = turn_off(vib);
+	if (retval)
+		return retval;
+
+	return count;
+}
+static DEVICE_ATTR_WO(timeout);
+
+static struct attribute *vibrator_attrs[] = {
+	&dev_attr_timeout.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(vibrator);
+
+static struct class vibrator_class = {
+	.name		= "vibrator",
+	.owner		= THIS_MODULE,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
+	.dev_groups	= vibrator_groups,
+#endif
+};
+
+static int minor;
+
+static int gb_vibrator_connection_init(struct gb_connection *connection)
+{
+	struct gb_vibrator_device *vib;
+	struct device *dev;
+	int retval;
+
+	vib = kzalloc(sizeof(*vib), GFP_KERNEL);
+	if (!vib)
+		return -ENOMEM;
+
+	vib->connection = connection;
+
+	retval = get_version(vib);
+	if (retval)
+		goto error;
+
+	/*
+	 * FIXME: for now we create a device in sysfs for the vibrator, but odds
+	 * are there is a "real" device somewhere in the kernel for this, but I
+	 * can't find it at the moment...
+	 */
+	dev = device_create(&vibrator_class, NULL, MKDEV(0, 0), vib,
+			    "vibrator%d", minor);
+	if (IS_ERR(dev)) {
+		retval = -EINVAL;
+		goto error;
+	}
+	minor++;
+	vib->dev = dev;
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,11,0)
+	/*
+	 * Newer kernels handle this in a race-free manner, for us, we need
+	 * to "open code this :(
+	 */
+	retval = sysfs_create_group(&dev->kobj, vibrator_groups[0]);
+	if (retval) {
+		device_unregister(dev);
+		goto error;
+	}
+#endif
+
+	return 0;
+
+error:
+	kfree(vib);
+	return retval;
+}
+
+static void gb_vibrator_connection_exit(struct gb_connection *connection)
+{
+	struct gb_vibrator_device *vib = connection->private;
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,11,0)
+	sysfs_remove_group(&vib->dev->kobj, vibrator_groups[0]);
+#endif
+	device_unregister(vib->dev);
+	kfree(vib);
+}
+
+static struct gb_protocol vibrator_protocol = {
+	.id			= GREYBUS_PROTOCOL_VIBRATOR,
+	.major			= 0,
+	.minor			= 1,
+	.connection_init	= gb_vibrator_connection_init,
+	.connection_exit	= gb_vibrator_connection_exit,
+	.request_recv		= NULL,	/* no incoming requests */
+};
+
+bool gb_vibrator_protocol_init(void)
+{
+	int retval;
+
+	retval = class_register(&vibrator_class);
+	if (retval)
+		return retval;
+
+	return gb_protocol_register(&vibrator_protocol);
+}
+
+void gb_vibrator_protocol_exit(void)
+{
+	gb_protocol_deregister(&vibrator_protocol);
+	class_unregister(&vibrator_class);
+}