[PATCH v2 03/11] drm/bridge: aux-hpd: Support USB Type-C DP altmodes via DRM lane assignment
Stephen Boyd
swboyd at chromium.org
Thu Aug 15 00:34:08 UTC 2024
Extend the aux-hpd bridge driver to support assigning DP lanes to USB
Type-C pins. Existing users of this driver only need the HPD signaling
support, so leave that in place and wrap the code with a variant that
supports more features of USB Type-C DP altmode, i.e. pin
configurations. Prefix that code with 'drm_dp_typec_bridge' to
differentiate it from the existing 'drm_aux_hpd_bridge' code.
Users allocate the bridge by passing a struct drm_dp_typec_bridge_desc
to devm_drm_dp_typec_bridge_alloc() and then use the returned pointer
for further operations like notifying HPD to the bridge chain or setting
the DP altmode pin configuration. All these APIs take an opaque struct
drm_dp_typec_bridge_dev pointer so that the wrong 'struct device' can't
be mistakenly used.
Note: The pin assignment function doesn't actively change the lane
configuration. Instead it stashes the lane assignment and requests the
assignment on the previous bridge in the chain during the atomic check
phase. Assume the USB Type-C pins are in the normal orientation for now.
A future patch will support type-c port orientation.
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>
Cc: Dmitry Baryshkov <dmitry.baryshkov at linaro.org>
Signed-off-by: Stephen Boyd <swboyd at chromium.org>
---
drivers/gpu/drm/bridge/aux-hpd-bridge.c | 319 +++++++++++++++++++++++-
include/drm/bridge/aux-bridge.h | 52 ++++
2 files changed, 359 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c
index 6886db2d9e00..716b2220bdd5 100644
--- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c
+++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c
@@ -7,8 +7,11 @@
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/usb/typec_dp.h>
+#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_print.h>
#include <drm/bridge/aux-bridge.h>
static DEFINE_IDA(drm_aux_hpd_bridge_ida);
@@ -18,6 +21,48 @@ struct drm_aux_hpd_bridge_data {
struct device *dev;
};
+enum dp_lane {
+ DP_ML0 = 0, /* DP pins 1/3 */
+ DP_ML1 = 1, /* DP pins 4/6 */
+ DP_ML2 = 2, /* DP pins 7/9 */
+ DP_ML3 = 3, /* DP pins 10/12 */
+};
+
+struct drm_dp_typec_bridge_data {
+ u8 dp_lanes[DP_ML3 + 1];
+ size_t num_lanes;
+ struct drm_aux_hpd_bridge_data hpd_bridge;
+};
+
+static inline struct drm_dp_typec_bridge_data *
+hpd_bridge_to_typec_bridge_data(struct drm_aux_hpd_bridge_data *hpd_data)
+{
+ return container_of(hpd_data, struct drm_dp_typec_bridge_data, hpd_bridge);
+}
+
+static inline struct drm_dp_typec_bridge_data *
+to_drm_dp_typec_bridge_data(struct drm_bridge *bridge)
+{
+ struct drm_aux_hpd_bridge_data *hpd_data;
+
+ hpd_data = container_of(bridge, struct drm_aux_hpd_bridge_data, bridge);
+
+ return hpd_bridge_to_typec_bridge_data(hpd_data);
+}
+
+struct drm_dp_typec_bridge_dev {
+ struct auxiliary_device adev;
+ size_t max_lanes;
+};
+
+static inline struct drm_dp_typec_bridge_dev *
+to_drm_dp_typec_bridge_dev(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ return container_of(adev, struct drm_dp_typec_bridge_dev, adev);
+}
+
static void drm_aux_hpd_bridge_release(struct device *dev)
{
struct auxiliary_device *adev = to_auxiliary_dev(dev);
@@ -30,6 +75,22 @@ static void drm_aux_hpd_bridge_release(struct device *dev)
kfree(adev);
}
+static void drm_dp_typec_bridge_release(struct device *dev)
+{
+ struct drm_dp_typec_bridge_dev *typec_bridge_dev;
+ struct auxiliary_device *adev;
+
+ typec_bridge_dev = to_drm_dp_typec_bridge_dev(dev);
+ adev = &typec_bridge_dev->adev;
+
+ ida_free(&drm_aux_hpd_bridge_ida, adev->id);
+
+ of_node_put(adev->dev.platform_data);
+ of_node_put(adev->dev.of_node);
+
+ kfree(typec_bridge_dev);
+}
+
static void drm_aux_hpd_bridge_free_adev(void *_adev)
{
auxiliary_device_uninit(_adev);
@@ -133,6 +194,72 @@ struct device *drm_dp_hpd_bridge_register(struct device *parent, struct device_n
}
EXPORT_SYMBOL_GPL(drm_dp_hpd_bridge_register);
+/**
+ * devm_drm_dp_typec_bridge_alloc - Allocate a USB type-C DisplayPort bridge
+ * @parent: device instance providing this bridge
+ * @desc: device node pointer corresponding to this bridge instance
+ *
+ * Creates a DRM bridge with the type set to DRM_MODE_CONNECTOR_DisplayPort,
+ * which terminates the bridge chain and is able to send the HPD events along
+ * with remap DP lanes to match USB type-c DP altmode pin assignments.
+ *
+ * Return: device instance that will handle created bridge or an error code
+ * encoded into the pointer.
+ */
+struct drm_dp_typec_bridge_dev *
+devm_drm_dp_typec_bridge_alloc(struct device *parent, const struct drm_dp_typec_bridge_desc *desc)
+{
+ struct drm_dp_typec_bridge_dev *typec_bridge_dev;
+ struct auxiliary_device *adev;
+ int ret;
+
+ typec_bridge_dev = kzalloc(sizeof(*typec_bridge_dev), GFP_KERNEL);
+ if (!typec_bridge_dev)
+ return ERR_PTR(-ENOMEM);
+ adev = &typec_bridge_dev->adev;
+
+ ret = ida_alloc(&drm_aux_hpd_bridge_ida, GFP_KERNEL);
+ if (ret < 0) {
+ kfree(adev);
+ return ERR_PTR(ret);
+ }
+
+ adev->id = ret;
+ adev->name = "dp_typec_bridge";
+ adev->dev.parent = parent;
+ adev->dev.of_node = of_node_get(parent->of_node);
+ adev->dev.release = drm_dp_typec_bridge_release;
+ adev->dev.platform_data = of_node_get(desc->of_node);
+ typec_bridge_dev->max_lanes = desc->num_dp_lanes;
+
+ ret = auxiliary_device_init(adev);
+ if (ret) {
+ of_node_put(adev->dev.platform_data);
+ of_node_put(adev->dev.of_node);
+ ida_free(&drm_aux_hpd_bridge_ida, adev->id);
+ kfree(adev);
+ return ERR_PTR(ret);
+ }
+
+ return typec_bridge_dev;
+}
+EXPORT_SYMBOL_GPL(devm_drm_dp_typec_bridge_alloc);
+
+/**
+ * devm_drm_dp_typec_bridge_add - register a USB type-C DisplayPort bridge
+ * @dev: struct device to tie registration lifetime to
+ * @typec_bridge_dev: USB type-c DisplayPort bridge to be registered
+ *
+ * Returns: zero on success or a negative errno
+ */
+int devm_drm_dp_typec_bridge_add(struct device *dev, struct drm_dp_typec_bridge_dev *typec_bridge_dev)
+{
+ struct auxiliary_device *adev = &typec_bridge_dev->adev;
+
+ return devm_drm_dp_hpd_bridge_add(dev, adev);
+}
+EXPORT_SYMBOL_GPL(devm_drm_dp_typec_bridge_add);
+
/**
* drm_aux_hpd_bridge_notify - notify hot plug detection events
* @dev: device created for the HPD bridge
@@ -155,38 +282,206 @@ void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status sta
}
EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify);
+/**
+ * drm_dp_typec_bridge_notify - notify hot plug detection events
+ * @typec_bridge_dev: device created for the type-C bridge
+ * @status: output connection status
+ *
+ * A wrapper around drm_bridge_hpd_notify() that is used to report hot plug
+ * detection events for bridges created via devm_drm_dp_typec_bridge_alloc().
+ *
+ * This function shall be called in a context that can sleep.
+ */
+void drm_dp_typec_bridge_notify(struct drm_dp_typec_bridge_dev *typec_bridge_dev,
+ enum drm_connector_status status)
+{
+ drm_aux_hpd_bridge_notify(&typec_bridge_dev->adev.dev, status);
+}
+EXPORT_SYMBOL_GPL(drm_dp_typec_bridge_notify);
+
static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
}
+static int dp_lane_to_typec_lane(enum dp_lane lane)
+{
+ switch (lane) {
+ case DP_ML0:
+ return USB_SSTX2;
+ case DP_ML1:
+ return USB_SSRX2;
+ case DP_ML2:
+ return USB_SSTX1;
+ case DP_ML3:
+ return USB_SSRX1;
+ }
+
+ return -EINVAL;
+}
+
+static int typec_to_dp_lane(enum usb_ss_lane lane)
+{
+ switch (lane) {
+ case USB_SSRX1:
+ return DP_ML3;
+ case USB_SSTX1:
+ return DP_ML2;
+ case USB_SSTX2:
+ return DP_ML0;
+ case USB_SSRX2:
+ return DP_ML1;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * drm_dp_typec_bridge_assign_pins - Assign DisplayPort (DP) lanes to USB type-C pins
+ * @typec_bridge_dev: Device created for the type-c bridge
+ * @conf: DisplayPort altmode configure command VDO content
+ * @lane_mapping: Physical (array index) to logical (array value) USB type-C lane mapping
+ *
+ * Assign DP lanes to the USB type-C pins for the DP altmode configuration
+ * @conf, while taking into account the USB type-C @lane_mapping. Future atomic
+ * checks on this bridge will request the lane assignment from the previous
+ * bridge so that the DP signal is sent to the proper USB type-C pins.
+ *
+ * Return: 0 on success, negative value for failure.
+ */
+int drm_dp_typec_bridge_assign_pins(struct drm_dp_typec_bridge_dev *typec_bridge_dev,
+ u32 conf,
+ enum usb_ss_lane lane_mapping[NUM_USB_SS])
+{
+ struct auxiliary_device *adev = &typec_bridge_dev->adev;
+ struct drm_aux_hpd_bridge_data *hpd_data = auxiliary_get_drvdata(adev);
+ struct drm_dp_typec_bridge_data *data;
+ u8 *dp_lanes;
+ size_t num_lanes, max_lanes;
+ int i, typec_lane;
+ u8 pin_assign;
+
+ if (!hpd_data)
+ return -EINVAL;
+
+ data = hpd_bridge_to_typec_bridge_data(hpd_data);
+ dp_lanes = data->dp_lanes;
+
+ pin_assign = DP_CONF_GET_PIN_ASSIGN(conf);
+ if (pin_assign == DP_PIN_ASSIGN_D)
+ num_lanes = 2;
+ else
+ num_lanes = 4;
+ max_lanes = typec_bridge_dev->max_lanes;
+ data->num_lanes = num_lanes = min(num_lanes, max_lanes);
+
+ for (i = 0; i < num_lanes; i++) {
+ /* Get physical type-c lane for DP lane */
+ typec_lane = dp_lane_to_typec_lane(i);
+ if (typec_lane < 0) {
+ dev_err(&adev->dev, "Invalid type-c lane configuration at DP_ML%d\n", i);
+ return -EINVAL;
+ }
+
+ /* Map physical to logical type-c lane */
+ typec_lane = lane_mapping[typec_lane];
+
+ /* Map logical type-c lane to logical DP lane */
+ dp_lanes[i] = typec_to_dp_lane(typec_lane);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(drm_dp_typec_bridge_assign_pins);
+
+static int drm_dp_typec_bridge_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_dp_typec_bridge_data *data;
+ struct drm_lane_cfg *in_lanes;
+ u8 *dp_lanes;
+ size_t num_lanes;
+ int i;
+
+ data = to_drm_dp_typec_bridge_data(bridge);
+ num_lanes = data->num_lanes;
+ if (!num_lanes)
+ return 0;
+ dp_lanes = data->dp_lanes;
+
+ in_lanes = kcalloc(num_lanes, sizeof(*in_lanes), GFP_KERNEL);
+ if (!in_lanes)
+ return -ENOMEM;
+
+ bridge_state->input_bus_cfg.lanes = in_lanes;
+ bridge_state->input_bus_cfg.num_lanes = num_lanes;
+
+ for (i = 0; i < num_lanes; i++)
+ in_lanes[i].logical = dp_lanes[i];
+
+ return 0;
+}
+
static const struct drm_bridge_funcs drm_aux_hpd_bridge_funcs = {
.attach = drm_aux_hpd_bridge_attach,
};
+static const struct drm_bridge_funcs drm_dp_typec_bridge_funcs = {
+ .attach = drm_aux_hpd_bridge_attach,
+ .atomic_check = drm_dp_typec_bridge_atomic_check,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+};
+
+enum drm_aux_bridge_type {
+ DRM_AUX_HPD_BRIDGE,
+ DRM_AUX_TYPEC_BRIDGE,
+};
+
static int drm_aux_hpd_bridge_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
- struct drm_aux_hpd_bridge_data *data;
+ struct device *dev = &auxdev->dev;
+ struct drm_aux_hpd_bridge_data *hpd_data;
+ struct drm_dp_typec_bridge_data *typec_data;
+ struct drm_bridge *bridge;
+ u8 dp_lanes[] = { DP_ML0, DP_ML1, DP_ML2, DP_ML3 };
- data = devm_kzalloc(&auxdev->dev, sizeof(*data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
+ if (id->driver_data == DRM_AUX_HPD_BRIDGE) {
+ hpd_data = devm_kzalloc(dev, sizeof(*hpd_data), GFP_KERNEL);
+ if (!hpd_data)
+ return -ENOMEM;
+ bridge = &hpd_data->bridge;
+ bridge->funcs = &drm_aux_hpd_bridge_funcs;
+ } else if (id->driver_data == DRM_AUX_TYPEC_BRIDGE) {
+ typec_data = devm_kzalloc(dev, sizeof(*typec_data), GFP_KERNEL);
+ if (!typec_data)
+ return -ENOMEM;
+ hpd_data = &typec_data->hpd_bridge;
+ bridge = &hpd_data->bridge;
+ bridge->funcs = &drm_dp_typec_bridge_funcs;
+ memcpy(typec_data->dp_lanes, dp_lanes, sizeof(typec_data->dp_lanes));
+ } else {
+ return -ENODEV;
+ }
- data->dev = &auxdev->dev;
- data->bridge.funcs = &drm_aux_hpd_bridge_funcs;
- data->bridge.of_node = dev_get_platdata(data->dev);
- data->bridge.ops = DRM_BRIDGE_OP_HPD;
- data->bridge.type = id->driver_data;
+ hpd_data->dev = dev;
+ bridge->of_node = dev_get_platdata(dev);
+ bridge->ops = DRM_BRIDGE_OP_HPD;
+ bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
- auxiliary_set_drvdata(auxdev, data);
+ auxiliary_set_drvdata(auxdev, hpd_data);
- return devm_drm_bridge_add(data->dev, &data->bridge);
+ return devm_drm_bridge_add(dev, bridge);
}
static const struct auxiliary_device_id drm_aux_hpd_bridge_table[] = {
- { .name = KBUILD_MODNAME ".dp_hpd_bridge", .driver_data = DRM_MODE_CONNECTOR_DisplayPort, },
+ { .name = KBUILD_MODNAME ".dp_hpd_bridge", .driver_data = DRM_AUX_HPD_BRIDGE, },
+ { .name = KBUILD_MODNAME ".dp_typec_bridge", .driver_data = DRM_AUX_TYPEC_BRIDGE, },
{},
};
MODULE_DEVICE_TABLE(auxiliary, drm_aux_hpd_bridge_table);
diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h
index c2f5a855512f..aaace808772c 100644
--- a/include/drm/bridge/aux-bridge.h
+++ b/include/drm/bridge/aux-bridge.h
@@ -20,12 +20,40 @@ static inline int drm_aux_bridge_register(struct device *parent)
}
#endif
+struct drm_dp_typec_bridge_dev;
+
+/**
+ * struct drm_dp_typec_bridge_desc - drm_dp_typec_bridge descriptor
+ * @of_node: device node pointer corresponding to this bridge instance
+ * @num_dp_lanes: number of input DP lanes possible (1, 2 or 4)
+ */
+struct drm_dp_typec_bridge_desc {
+ struct device_node *of_node;
+ size_t num_dp_lanes;
+};
+
+enum usb_ss_lane {
+ USB_SSRX1 = 0, /* Type-C pins B11/B10 */
+ USB_SSTX1 = 1, /* Type-C pins A2/A3 */
+ USB_SSTX2 = 2, /* Type-C pins A11/A10 */
+ USB_SSRX2 = 3, /* Type-C pins B2/B3 */
+};
+
+#define NUM_USB_SS (USB_SSRX2 + 1)
+
#if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE)
struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, struct device_node *np);
int devm_drm_dp_hpd_bridge_add(struct device *dev, struct auxiliary_device *adev);
struct device *drm_dp_hpd_bridge_register(struct device *parent,
struct device_node *np);
void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status);
+struct drm_dp_typec_bridge_dev *devm_drm_dp_typec_bridge_alloc(struct device *parent,
+ const struct drm_dp_typec_bridge_desc *desc);
+int devm_drm_dp_typec_bridge_add(struct device *dev, struct drm_dp_typec_bridge_dev *typec_bridge_dev);
+void drm_dp_typec_bridge_notify(struct drm_dp_typec_bridge_dev *typec_bridge_dev,
+ enum drm_connector_status status);
+int drm_dp_typec_bridge_assign_pins(struct drm_dp_typec_bridge_dev *typec_bridge_dev, u32 conf,
+ enum usb_ss_lane lane_mapping[NUM_USB_SS]);
#else
static inline struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent,
struct device_node *np)
@@ -44,9 +72,33 @@ static inline struct device *drm_dp_hpd_bridge_register(struct device *parent,
return NULL;
}
+static inline struct drm_dp_typec_bridge_dev *
+devm_drm_dp_typec_bridge_alloc(struct device *parent, const struct drm_dp_typec_bridge_desc *desc)
+{
+ return NULL;
+}
+
+static inline int devm_drm_dp_typec_bridge_add(struct device *dev,
+ struct drm_dp_typec_bridge_dev *typec_bridge_dev)
+{
+ return 0;
+}
+
static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status)
{
}
+
+static inline void drm_dp_typec_bridge_notify(struct drm_dp_typec_bridge_dev *typec_bridge_dev,
+ enum drm_connector_status status)
+{
+}
+
+static inline int drm_dp_typec_bridge_assign_pins(struct drm_dp_typec_bridge_dev *typec_bridge_dev,
+ u32 conf,
+ enum usb_ss_lane lane_mapping[NUM_USB_SS])
+{
+ return 0;
+}
#endif
#endif
--
https://chromeos.dev
More information about the dri-devel
mailing list