root/drivers/staging/greybus/vibrator.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Greybus Vibrator protocol driver.
 *
 * Copyright 2014 Google Inc.
 * Copyright 2014 Linaro Ltd.
 */

#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 <linux/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",
        .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_obj(*vib);
        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_alloc(&minors, 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_free(&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_free(&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_DESCRIPTION("Greybus Vibrator protocol driver");
MODULE_LICENSE("GPL v2");