@@ -7,6 +7,7 @@
*/
#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
@@ -427,6 +428,41 @@ static int cros_typec_init_ports(struct cros_typec_data *typec)
return ret;
}
+static void cros_typec_dp_bridge_hpd_notify(struct drm_bridge *bridge, enum drm_connector_status status)
+{
+ struct cros_typec_dp_bridge *dp_bridge = bridge_to_cros_typec_dp_bridge(bridge);
+ struct cros_typec_data *typec = dp_bridge->typec_data;
+ struct gpio_desc *mux_gpio = dp_bridge->mux_gpio;
+ int val;
+ DECLARE_BITMAP(orig, EC_USB_PD_MAX_PORTS);
+ DECLARE_BITMAP(changed, EC_USB_PD_MAX_PORTS);
+
+ if (!mux_gpio)
+ return;
+
+ /* This bridge signals HPD so it must be able to detect HPD properly */
+ if (dp_bridge->bridge.ops & DRM_BRIDGE_OP_HPD)
+ return;
+
+ bitmap_copy(orig, dp_bridge->hpd_asserted, EC_USB_PD_MAX_PORTS);
+ bitmap_zero(changed, EC_USB_PD_MAX_PORTS);
+
+ if (status == connector_status_connected) {
+ val = gpiod_get_value_cansleep(mux_gpio);
+ if (val < 0) {
+ dev_err(typec->dev, "Failed to read mux gpio\n");
+ return;
+ }
+ __set_bit(val, changed);
+ }
+
+ bitmap_copy(dp_bridge->hpd_asserted, changed, EC_USB_PD_MAX_PORTS);
+
+ /* Refresh port state. */
+ if (!bitmap_equal(orig, changed, EC_USB_PD_MAX_PORTS))
+ schedule_work(&typec->port_work);
+}
+
static int cros_typec_dp_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
@@ -435,6 +471,7 @@ static int cros_typec_dp_bridge_attach(struct drm_bridge *bridge,
static const struct drm_bridge_funcs cros_typec_dp_bridge_funcs = {
.attach = cros_typec_dp_bridge_attach,
+ .hpd_notify = cros_typec_dp_bridge_hpd_notify,
};
static int cros_typec_init_dp_bridge(struct cros_typec_data *typec)
@@ -452,6 +489,11 @@ static int cros_typec_init_dp_bridge(struct cros_typec_data *typec)
if (!dp_bridge)
return -ENOMEM;
typec->dp_bridge = dp_bridge;
+ dp_bridge->typec_data = typec;
+
+ dp_bridge->mux_gpio = devm_gpiod_get_optional(dev, "mux", GPIOD_ASIS);
+ if (IS_ERR(dp_bridge->mux_gpio))
+ return dev_err_probe(dev, PTR_ERR(dp_bridge->mux_gpio), "failed to get mux gpio\n");
bridge = &dp_bridge->bridge;
bridge->funcs = &cros_typec_dp_bridge_funcs;
@@ -662,6 +704,77 @@ static int cros_typec_enable_usb4(struct cros_typec_data *typec,
return typec_mux_set(port->mux, &port->state);
}
+/*
+ * Some ECs like to tell AP that both ports have DP enabled when that's
+ * impossible because the EC is muxing DP to one or the other port. Check the
+ * mux on the EC in this case and ignore what the EC tells us about DP on the
+ * port that isn't actually muxed for DP.
+ */
+void cros_typec_check_dp(struct cros_typec_data *typec,
+ struct ec_response_usb_pd_mux_info *resp,
+ struct cros_typec_port *port)
+{
+ struct cros_typec_dp_bridge *dp_bridge = typec->dp_bridge;
+ struct gpio_desc *mux_gpio;
+ int val;
+
+ /* Never registered a drm_bridge. Skip. */
+ if (!dp_bridge)
+ return;
+
+ /* Don't need to override DP enabled when DP isn't enabled. */
+ if (!(resp->flags & USB_PD_MUX_DP_ENABLED))
+ return;
+
+ mux_gpio = dp_bridge->mux_gpio;
+ /* EC mux is required to determine which port actually has DP on it. */
+ if (!mux_gpio)
+ return;
+
+ val = gpiod_get_value_cansleep(mux_gpio);
+ if (val < 0) {
+ dev_err(typec->dev, "Failed to read mux gpio\n");
+ return;
+ }
+
+ /* Only the muxed port can have DP enabled. Ignore. */
+ if (val != port->port_num)
+ resp->flags &= ~USB_PD_MUX_DP_ENABLED;
+}
+
+/*
+ * Some ECs don't notify AP when HPD goes high or low because their firmware is
+ * broken. Capture the state of HPD in cros_typec_dp_bridge_hpd_notify() and
+ * inject the asserted state into the EC's response (deasserted is the
+ * default).
+ */
+static void cros_typec_inject_hpd(struct cros_typec_data *typec,
+ struct ec_response_usb_pd_mux_info *resp,
+ struct cros_typec_port *port)
+{
+ struct cros_typec_dp_bridge *dp_bridge = typec->dp_bridge;
+
+ /* Never registered a drm_bridge. Skip. */
+ if (!dp_bridge)
+ return;
+
+ /* Don't need to inject HPD level when DP isn't enabled. */
+ if (!(resp->flags & USB_PD_MUX_DP_ENABLED))
+ return;
+
+ /* This bridge signals HPD so it doesn't need to be reinjected */
+ if (dp_bridge->bridge.ops & DRM_BRIDGE_OP_HPD)
+ return;
+
+ /*
+ * The default setting is HPD deasserted. Ignore if nothing to inject.
+ */
+ if (!test_bit(port->port_num, dp_bridge->hpd_asserted))
+ return;
+
+ resp->flags |= USB_PD_MUX_HPD_LVL;
+}
+
static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num,
struct ec_response_usb_pd_control_v2 *pd_ctrl)
{
@@ -682,6 +795,8 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num,
port_num, ret);
return ret;
}
+ cros_typec_check_dp(typec, &resp, port);
+ cros_typec_inject_hpd(typec, &resp, port);
/* No change needs to be made, let's exit early. */
if (port->mux_flags == resp.flags && port->role == pd_ctrl->role)
@@ -6,6 +6,7 @@
#include <linux/list.h>
#include <linux/notifier.h>
#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/types.h>
#include <linux/usb/pd.h>
#include <linux/usb/role.h>
#include <linux/usb/typec.h>
@@ -88,6 +89,19 @@ struct cros_typec_port {
struct cros_typec_dp_bridge {
struct drm_bridge bridge;
+ struct cros_typec_data *typec_data;
+ struct gpio_desc *mux_gpio;
+ DECLARE_BITMAP(hpd_asserted, EC_USB_PD_MAX_PORTS);
};
+static inline struct cros_typec_dp_bridge *
+bridge_to_cros_typec_dp_bridge(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct cros_typec_dp_bridge, bridge);
+}
+
+void cros_typec_check_dp(struct cros_typec_data *typec,
+ struct ec_response_usb_pd_mux_info *resp,
+ struct cros_typec_port *port);
+
#endif /* __CROS_EC_TYPEC__ */
@@ -82,6 +82,7 @@ static int cros_typec_altmode_enter(struct typec_altmode *alt, u32 *vdo)
if (ret < 0)
return ret;
+ cros_typec_check_dp(adata->port->typec_data, &resp, adata->port);
if (!(resp.flags & flags))
return -EINVAL;
} else {
@@ -147,6 +148,7 @@ static int cros_typec_altmode_exit(struct typec_altmode *alt)
if (ret < 0)
return ret;
+ cros_typec_check_dp(adata->port->typec_data, &resp, adata->port);
if (resp.flags & flags)
return -EINVAL;
} else {
Most ARM based chromebooks with two usb-c-connector nodes and one DP controller are muxing the DP lanes between the two USB ports. This is done so that the type-c ports are at least equal in capability if not functionality. Either an analog mux is used to steer the DP signal to one or the other port, or a DP bridge chip has two lanes (e.g. DP ML0/ML1) wired to one type-c port while the other two (e.g. DP ML2/ML3) are wired to another type-c port. If a user connects a DP capable cable to both usb-c-connectors the EC likes to inform the AP that both ports have entered DP altmode, even though one of those ports can't actually display anything because the DP lanes aren't steered there. The answer to this problem is to look at the HPD bit in the EC messages. The port that isn't steered for DP won't ever see HPD be asserted, because the EC hides HPD state for the other port. This isn't a great solution though, because some EC firmwares don't even signal HPD state in the message at all. Oops! And it really does throw the whole type-c subsystem for a loop when the port has DP altmode present but it can't be entered properly. Let's fix these problems by doing two things. First, we'll only allow the port that's steered for DP to enter DP mode. Do that by checking the mux-gpios whenever we see that the EC tells us DP mode has been entered. If the mux isn't selecting this port, remove the flag from the message so that DP mode doesn't look to be entered. Second, inject HPD into the EC message when the EC has busted firmware. In this case, DT authors add 'no-hpd' to the typec node (essentially only on Trogdor). Listen for HPD events from the drm_bridge and read the mux when HPD is asserted to figure out which port actually had HPD asserted on it. When the port state is processed, check the bit against the port and if DP mode is entered, i.e. the mux is still steering toward that port, check if HPD is asserted on that port and inject HPD. This is necessary so that the typec framework can update the HPD state in sysfs, and eventually call drm_connector_oob_hotplug_event() from the DP altmode driver. 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/cros_ec_typec.c | 115 +++++++++++++++++++ drivers/platform/chrome/cros_ec_typec.h | 14 +++ drivers/platform/chrome/cros_typec_altmode.c | 2 + 3 files changed, 131 insertions(+)