]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/input/input-polldev.c
Input: implement managed polled input devices
[karo-tx-linux.git] / drivers / input / input-polldev.c
index 4b191908d5de216e4f8b126354e31266497b1d2e..3664f81655ca83d116733ecad3b70f481c5115d6 100644 (file)
@@ -176,6 +176,91 @@ struct input_polled_dev *input_allocate_polled_device(void)
 }
 EXPORT_SYMBOL(input_allocate_polled_device);
 
+struct input_polled_devres {
+       struct input_polled_dev *polldev;
+};
+
+static int devm_input_polldev_match(struct device *dev, void *res, void *data)
+{
+       struct input_polled_devres *devres = res;
+
+       return devres->polldev == data;
+}
+
+static void devm_input_polldev_release(struct device *dev, void *res)
+{
+       struct input_polled_devres *devres = res;
+       struct input_polled_dev *polldev = devres->polldev;
+
+       dev_dbg(dev, "%s: dropping reference/freeing %s\n",
+               __func__, dev_name(&polldev->input->dev));
+
+       input_put_device(polldev->input);
+       kfree(polldev);
+}
+
+static void devm_input_polldev_unregister(struct device *dev, void *res)
+{
+       struct input_polled_devres *devres = res;
+       struct input_polled_dev *polldev = devres->polldev;
+
+       dev_dbg(dev, "%s: unregistering device %s\n",
+               __func__, dev_name(&polldev->input->dev));
+       input_unregister_device(polldev->input);
+
+       /*
+        * Note that we are still holding extra reference to the input
+        * device so it will stick around until devm_input_polldev_release()
+        * is called.
+        */
+}
+
+/**
+ * devm_input_allocate_polled_device - allocate managed polled device
+ * @dev: device owning the polled device being created
+ *
+ * Returns prepared &struct input_polled_dev or %NULL.
+ *
+ * Managed polled input devices do not need to be explicitly unregistered
+ * or freed as it will be done automatically when owner device unbinds
+ * from * its driver (or binding fails). Once such managed polled device
+ * is allocated, it is ready to be set up and registered in the same
+ * fashion as regular polled input devices (using
+ * input_register_polled_device() function).
+ *
+ * If you want to manually unregister and free such managed polled devices,
+ * it can be still done by calling input_unregister_polled_device() and
+ * input_free_polled_device(), although it is rarely needed.
+ *
+ * NOTE: the owner device is set up as parent of input device and users
+ * should not override it.
+ */
+struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev)
+{
+       struct input_polled_dev *polldev;
+       struct input_polled_devres *devres;
+
+       devres = devres_alloc(devm_input_polldev_release, sizeof(*devres),
+                             GFP_KERNEL);
+       if (!devres)
+               return NULL;
+
+       polldev = input_allocate_polled_device();
+       if (!polldev) {
+               devres_free(devres);
+               return NULL;
+       }
+
+       polldev->input->dev.parent = dev;
+       polldev->devres_managed = true;
+
+       devres->polldev = polldev;
+       devres_add(dev, devres);
+
+       return polldev;
+}
+EXPORT_SYMBOL(devm_input_allocate_polled_device);
+
 /**
  * input_free_polled_device - free memory allocated for polled device
  * @dev: device to free
@@ -186,7 +271,12 @@ EXPORT_SYMBOL(input_allocate_polled_device);
 void input_free_polled_device(struct input_polled_dev *dev)
 {
        if (dev) {
-               input_free_device(dev->input);
+               if (dev->devres_managed)
+                       WARN_ON(devres_destroy(dev->input->dev.parent,
+                                               devm_input_polldev_release,
+                                               devm_input_polldev_match,
+                                               dev));
+               input_put_device(dev->input);
                kfree(dev);
        }
 }
@@ -204,9 +294,19 @@ EXPORT_SYMBOL(input_free_polled_device);
  */
 int input_register_polled_device(struct input_polled_dev *dev)
 {
+       struct input_polled_devres *devres = NULL;
        struct input_dev *input = dev->input;
        int error;
 
+       if (dev->devres_managed) {
+               devres = devres_alloc(devm_input_polldev_unregister,
+                                     sizeof(*devres), GFP_KERNEL);
+               if (!devres)
+                       return -ENOMEM;
+
+               devres->polldev = dev;
+       }
+
        input_set_drvdata(input, dev);
        INIT_DELAYED_WORK(&dev->work, input_polled_device_work);
 
@@ -221,8 +321,10 @@ int input_register_polled_device(struct input_polled_dev *dev)
        input->dev.groups = input_polldev_attribute_groups;
 
        error = input_register_device(input);
-       if (error)
+       if (error) {
+               devres_free(devres);
                return error;
+       }
 
        /*
         * Take extra reference to the underlying input device so
@@ -233,6 +335,12 @@ int input_register_polled_device(struct input_polled_dev *dev)
         */
        input_get_device(input);
 
+       if (dev->devres_managed) {
+               dev_dbg(input->dev.parent, "%s: registering %s with devres.\n",
+                       __func__, dev_name(&input->dev));
+               devres_add(input->dev.parent, devres);
+       }
+
        return 0;
 }
 EXPORT_SYMBOL(input_register_polled_device);
@@ -247,6 +355,12 @@ EXPORT_SYMBOL(input_register_polled_device);
  */
 void input_unregister_polled_device(struct input_polled_dev *dev)
 {
+       if (dev->devres_managed)
+               WARN_ON(devres_destroy(dev->input->dev.parent,
+                                       devm_input_polldev_unregister,
+                                       devm_input_polldev_match,
+                                       dev));
+
        input_unregister_device(dev->input);
 }
 EXPORT_SYMBOL(input_unregister_polled_device);