aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Baryshkov <dmitry.baryshkov@linaro.org>2021-04-01 19:35:46 +0300
committerDmitry Baryshkov <dmitry.baryshkov@linaro.org>2022-06-07 20:30:00 +0300
commite52639dfe5b60cf853c64b02e0ca7946b2599af2 (patch)
treee822eb81c3a2a590d1415ed0bd559ff99d7b7692
parent51e56eed578c78dbebfe35232057df53558e9574 (diff)
phy: qcom-qmp: Register as a typec switch for orientation detection
The lane select switch for USB typec orientation is within the USB QMP PHY. the current device. It could be connected through an endpoint, to an independent device handling the typec detection, ie the QCOM SPMI typec driver. Co-developed-by-by: Wesley Cheng <wcheng@codeaurora.org> Signed-off-by: Wesley Cheng <wcheng@codeaurora.org> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
-rw-r--r--drivers/phy/qualcomm/Kconfig8
-rw-r--r--drivers/phy/qualcomm/phy-qcom-qmp.c138
2 files changed, 110 insertions, 36 deletions
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 5c98850f5a36..fbc0f80852d1 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -58,6 +58,14 @@ config PHY_QCOM_QMP
Enable this to support the QMP PHY transceiver that is used
with controllers such as PCIe, UFS, and USB on Qualcomm chips.
+config PHY_QCOM_QMP_TYPEC
+ def_bool PHY_QCOM_QMP=y && TYPEC=y || PHY_QCOM_QMP=m && TYPEC
+ help
+ Register a type C switch from the QMP PHY driver for type C
+ orientation support. This has dependencies with if the type C kernel
+ configuration is enabled or not. This support will not be present if
+ USB type C is disabled.
+
config PHY_QCOM_QUSB2
tristate "Qualcomm QUSB2 PHY Driver"
depends on OF && (ARCH_QCOM || COMPILE_TEST)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c
index c7309e981bfb..04af11be9caa 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp.c
@@ -19,6 +19,7 @@
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/slab.h>
+#include <linux/usb/typec_mux.h>
#include <dt-bindings/phy/phy.h>
@@ -67,6 +68,10 @@
/* QPHY_V3_PCS_MISC_CLAMP_ENABLE register bits */
#define CLAMP_EN BIT(0) /* enables i/o clamp_n */
+/* QPHY_V3_DP_COM_TYPEC_CTRL register bits */
+#define SW_PORTSELECT_VAL BIT(0)
+#define SW_PORTSELECT_MUX BIT(1)
+
#define PHY_INIT_COMPLETE_TIMEOUT 10000
#define POWER_DOWN_DELAY_US_MIN 10
#define POWER_DOWN_DELAY_US_MAX 11
@@ -3270,6 +3275,8 @@ struct qmp_phy_dp_clks {
* @phy_mutex: mutex lock for PHY common block initialization
* @init_count: phy common block initialization count
* @ufs_reset: optional UFS PHY reset handle
+ * @sw: typec switch for receiving orientation changes
+ * @orientation: carries current CC orientation
*/
struct qcom_qmp {
struct device *dev;
@@ -3285,6 +3292,8 @@ struct qcom_qmp {
int init_count;
struct reset_control *ufs_reset;
+ struct typec_switch_dev *sw;
+ enum typec_orientation orientation;
};
static void qcom_qmp_v3_phy_dp_aux_init(struct qmp_phy *qphy);
@@ -4723,30 +4732,26 @@ static void qcom_qmp_v3_phy_configure_dp_tx(struct qmp_phy *qphy)
static bool qcom_qmp_phy_configure_dp_mode(struct qmp_phy *qphy)
{
+ const struct phy_configure_opts_dp *dp_opts = &qphy->dp_opts;
+ struct qcom_qmp *qmp = qphy->qmp;
u32 val;
- bool reverse = false;
+ bool reverse = qmp->orientation == TYPEC_ORIENTATION_REVERSE;
val = DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN |
DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN;
- /*
- * TODO: Assume orientation is CC1 for now and two lanes, need to
- * use type-c connector to understand orientation and lanes.
- *
- * Otherwise val changes to be like below if this code understood
- * the orientation of the type-c cable.
- *
- * if (lane_cnt == 4 || orientation == ORIENTATION_CC2)
- * val |= DP_PHY_PD_CTL_LANE_0_1_PWRDN;
- * if (lane_cnt == 4 || orientation == ORIENTATION_CC1)
- * val |= DP_PHY_PD_CTL_LANE_2_3_PWRDN;
- * if (orientation == ORIENTATION_CC2)
- * writel(0x4c, qphy->pcs + QSERDES_V3_DP_PHY_MODE);
- */
+ if (dp_opts->lanes == 4 || reverse)
+ val |= DP_PHY_PD_CTL_LANE_0_1_PWRDN;
+ if (dp_opts->lanes == 4 || !reverse)
+ val |= DP_PHY_PD_CTL_LANE_2_3_PWRDN;
+
val |= DP_PHY_PD_CTL_LANE_2_3_PWRDN;
writel(val, qphy->pcs + QSERDES_DP_PHY_PD_CTL);
- writel(0x5c, qphy->pcs + QSERDES_DP_PHY_MODE);
+ if (reverse)
+ writel(0x4c, qphy->pcs + QSERDES_DP_PHY_MODE);
+ else
+ writel(0x5c, qphy->pcs + QSERDES_DP_PHY_MODE);
return reverse;
}
@@ -5066,6 +5071,30 @@ static int qcom_qmp_dp_phy_calibrate(struct phy *phy)
return 0;
}
+static void qcom_qmp_phy_dp_com_reset(struct qcom_qmp *qmp, unsigned int reset_bits)
+{
+ void __iomem *dp_com = qmp->dp_com;
+ unsigned int val;
+
+ /* override hardware control for reset of qmp phy */
+ qphy_setbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL,
+ reset_bits);
+
+ val = SW_PORTSELECT_MUX;
+ if (qmp->orientation == TYPEC_ORIENTATION_REVERSE)
+ val |= SW_PORTSELECT_VAL;
+ qphy_setbits(dp_com, QPHY_V3_DP_COM_TYPEC_CTRL, val);
+
+ qphy_setbits(dp_com, QPHY_V3_DP_COM_PHY_MODE_CTRL,
+ USB3_MODE | DP_MODE);
+
+ qphy_clrbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL,
+ reset_bits);
+
+ qphy_clrbits(dp_com, QPHY_V3_DP_COM_SWI_CTRL, 0x03);
+ qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET);
+}
+
static int qcom_qmp_phy_com_init(struct qmp_phy *qphy)
{
struct qcom_qmp *qmp = qphy->qmp;
@@ -5113,24 +5142,9 @@ static int qcom_qmp_phy_com_init(struct qmp_phy *qphy)
if (cfg->has_phy_dp_com_ctrl) {
qphy_setbits(dp_com, QPHY_V3_DP_COM_POWER_DOWN_CTRL,
SW_PWRDN);
- /* override hardware control for reset of qmp phy */
- qphy_setbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL,
- SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
- SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET);
-
- /* Default type-c orientation, i.e CC1 */
- qphy_setbits(dp_com, QPHY_V3_DP_COM_TYPEC_CTRL, 0x02);
-
- qphy_setbits(dp_com, QPHY_V3_DP_COM_PHY_MODE_CTRL,
- USB3_MODE | DP_MODE);
-
- /* bring both QMP USB and QMP DP PHYs PCS block out of reset */
- qphy_clrbits(dp_com, QPHY_V3_DP_COM_RESET_OVRD_CTRL,
- SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
- SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET);
-
- qphy_clrbits(dp_com, QPHY_V3_DP_COM_SWI_CTRL, 0x03);
- qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET);
+ qcom_qmp_phy_dp_com_reset(qmp,
+ SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
+ SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET);
}
if (cfg->has_phy_com_ctrl) {
@@ -6181,6 +6195,47 @@ static const struct dev_pm_ops qcom_qmp_phy_pm_ops = {
qcom_qmp_phy_runtime_resume, NULL)
};
+#if IS_ENABLED(CONFIG_PHY_QCOM_QMP_TYPEC)
+static int qcom_qmp_phy_typec_switch_set(struct typec_switch_dev *sw,
+ enum typec_orientation orientation)
+{
+ struct qcom_qmp *qmp = typec_switch_get_drvdata(sw);
+
+ qmp->orientation = orientation;
+ if (qmp->init_count) {
+ // FIXME: is this necessary?
+ // FIXME: reset DP part ?
+ // SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
+ qcom_qmp_phy_dp_com_reset(qmp,
+ SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET);
+ }
+
+ return 0;
+}
+
+static int qcom_qmp_phy_typec_switch_register(struct qcom_qmp *qmp, const struct qmp_phy_cfg *cfg)
+{
+ struct typec_switch_desc sw_desc;
+ struct device *dev = qmp->dev;
+
+ sw_desc.drvdata = qmp;
+ sw_desc.fwnode = dev->fwnode;
+ sw_desc.set = qcom_qmp_phy_typec_switch_set;
+ qmp->sw = typec_switch_register(dev, &sw_desc);
+ if (IS_ERR(qmp->sw)) {
+ dev_err(dev, "Error registering typec switch: %ld\n",
+ PTR_ERR(qmp->sw));
+ }
+
+ return 0;
+}
+#else
+static int qcom_qmp_phy_typec_switch_register(struct qcom_qmp *qmp, const struct qmp_phy_cfg *cfg)
+{
+ return 0;
+}
+#endif
+
static int qcom_qmp_phy_probe(struct platform_device *pdev)
{
struct qcom_qmp *qmp;
@@ -6263,7 +6318,12 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
return ret;
}
- num = of_get_available_child_count(dev->of_node);
+ /* cound child nodes ingoring connection graph ports */
+ num = 0;
+ for_each_available_child_of_node(dev->of_node, child)
+ if (strncmp("port", child->name, 4))
+ num++;
+
/* do we have a rogue child node ? */
if (num > expected_phys)
return -EINVAL;
@@ -6290,7 +6350,10 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
serdes = usb_serdes;
}
- /* Create per-lane phy */
+ /* Ignore conngraph nodes */
+ if (!strncmp("port", child->name, 4))
+ continue;
+
ret = qcom_qmp_phy_create(dev, child, id, serdes, cfg);
if (ret) {
dev_err(dev, "failed to create lane%d phy, %d\n",
@@ -6320,6 +6383,9 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev)
id++;
}
+ if (cfg->has_phy_dp_com_ctrl)
+ qcom_qmp_phy_typec_switch_register(qmp, cfg);
+
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (!IS_ERR(phy_provider))
dev_info(dev, "Registered Qcom-QMP phy\n");