spi/of: Add OF notifier handler
authorPantelis Antoniou <pantelis.antoniou@konsulko.com>
Tue, 28 Oct 2014 20:36:05 +0000 (22:36 +0200)
committerGrant Likely <grant.likely@linaro.org>
Tue, 25 Nov 2014 15:36:24 +0000 (15:36 +0000)
Add OF notifier handler needed for creating/destroying spi devices
according to dynamic runtime changes in the DT live tree. This code is
enabled when CONFIG_OF_DYNAMIC is selected.

Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
Signed-off-by: Grant Likely <grant.likely@linaro.org>
Reviewed-by: Mark Brown <broonie@kernel.org>
Cc: <linux-spi@vger.kernel.org>
drivers/spi/spi.c

index a3557c57d4f794334726398193d419fa1ca5f9ab..bf1cab0fad66115d56bb2231cbab567241f8cb2a 100644 (file)
@@ -2317,6 +2317,86 @@ EXPORT_SYMBOL_GPL(spi_write_then_read);
 
 /*-------------------------------------------------------------------------*/
 
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+static int __spi_of_device_match(struct device *dev, void *data)
+{
+       return dev->of_node == data;
+}
+
+/* must call put_device() when done with returned spi_device device */
+static struct spi_device *of_find_spi_device_by_node(struct device_node *node)
+{
+       struct device *dev = bus_find_device(&spi_bus_type, NULL, node,
+                                               __spi_of_device_match);
+       return dev ? to_spi_device(dev) : NULL;
+}
+
+static int __spi_of_master_match(struct device *dev, const void *data)
+{
+       return dev->of_node == data;
+}
+
+/* the spi masters are not using spi_bus, so we find it with another way */
+static struct spi_master *of_find_spi_master_by_node(struct device_node *node)
+{
+       struct device *dev;
+
+       dev = class_find_device(&spi_master_class, NULL, node,
+                               __spi_of_master_match);
+       if (!dev)
+               return NULL;
+
+       /* reference got in class_find_device */
+       return container_of(dev, struct spi_master, dev);
+}
+
+static int of_spi_notify(struct notifier_block *nb, unsigned long action,
+                        void *arg)
+{
+       struct of_reconfig_data *rd = arg;
+       struct spi_master *master;
+       struct spi_device *spi;
+
+       switch (of_reconfig_get_state_change(action, arg)) {
+       case OF_RECONFIG_CHANGE_ADD:
+               master = of_find_spi_master_by_node(rd->dn->parent);
+               if (master == NULL)
+                       return NOTIFY_OK;       /* not for us */
+
+               spi = of_register_spi_device(master, rd->dn);
+               put_device(&master->dev);
+
+               if (IS_ERR(spi)) {
+                       pr_err("%s: failed to create for '%s'\n",
+                                       __func__, rd->dn->full_name);
+                       return notifier_from_errno(PTR_ERR(spi));
+               }
+               break;
+
+       case OF_RECONFIG_CHANGE_REMOVE:
+               /* find our device by node */
+               spi = of_find_spi_device_by_node(rd->dn);
+               if (spi == NULL)
+                       return NOTIFY_OK;       /* no? not meant for us */
+
+               /* unregister takes one ref away */
+               spi_unregister_device(spi);
+
+               /* and put the reference of the find */
+               put_device(&spi->dev);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block spi_of_notifier = {
+       .notifier_call = of_spi_notify,
+};
+#else /* IS_ENABLED(CONFIG_OF_DYNAMIC) */
+extern struct notifier_block spi_of_notifier;
+#endif /* IS_ENABLED(CONFIG_OF_DYNAMIC) */
+
 static int __init spi_init(void)
 {
        int     status;
@@ -2334,6 +2414,10 @@ static int __init spi_init(void)
        status = class_register(&spi_master_class);
        if (status < 0)
                goto err2;
+
+       if (IS_ENABLED(CONFIG_OF))
+               WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
+
        return 0;
 
 err2: