[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20160916141143.GH1252@kroah.com>
Date: Fri, 16 Sep 2016 16:11:43 +0200
From: Greg KH <gregkh@...uxfoundation.org>
To: Arnd Bergmann <arnd@...db.de>, linux-kernel@...r.kernel.org
Cc: Johan Hovold <johan@...oldconsulting.com>,
Rui Miguel Silva <rmfrfs@...il.com>,
Laurent Pinchart <laurent.pinchart@...asonboard.com>,
Sandeep Patil <sspatil@...gle.com>,
Matt Porter <mporter@...nel.crashing.org>,
John Stultz <john.stultz@...aro.org>,
Rob Herring <robh@...nel.org>,
Viresh Kumar <viresh.kumar@...aro.org>,
Alex Elder <elder@...aro.org>, David Lin <dtwlin@...gle.com>,
Bryan O'Donoghue <pure.logic@...us-software.ie>,
Vaibhav Agarwal <vaibhav.agarwal@...aro.org>,
Mark Greer <mgreer@...malcreek.com>
Subject: [patch 20/32] greybus: vibrator driver
This driver implements the Greybus vibrator class protocol.
Signed-off-by: Greg Kroah-Hartman <gregkh@...uxfoundation.org>
---
drivers/greybus/vibrator.c | 249 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 249 insertions(+)
--- /dev/null
+++ b/drivers/greybus/vibrator.c
@@ -0,0 +1,249 @@
+/*
+ * Greybus Vibrator protocol driver.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * 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 <linux/idr.h>
+#include <linux/pm_runtime.h>
+
+#include "greybus.h"
+
+struct gb_vibrator_device {
+ struct gb_connection *connection;
+ struct device *dev;
+ int minor; /* vibrator minor number */
+ struct delayed_work delayed_work;
+};
+
+/* Greybus Vibrator operation types */
+#define GB_VIBRATOR_TYPE_ON 0x02
+#define GB_VIBRATOR_TYPE_OFF 0x03
+
+static int turn_off(struct gb_vibrator_device *vib)
+{
+ struct gb_bundle *bundle = vib->connection->bundle;
+ int ret;
+
+ ret = gb_operation_sync(vib->connection, GB_VIBRATOR_TYPE_OFF,
+ NULL, 0, NULL, 0);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int turn_on(struct gb_vibrator_device *vib, u16 timeout_ms)
+{
+ struct gb_bundle *bundle = vib->connection->bundle;
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ /* Vibrator was switched ON earlier */
+ if (cancel_delayed_work_sync(&vib->delayed_work))
+ turn_off(vib);
+
+ ret = gb_operation_sync(vib->connection, GB_VIBRATOR_TYPE_ON,
+ NULL, 0, NULL, 0);
+ if (ret) {
+ gb_pm_runtime_put_autosuspend(bundle);
+ return ret;
+ }
+
+ schedule_delayed_work(&vib->delayed_work, msecs_to_jiffies(timeout_ms));
+
+ return 0;
+}
+
+static void gb_vibrator_worker(struct work_struct *work)
+{
+ struct delayed_work *delayed_work = to_delayed_work(work);
+ struct gb_vibrator_device *vib =
+ container_of(delayed_work, struct gb_vibrator_device, delayed_work);
+
+ turn_off(vib);
+}
+
+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)
+ 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,
+ .dev_groups = vibrator_groups,
+};
+
+static DEFINE_IDA(minors);
+
+static int gb_vibrator_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_vibrator_device *vib;
+ struct device *dev;
+ int retval;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_VIBRATOR)
+ return -ENODEV;
+
+ vib = kzalloc(sizeof(*vib), GFP_KERNEL);
+ if (!vib)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ NULL);
+ if (IS_ERR(connection)) {
+ retval = PTR_ERR(connection);
+ goto err_free_vib;
+ }
+ gb_connection_set_data(connection, vib);
+
+ vib->connection = connection;
+
+ greybus_set_drvdata(bundle, vib);
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto err_connection_destroy;
+
+ /*
+ * 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...
+ */
+ vib->minor = ida_simple_get(&minors, 0, 0, GFP_KERNEL);
+ if (vib->minor < 0) {
+ retval = vib->minor;
+ goto err_connection_disable;
+ }
+ dev = device_create(&vibrator_class, &bundle->dev,
+ MKDEV(0, 0), vib, "vibrator%d", vib->minor);
+ if (IS_ERR(dev)) {
+ retval = -EINVAL;
+ goto err_ida_remove;
+ }
+ vib->dev = dev;
+
+ INIT_DELAYED_WORK(&vib->delayed_work, gb_vibrator_worker);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+err_ida_remove:
+ ida_simple_remove(&minors, vib->minor);
+err_connection_disable:
+ gb_connection_disable(connection);
+err_connection_destroy:
+ gb_connection_destroy(connection);
+err_free_vib:
+ kfree(vib);
+
+ return retval;
+}
+
+static void gb_vibrator_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_vibrator_device *vib = greybus_get_drvdata(bundle);
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ gb_pm_runtime_get_noresume(bundle);
+
+ if (cancel_delayed_work_sync(&vib->delayed_work))
+ turn_off(vib);
+
+ device_unregister(vib->dev);
+ ida_simple_remove(&minors, vib->minor);
+ gb_connection_disable(vib->connection);
+ gb_connection_destroy(vib->connection);
+ kfree(vib);
+}
+
+static const struct greybus_bundle_id gb_vibrator_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_VIBRATOR) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_vibrator_id_table);
+
+static struct greybus_driver gb_vibrator_driver = {
+ .name = "vibrator",
+ .probe = gb_vibrator_probe,
+ .disconnect = gb_vibrator_disconnect,
+ .id_table = gb_vibrator_id_table,
+};
+
+static __init int gb_vibrator_init(void)
+{
+ int retval;
+
+ retval = class_register(&vibrator_class);
+ if (retval)
+ return retval;
+
+ retval = greybus_register(&gb_vibrator_driver);
+ if (retval)
+ goto err_class_unregister;
+
+ return 0;
+
+err_class_unregister:
+ class_unregister(&vibrator_class);
+
+ return retval;
+}
+module_init(gb_vibrator_init);
+
+static __exit void gb_vibrator_exit(void)
+{
+ greybus_deregister(&gb_vibrator_driver);
+ class_unregister(&vibrator_class);
+ ida_destroy(&minors);
+}
+module_exit(gb_vibrator_exit);
+
+MODULE_LICENSE("GPL v2");
Powered by blists - more mailing lists