#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
{ 0x22, 0xff, 0xff, 0xff }
};
+struct qmp_combo_lane_mapping {
+ unsigned int lanes_count;
+ enum typec_orientation orientation;
+ u32 lanes[4];
+};
+
+static const struct qmp_combo_lane_mapping usb3_data_lanes[] = {
+ { 2, TYPEC_ORIENTATION_NORMAL, { 1, 0 }},
+ { 2, TYPEC_ORIENTATION_REVERSE, { 2, 3 }},
+};
+
+static const struct qmp_combo_lane_mapping dp_data_lanes[] = {
+ { 1, TYPEC_ORIENTATION_NORMAL, { 3 }},
+ { 1, TYPEC_ORIENTATION_REVERSE, { 0 }},
+ { 2, TYPEC_ORIENTATION_NORMAL, { 3, 2 }},
+ { 2, TYPEC_ORIENTATION_REVERSE, { 0, 1 }},
+ { 4, TYPEC_ORIENTATION_NORMAL, { 3, 2, 1, 0 }},
+ { 4, TYPEC_ORIENTATION_REVERSE, { 0, 1, 2, 3 }},
+};
+
struct qmp_combo;
struct qmp_combo_offsets {
return ERR_PTR(-EINVAL);
}
+static void qmp_combo_find_lanes_orientation(const struct qmp_combo_lane_mapping *mapping,
+ unsigned int mapping_count,
+ u32 *lanes, unsigned int lanes_count,
+ enum typec_orientation *orientation)
+{
+ int i;
+
+ for (i = 0; i < mapping_count; i++) {
+ if (mapping[i].lanes_count != lanes_count)
+ continue;
+ if (!memcmp(mapping[i].lanes, lanes, sizeof(u32) * lanes_count)) {
+ *orientation = mapping[i].orientation;
+ return;
+ }
+ }
+}
+
+static int qmp_combo_get_dt_lanes_mapping(struct device *dev, unsigned int endpoint,
+ u32 *data_lanes, unsigned int max,
+ unsigned int *count)
+{
+ struct device_node *ep __free(device_node) = NULL;
+ int ret;
+
+ ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, endpoint);
+ if (!ep)
+ return -EINVAL;
+
+ ret = of_property_count_u32_elems(ep, "data-lanes");
+ if (ret < 0)
+ return ret;
+
+ *count = ret;
+ if (*count > max)
+ return -EINVAL;
+
+ return of_property_read_u32_array(ep, "data-lanes", data_lanes,
+ min_t(unsigned int, *count, max));
+}
+
+static int qmp_combo_get_dt_dp_orientation(struct device *dev,
+ enum typec_orientation *orientation)
+{
+ unsigned int count;
+ u32 data_lanes[4];
+ int ret;
+
+ /* DP is described on the first endpoint of the first port */
+ ret = qmp_combo_get_dt_lanes_mapping(dev, 0, data_lanes, 4, &count);
+ if (ret < 0)
+ return ret == -EINVAL ? 0 : ret;
+
+ /* Search for a match and only update orientation if found */
+ qmp_combo_find_lanes_orientation(dp_data_lanes, ARRAY_SIZE(dp_data_lanes),
+ data_lanes, count, orientation);
+
+ return 0;
+}
+
+static int qmp_combo_get_dt_usb3_orientation(struct device *dev,
+ enum typec_orientation *orientation)
+{
+ unsigned int count;
+ u32 data_lanes[2];
+ int ret;
+
+ /* USB3 is described on the second endpoint of the first port */
+ ret = qmp_combo_get_dt_lanes_mapping(dev, 1, data_lanes, 2, &count);
+ if (ret < 0)
+ return ret == -EINVAL ? 0 : ret;
+
+ /* Search for a match and only update orientation if found */
+ qmp_combo_find_lanes_orientation(usb3_data_lanes, ARRAY_SIZE(usb3_data_lanes),
+ data_lanes, count, orientation);
+
+ return 0;
+}
+
static int qmp_combo_probe(struct platform_device *pdev)
{
struct qmp_combo *qmp;
if (ret)
goto err_node_put;
- ret = qmp_combo_typec_register(qmp);
- if (ret)
- goto err_node_put;
+ qmp->qmpphy_mode = QMPPHY_MODE_USB3DP;
+
+ if (of_property_present(dev->of_node, "mode-switch") ||
+ of_property_present(dev->of_node, "orientation-switch")) {
+ ret = qmp_combo_typec_register(qmp);
+ if (ret)
+ goto err_node_put;
+ } else {
+ enum typec_orientation dp_orientation = TYPEC_ORIENTATION_NONE;
+ enum typec_orientation usb3_orientation = TYPEC_ORIENTATION_NONE;
+
+ ret = qmp_combo_get_dt_dp_orientation(dev, &dp_orientation);
+ if (ret)
+ goto err_node_put;
+
+ ret = qmp_combo_get_dt_usb3_orientation(dev, &usb3_orientation);
+ if (ret)
+ goto err_node_put;
+
+ if (dp_orientation == TYPEC_ORIENTATION_NONE &&
+ usb3_orientation != TYPEC_ORIENTATION_NONE) {
+ qmp->qmpphy_mode = QMPPHY_MODE_USB3_ONLY;
+ qmp->orientation = usb3_orientation;
+ } else if (usb3_orientation == TYPEC_ORIENTATION_NONE &&
+ dp_orientation != TYPEC_ORIENTATION_NONE) {
+ qmp->qmpphy_mode = QMPPHY_MODE_DP_ONLY;
+ qmp->orientation = dp_orientation;
+ } else if (dp_orientation != TYPEC_ORIENTATION_NONE &&
+ dp_orientation == usb3_orientation) {
+ qmp->qmpphy_mode = QMPPHY_MODE_USB3DP;
+ qmp->orientation = dp_orientation;
+ } else {
+ dev_warn(dev, "unable to determine orientation & mode from data-lanes");
+ }
+ }
ret = drm_aux_bridge_register(dev);
if (ret)
if (ret)
goto err_node_put;
- /*
- * The hw default is USB3_ONLY, but USB3+DP mode lets us more easily
- * check both sub-blocks' init tables for blunders at probe time.
- */
- qmp->qmpphy_mode = QMPPHY_MODE_USB3DP;
qmp->usb_phy = devm_phy_create(dev, usb_np, &qmp_combo_usb_phy_ops);
if (IS_ERR(qmp->usb_phy)) {