platform/x86: intel_cht_int33fe: Supply fwnodes for the external dependencies
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>
Fri, 31 May 2019 14:15:46 +0000 (17:15 +0300)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 3 Jun 2019 08:55:39 +0000 (10:55 +0200)
Supplying also external devices, the DisplayPort connector
and the USB role switch, software fwnodes. After this the
driver has access to all the components tied to the USB
Type-C connector and can start creating software node
references to actually associate them with the USB Type-C
connector device.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Tested-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/platform/x86/intel_cht_int33fe.c

index dcfe47baff723555ea697aead0321c925e0fec6d..9e1848e0343d9658f609e1e551275fced19b7012 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
+#include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
@@ -32,6 +33,8 @@ enum {
        INT33FE_NODE_FUSB302,
        INT33FE_NODE_MAX17047,
        INT33FE_NODE_PI3USB30532,
+       INT33FE_NODE_DISPLAYPORT,
+       INT33FE_NODE_ROLE_SWITCH,
        INT33FE_NODE_USB_CONNECTOR,
        INT33FE_NODE_MAX,
 };
@@ -42,6 +45,9 @@ struct cht_int33fe_data {
        struct i2c_client *pi3usb30532;
        /* Contain a list-head must be per device */
        struct device_connection connections[4];
+
+       struct fwnode_handle *dp;
+       struct fwnode_handle *mux;
 };
 
 /*
@@ -110,10 +116,126 @@ static const struct software_node nodes[] = {
        { "fusb302", NULL, fusb302_props },
        { "max17047", NULL, max17047_props },
        { "pi3usb30532" },
+       { "displayport" },
+       { "usb-role-switch" },
        { "connector", &nodes[0], usb_connector_props },
        { }
 };
 
+static int cht_int33fe_setup_mux(struct cht_int33fe_data *data)
+{
+       struct fwnode_handle *fwnode;
+       struct device *dev;
+       struct device *p;
+
+       fwnode = software_node_fwnode(&nodes[INT33FE_NODE_ROLE_SWITCH]);
+       if (!fwnode)
+               return -ENODEV;
+
+       /* First finding the platform device */
+       p = bus_find_device_by_name(&platform_bus_type, NULL,
+                                   "intel_xhci_usb_sw");
+       if (!p)
+               return -EPROBE_DEFER;
+
+       /* Then the mux child device */
+       dev = device_find_child_by_name(p, "intel_xhci_usb_sw-role-switch");
+       put_device(p);
+       if (!dev)
+               return -EPROBE_DEFER;
+
+       /* If there already is a node for the mux, using that one. */
+       if (dev->fwnode)
+               fwnode_remove_software_node(fwnode);
+       else
+               dev->fwnode = fwnode;
+
+       data->mux = fwnode_handle_get(dev->fwnode);
+       put_device(dev);
+
+       return 0;
+}
+
+static int cht_int33fe_setup_dp(struct cht_int33fe_data *data)
+{
+       struct fwnode_handle *fwnode;
+       struct pci_dev *pdev;
+
+       fwnode = software_node_fwnode(&nodes[INT33FE_NODE_DISPLAYPORT]);
+       if (!fwnode)
+               return -ENODEV;
+
+       /* First let's find the GPU PCI device */
+       pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+       if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) {
+               pci_dev_put(pdev);
+               return -ENODEV;
+       }
+
+       /* Then the DP child device node */
+       data->dp = device_get_named_child_node(&pdev->dev, "DD02");
+       pci_dev_put(pdev);
+       if (!data->dp)
+               return -ENODEV;
+
+       fwnode->secondary = ERR_PTR(-ENODEV);
+       data->dp->secondary = fwnode;
+
+       return 0;
+}
+
+static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data)
+{
+       software_node_unregister_nodes(nodes);
+
+       if (data->mux) {
+               fwnode_handle_put(data->mux);
+               data->mux = NULL;
+       }
+
+       if (data->dp) {
+               data->dp->secondary = NULL;
+               fwnode_handle_put(data->dp);
+               data->dp = NULL;
+       }
+}
+
+static int cht_int33fe_add_nodes(struct cht_int33fe_data *data)
+{
+       int ret;
+
+       ret = software_node_register_nodes(nodes);
+       if (ret)
+               return ret;
+
+       /* The devices that are not created in this driver need extra steps. */
+
+       /*
+        * There is no ACPI device node for the USB role mux, so we need to find
+        * the mux device and assign our node directly to it. That means we
+        * depend on the mux driver. This function will return -PROBE_DEFER
+        * until the mux device is registered.
+        */
+       ret = cht_int33fe_setup_mux(data);
+       if (ret)
+               goto err_remove_nodes;
+
+       /*
+        * The DP connector does have ACPI device node. In this case we can just
+        * find that ACPI node and assign our node as the secondary node to it.
+        */
+       ret = cht_int33fe_setup_dp(data);
+       if (ret)
+               goto err_remove_nodes;
+
+       return 0;
+
+err_remove_nodes:
+       cht_int33fe_remove_nodes(data);
+
+       return ret;
+}
+
 static int
 cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
 {
@@ -212,7 +334,7 @@ static int cht_int33fe_probe(struct platform_device *pdev)
        if (!data)
                return -ENOMEM;
 
-       ret = software_node_register_nodes(nodes);
+       ret = cht_int33fe_add_nodes(data);
        if (ret)
                return ret;
 
@@ -281,7 +403,7 @@ out_unregister_max17047:
        device_connections_remove(data->connections);
 
 out_remove_nodes:
-       software_node_unregister_nodes(nodes);
+       cht_int33fe_remove_nodes(data);
 
        return ret;
 }
@@ -295,7 +417,7 @@ static int cht_int33fe_remove(struct platform_device *pdev)
        i2c_unregister_device(data->max17047);
 
        device_connections_remove(data->connections);
-       software_node_unregister_nodes(nodes);
+       cht_int33fe_remove_nodes(data);
 
        return 0;
 }