@@ -247,6 +247,7 @@ config CROS_EC_TYPEC
depends on MFD_CROS_EC_DEV && TYPEC
depends on CROS_USBPD_NOTIFY
depends on USB_ROLE_SWITCH
+ depends on DRM_BRIDGE
default MFD_CROS_EC_DEV
select CROS_EC_TYPEC_ALTMODES if TYPEC_DP_ALTMODE
select CROS_EC_TYPEC_ALTMODES if TYPEC_TBT_ALTMODE
@@ -9,6 +9,7 @@
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_usbpd_notify.h>
#include <linux/platform_device.h>
@@ -16,6 +17,8 @@
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_tbt.h>
+#include <drm/drm_bridge.h>
+
#include "cros_ec_typec.h"
#include "cros_typec_vdm.h"
#include "cros_typec_altmode.h"
@@ -337,6 +340,9 @@ static int cros_typec_init_ports(struct cros_typec_data *typec)
u32 port_num = 0;
nports = device_get_child_node_count(dev);
+ /* Don't count any 'ports' child node */
+ if (of_graph_is_present(dev->of_node))
+ nports--;
if (nports == 0) {
dev_err(dev, "No port entries found.\n");
return -ENODEV;
@@ -350,6 +356,10 @@ static int cros_typec_init_ports(struct cros_typec_data *typec)
/* DT uses "reg" to specify port number. */
port_prop = dev->of_node ? "reg" : "port-number";
device_for_each_child_node(dev, fwnode) {
+ /* An OF graph isn't a connector */
+ if (fwnode_name_eq(fwnode, "ports"))
+ continue;
+
if (fwnode_property_read_u32(fwnode, port_prop, &port_num)) {
ret = -EINVAL;
dev_err(dev, "No port-number for port, aborting.\n");
@@ -417,6 +427,42 @@ static int cros_typec_init_ports(struct cros_typec_data *typec)
return ret;
}
+static int cros_typec_dp_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
+}
+
+static const struct drm_bridge_funcs cros_typec_dp_bridge_funcs = {
+ .attach = cros_typec_dp_bridge_attach,
+};
+
+static int cros_typec_init_dp_bridge(struct cros_typec_data *typec)
+{
+ struct device *dev = typec->dev;
+ struct cros_typec_dp_bridge *dp_bridge;
+ struct drm_bridge *bridge;
+ struct device_node *np = dev->of_node;
+
+ /* Not capable of DP altmode switching. Ignore. */
+ if (!of_graph_is_present(np))
+ return 0;
+
+ dp_bridge = devm_kzalloc(dev, sizeof(*dp_bridge), GFP_KERNEL);
+ if (!dp_bridge)
+ return -ENOMEM;
+ typec->dp_bridge = dp_bridge;
+
+ bridge = &dp_bridge->bridge;
+ bridge->funcs = &cros_typec_dp_bridge_funcs;
+ bridge->of_node = np;
+ bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
+ if (!device_property_read_bool(dev, "no-hpd"))
+ bridge->ops |= DRM_BRIDGE_OP_HPD;
+
+ return devm_drm_bridge_add(dev, bridge);
+}
+
static int cros_typec_usb_safe_state(struct cros_typec_port *port)
{
int ret;
@@ -1276,6 +1322,10 @@ static int cros_typec_probe(struct platform_device *pdev)
typec->num_ports = EC_USB_PD_MAX_PORTS;
}
+ ret = cros_typec_init_dp_bridge(typec);
+ if (ret < 0)
+ return ret;
+
ret = cros_typec_init_ports(typec);
if (ret < 0)
return ret;
@@ -14,6 +14,8 @@
#include <linux/usb/typec_retimer.h>
#include <linux/workqueue.h>
+#include <drm/drm_bridge.h>
+
/* Supported alt modes. */
enum {
CROS_EC_ALTMODE_DP = 0,
@@ -35,6 +37,7 @@ struct cros_typec_data {
unsigned int pd_ctrl_ver;
/* Array of ports, indexed by port number. */
struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS];
+ struct cros_typec_dp_bridge *dp_bridge;
struct notifier_block nb;
struct work_struct port_work;
bool typec_cmd_supported;
@@ -83,4 +86,8 @@ struct cros_typec_port {
struct cros_typec_data *typec_data;
};
+struct cros_typec_dp_bridge {
+ struct drm_bridge bridge;
+};
+
#endif /* __CROS_EC_TYPEC__ */
On Trogdor platforms, the USB DP altmode is entered and exited by the EC depending on if DP altmode is possible and if HPD is asserted for a port. Trogdor has two USB-C connectors but the AP only supports one DP controller, so the first USB-C connector to assert HPD "wins". The DP controller on the AP is fixed to output two lanes DP that goes to an analog mux that steers the DP lanes to one of the two USB-C connectors. The HPD state in the DP altmode is "captured" by the EC and redriven from a GPIO on the EC to the AP's GPIO that is muxed to the DisplayPort controller inside the AP SoC. This allows both HPD high/low and HPD IRQ to be signaled from the EC as well as making DP altmode possible on either USB-C connector except at the same time. Add a drm_bridge to the ChromeOS EC driver to represent this analog mux on Trogdor and teach the kernel that DP altmode is using this DP controller on the AP. When the DT node has a graph binding, we assume that we're muxing DP to one of many USB-C connectors and we terminate the bridge chain here. In almost all cases we want this bridge to be the one that signals HPD because the EC is the one managing HPD and redriving the GPIO, except for in the case that the DP altmode driver is enabled in which case HPD will be signaled with drm_bridge_connector_oob_hotplug_event(). Unfortunately Trogdor EC firmwares have a bug where HPD state isn't discoverable properly, so we skip signaling HPD in that case if the "no-hpd" property exists in the node. Cc: Benson Leung <bleung@chromium.org> Cc: Tzung-Bi Shih <tzungbi@kernel.org> Cc: <chrome-platform@lists.linux.dev> Cc: Pin-yen Lin <treapking@chromium.org> Cc: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> Cc: Ćukasz Bartosik <ukaszb@chromium.org> Cc: Jameson Thies <jthies@google.com> Cc: Andrei Kuchynski <akuchynski@chromium.org> Signed-off-by: Stephen Boyd <swboyd@chromium.org> --- drivers/platform/chrome/Kconfig | 1 + drivers/platform/chrome/cros_ec_typec.c | 50 +++++++++++++++++++++++++ drivers/platform/chrome/cros_ec_typec.h | 7 ++++ 3 files changed, 58 insertions(+)