[PATCH 14/22] platform/chrome: cros_typec_switch: Add support for signaling HPD to drm_bridge

Dmitry Baryshkov dmitry.baryshkov at linaro.org
Sat Feb 10 06:10:31 PST 2024


On Sat, 10 Feb 2024 at 09:14, Stephen Boyd <swboyd at chromium.org> wrote:
>
> We can imagine that logically the EC is a device that has some number of
> DisplayPort (DP) connector inputs, some number of USB3 connector inputs,
> and some number of USB type-c connector outputs. If you squint enough it
> looks like a USB type-c dock. Logically there's a crossbar pin
> assignment capability within the EC that can assign USB and DP lanes to
> USB type-c lanes in the connector (i.e. USB type-c pin configurations).
> In reality, the EC is a microcontroller that has some TCPCs and
> redrivers connected to it over something like i2c and DP/USB from the AP
> is wired directly to those ICs, not the EC.
>
> This design allows the EC to abstract many possible USB and DP hardware
> configurations away from the AP (kernel) so that the AP can largely deal
> with USB and DP without thinking about USB Type-C much at all. The DP
> and USB data originate in the AP, not the EC, so it helps to think that
> the EC takes the DP and USB data as input to mux onto USB type-c ports
> even if it really doesn't do that. With this split design, the EC
> forwards the DP HPD state to the AP via a GPIO that's connected to the
> DP phy.
>
> Having that HPD state signaled directly to the DP phy uses precious
> hardware resources, a GPIO or two and a wire, and it also forces the
> TCPM to live on the EC. If we want to save costs and move more control
> of USB type-c to the kernel it's in our interest to get rid of the HPD
> GPIO entirely and signal HPD to the DP phy some other way. Luckily, the
> EC already exposes information about the USB Type-C stack to the kernel
> via the host command interface in the "google,cros-ec-typec" compatible
> driver, which parses EC messages related to USB type-c and effectively
> "replays" those messages to the kernel's USB typec subsystem. This
> includes the state of HPD, which can be interrogated and acted upon by
> registering a 'struct typec_mux_dev' with the typec subsystem.
>
> On DT based systems, the DP display pipeline is abstracted via a 'struct
> drm_bridge'. If we want to signal HPD state within the kernel we need to
> hook into the drm_bridge framework somehow to call
> drm_bridge_hpd_notify() when HPD state changes in the typec framework.
> Make a drm_bridge in the EC that attaches onto the end of the DP bridge
> chain and logically moves the display data onto a usb-c-connector.
> Signal HPD when the typec HPD state changes, as long as this new
> drm_bridge is the one that's supposed to signal HPD. Do that by
> registering a 'struct typec_mux_dev' with the typec framework and
> associating that struct with a usb-c-connector node and a drm_bridge.
>
> To keep this patch minimal, just signal HPD state to the drm_bridge
> chain. Later patches will add more features. Eventually we'll be able to
> inform userspace about which usb-c-connector node is displaying DP and
> what USB devices are connected to a connector. Note that this code is
> placed in the cros_typec_switch driver because that's where mode-switch
> devices on the EC are controlled by the AP. Logically this drm_bridge
> sits in front of the mode-switch on the EC, and if there is anything to
> control on the EC the 'EC_FEATURE_TYPEC_AP_MUX_SET' feature will be set.
>
> Cc: Prashant Malani <pmalani at chromium.org>
> Cc: Benson Leung <bleung at chromium.org>
> Cc: Tzung-Bi Shih <tzungbi at kernel.org>
> Cc: <chrome-platform at lists.linux.dev>
> Cc: Pin-yen Lin <treapking at chromium.org>
> Signed-off-by: Stephen Boyd <swboyd at chromium.org>
> ---
>  drivers/platform/chrome/Kconfig             |   3 +-
>  drivers/platform/chrome/cros_typec_switch.c | 218 ++++++++++++++++++--
>  2 files changed, 204 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
> index 7a83346bfa53..910aa8be9c84 100644
> --- a/drivers/platform/chrome/Kconfig
> +++ b/drivers/platform/chrome/Kconfig
> @@ -287,7 +287,8 @@ config CHROMEOS_PRIVACY_SCREEN
>
>  config CROS_TYPEC_SWITCH
>         tristate "ChromeOS EC Type-C Switch Control"
> -       depends on MFD_CROS_EC_DEV && TYPEC && ACPI
> +       depends on MFD_CROS_EC_DEV
> +       depends on TYPEC
>         default MFD_CROS_EC_DEV
>         help
>           If you say Y here, you get support for configuring the ChromeOS EC Type-C
> diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c
> index 769de2889f2f..d8fb6662cf8d 100644
> --- a/drivers/platform/chrome/cros_typec_switch.c
> +++ b/drivers/platform/chrome/cros_typec_switch.c
> @@ -10,6 +10,7 @@
>  #include <linux/delay.h>
>  #include <linux/iopoll.h>
>  #include <linux/module.h>
> +#include <linux/of.h>
>  #include <linux/platform_data/cros_ec_commands.h>
>  #include <linux/platform_data/cros_ec_proto.h>
>  #include <linux/platform_device.h>
> @@ -18,6 +19,15 @@
>  #include <linux/usb/typec_mux.h>
>  #include <linux/usb/typec_retimer.h>
>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_print.h>
> +
> +struct cros_typec_dp_bridge {
> +       struct cros_typec_switch_data *sdata;
> +       bool hpd_enabled;
> +       struct drm_bridge bridge;
> +};

Is there any chance that you can use drm_dp_hpd_bridge_register() /
drm_aux_hpd_bridge_notify() instead of implementing another
drm_bridge?
If something is missing from the existing implementation we can
probably work that out.

> +
>  /* Handles and other relevant data required for each port's switches. */
>  struct cros_typec_port {
>         int port_num;
> @@ -30,7 +40,9 @@ struct cros_typec_port {
>  struct cros_typec_switch_data {
>         struct device *dev;
>         struct cros_ec_device *ec;
> +       bool typec_cmd_supported;
>         struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS];
> +       struct cros_typec_dp_bridge *typec_dp_bridge;
>  };
>
>  static int cros_typec_cmd_mux_set(struct cros_typec_switch_data *sdata, int port_num, u8 index,
> @@ -143,13 +155,60 @@ static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int po
>         return 0;
>  }
>
> +static int cros_typec_dp_port_switch_set(struct typec_mux_dev *mode_switch,
> +                                        struct typec_mux_state *state)
> +{
> +       struct cros_typec_port *port;
> +       const struct typec_displayport_data *dp_data;
> +       struct cros_typec_dp_bridge *typec_dp_bridge;
> +       struct drm_bridge *bridge;
> +       bool hpd_asserted;
> +
> +       port = typec_mux_get_drvdata(mode_switch);
> +       typec_dp_bridge = port->sdata->typec_dp_bridge;
> +       if (!typec_dp_bridge)
> +               return 0;
> +
> +       bridge = &typec_dp_bridge->bridge;
> +
> +       if (state->mode == TYPEC_STATE_SAFE || state->mode == TYPEC_STATE_USB) {
> +               if (typec_dp_bridge->hpd_enabled)
> +                       drm_bridge_hpd_notify(bridge, connector_status_disconnected);
> +
> +               return 0;
> +       }
> +
> +       if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) {
> +               if (typec_dp_bridge->hpd_enabled) {
> +                       dp_data = state->data;
> +                       hpd_asserted = dp_data->status & DP_STATUS_HPD_STATE;
> +
> +                       if (hpd_asserted)
> +                               drm_bridge_hpd_notify(bridge, connector_status_connected);
> +                       else
> +                               drm_bridge_hpd_notify(bridge, connector_status_disconnected);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
>  static int cros_typec_mode_switch_set(struct typec_mux_dev *mode_switch,
>                                       struct typec_mux_state *state)
>  {
>         struct cros_typec_port *port = typec_mux_get_drvdata(mode_switch);
> +       struct cros_typec_switch_data *sdata = port->sdata;
> +       int ret;
> +
> +       ret = cros_typec_dp_port_switch_set(mode_switch, state);
> +       if (ret)
> +               return ret;
>
>         /* Mode switches have index 0. */
> -       return cros_typec_configure_mux(port->sdata, port->port_num, 0, state->mode, state->alt);
> +       if (sdata->typec_cmd_supported)
> +               return cros_typec_configure_mux(port->sdata, port->port_num, 0, state->mode, state->alt);
> +
> +       return 0;
>  }
>
>  static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state)
> @@ -201,12 +260,77 @@ static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwno
>         return PTR_ERR_OR_ZERO(port->retimer);
>  }
>
> +static int
> +cros_typec_dp_bridge_attach(struct drm_bridge *bridge,
> +                           enum drm_bridge_attach_flags flags)
> +{
> +       if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
> +               DRM_ERROR("Fix bridge driver to make connector optional!\n");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static 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);
> +}
> +
> +static void cros_typec_dp_bridge_hpd_enable(struct drm_bridge *bridge)
> +{
> +       struct cros_typec_dp_bridge *typec_dp_bridge;
> +
> +       typec_dp_bridge = bridge_to_cros_typec_dp_bridge(bridge);
> +       typec_dp_bridge->hpd_enabled = true;
> +}
> +
> +static void cros_typec_dp_bridge_hpd_disable(struct drm_bridge *bridge)
> +{
> +       struct cros_typec_dp_bridge *typec_dp_bridge;
> +
> +       typec_dp_bridge = bridge_to_cros_typec_dp_bridge(bridge);
> +       typec_dp_bridge->hpd_enabled = false;
> +}
> +
> +static const struct drm_bridge_funcs cros_typec_dp_bridge_funcs = {
> +       .attach = cros_typec_dp_bridge_attach,
> +       .hpd_enable = cros_typec_dp_bridge_hpd_enable,
> +       .hpd_disable = cros_typec_dp_bridge_hpd_disable,
> +};
> +
> +static int cros_typec_register_dp_bridge(struct cros_typec_switch_data *sdata,
> +                                        struct fwnode_handle *fwnode)
> +{
> +       struct cros_typec_dp_bridge *typec_dp_bridge;
> +       struct drm_bridge *bridge;
> +       struct device *dev = sdata->dev;
> +
> +       typec_dp_bridge = devm_kzalloc(dev, sizeof(*typec_dp_bridge), GFP_KERNEL);
> +       if (!typec_dp_bridge)
> +               return -ENOMEM;
> +
> +       typec_dp_bridge->sdata = sdata;
> +       sdata->typec_dp_bridge = typec_dp_bridge;
> +       bridge = &typec_dp_bridge->bridge;
> +
> +       bridge->funcs = &cros_typec_dp_bridge_funcs;
> +       bridge->of_node = dev->of_node;
> +       bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
> +       bridge->ops |= DRM_BRIDGE_OP_HPD;
> +
> +       return devm_drm_bridge_add(dev, bridge);
> +}
> +
>  static int cros_typec_register_port(struct cros_typec_switch_data *sdata,
>                                     struct fwnode_handle *fwnode)
>  {
>         struct cros_typec_port *port;
>         struct device *dev = sdata->dev;
>         struct acpi_device *adev;
> +       struct device_node *np;
> +       struct fwnode_handle *port_node;
>         u32 index;
>         int ret;
>         const char *prop_name;
> @@ -218,9 +342,12 @@ static int cros_typec_register_port(struct cros_typec_switch_data *sdata,
>         adev = to_acpi_device_node(fwnode);
>         if (adev)
>                 prop_name = "_ADR";
> +       np = to_of_node(fwnode);
> +       if (np)
> +               prop_name = "reg";
>
> -       if (!adev)
> -               return dev_err_probe(fwnode->dev, -ENODEV, "Couldn't get ACPI handle\n");
> +       if (!adev && !np)
> +               return dev_err_probe(fwnode->dev, -ENODEV, "Couldn't get ACPI/OF device handle\n");
>
>         ret = fwnode_property_read_u32(fwnode, prop_name, &index);
>         if (ret)
> @@ -232,41 +359,84 @@ static int cros_typec_register_port(struct cros_typec_switch_data *sdata,
>         port->port_num = index;
>         sdata->ports[index] = port;
>
> +       port_node = fwnode;
> +       if (np)
> +               fwnode = fwnode_graph_get_port_parent(fwnode);
> +
>         if (fwnode_property_present(fwnode, "retimer-switch")) {
> -               ret = cros_typec_register_retimer(port, fwnode);
> -               if (ret)
> -                       return dev_err_probe(dev, ret, "Retimer switch register failed\n");
> +               ret = cros_typec_register_retimer(port, port_node);
> +               if (ret) {
> +                       dev_err_probe(dev, ret, "Retimer switch register failed\n");
> +                       goto out;
> +               }
>
>                 dev_dbg(dev, "Retimer switch registered for index %u\n", index);
>         }
>
> -       if (!fwnode_property_present(fwnode, "mode-switch"))
> -               return 0;
> +       if (fwnode_property_present(fwnode, "mode-switch")) {
> +               ret = cros_typec_register_mode_switch(port, port_node);
> +               if (ret) {
> +                       dev_err_probe(dev, ret, "Mode switch register failed\n");
> +                       goto out;
> +               }
>
> -       ret = cros_typec_register_mode_switch(port, fwnode);
> -       if (ret)
> -               return dev_err_probe(dev, ret, "Mode switch register failed\n");
> +               dev_dbg(dev, "Mode switch registered for index %u\n", index);
> +       }
>
> -       dev_dbg(dev, "Mode switch registered for index %u\n", index);
>
> +out:
> +       if (np)
> +               fwnode_handle_put(fwnode);
>         return ret;
>  }
>
>  static int cros_typec_register_switches(struct cros_typec_switch_data *sdata)
>  {
>         struct device *dev = sdata->dev;
> +       struct fwnode_handle *devnode;
>         struct fwnode_handle *fwnode;
> +       struct fwnode_endpoint endpoint;
>         int nports, ret;
>
>         nports = device_get_child_node_count(dev);
>         if (nports == 0)
>                 return dev_err_probe(dev, -ENODEV, "No switch devices found\n");
>
> -       device_for_each_child_node(dev, fwnode) {
> -               ret = cros_typec_register_port(sdata, fwnode);
> -               if (ret) {
> +       devnode = dev_fwnode(dev);
> +       if (fwnode_graph_get_endpoint_count(devnode, 0)) {
> +               fwnode_graph_for_each_endpoint(devnode, fwnode) {
> +                       ret = fwnode_graph_parse_endpoint(fwnode, &endpoint);
> +                       if (ret) {
> +                               fwnode_handle_put(fwnode);
> +                               goto err;
> +                       }
> +                       /* Skip if not a type-c output port */
> +                       if (endpoint.port != 2)
> +                               continue;
> +
> +                       ret = cros_typec_register_port(sdata, fwnode);
> +                       if (ret) {
> +                               fwnode_handle_put(fwnode);
> +                               goto err;
> +                       }
> +               }
> +       } else {
> +               device_for_each_child_node(dev, fwnode) {
> +                       ret = cros_typec_register_port(sdata, fwnode);
> +                       if (ret) {
> +                               fwnode_handle_put(fwnode);
> +                               goto err;
> +                       }
> +               }
> +       }
> +
> +       if (fwnode_property_present(devnode, "mode-switch")) {
> +               fwnode = fwnode_graph_get_endpoint_by_id(devnode, 0, 0, 0);
> +               if (fwnode) {
> +                       ret = cros_typec_register_dp_bridge(sdata, fwnode);
>                         fwnode_handle_put(fwnode);
> -                       goto err;
> +                       if (ret)
> +                               goto err;
>                 }
>         }
>
> @@ -280,6 +450,7 @@ static int cros_typec_switch_probe(struct platform_device *pdev)
>  {
>         struct device *dev = &pdev->dev;
>         struct cros_typec_switch_data *sdata;
> +       struct cros_ec_dev *ec_dev;
>
>         sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL);
>         if (!sdata)
> @@ -288,6 +459,12 @@ static int cros_typec_switch_probe(struct platform_device *pdev)
>         sdata->dev = dev;
>         sdata->ec = dev_get_drvdata(pdev->dev.parent);
>
> +       ec_dev = dev_get_drvdata(&sdata->ec->ec->dev);
> +       if (!ec_dev)
> +               return -EPROBE_DEFER;
> +
> +       sdata->typec_cmd_supported = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_AP_MUX_SET);
> +
>         platform_set_drvdata(pdev, sdata);
>
>         return cros_typec_register_switches(sdata);
> @@ -308,10 +485,19 @@ static const struct acpi_device_id cros_typec_switch_acpi_id[] = {
>  MODULE_DEVICE_TABLE(acpi, cros_typec_switch_acpi_id);
>  #endif
>
> +#ifdef CONFIG_OF
> +static const struct of_device_id cros_typec_switch_of_match_table[] = {
> +       { .compatible = "google,cros-ec-typec-switch" },
> +       {}
> +};
> +MODULE_DEVICE_TABLE(of, cros_typec_switch_of_match_table);
> +#endif
> +
>  static struct platform_driver cros_typec_switch_driver = {
>         .driver = {
>                 .name = "cros-typec-switch",
>                 .acpi_match_table = ACPI_PTR(cros_typec_switch_acpi_id),
> +               .of_match_table = of_match_ptr(cros_typec_switch_of_match_table),
>         },
>         .probe = cros_typec_switch_probe,
>         .remove_new = cros_typec_switch_remove,
> --
> https://chromeos.dev
>
>


-- 
With best wishes
Dmitry



More information about the linux-arm-kernel mailing list