summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <greg@kroah.com>2014-11-17 16:03:34 -0800
committerGreg Kroah-Hartman <greg@kroah.com>2014-11-17 16:03:34 -0800
commitac4029fb60abd00f63cfb4c626f1653a918dcc66 (patch)
tree509e3c650fd16763a47309f59f8b7c9eea7eb680
parent68190676b78a4736081e13d67d3d5bc2a519df5c (diff)
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>
-rw-r--r--drivers/staging/greybus/Makefile3
-rw-r--r--drivers/staging/greybus/kernel_ver.h12
-rw-r--r--drivers/staging/greybus/protocol.c5
-rw-r--r--drivers/staging/greybus/protocol.h3
-rw-r--r--drivers/staging/greybus/vibrator-gb.c298
5 files changed, 320 insertions, 1 deletions
diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile
index 7ec70fe6bd5d..198539cd0a06 100644
--- a/drivers/staging/greybus/Makefile
+++ b/drivers/staging/greybus/Makefile
@@ -14,7 +14,8 @@ greybus-y := core.o \
pwm-gb.o \
sdio-gb.o \
uart-gb.o \
- battery-gb.o
+ battery-gb.o \
+ vibrator-gb.o
obj-m += greybus.o
obj-m += es1-ap-usb.o
diff --git a/drivers/staging/greybus/kernel_ver.h b/drivers/staging/greybus/kernel_ver.h
index e4cda5ce65fa..ca0da11e4d8d 100644
--- a/drivers/staging/greybus/kernel_ver.h
+++ b/drivers/staging/greybus/kernel_ver.h
@@ -18,6 +18,18 @@
struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#endif
+#ifndef DEVICE_ATTR_WO
+#define DEVICE_ATTR_WO(_name) \
+ struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
+#endif
+
+#ifndef __ATTR_WO
+#define __ATTR_WO(_name) { \
+ .attr = { .name = __stringify(_name), .mode = S_IWUSR }, \
+ .store = _name##_store, \
+}
+#endif
+
#ifndef U8_MAX
#define U8_MAX ((u8)~0U)
#endif /* ! U8_MAX */
diff --git a/drivers/staging/greybus/protocol.c b/drivers/staging/greybus/protocol.c
index 8df2b4e7b802..346120f8d4e3 100644
--- a/drivers/staging/greybus/protocol.c
+++ b/drivers/staging/greybus/protocol.c
@@ -189,11 +189,16 @@ bool gb_protocol_init(void)
pr_err("error initializing sdio protocol\n");
ret = false;
}
+ if (!gb_vibrator_protocol_init()) {
+ pr_err("error initializing vibrator protocol\n");
+ ret = false;
+ }
return ret;
}
void gb_protocol_exit(void)
{
+ gb_vibrator_protocol_exit();
gb_sdio_protocol_exit();
gb_uart_protocol_exit();
gb_i2c_protocol_exit();
diff --git a/drivers/staging/greybus/protocol.h b/drivers/staging/greybus/protocol.h
index 1aeb34068a3f..69a40079a2c5 100644
--- a/drivers/staging/greybus/protocol.h
+++ b/drivers/staging/greybus/protocol.h
@@ -64,6 +64,9 @@ extern void gb_uart_protocol_exit(void);
extern bool gb_sdio_protocol_init(void);
extern void gb_sdio_protocol_exit(void);
+extern bool gb_vibrator_protocol_init(void);
+extern void gb_vibrator_protocol_exit(void);
+
bool gb_protocol_init(void);
void gb_protocol_exit(void);
diff --git a/drivers/staging/greybus/vibrator-gb.c b/drivers/staging/greybus/vibrator-gb.c
new file mode 100644
index 000000000000..2fcb2a45c254
--- /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);
+}