Commit 69152507 authored by Sam Protsenko's avatar Sam Protsenko Committed by Vinod Koul
Browse files

phy: exynos5-usbdrd: Add Exynos850 support



Implement Exynos850 USB 2.0 DRD PHY controller support. Exynos850 has
quite a different PHY controller than Exynos5 compatible controllers,
but it's still possible to implement it on top of existing
exynos5-usbdrd driver infrastructure.

Only UTMI+ (USB 2.0) PHY interface is implemented, as Exynos850 doesn't
support USB 3.0.

Only two clocks are used for this controller:
  - phy: bus clock, used for PHY registers access
  - ref: PHY reference clock (OSCCLK)

Signed-off-by: default avatarSam Protsenko <semen.protsenko@linaro.org>
Reviewed-by: default avatarKrzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Link: https://lore.kernel.org/r/20230819031731.22618-7-semen.protsenko@linaro.org


Signed-off-by: default avatarVinod Koul <vkoul@kernel.org>
parent 255ec387
Loading
Loading
Loading
Loading
+169 −0
Original line number Diff line number Diff line
@@ -145,6 +145,34 @@
#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_62M5		(0x20 << 4)
#define LANE0_TX_DEBUG_RXDET_MEAS_TIME_96M_100M		(0x40 << 4)

/* Exynos850: USB DRD PHY registers */
#define EXYNOS850_DRD_LINKCTRL			0x04
#define LINKCTRL_BUS_FILTER_BYPASS(_x)		((_x) << 4)
#define LINKCTRL_FORCE_QACT			BIT(8)

#define EXYNOS850_DRD_CLKRST			0x20
#define CLKRST_LINK_SW_RST			BIT(0)
#define CLKRST_PORT_RST				BIT(1)
#define CLKRST_PHY_SW_RST			BIT(3)

#define EXYNOS850_DRD_UTMI			0x50
#define UTMI_FORCE_SLEEP			BIT(0)
#define UTMI_FORCE_SUSPEND			BIT(1)
#define UTMI_DM_PULLDOWN			BIT(2)
#define UTMI_DP_PULLDOWN			BIT(3)
#define UTMI_FORCE_BVALID			BIT(4)
#define UTMI_FORCE_VBUSVALID			BIT(5)

#define EXYNOS850_DRD_HSP			0x54
#define HSP_COMMONONN				BIT(8)
#define HSP_EN_UTMISUSPEND			BIT(9)
#define HSP_VBUSVLDEXT				BIT(12)
#define HSP_VBUSVLDEXTSEL			BIT(13)
#define HSP_FSV_OUT_EN				BIT(24)

#define EXYNOS850_DRD_HSP_TEST			0x5c
#define HSP_TEST_SIDDQ				BIT(24)

#define KHZ	1000
#define MHZ	(KHZ * KHZ)

@@ -716,6 +744,129 @@ static const struct phy_ops exynos5_usbdrd_phy_ops = {
	.owner		= THIS_MODULE,
};

static void exynos850_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd)
{
	void __iomem *regs_base = phy_drd->reg_phy;
	u32 reg;

	/*
	 * Disable HWACG (hardware auto clock gating control). This will force
	 * QACTIVE signal in Q-Channel interface to HIGH level, to make sure
	 * the PHY clock is not gated by the hardware.
	 */
	reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL);
	reg |= LINKCTRL_FORCE_QACT;
	writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL);

	/* Start PHY Reset (POR=high) */
	reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
	reg |= CLKRST_PHY_SW_RST;
	writel(reg, regs_base + EXYNOS850_DRD_CLKRST);

	/* Enable UTMI+ */
	reg = readl(regs_base + EXYNOS850_DRD_UTMI);
	reg &= ~(UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP | UTMI_DP_PULLDOWN |
		 UTMI_DM_PULLDOWN);
	writel(reg, regs_base + EXYNOS850_DRD_UTMI);

	/* Set PHY clock and control HS PHY */
	reg = readl(regs_base + EXYNOS850_DRD_HSP);
	reg |= HSP_EN_UTMISUSPEND | HSP_COMMONONN;
	writel(reg, regs_base + EXYNOS850_DRD_HSP);

	/* Set VBUS Valid and D+ pull-up control by VBUS pad usage */
	reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL);
	reg |= LINKCTRL_BUS_FILTER_BYPASS(0xf);
	writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL);

	reg = readl(regs_base + EXYNOS850_DRD_UTMI);
	reg |= UTMI_FORCE_BVALID | UTMI_FORCE_VBUSVALID;
	writel(reg, regs_base + EXYNOS850_DRD_UTMI);

	reg = readl(regs_base + EXYNOS850_DRD_HSP);
	reg |= HSP_VBUSVLDEXT | HSP_VBUSVLDEXTSEL;
	writel(reg, regs_base + EXYNOS850_DRD_HSP);

	/* Power up PHY analog blocks */
	reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST);
	reg &= ~HSP_TEST_SIDDQ;
	writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST);

	/* Finish PHY reset (POR=low) */
	udelay(10); /* required before doing POR=low */
	reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
	reg &= ~(CLKRST_PHY_SW_RST | CLKRST_PORT_RST);
	writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
	udelay(75); /* required after POR=low for guaranteed PHY clock */

	/* Disable single ended signal out */
	reg = readl(regs_base + EXYNOS850_DRD_HSP);
	reg &= ~HSP_FSV_OUT_EN;
	writel(reg, regs_base + EXYNOS850_DRD_HSP);
}

static int exynos850_usbdrd_phy_init(struct phy *phy)
{
	struct phy_usb_instance *inst = phy_get_drvdata(phy);
	struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
	int ret;

	ret = clk_prepare_enable(phy_drd->clk);
	if (ret)
		return ret;

	/* UTMI or PIPE3 specific init */
	inst->phy_cfg->phy_init(phy_drd);

	clk_disable_unprepare(phy_drd->clk);

	return 0;
}

static int exynos850_usbdrd_phy_exit(struct phy *phy)
{
	struct phy_usb_instance *inst = phy_get_drvdata(phy);
	struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst);
	void __iomem *regs_base = phy_drd->reg_phy;
	u32 reg;
	int ret;

	ret = clk_prepare_enable(phy_drd->clk);
	if (ret)
		return ret;

	/* Set PHY clock and control HS PHY */
	reg = readl(regs_base + EXYNOS850_DRD_UTMI);
	reg &= ~(UTMI_DP_PULLDOWN | UTMI_DM_PULLDOWN);
	reg |= UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP;
	writel(reg, regs_base + EXYNOS850_DRD_UTMI);

	/* Power down PHY analog blocks */
	reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST);
	reg |= HSP_TEST_SIDDQ;
	writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST);

	/* Link reset */
	reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
	reg |= CLKRST_LINK_SW_RST;
	writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
	udelay(10); /* required before doing POR=low */
	reg &= ~CLKRST_LINK_SW_RST;
	writel(reg, regs_base + EXYNOS850_DRD_CLKRST);

	clk_disable_unprepare(phy_drd->clk);

	return 0;
}

static const struct phy_ops exynos850_usbdrd_phy_ops = {
	.init		= exynos850_usbdrd_phy_init,
	.exit		= exynos850_usbdrd_phy_exit,
	.power_on	= exynos5_usbdrd_phy_power_on,
	.power_off	= exynos5_usbdrd_phy_power_off,
	.owner		= THIS_MODULE,
};

static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd)
{
	unsigned long ref_rate;
@@ -782,6 +933,14 @@ static const struct exynos5_usbdrd_phy_config phy_cfg_exynos5[] = {
	},
};

static const struct exynos5_usbdrd_phy_config phy_cfg_exynos850[] = {
	{
		.id		= EXYNOS5_DRDPHY_UTMI,
		.phy_isol	= exynos5_usbdrd_phy_isol,
		.phy_init	= exynos850_usbdrd_utmi_init,
	},
};

static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = {
	.phy_cfg		= phy_cfg_exynos5,
	.phy_ops		= &exynos5_usbdrd_phy_ops,
@@ -812,6 +971,13 @@ static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = {
	.has_common_clk_gate	= false,
};

static const struct exynos5_usbdrd_phy_drvdata exynos850_usbdrd_phy = {
	.phy_cfg		= phy_cfg_exynos850,
	.phy_ops		= &exynos850_usbdrd_phy_ops,
	.pmu_offset_usbdrd0_phy	= EXYNOS5_USBDRD_PHY_CONTROL,
	.has_common_clk_gate	= true,
};

static const struct of_device_id exynos5_usbdrd_phy_of_match[] = {
	{
		.compatible = "samsung,exynos5250-usbdrd-phy",
@@ -825,6 +991,9 @@ static const struct of_device_id exynos5_usbdrd_phy_of_match[] = {
	}, {
		.compatible = "samsung,exynos7-usbdrd-phy",
		.data = &exynos7_usbdrd_phy
	}, {
		.compatible = "samsung,exynos850-usbdrd-phy",
		.data = &exynos850_usbdrd_phy
	},
	{ },
};