@@ -19,20 +19,28 @@
#include <linux/usb/typec_mux.h>
#include <linux/usb/typec_retimer.h>
+#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_print.h>
struct cros_typec_dp_bridge {
+ /* TODO: Add mutex lock to protect active_port with respect to drm/typec framework calls */
+ struct cros_typec_port *active_port;
struct cros_typec_switch_data *sdata;
+ size_t max_lanes;
bool hpd_enabled;
struct drm_bridge bridge;
};
+#define USBC_LANES_COUNT 4
+
/* Handles and other relevant data required for each port's switches. */
struct cros_typec_port {
int port_num;
struct typec_mux_dev *mode_switch;
struct typec_retimer *retimer;
+ size_t num_dp_lanes;
+ u32 lane_mapping[USBC_LANES_COUNT];
struct cros_typec_switch_data *sdata;
};
@@ -163,6 +171,8 @@ static int cros_typec_dp_port_switch_set(struct typec_mux_dev *mode_switch,
struct cros_typec_dp_bridge *typec_dp_bridge;
struct drm_bridge *bridge;
bool hpd_asserted;
+ u8 pin_assign;
+ size_t num_lanes, max_lanes;
port = typec_mux_get_drvdata(mode_switch);
typec_dp_bridge = port->sdata->typec_dp_bridge;
@@ -172,17 +182,41 @@ static int cros_typec_dp_port_switch_set(struct typec_mux_dev *mode_switch,
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);
+ /* Clear active port when port isn't in DP mode */
+ port->num_dp_lanes = 0;
+ if (typec_dp_bridge->active_port == port) {
+ typec_dp_bridge->active_port = NULL;
+ 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;
+ dp_data = state->data;
+ hpd_asserted = dp_data->status & DP_STATUS_HPD_STATE;
+ /*
+ * Assume the first port to have HPD asserted is the one muxed
+ * to DP (i.e. active_port). When there's only one port this
+ * delays setting the active_port until HPD is asserted, but
+ * before that the drm_connector looks disconnected so
+ * active_port doesn't need to be set.
+ */
+ if (hpd_asserted && !typec_dp_bridge->active_port)
+ typec_dp_bridge->active_port = port;
+ /* Determine number of logical DP lanes from pin assignment */
+ pin_assign = DP_CONF_GET_PIN_ASSIGN(dp_data->conf);
+ if (pin_assign == DP_PIN_ASSIGN_D)
+ num_lanes = 2;
+ else
+ num_lanes = 4;
+ max_lanes = typec_dp_bridge->max_lanes;
+ port->num_dp_lanes = min(num_lanes, max_lanes);
+
+ /* Only notify hpd state for the port that has entered DP mode. */
+ if (typec_dp_bridge->hpd_enabled && typec_dp_bridge->active_port == port) {
if (hpd_asserted)
drm_bridge_hpd_notify(bridge, connector_status_connected);
else
@@ -278,6 +312,81 @@ bridge_to_cros_typec_dp_bridge(struct drm_bridge *bridge)
return container_of(bridge, struct cros_typec_dp_bridge, bridge);
}
+static int dp_lane_to_typec_lane(unsigned int dp_lane)
+{
+ switch (dp_lane) {
+ case 0:
+ return 2;
+ case 1:
+ return 3;
+ case 2:
+ return 1;
+ case 3:
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int typec_to_dp_lane(unsigned int typec_lane)
+{
+ switch (typec_lane) {
+ case 0:
+ return 3;
+ case 1:
+ return 2;
+ case 2:
+ return 0;
+ case 3:
+ return 1;
+ }
+
+ return -EINVAL;
+}
+
+static int cros_typec_dp_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 cros_typec_dp_bridge *typec_dp_bridge;
+ struct drm_lane_cfg *in_lanes;
+ size_t num_lanes;
+ struct cros_typec_port *port;
+ int i, typec_lane;
+
+ typec_dp_bridge = bridge_to_cros_typec_dp_bridge(bridge);
+ if (!typec_dp_bridge->active_port)
+ return -ENODEV;
+
+ port = typec_dp_bridge->active_port;
+
+ num_lanes = port->num_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++) {
+ /* Get physical type-c lane for DP lane */
+ typec_lane = dp_lane_to_typec_lane(i);
+ if (typec_lane < 0) {
+ DRM_ERROR("Invalid type-c lane configuration\n");
+ return -EINVAL;
+ }
+
+ /* Map to logical type-c lane */
+ typec_lane = port->lane_mapping[typec_lane];
+
+ /* Map logical type-c lane to logical DP lane */
+ in_lanes[i].logical = typec_to_dp_lane(typec_lane);
+ }
+
+ return 0;
+}
+
static void cros_typec_dp_bridge_hpd_enable(struct drm_bridge *bridge)
{
struct cros_typec_dp_bridge *typec_dp_bridge;
@@ -296,6 +405,10 @@ static void cros_typec_dp_bridge_hpd_disable(struct drm_bridge *bridge)
static const struct drm_bridge_funcs cros_typec_dp_bridge_funcs = {
.attach = cros_typec_dp_bridge_attach,
+ .atomic_check = cros_typec_dp_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,
.hpd_enable = cros_typec_dp_bridge_hpd_enable,
.hpd_disable = cros_typec_dp_bridge_hpd_disable,
};
@@ -305,6 +418,7 @@ static int cros_typec_register_dp_bridge(struct cros_typec_switch_data *sdata,
{
struct cros_typec_dp_bridge *typec_dp_bridge;
struct drm_bridge *bridge;
+ int num_lanes;
struct device *dev = sdata->dev;
typec_dp_bridge = devm_kzalloc(dev, sizeof(*typec_dp_bridge), GFP_KERNEL);
@@ -313,6 +427,12 @@ static int cros_typec_register_dp_bridge(struct cros_typec_switch_data *sdata,
typec_dp_bridge->sdata = sdata;
sdata->typec_dp_bridge = typec_dp_bridge;
+
+ num_lanes = fwnode_property_count_u32(fwnode, "data-lanes");
+ if (num_lanes < 0)
+ num_lanes = 4;
+ typec_dp_bridge->max_lanes = num_lanes;
+
bridge = &typec_dp_bridge->bridge;
bridge->funcs = &cros_typec_dp_bridge_funcs;
@@ -333,6 +453,7 @@ static int cros_typec_register_port(struct cros_typec_switch_data *sdata,
struct fwnode_handle *port_node;
u32 index;
int ret;
+ const u32 default_lane_mapping[] = { 0, 1, 2, 3 };
const char *prop_name;
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
@@ -359,6 +480,11 @@ static int cros_typec_register_port(struct cros_typec_switch_data *sdata,
port->port_num = index;
sdata->ports[index] = port;
+ if (fwnode_property_read_u32_array(fwnode, "data-lanes",
+ port->lane_mapping,
+ ARRAY_SIZE(port->lane_mapping)))
+ memcpy(port->lane_mapping, default_lane_mapping, sizeof(default_lane_mapping));
+
port_node = fwnode;
if (np)
fwnode = fwnode_graph_get_port_parent(fwnode);
Implement DP lane assignment in the drm_bridge atomic_check() callback so that DP altmode configurations like pinconf D can be supported and DP muxing can be implemented. In the DP altmode spec, pinconf C assigns all 4 SuperSpeed lanes in the usb-c-connector node to DP, while pinconf D assigns 2 SuperSpeed lanes to DP and 2 SuperSpeed lanes to USB. Use the 'data-lanes' property from the input DP graph port to calculate the maximum number of lanes coming from the DP source (either 2 or 4) and limit the lanes requested to the smaller of this or the pin configuration. Once we know the maximum number of lanes that can be assigned, map the DP lanes to the active type-c output port with the assigned pin configuration. Use the 'data-lanes' property from the active type-c output port to determine which logical DP lanes should be assigned to the output of the drm_bridge. For now assume the type-c pins are in the normal orientation. This design supports different DP altmode pin configurations and also allows us to effectively mux one DP phy to two different USB type-c connectors by wiring the physical DP lanes to one or the other USB type-c connectors in the hardware. For example, DP ML0/ML1 are hardwired to one USB type-c connector and DP ML2/ML3 are hardwired to the other connector. The 'data-lanes' of the first USB type-c port would be the default "<0 1 2 3>" while the 'data-lanes' of the second USB type-c port would be "<2 3 0 1>". Depending on the active USB type-c port, map the logical DP lane to the logical type-c lane, and then find the physical type-c lane corresponding to that logical lane from the type-c port's 'data-lanes' property. Once we have that, map the physical type-c lane to the physical DP lane and request that physical DP lane as the logical DP lane through the DRM lane assignment logic on the input of the drm_bridge. Cc: Prashant Malani <pmalani@chromium.org> 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> Signed-off-by: Stephen Boyd <swboyd@chromium.org> --- drivers/platform/chrome/cros_typec_switch.c | 136 +++++++++++++++++++- 1 file changed, 131 insertions(+), 5 deletions(-)