usb: phy: qmp: Add QMP USB DP combo PHY related functionality
This change adds QMP USB DP combo PHY related initialization and
reset sequence using required set of register set. Global reset is
not required to perform when there is no port select change with
QMP USB DP combo PHY.
vls_clamp_reg related offset and phy_phy_reset are not needed with
QMP USB DP combo PHY. Hence fail probe only if these properties are
not available for USB only PHY.
Change-Id: Ib56df85fd0210ba6ad24a5865330fc43c957b284
Signed-off-by: Mayank Rana <mrana@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/usb/msm-phy.txt b/Documentation/devicetree/bindings/usb/msm-phy.txt
index 8e5782a..e508a4f 100644
--- a/Documentation/devicetree/bindings/usb/msm-phy.txt
+++ b/Documentation/devicetree/bindings/usb/msm-phy.txt
@@ -4,12 +4,12 @@
Required properties:
- compatible: Should be "qcom,usb-ssphy-qmp", "qcom,usb-ssphy-qmp-v1" or
- "qcom,usb-ssphy-qmp-v2"
+ "qcom,usb-ssphy-qmp-v2" or "qcom,usb-ssphy-qmp-dp-combo"
- reg: Address and length of the register set for the device
Required regs are:
"qmp_phy_base" : QMP PHY Base register set.
- "vls_clamp_reg" : top-level CSR register to be written to enable phy vls
- clamp which allows phy to detect autonomous mode.
+ clamp which allows phy to detect autonomous mode. (optional for USB DP PHY)
- <supply-name>-supply: phandle to the regulator device tree node
Required "supply-name" examples are:
"vdd" : vdd supply for SSPHY digital circuit operation
@@ -24,13 +24,28 @@
- qcom,qmp-phy-init-seq: QMP PHY initialization sequence with reg offset, its
value, delay after register write. It is not must property to have for emulation.
- qcom,qmp-phy-reg-offset: Provides important phy register offsets in an order
- defined in the phy driver. Provide below mentioned register offsets in order:
+ defined in the phy driver.
+ Provide below mentioned register offsets in order for non USB DP combo PHY:
USB3_PHY_PCS_STATUS,
USB3_PHY_AUTONOMOUS_MODE_CTRL,
USB3_PHY_LFPS_RXTERM_IRQ_CLEAR,
USB3_PHY_POWER_DOWN_CONTROL,
USB3_PHY_SW_RESET,
USB3_PHY_START
+
+ In addion to above following set of registers offset needed for USB DP combo PHY in mentioned order:
+ USB3_DP_DP_PHY_PD_CTL,
+ USB3_DP_COM_POWER_DOWN_CTRL,
+ USB3_DP_COM_SW_RESET,
+ USB3_DP_COM_RESET_OVRD_CTRL,
+ USB3_DP_COM_PHY_MODE_CTRL,
+ USB3_DP_COM_TYPEC_CTRL,
+ USB3_DP_COM_SWI_CTRL,
+ USB3_PCS_MISC_CLAMP_ENABLE
+
+ Optional register for configuring USB Type-C port select if available:
+ USB3_PHY_PCS_MISC_TYPEC_CTRL
+
- resets: reset specifier pair consists of phandle for the reset controller
and reset lines used by this controller.
- reset-names: reset signal name strings sorted in the same order as the resets
diff --git a/drivers/usb/phy/phy-msm-ssusb-qmp.c b/drivers/usb/phy/phy-msm-ssusb-qmp.c
index ee521a0..9ef34c3 100644
--- a/drivers/usb/phy/phy-msm-ssusb-qmp.c
+++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c
@@ -46,13 +46,39 @@
#define ALFPS_DTCT_EN BIT(1)
#define ARCVR_DTCT_EVENT_SEL BIT(4)
-/* PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL bits */
+/*
+ * register bits
+ * PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL - for QMP USB PHY
+ * USB3_DP_COM_PHY_MODE_CTRL - for QMP USB DP Combo PHY
+ */
/* 0 - selects Lane A. 1 - selects Lane B */
#define SW_PORTSELECT BIT(0)
/* port select mux: 1 - sw control. 0 - HW control*/
#define SW_PORTSELECT_MX BIT(1)
+/* USB3_DP_PHY_USB3_DP_COM_SWI_CTRL bits */
+
+/* LANE related register read/write with USB3 */
+#define USB3_SWI_ACT_ACCESS_EN BIT(0)
+/* LANE related register read/write with DP */
+#define DP_SWI_ACT_ACCESS_EN BIT(1)
+
+/* USB3_DP_COM_RESET_OVRD_CTRL bits */
+
+/* DP PHY soft reset */
+#define SW_DPPHY_RESET BIT(0)
+/* mux to select DP PHY reset control, 0:HW control, 1: software reset */
+#define SW_DPPHY_RESET_MUX BIT(1)
+/* USB3 PHY soft reset */
+#define SW_USB3PHY_RESET BIT(2)
+/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */
+#define SW_USB3PHY_RESET_MUX BIT(3)
+
+/* USB3_DP_COM_PHY_MODE_CTRL bits */
+#define USB3_MODE BIT(0) /* enables USB3 mode */
+#define DP_MODE BIT(1) /* enables DP mode */
+
enum qmp_phy_rev_reg {
USB3_PHY_PCS_STATUS,
USB3_PHY_AUTONOMOUS_MODE_CTRL,
@@ -60,6 +86,17 @@
USB3_PHY_POWER_DOWN_CONTROL,
USB3_PHY_SW_RESET,
USB3_PHY_START,
+
+ /* USB DP Combo PHY related */
+ USB3_DP_DP_PHY_PD_CTL,
+ USB3_DP_COM_POWER_DOWN_CTRL,
+ USB3_DP_COM_SW_RESET,
+ USB3_DP_COM_RESET_OVRD_CTRL,
+ USB3_DP_COM_PHY_MODE_CTRL,
+ USB3_DP_COM_TYPEC_CTRL,
+ USB3_DP_COM_SWI_CTRL,
+ USB3_PCS_MISC_CLAMP_ENABLE,
+ /* TypeC port select configuration (optional) */
USB3_PHY_PCS_MISC_TYPEC_CTRL,
USB3_PHY_REG_MAX,
};
@@ -99,6 +136,8 @@
int init_seq_len;
unsigned int *qmp_phy_reg_offset;
int reg_offset_cnt;
+
+ int port_select;
};
static const struct of_device_id msm_usb_id_table[] = {
@@ -111,6 +150,9 @@
{
.compatible = "qcom,usb-ssphy-qmp-v2",
},
+ {
+ .compatible = "qcom,usb-ssphy-qmp-dp-combo",
+ },
{ },
};
MODULE_DEVICE_TABLE(of, msm_usb_id_table);
@@ -132,6 +174,21 @@
phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]);
}
+static void msm_ssusb_qmp_clamp_enable(struct msm_ssphy_qmp *phy, bool val)
+{
+ switch (phy->phy.type) {
+ case USB_PHY_TYPE_USB3_DP:
+ writel_relaxed(!val, phy->base +
+ phy->phy_reg[USB3_PCS_MISC_CLAMP_ENABLE]);
+ break;
+ case USB_PHY_TYPE_USB3:
+ writel_relaxed(!!val, phy->vls_clamp_reg);
+ break;
+ default:
+ break;
+ }
+}
+
static void msm_ssusb_qmp_enable_autonomous(struct msm_ssphy_qmp *phy,
int enable)
{
@@ -152,11 +209,9 @@
val &= ~ARCVR_DTCT_EVENT_SEL;
writeb_relaxed(val, phy->base + autonomous_mode_offset);
}
-
- /* clamp phy level shifter to perform autonomous detection */
- writel_relaxed(0x1, phy->vls_clamp_reg);
+ msm_ssusb_qmp_clamp_enable(phy, true);
} else {
- writel_relaxed(0x0, phy->vls_clamp_reg);
+ msm_ssusb_qmp_clamp_enable(phy, false);
writeb_relaxed(0, phy->base + autonomous_mode_offset);
msm_ssusb_qmp_clr_lfps_rxterm_int(phy);
}
@@ -273,12 +328,100 @@
return 0;
}
+static void usb_qmp_update_portselect_phymode(struct msm_ssphy_qmp *phy)
+{
+ int val;
+
+ /* perform lane selection */
+ val = -EINVAL;
+ if (phy->phy.flags & PHY_LANE_A) {
+ val = SW_PORTSELECT_MX;
+ phy->port_select = PHY_LANE_A;
+ }
+
+ if (phy->phy.flags & PHY_LANE_B) {
+ val = SW_PORTSELECT | SW_PORTSELECT_MX;
+ phy->port_select = PHY_LANE_B;
+ }
+
+ switch (phy->phy.type) {
+ case USB_PHY_TYPE_USB3_DP:
+ /* override hardware control for reset of qmp phy */
+ writel_relaxed(SW_DPPHY_RESET_MUX | SW_DPPHY_RESET |
+ SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET,
+ phy->base + phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]);
+
+ /* update port select */
+ if (val > 0) {
+ dev_err(phy->phy.dev,
+ "USB DP QMP PHY: Update TYPEC CTRL(%d)\n", val);
+ writel_relaxed(val, phy->base +
+ phy->phy_reg[USB3_DP_COM_TYPEC_CTRL]);
+ }
+
+ writel_relaxed(USB3_MODE | DP_MODE,
+ phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]);
+
+ /* activate register access of LANE for both USB3 and DP */
+ writel_relaxed(USB3_SWI_ACT_ACCESS_EN | DP_SWI_ACT_ACCESS_EN,
+ phy->base + phy->phy_reg[USB3_DP_COM_SWI_CTRL]);
+
+ /* bring both QMP USB and QMP DP PHYs PCS block out of reset */
+ writel_relaxed(0x00,
+ phy->base + phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]);
+ break;
+ case USB_PHY_TYPE_USB3:
+ if (val > 0) {
+ dev_err(phy->phy.dev,
+ "USB QMP PHY: Update TYPEC CTRL(%d)\n", val);
+ writel_relaxed(val, phy->base +
+ phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]);
+ }
+ break;
+ default:
+ dev_err(phy->phy.dev, "portselect: Unknown USB QMP PHY type\n");
+ break;
+ }
+
+ /* Make sure above selection and reset sequence is gone through */
+ mb();
+}
+
+static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy)
+{
+ switch (phy->phy.type) {
+ case USB_PHY_TYPE_USB3_DP:
+ /* power up USB3 and DP common logic block */
+ writel_relaxed(0x01,
+ phy->base + phy->phy_reg[USB3_DP_COM_POWER_DOWN_CTRL]);
+
+ /*
+ * Don't write 0x0 to DP_COM_SW_RESET as next operation is to
+ * update phymode and port select which needs DP_COM_SW_RESET
+ * as 0x1.
+ */
+
+ /* intentional fall-through */
+ case USB_PHY_TYPE_USB3:
+ /* power up USB3 PHY */
+ writel_relaxed(0x01,
+ phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
+ break;
+ default:
+ dev_err(phy->phy.dev, "phy_powerup: Unknown USB QMP PHY type\n");
+ break;
+ }
+
+ /* Make sure that above write completed to power up PHY */
+ mb();
+}
+
/* SSPHY Initialization */
static int msm_ssphy_qmp_init(struct usb_phy *uphy)
{
struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp,
phy);
- int ret, val;
+ int ret;
unsigned int init_timeout_usec = INIT_MAX_TIME_USEC;
const struct qmp_reg_val *reg = NULL;
@@ -297,11 +440,11 @@
msm_ssphy_qmp_enable_clks(phy, true);
- writel_relaxed(0x01,
- phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]);
+ /* power up PHY */
+ usb_qmp_powerup_phy(phy);
- /* Make sure that above write completed to get PHY into POWER DOWN */
- mb();
+ /* select appropriate port select and PHY mode if applicable */
+ usb_qmp_update_portselect_phymode(phy);
reg = (struct qmp_reg_val *)phy->qmp_phy_init_seq;
@@ -312,20 +455,15 @@
return ret;
}
- /* perform lane selection */
- val = -EINVAL;
- if (phy->phy.flags & PHY_LANE_A)
- val = SW_PORTSELECT_MX;
+ /* perform software reset of PHY common logic */
+ if (phy->phy.type == USB_PHY_TYPE_USB3_DP)
+ writel_relaxed(0x00,
+ phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]);
- if (phy->phy.flags & PHY_LANE_B)
- val = SW_PORTSELECT | SW_PORTSELECT_MX;
-
- if (val > 0)
- writel_relaxed(val,
- phy->base + phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]);
-
- writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]);
+ /* perform software reset of PCS/Serdes */
writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]);
+ /* start PCS/Serdes to operation mode */
+ writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]);
/* Make sure above write completed to bring PHY out of reset */
mb();
@@ -350,6 +488,38 @@
return 0;
}
+static int msm_ssphy_qmp_dp_combo_reset(struct usb_phy *uphy)
+{
+ struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp,
+ phy);
+ int ret = 0;
+
+ /*
+ * Avoid global reset if there is no change in port select which
+ * shall be useful while changing PHY mode i.e. transition from
+ * 4 LANE/2 LANE.
+ */
+ if ((((phy->phy.flags & PHY_LANE_A) == phy->port_select)) ||
+ ((phy->phy.flags & PHY_LANE_B) == phy->port_select))
+ goto exit;
+
+ dev_dbg(uphy->dev, "Global reset of QMP DP combo phy\n");
+ /* Assert QMP USB DP combo PHY reset */
+ ret = reset_control_assert(phy->phy_reset);
+ if (ret) {
+ dev_err(uphy->dev, "phy_reset assert failed\n");
+ goto exit;
+ }
+
+ /* De-Assert QMP USB DP combo PHY reset */
+ ret = reset_control_deassert(phy->phy_reset);
+ if (ret)
+ dev_err(uphy->dev, "phy_reset deassert failed\n");
+
+exit:
+ return ret;
+}
+
static int msm_ssphy_qmp_reset(struct usb_phy *uphy)
{
struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp,
@@ -623,6 +793,11 @@
if (!phy)
return -ENOMEM;
+ phy->phy.type = USB_PHY_TYPE_USB3;
+ if (of_device_is_compatible(dev->of_node,
+ "qcom,usb-ssphy-qmp-dp-combo"))
+ phy->phy.type = USB_PHY_TYPE_USB3_DP;
+
ret = msm_ssphy_qmp_get_clks(phy, dev);
if (ret)
goto err;
@@ -634,11 +809,14 @@
goto err;
}
- phy->phy_phy_reset = devm_reset_control_get(dev, "phy_phy_reset");
- if (IS_ERR(phy->phy_phy_reset)) {
- ret = PTR_ERR(phy->phy_phy_reset);
- dev_dbg(dev, "failed to get phy_phy_reset\n");
- goto err;
+ if (phy->phy.type == USB_PHY_TYPE_USB3) {
+ phy->phy_phy_reset = devm_reset_control_get(dev,
+ "phy_phy_reset");
+ if (IS_ERR(phy->phy_phy_reset)) {
+ ret = PTR_ERR(phy->phy_phy_reset);
+ dev_dbg(dev, "failed to get phy_phy_reset\n");
+ goto err;
+ }
}
of_get_property(dev->of_node, "qcom,qmp-phy-reg-offset", &size);
@@ -673,22 +851,25 @@
dev_err(dev, "failed getting qmp_phy_base\n");
return -ENODEV;
}
- phy->base = devm_ioremap_resource(dev, res);
- if (IS_ERR(phy->base)) {
+
+ /*
+ * For USB QMP DP combo PHY, common set of registers shall be accessed
+ * by DP driver as well.
+ */
+ phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+ if (IS_ERR_OR_NULL(phy->base)) {
ret = PTR_ERR(phy->base);
goto err;
}
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- "vls_clamp_reg");
- if (!res) {
- dev_err(dev, "failed getting vls_clamp_reg\n");
- return -ENODEV;
- }
- phy->vls_clamp_reg = devm_ioremap_resource(dev, res);
- if (IS_ERR(phy->vls_clamp_reg)) {
- dev_err(dev, "couldn't find vls_clamp_reg address.\n");
- return PTR_ERR(phy->vls_clamp_reg);
+ if (phy->phy.type == USB_PHY_TYPE_USB3) {
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "vls_clamp_reg");
+ phy->vls_clamp_reg = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->vls_clamp_reg)) {
+ dev_err(dev, "couldn't find vls_clamp_reg address.\n");
+ return PTR_ERR(phy->vls_clamp_reg);
+ }
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
@@ -787,8 +968,11 @@
phy->phy.set_suspend = msm_ssphy_qmp_set_suspend;
phy->phy.notify_connect = msm_ssphy_qmp_notify_connect;
phy->phy.notify_disconnect = msm_ssphy_qmp_notify_disconnect;
- phy->phy.reset = msm_ssphy_qmp_reset;
- phy->phy.type = USB_PHY_TYPE_USB3;
+
+ if (phy->phy.type == USB_PHY_TYPE_USB3_DP)
+ phy->phy.reset = msm_ssphy_qmp_dp_combo_reset;
+ else
+ phy->phy.reset = msm_ssphy_qmp_reset;
ret = usb_add_phy_dev(&phy->phy);
diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h
index 263f20a..ffb6393 100644
--- a/include/linux/usb/phy.h
+++ b/include/linux/usb/phy.h
@@ -45,6 +45,7 @@
USB_PHY_TYPE_UNDEFINED,
USB_PHY_TYPE_USB2,
USB_PHY_TYPE_USB3,
+ USB_PHY_TYPE_USB3_DP,
};
/* OTG defines lots of enumeration states before device reset */