Message ID | 20250416000208.3568635-7-swboyd@chromium.org |
---|---|
State | New |
Headers | show |
Series | platform/chrome: Support for USB DP altmode muxing w/ DT | expand |
Quoting Dmitry Baryshkov (2025-04-24 03:51:17) > > diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c > > index 2cbe29f08064..27324cf0c0c6 100644 > > --- a/drivers/platform/chrome/cros_ec_typec.c > > +++ b/drivers/platform/chrome/cros_ec_typec.c > > @@ -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--; > > Should this be a separate commit? > > > 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; > > + > > ... together with this chunk. I check for the of_graph being present below. It's all sorta related. > > > 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); > > Could you please use aux-hpd-bridge instead? I can extend that to call some function when hpd changes. If I make a device node for the mux and the TCPCs then it may be possible to push everything into aux-hpd-bridge and use it for all three of them. > > BTW: what is the usecase for the no-hpd handling here? > Looks like you figured it out. I want to capture the HPD state so I can then go read the EC mux to figure out which way the mux is pointing. On trogdor the EC doesn't tell us which typec port has HPD asserted so we snoop the HPD state from the drm_bridge, read the mux when HPD changes, and pick the right typec port while also injecting HPD into the state of the port. The no-hpd property is used on Trogdor to indicate that the bridge in the EC doesn't signal HPD properly, ensuring the previous bridge handles the HPD detection.
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 1b2f2bd09662..0ed8637b8853 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -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 diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 2cbe29f08064..27324cf0c0c6 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -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; diff --git a/drivers/platform/chrome/cros_ec_typec.h b/drivers/platform/chrome/cros_ec_typec.h index 9fd5342bb0ad..090f8f5c0492 100644 --- a/drivers/platform/chrome/cros_ec_typec.h +++ b/drivers/platform/chrome/cros_ec_typec.h @@ -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(+)