@@ -23,6 +23,7 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
+#include <linux/usb/typec.h>
/* Exynos USB PHY registers */
#define EXYNOS5_FSEL_9MHZ6 0x0
@@ -209,6 +210,10 @@
#define EXYNOS9_PMA_USBDP_CMN_REG00B8 0x02e0
#define CMN_REG00B8_LANE_MUX_SEL_DP GENMASK(3, 0)
+#define CMN_REG00B8_LANE_MUX_SEL_DP_LANE3 BIT(3)
+#define CMN_REG00B8_LANE_MUX_SEL_DP_LANE2 BIT(2)
+#define CMN_REG00B8_LANE_MUX_SEL_DP_LANE1 BIT(1)
+#define CMN_REG00B8_LANE_MUX_SEL_DP_LANE0 BIT(0)
#define EXYNOS9_PMA_USBDP_CMN_REG01C0 0x0700
#define CMN_REG01C0_ANA_LCPLL_LOCK_DONE BIT(7)
@@ -383,11 +388,13 @@ struct exynos5_usbdrd_phy_drvdata {
* @clks: clocks for register access
* @core_clks: core clocks for phy (ref, pipe3, utmi+, ITP, etc. as required)
* @drv_data: pointer to SoC level driver data structure
+ * @phy_mutex: mutex protecting phy_init/exit & TCPC callbacks
* @phys: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY
* instances each with its 'phy' and 'phy_cfg'.
* @extrefclk: frequency select settings when using 'separate
* reference clocks' for SS and HS operations
* @regulators: regulators for phy
+ * @orientation: TypeC connector orientation - normal or flipped
*/
struct exynos5_usbdrd_phy {
struct device *dev;
@@ -397,6 +404,7 @@ struct exynos5_usbdrd_phy {
struct clk_bulk_data *clks;
struct clk_bulk_data *core_clks;
const struct exynos5_usbdrd_phy_drvdata *drv_data;
+ struct mutex phy_mutex;
struct phy_usb_instance {
struct phy *phy;
u32 index;
@@ -406,6 +414,8 @@ struct exynos5_usbdrd_phy {
} phys[EXYNOS5_DRDPHYS_NUM];
u32 extrefclk;
struct regulator_bulk_data *regulators;
+
+ enum typec_orientation orientation;
};
static inline
@@ -647,22 +657,38 @@ exynos5_usbdrd_usbdp_g2_v4_pma_lane_mux_sel(struct exynos5_usbdrd_phy *phy_drd)
/* lane configuration: USB on all lanes */
reg = readl(regs_base + EXYNOS9_PMA_USBDP_CMN_REG00B8);
reg &= ~CMN_REG00B8_LANE_MUX_SEL_DP;
- writel(reg, regs_base + EXYNOS9_PMA_USBDP_CMN_REG00B8);
-
/*
- * FIXME: below code supports one connector orientation only. It needs
- * updating once we can receive connector events.
+ * USB on lanes 0 & 1 in normal mode, or 2 & 3 if reversed, DP on the
+ * other ones.
*/
+ reg |= FIELD_PREP(CMN_REG00B8_LANE_MUX_SEL_DP,
+ ((phy_drd->orientation == TYPEC_ORIENTATION_NORMAL)
+ ? (CMN_REG00B8_LANE_MUX_SEL_DP_LANE3
+ | CMN_REG00B8_LANE_MUX_SEL_DP_LANE2)
+ : (CMN_REG00B8_LANE_MUX_SEL_DP_LANE1
+ | CMN_REG00B8_LANE_MUX_SEL_DP_LANE0)));
+ writel(reg, regs_base + EXYNOS9_PMA_USBDP_CMN_REG00B8);
+
/* override of TX receiver detector and comparator: lane 1 */
reg = readl(regs_base + EXYNOS9_PMA_USBDP_TRSV_REG0413);
- reg &= ~TRSV_REG0413_OVRD_LN1_TX_RXD_COMP_EN;
- reg &= ~TRSV_REG0413_OVRD_LN1_TX_RXD_EN;
+ if (phy_drd->orientation == TYPEC_ORIENTATION_NORMAL) {
+ reg &= ~TRSV_REG0413_OVRD_LN1_TX_RXD_COMP_EN;
+ reg &= ~TRSV_REG0413_OVRD_LN1_TX_RXD_EN;
+ } else {
+ reg |= TRSV_REG0413_OVRD_LN1_TX_RXD_COMP_EN;
+ reg |= TRSV_REG0413_OVRD_LN1_TX_RXD_EN;
+ }
writel(reg, regs_base + EXYNOS9_PMA_USBDP_TRSV_REG0413);
/* lane 3 */
reg = readl(regs_base + EXYNOS9_PMA_USBDP_TRSV_REG0813);
- reg |= TRSV_REG0813_OVRD_LN3_TX_RXD_COMP_EN;
- reg |= TRSV_REG0813_OVRD_LN3_TX_RXD_EN;
+ if (phy_drd->orientation == TYPEC_ORIENTATION_NORMAL) {
+ reg |= TRSV_REG0813_OVRD_LN3_TX_RXD_COMP_EN;
+ reg |= TRSV_REG0813_OVRD_LN3_TX_RXD_EN;
+ } else {
+ reg &= ~TRSV_REG0813_OVRD_LN3_TX_RXD_COMP_EN;
+ reg &= ~TRSV_REG0813_OVRD_LN3_TX_RXD_EN;
+ }
writel(reg, regs_base + EXYNOS9_PMA_USBDP_TRSV_REG0813);
}
@@ -700,21 +726,18 @@ exynos5_usbdrd_usbdp_g2_v4_pma_check_cdr_lock(struct exynos5_usbdrd_phy *phy_drd
int err;
err = readl_poll_timeout(
- phy_drd->reg_pma + EXYNOS9_PMA_USBDP_TRSV_REG03C3,
- reg, (reg & locked) == locked, sleep_us, timeout_us);
- if (!err)
- return;
-
- dev_err(phy_drd->dev,
- "timed out waiting for CDR lock (l0): %#.8x, retrying\n", reg);
-
- /* based on cable orientation, this might be on the other phy port */
- err = readl_poll_timeout(
- phy_drd->reg_pma + EXYNOS9_PMA_USBDP_TRSV_REG07C3,
+ /* lane depends on cable orientation */
+ (phy_drd->reg_pma
+ + ((phy_drd->orientation == TYPEC_ORIENTATION_NORMAL)
+ ? EXYNOS9_PMA_USBDP_TRSV_REG03C3
+ : EXYNOS9_PMA_USBDP_TRSV_REG07C3)),
reg, (reg & locked) == locked, sleep_us, timeout_us);
if (err)
dev_err(phy_drd->dev,
- "timed out waiting for CDR lock (l2): %#.8x\n", reg);
+ "timed out waiting for CDR(l%d) lock: %#.8x\n",
+ ((phy_drd->orientation == TYPEC_ORIENTATION_NORMAL)
+ ? 0
+ : 2), reg);
}
static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd)
@@ -1184,7 +1207,8 @@ static int exynos850_usbdrd_phy_init(struct phy *phy)
return ret;
/* UTMI or PIPE3 specific init */
- inst->phy_cfg->phy_init(phy_drd);
+ scoped_guard(mutex, &phy_drd->phy_mutex)
+ inst->phy_cfg->phy_init(phy_drd);
clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks);
@@ -1203,6 +1227,8 @@ static int exynos850_usbdrd_phy_exit(struct phy *phy)
if (ret)
return ret;
+ guard(mutex)(&phy_drd->phy_mutex);
+
/* Set PHY clock and control HS PHY */
reg = readl(regs_base + EXYNOS850_DRD_UTMI);
reg &= ~(UTMI_DP_PULLDOWN | UTMI_DM_PULLDOWN);
@@ -1701,6 +1727,10 @@ static int exynos5_usbdrd_phy_probe(struct platform_device *pdev)
return -EINVAL;
phy_drd->drv_data = drv_data;
+ ret = devm_mutex_init(dev, &phy_drd->phy_mutex);
+ if (ret)
+ return ret;
+
if (of_property_present(dev->of_node, "reg-names")) {
void __iomem *reg;