MLK-17275-1: drm/bridge: adv7511: Add support for OF_DYNAMIC
authorRobert Chiras <robert.chiras@nxp.com>
Thu, 21 Dec 2017 08:59:20 +0000 (10:59 +0200)
committerNitin Garg <nitin.garg@nxp.com>
Tue, 20 Mar 2018 19:52:41 +0000 (14:52 -0500)
When CONFIG_OF_DYNAMIC is used, and this driver is enabled in
devicetree, but fails to probe a physical i2c client, it should disable
it's remote endpoint, so that the DRM master device won't fail to bind
the other available devices.
Usually, the remote endpoint of this device is a DRM encoder. If a DRM
encoder fails to bind, the DRM master device will also fail to bind.
This is why, we should disable the encoder node dynamically in
devicetree.

Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
Reviewed-by: Laurentiu Palcu <laurentiu.palcu@nxp.com>
drivers/gpu/drm/bridge/adv7511/adv7511_drv.c

index e2f0a8a..6539c27 100644 (file)
@@ -9,7 +9,9 @@
 #include <linux/device.h>
 #include <linux/gpio/consumer.h>
 #include <linux/module.h>
+#include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/of_graph.h>
 #include <linux/slab.h>
 
 #include <drm/drmP.h>
@@ -967,6 +969,11 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
        struct adv7511_link_config link_config;
        struct adv7511 *adv7511;
        struct device *dev = &i2c->dev;
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+       struct device_node *remote_node = NULL, *endpoint = NULL;
+       struct of_changeset ocs;
+       struct property *prop;
+#endif
        unsigned int main_i2c_addr = i2c->addr << 1;
        unsigned int edid_i2c_addr = main_i2c_addr + 4;
        unsigned int val;
@@ -1010,12 +1017,14 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
        }
 
        adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config);
-       if (IS_ERR(adv7511->regmap))
-               return PTR_ERR(adv7511->regmap);
+       if (IS_ERR(adv7511->regmap)) {
+               ret = PTR_ERR(adv7511->regmap);
+               goto of_reconfig;
+       }
 
        ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val);
        if (ret)
-               return ret;
+               goto of_reconfig;
        dev_dbg(dev, "Rev. %d\n", val);
 
        if (adv7511->type == ADV7511)
@@ -1085,6 +1094,45 @@ err_unregister_cec:
        adv7533_uninit_cec(adv7511);
 err_i2c_unregister_edid:
        i2c_unregister_device(adv7511->i2c_edid);
+of_reconfig:
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+       endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+       if (endpoint)
+               remote_node = of_graph_get_remote_port_parent(endpoint);
+
+       if (remote_node) {
+               int num_endpoints = 0;
+
+               /*
+                * Remote node should have two endpoints (input and output: us)
+                * If remote node has more than two endpoints, probably that it
+                * has more outputs, so there is no need to disable it.
+                */
+               endpoint = NULL;
+               while ((endpoint = of_graph_get_next_endpoint(remote_node,
+                                                             endpoint)))
+                       num_endpoints++;
+
+               if (num_endpoints > 2) {
+                       of_node_put(remote_node);
+                       return ret;
+               }
+
+               prop = devm_kzalloc(dev, sizeof(*prop), GFP_KERNEL);
+               prop->name = devm_kstrdup(dev, "status", GFP_KERNEL);
+               prop->value = devm_kstrdup(dev, "disabled", GFP_KERNEL);
+               prop->length = 9;
+               of_changeset_init(&ocs);
+               of_changeset_update_property(&ocs, remote_node, prop);
+               ret = of_changeset_apply(&ocs);
+               if (!ret)
+                       dev_warn(dev,
+                               "Probe failed. Remote port '%s' disabled\n",
+                               remote_node->full_name);
+
+               of_node_put(remote_node);
+       };
+#endif
 
        return ret;
 }
@@ -1100,9 +1148,10 @@ static int adv7511_remove(struct i2c_client *i2c)
 
        drm_bridge_remove(&adv7511->bridge);
 
-       i2c_unregister_device(adv7511->i2c_edid);
-
-       kfree(adv7511->edid);
+       if (adv7511->i2c_edid) {
+               i2c_unregister_device(adv7511->i2c_edid);
+               kfree(adv7511->edid);
+       }
 
        return 0;
 }