greybus: vibrator-gb: add vibrator driver
authorGreg Kroah-Hartman <greg@kroah.com>
Tue, 18 Nov 2014 00:03:34 +0000 (16:03 -0800)
committerGreg Kroah-Hartman <greg@kroah.com>
Tue, 18 Nov 2014 00:03:34 +0000 (16:03 -0800)
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>
drivers/staging/greybus/Makefile
drivers/staging/greybus/kernel_ver.h
drivers/staging/greybus/protocol.c
drivers/staging/greybus/protocol.h
drivers/staging/greybus/vibrator-gb.c [new file with mode: 0644]

index 7ec70fe6bd5dad9789f61b834d86ae80ba7f64cf..198539cd0a0639225d2db7ff8e0d891a9449aa11 100644 (file)
@@ -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
index e4cda5ce65fa479e334b082a66ec0b514659ed22..ca0da11e4d8d73a29dbcfa55188e2a201b52b988 100644 (file)
        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 */
index 8df2b4e7b802fb366aec4093b9f0a75363894069..346120f8d4e34b9eda0e7a72c5ab42ba5ea23e52 100644 (file)
@@ -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();
index 1aeb34068a3fef025d9cab14da587b26b7d641e5..69a40079a2c53338563ca57df50d2b8a6530f60b 100644 (file)
@@ -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 (file)
index 0000000..2fcb2a4
--- /dev/null
@@ -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);
+}