Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
// 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",
	.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");