1ef19b117SAnsuel Smith // SPDX-License-Identifier: GPL-2.0-only
2ef19b117SAnsuel Smith 
3ef19b117SAnsuel Smith #include <linux/clk.h>
4ef19b117SAnsuel Smith #include <linux/err.h>
5ef19b117SAnsuel Smith #include <linux/io.h>
6ef19b117SAnsuel Smith #include <linux/module.h>
7ef19b117SAnsuel Smith #include <linux/of_device.h>
8ef19b117SAnsuel Smith #include <linux/phy/phy.h>
9ef19b117SAnsuel Smith #include <linux/platform_device.h>
10ef19b117SAnsuel Smith #include <linux/delay.h>
11ef19b117SAnsuel Smith #include <linux/regmap.h>
12ef19b117SAnsuel Smith #include <linux/mfd/syscon.h>
13ef19b117SAnsuel Smith 
14ef19b117SAnsuel Smith /* USB QSCRATCH Hardware registers */
15ef19b117SAnsuel Smith #define QSCRATCH_GENERAL_CFG		(0x08)
16ef19b117SAnsuel Smith #define HSUSB_PHY_CTRL_REG		(0x10)
17ef19b117SAnsuel Smith 
18ef19b117SAnsuel Smith /* PHY_CTRL_REG */
19ef19b117SAnsuel Smith #define HSUSB_CTRL_DMSEHV_CLAMP		BIT(24)
20ef19b117SAnsuel Smith #define HSUSB_CTRL_USB2_SUSPEND		BIT(23)
21ef19b117SAnsuel Smith #define HSUSB_CTRL_UTMI_CLK_EN		BIT(21)
22ef19b117SAnsuel Smith #define HSUSB_CTRL_UTMI_OTG_VBUS_VALID	BIT(20)
23ef19b117SAnsuel Smith #define HSUSB_CTRL_USE_CLKCORE		BIT(18)
24ef19b117SAnsuel Smith #define HSUSB_CTRL_DPSEHV_CLAMP		BIT(17)
25ef19b117SAnsuel Smith #define HSUSB_CTRL_COMMONONN		BIT(11)
26ef19b117SAnsuel Smith #define HSUSB_CTRL_ID_HV_CLAMP		BIT(9)
27ef19b117SAnsuel Smith #define HSUSB_CTRL_OTGSESSVLD_CLAMP	BIT(8)
28ef19b117SAnsuel Smith #define HSUSB_CTRL_CLAMP_EN		BIT(7)
29ef19b117SAnsuel Smith #define HSUSB_CTRL_RETENABLEN		BIT(1)
30ef19b117SAnsuel Smith #define HSUSB_CTRL_POR			BIT(0)
31ef19b117SAnsuel Smith 
32ef19b117SAnsuel Smith /* QSCRATCH_GENERAL_CFG */
33ef19b117SAnsuel Smith #define HSUSB_GCFG_XHCI_REV		BIT(2)
34ef19b117SAnsuel Smith 
35ef19b117SAnsuel Smith /* USB QSCRATCH Hardware registers */
36ef19b117SAnsuel Smith #define SSUSB_PHY_CTRL_REG		(0x00)
37ef19b117SAnsuel Smith #define SSUSB_PHY_PARAM_CTRL_1		(0x04)
38ef19b117SAnsuel Smith #define SSUSB_PHY_PARAM_CTRL_2		(0x08)
39ef19b117SAnsuel Smith #define CR_PROTOCOL_DATA_IN_REG		(0x0c)
40ef19b117SAnsuel Smith #define CR_PROTOCOL_DATA_OUT_REG	(0x10)
41ef19b117SAnsuel Smith #define CR_PROTOCOL_CAP_ADDR_REG	(0x14)
42ef19b117SAnsuel Smith #define CR_PROTOCOL_CAP_DATA_REG	(0x18)
43ef19b117SAnsuel Smith #define CR_PROTOCOL_READ_REG		(0x1c)
44ef19b117SAnsuel Smith #define CR_PROTOCOL_WRITE_REG		(0x20)
45ef19b117SAnsuel Smith 
46ef19b117SAnsuel Smith /* PHY_CTRL_REG */
47ef19b117SAnsuel Smith #define SSUSB_CTRL_REF_USE_PAD		BIT(28)
48ef19b117SAnsuel Smith #define SSUSB_CTRL_TEST_POWERDOWN	BIT(27)
49ef19b117SAnsuel Smith #define SSUSB_CTRL_LANE0_PWR_PRESENT	BIT(24)
50ef19b117SAnsuel Smith #define SSUSB_CTRL_SS_PHY_EN		BIT(8)
51ef19b117SAnsuel Smith #define SSUSB_CTRL_SS_PHY_RESET		BIT(7)
52ef19b117SAnsuel Smith 
53ef19b117SAnsuel Smith /* SSPHY control registers - Does this need 0x30? */
54ef19b117SAnsuel Smith #define SSPHY_CTRL_RX_OVRD_IN_HI(lane)	(0x1006 + 0x100 * (lane))
55ef19b117SAnsuel Smith #define SSPHY_CTRL_TX_OVRD_DRV_LO(lane)	(0x1002 + 0x100 * (lane))
56ef19b117SAnsuel Smith 
57ef19b117SAnsuel Smith /* SSPHY SoC version specific values */
58ef19b117SAnsuel Smith #define SSPHY_RX_EQ_VALUE		4 /* Override value for rx_eq */
59ef19b117SAnsuel Smith /* Override value for transmit preemphasis */
60ef19b117SAnsuel Smith #define SSPHY_TX_DEEMPH_3_5DB		23
61ef19b117SAnsuel Smith /* Override value for mpll */
62ef19b117SAnsuel Smith #define SSPHY_MPLL_VALUE		0
63ef19b117SAnsuel Smith 
64ef19b117SAnsuel Smith /* QSCRATCH PHY_PARAM_CTRL1 fields */
65ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_TX_FULL_SWING_MASK	GENMASK(26, 19)
66ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK	GENMASK(19, 13)
67ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK	GENMASK(13, 7)
68ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_LOS_BIAS_MASK		GENMASK(7, 2)
69ef19b117SAnsuel Smith 
70ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_MASK				\
71ef19b117SAnsuel Smith 		(PHY_PARAM_CTRL1_TX_FULL_SWING_MASK |	\
72ef19b117SAnsuel Smith 		 PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK |	\
73ef19b117SAnsuel Smith 		 PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK |	\
74ef19b117SAnsuel Smith 		 PHY_PARAM_CTRL1_LOS_BIAS_MASK)
75ef19b117SAnsuel Smith 
76ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_TX_FULL_SWING(x)	\
77ef19b117SAnsuel Smith 		(((x) << 20) & PHY_PARAM_CTRL1_TX_FULL_SWING_MASK)
78ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_TX_DEEMPH_6DB(x)	\
79ef19b117SAnsuel Smith 		(((x) << 14) & PHY_PARAM_CTRL1_TX_DEEMPH_6DB_MASK)
80ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB(x)	\
81ef19b117SAnsuel Smith 		(((x) <<  8) & PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB_MASK)
82ef19b117SAnsuel Smith #define PHY_PARAM_CTRL1_LOS_BIAS(x)	\
83ef19b117SAnsuel Smith 		(((x) <<  3) & PHY_PARAM_CTRL1_LOS_BIAS_MASK)
84ef19b117SAnsuel Smith 
85ef19b117SAnsuel Smith /* RX OVRD IN HI bits */
86ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_RESET_OVRD		BIT(13)
87ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_RX_RESET		BIT(12)
88ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_EQ_OVRD		BIT(11)
89ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_EQ_MASK		GENMASK(10, 7)
90ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_EQ(x)			((x) << 8)
91ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_EQ_EN_OVRD		BIT(7)
92ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_EQ_EN			BIT(6)
93ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_LOS_FILTER_OVRD	BIT(5)
94ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_LOS_FILTER_MASK	GENMASK(4, 2)
95ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_RATE_OVRD		BIT(2)
96ef19b117SAnsuel Smith #define RX_OVRD_IN_HI_RX_RATE_MASK		GENMASK(2, 0)
97ef19b117SAnsuel Smith 
98ef19b117SAnsuel Smith /* TX OVRD DRV LO register bits */
99ef19b117SAnsuel Smith #define TX_OVRD_DRV_LO_AMPLITUDE_MASK		GENMASK(6, 0)
100ef19b117SAnsuel Smith #define TX_OVRD_DRV_LO_PREEMPH_MASK		GENMASK(13, 6)
101ef19b117SAnsuel Smith #define TX_OVRD_DRV_LO_PREEMPH(x)		((x) << 7)
102ef19b117SAnsuel Smith #define TX_OVRD_DRV_LO_EN			BIT(14)
103ef19b117SAnsuel Smith 
104ef19b117SAnsuel Smith /* MPLL bits */
105ef19b117SAnsuel Smith #define SSPHY_MPLL_MASK				GENMASK(8, 5)
106ef19b117SAnsuel Smith #define SSPHY_MPLL(x)				((x) << 5)
107ef19b117SAnsuel Smith 
108ef19b117SAnsuel Smith /* SS CAP register bits */
109ef19b117SAnsuel Smith #define SS_CR_CAP_ADDR_REG			BIT(0)
110ef19b117SAnsuel Smith #define SS_CR_CAP_DATA_REG			BIT(0)
111ef19b117SAnsuel Smith #define SS_CR_READ_REG				BIT(0)
112ef19b117SAnsuel Smith #define SS_CR_WRITE_REG				BIT(0)
113ef19b117SAnsuel Smith 
114ef19b117SAnsuel Smith struct usb_phy {
115ef19b117SAnsuel Smith 	void __iomem		*base;
116ef19b117SAnsuel Smith 	struct device		*dev;
117ef19b117SAnsuel Smith 	struct clk		*xo_clk;
118ef19b117SAnsuel Smith 	struct clk		*ref_clk;
119ef19b117SAnsuel Smith 	u32			rx_eq;
120ef19b117SAnsuel Smith 	u32			tx_deamp_3_5db;
121ef19b117SAnsuel Smith 	u32			mpll;
122ef19b117SAnsuel Smith };
123ef19b117SAnsuel Smith 
124ef19b117SAnsuel Smith struct phy_drvdata {
125ef19b117SAnsuel Smith 	struct phy_ops	ops;
126ef19b117SAnsuel Smith 	u32		clk_rate;
127ef19b117SAnsuel Smith };
128ef19b117SAnsuel Smith 
129ef19b117SAnsuel Smith /**
130ef19b117SAnsuel Smith  * Write register and read back masked value to confirm it is written
131ef19b117SAnsuel Smith  *
132ef19b117SAnsuel Smith  * @base - QCOM DWC3 PHY base virtual address.
133ef19b117SAnsuel Smith  * @offset - register offset.
134ef19b117SAnsuel Smith  * @mask - register bitmask specifying what should be updated
135ef19b117SAnsuel Smith  * @val - value to write.
136ef19b117SAnsuel Smith  */
137ef19b117SAnsuel Smith static inline void usb_phy_write_readback(struct usb_phy *phy_dwc3,
138ef19b117SAnsuel Smith 					  u32 offset,
139ef19b117SAnsuel Smith 					  const u32 mask, u32 val)
140ef19b117SAnsuel Smith {
141ef19b117SAnsuel Smith 	u32 write_val, tmp = readl(phy_dwc3->base + offset);
142ef19b117SAnsuel Smith 
143ef19b117SAnsuel Smith 	tmp &= ~mask;		/* retain other bits */
144ef19b117SAnsuel Smith 	write_val = tmp | val;
145ef19b117SAnsuel Smith 
146ef19b117SAnsuel Smith 	writel(write_val, phy_dwc3->base + offset);
147ef19b117SAnsuel Smith 
148ef19b117SAnsuel Smith 	/* Read back to see if val was written */
149ef19b117SAnsuel Smith 	tmp = readl(phy_dwc3->base + offset);
150ef19b117SAnsuel Smith 	tmp &= mask;		/* clear other bits */
151ef19b117SAnsuel Smith 
152ef19b117SAnsuel Smith 	if (tmp != val)
153ef19b117SAnsuel Smith 		dev_err(phy_dwc3->dev, "write: %x to QSCRATCH: %x FAILED\n", val, offset);
154ef19b117SAnsuel Smith }
155ef19b117SAnsuel Smith 
156ef19b117SAnsuel Smith static int wait_for_latch(void __iomem *addr)
157ef19b117SAnsuel Smith {
158ef19b117SAnsuel Smith 	u32 retry = 10;
159ef19b117SAnsuel Smith 
160ef19b117SAnsuel Smith 	while (true) {
161ef19b117SAnsuel Smith 		if (!readl(addr))
162ef19b117SAnsuel Smith 			break;
163ef19b117SAnsuel Smith 
164ef19b117SAnsuel Smith 		if (--retry == 0)
165ef19b117SAnsuel Smith 			return -ETIMEDOUT;
166ef19b117SAnsuel Smith 
167ef19b117SAnsuel Smith 		usleep_range(10, 20);
168ef19b117SAnsuel Smith 	}
169ef19b117SAnsuel Smith 
170ef19b117SAnsuel Smith 	return 0;
171ef19b117SAnsuel Smith }
172ef19b117SAnsuel Smith 
173ef19b117SAnsuel Smith /**
174ef19b117SAnsuel Smith  * Write SSPHY register
175ef19b117SAnsuel Smith  *
176ef19b117SAnsuel Smith  * @base - QCOM DWC3 PHY base virtual address.
177ef19b117SAnsuel Smith  * @addr - SSPHY address to write.
178ef19b117SAnsuel Smith  * @val - value to write.
179ef19b117SAnsuel Smith  */
180ef19b117SAnsuel Smith static int usb_ss_write_phycreg(struct usb_phy *phy_dwc3,
181ef19b117SAnsuel Smith 				u32 addr, u32 val)
182ef19b117SAnsuel Smith {
183ef19b117SAnsuel Smith 	int ret;
184ef19b117SAnsuel Smith 
185ef19b117SAnsuel Smith 	writel(addr, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
186ef19b117SAnsuel Smith 	writel(SS_CR_CAP_ADDR_REG,
187ef19b117SAnsuel Smith 	       phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
188ef19b117SAnsuel Smith 
189ef19b117SAnsuel Smith 	ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
190ef19b117SAnsuel Smith 	if (ret)
191ef19b117SAnsuel Smith 		goto err_wait;
192ef19b117SAnsuel Smith 
193ef19b117SAnsuel Smith 	writel(val, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
194ef19b117SAnsuel Smith 	writel(SS_CR_CAP_DATA_REG,
195ef19b117SAnsuel Smith 	       phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG);
196ef19b117SAnsuel Smith 
197ef19b117SAnsuel Smith 	ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG);
198ef19b117SAnsuel Smith 	if (ret)
199ef19b117SAnsuel Smith 		goto err_wait;
200ef19b117SAnsuel Smith 
201ef19b117SAnsuel Smith 	writel(SS_CR_WRITE_REG, phy_dwc3->base + CR_PROTOCOL_WRITE_REG);
202ef19b117SAnsuel Smith 
203ef19b117SAnsuel Smith 	ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_WRITE_REG);
204ef19b117SAnsuel Smith 
205ef19b117SAnsuel Smith err_wait:
206ef19b117SAnsuel Smith 	if (ret)
207ef19b117SAnsuel Smith 		dev_err(phy_dwc3->dev, "timeout waiting for latch\n");
208ef19b117SAnsuel Smith 	return ret;
209ef19b117SAnsuel Smith }
210ef19b117SAnsuel Smith 
211ef19b117SAnsuel Smith /**
212ef19b117SAnsuel Smith  * Read SSPHY register.
213ef19b117SAnsuel Smith  *
214ef19b117SAnsuel Smith  * @base - QCOM DWC3 PHY base virtual address.
215ef19b117SAnsuel Smith  * @addr - SSPHY address to read.
216ef19b117SAnsuel Smith  */
217ef19b117SAnsuel Smith static int usb_ss_read_phycreg(struct usb_phy *phy_dwc3,
218ef19b117SAnsuel Smith 			       u32 addr, u32 *val)
219ef19b117SAnsuel Smith {
220ef19b117SAnsuel Smith 	int ret;
221ef19b117SAnsuel Smith 
222ef19b117SAnsuel Smith 	writel(addr, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
223ef19b117SAnsuel Smith 	writel(SS_CR_CAP_ADDR_REG,
224ef19b117SAnsuel Smith 	       phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
225ef19b117SAnsuel Smith 
226ef19b117SAnsuel Smith 	ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
227ef19b117SAnsuel Smith 	if (ret)
228ef19b117SAnsuel Smith 		goto err_wait;
229ef19b117SAnsuel Smith 
230ef19b117SAnsuel Smith 	/*
231ef19b117SAnsuel Smith 	 * Due to hardware bug, first read of SSPHY register might be
232ef19b117SAnsuel Smith 	 * incorrect. Hence as workaround, SW should perform SSPHY register
233ef19b117SAnsuel Smith 	 * read twice, but use only second read and ignore first read.
234ef19b117SAnsuel Smith 	 */
235ef19b117SAnsuel Smith 	writel(SS_CR_READ_REG, phy_dwc3->base + CR_PROTOCOL_READ_REG);
236ef19b117SAnsuel Smith 
237ef19b117SAnsuel Smith 	ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_READ_REG);
238ef19b117SAnsuel Smith 	if (ret)
239ef19b117SAnsuel Smith 		goto err_wait;
240ef19b117SAnsuel Smith 
241ef19b117SAnsuel Smith 	/* throwaway read */
242ef19b117SAnsuel Smith 	readl(phy_dwc3->base + CR_PROTOCOL_DATA_OUT_REG);
243ef19b117SAnsuel Smith 
244ef19b117SAnsuel Smith 	writel(SS_CR_READ_REG, phy_dwc3->base + CR_PROTOCOL_READ_REG);
245ef19b117SAnsuel Smith 
246ef19b117SAnsuel Smith 	ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_READ_REG);
247ef19b117SAnsuel Smith 	if (ret)
248ef19b117SAnsuel Smith 		goto err_wait;
249ef19b117SAnsuel Smith 
250ef19b117SAnsuel Smith 	*val = readl(phy_dwc3->base + CR_PROTOCOL_DATA_OUT_REG);
251ef19b117SAnsuel Smith 
252ef19b117SAnsuel Smith err_wait:
253ef19b117SAnsuel Smith 	return ret;
254ef19b117SAnsuel Smith }
255ef19b117SAnsuel Smith 
256ef19b117SAnsuel Smith static int qcom_ipq806x_usb_hs_phy_init(struct phy *phy)
257ef19b117SAnsuel Smith {
258ef19b117SAnsuel Smith 	struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
259ef19b117SAnsuel Smith 	int ret;
260ef19b117SAnsuel Smith 	u32 val;
261ef19b117SAnsuel Smith 
262ef19b117SAnsuel Smith 	ret = clk_prepare_enable(phy_dwc3->xo_clk);
263ef19b117SAnsuel Smith 	if (ret)
264ef19b117SAnsuel Smith 		return ret;
265ef19b117SAnsuel Smith 
266ef19b117SAnsuel Smith 	ret = clk_prepare_enable(phy_dwc3->ref_clk);
267ef19b117SAnsuel Smith 	if (ret) {
268ef19b117SAnsuel Smith 		clk_disable_unprepare(phy_dwc3->xo_clk);
269ef19b117SAnsuel Smith 		return ret;
270ef19b117SAnsuel Smith 	}
271ef19b117SAnsuel Smith 
272ef19b117SAnsuel Smith 	/*
273ef19b117SAnsuel Smith 	 * HSPHY Initialization: Enable UTMI clock, select 19.2MHz fsel
274ef19b117SAnsuel Smith 	 * enable clamping, and disable RETENTION (power-on default is ENABLED)
275ef19b117SAnsuel Smith 	 */
276ef19b117SAnsuel Smith 	val = HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_DMSEHV_CLAMP |
277ef19b117SAnsuel Smith 		HSUSB_CTRL_RETENABLEN  | HSUSB_CTRL_COMMONONN |
278ef19b117SAnsuel Smith 		HSUSB_CTRL_OTGSESSVLD_CLAMP | HSUSB_CTRL_ID_HV_CLAMP |
279ef19b117SAnsuel Smith 		HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_UTMI_OTG_VBUS_VALID |
280ef19b117SAnsuel Smith 		HSUSB_CTRL_UTMI_CLK_EN | HSUSB_CTRL_CLAMP_EN | 0x70;
281ef19b117SAnsuel Smith 
282ef19b117SAnsuel Smith 	/* use core clock if external reference is not present */
283ef19b117SAnsuel Smith 	if (!phy_dwc3->xo_clk)
284ef19b117SAnsuel Smith 		val |= HSUSB_CTRL_USE_CLKCORE;
285ef19b117SAnsuel Smith 
286ef19b117SAnsuel Smith 	writel(val, phy_dwc3->base + HSUSB_PHY_CTRL_REG);
287ef19b117SAnsuel Smith 	usleep_range(2000, 2200);
288ef19b117SAnsuel Smith 
289ef19b117SAnsuel Smith 	/* Disable (bypass) VBUS and ID filters */
290ef19b117SAnsuel Smith 	writel(HSUSB_GCFG_XHCI_REV, phy_dwc3->base + QSCRATCH_GENERAL_CFG);
291ef19b117SAnsuel Smith 
292ef19b117SAnsuel Smith 	return 0;
293ef19b117SAnsuel Smith }
294ef19b117SAnsuel Smith 
295ef19b117SAnsuel Smith static int qcom_ipq806x_usb_hs_phy_exit(struct phy *phy)
296ef19b117SAnsuel Smith {
297ef19b117SAnsuel Smith 	struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
298ef19b117SAnsuel Smith 
299ef19b117SAnsuel Smith 	clk_disable_unprepare(phy_dwc3->ref_clk);
300ef19b117SAnsuel Smith 	clk_disable_unprepare(phy_dwc3->xo_clk);
301ef19b117SAnsuel Smith 
302ef19b117SAnsuel Smith 	return 0;
303ef19b117SAnsuel Smith }
304ef19b117SAnsuel Smith 
305ef19b117SAnsuel Smith static int qcom_ipq806x_usb_ss_phy_init(struct phy *phy)
306ef19b117SAnsuel Smith {
307ef19b117SAnsuel Smith 	struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
308ef19b117SAnsuel Smith 	int ret;
309ef19b117SAnsuel Smith 	u32 data;
310ef19b117SAnsuel Smith 
311ef19b117SAnsuel Smith 	ret = clk_prepare_enable(phy_dwc3->xo_clk);
312ef19b117SAnsuel Smith 	if (ret)
313ef19b117SAnsuel Smith 		return ret;
314ef19b117SAnsuel Smith 
315ef19b117SAnsuel Smith 	ret = clk_prepare_enable(phy_dwc3->ref_clk);
316ef19b117SAnsuel Smith 	if (ret) {
317ef19b117SAnsuel Smith 		clk_disable_unprepare(phy_dwc3->xo_clk);
318ef19b117SAnsuel Smith 		return ret;
319ef19b117SAnsuel Smith 	}
320ef19b117SAnsuel Smith 
321ef19b117SAnsuel Smith 	/* reset phy */
322ef19b117SAnsuel Smith 	data = readl(phy_dwc3->base + SSUSB_PHY_CTRL_REG);
323ef19b117SAnsuel Smith 	writel(data | SSUSB_CTRL_SS_PHY_RESET,
324ef19b117SAnsuel Smith 	       phy_dwc3->base + SSUSB_PHY_CTRL_REG);
325ef19b117SAnsuel Smith 	usleep_range(2000, 2200);
326ef19b117SAnsuel Smith 	writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
327ef19b117SAnsuel Smith 
328ef19b117SAnsuel Smith 	/* clear REF_PAD if we don't have XO clk */
329ef19b117SAnsuel Smith 	if (!phy_dwc3->xo_clk)
330ef19b117SAnsuel Smith 		data &= ~SSUSB_CTRL_REF_USE_PAD;
331ef19b117SAnsuel Smith 	else
332ef19b117SAnsuel Smith 		data |= SSUSB_CTRL_REF_USE_PAD;
333ef19b117SAnsuel Smith 
334ef19b117SAnsuel Smith 	writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
335ef19b117SAnsuel Smith 
336ef19b117SAnsuel Smith 	/* wait for ref clk to become stable, this can take up to 30ms */
337ef19b117SAnsuel Smith 	msleep(30);
338ef19b117SAnsuel Smith 
339ef19b117SAnsuel Smith 	data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT;
340ef19b117SAnsuel Smith 	writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
341ef19b117SAnsuel Smith 
342ef19b117SAnsuel Smith 	/*
343ef19b117SAnsuel Smith 	 * WORKAROUND: There is SSPHY suspend bug due to which USB enumerates
344ef19b117SAnsuel Smith 	 * in HS mode instead of SS mode. Workaround it by asserting
345ef19b117SAnsuel Smith 	 * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode
346ef19b117SAnsuel Smith 	 */
347ef19b117SAnsuel Smith 	ret = usb_ss_read_phycreg(phy_dwc3, 0x102D, &data);
348ef19b117SAnsuel Smith 	if (ret)
349ef19b117SAnsuel Smith 		goto err_phy_trans;
350ef19b117SAnsuel Smith 
351ef19b117SAnsuel Smith 	data |= (1 << 7);
352ef19b117SAnsuel Smith 	ret = usb_ss_write_phycreg(phy_dwc3, 0x102D, data);
353ef19b117SAnsuel Smith 	if (ret)
354ef19b117SAnsuel Smith 		goto err_phy_trans;
355ef19b117SAnsuel Smith 
356ef19b117SAnsuel Smith 	ret = usb_ss_read_phycreg(phy_dwc3, 0x1010, &data);
357ef19b117SAnsuel Smith 	if (ret)
358ef19b117SAnsuel Smith 		goto err_phy_trans;
359ef19b117SAnsuel Smith 
360ef19b117SAnsuel Smith 	data &= ~0xff0;
361ef19b117SAnsuel Smith 	data |= 0x20;
362ef19b117SAnsuel Smith 	ret = usb_ss_write_phycreg(phy_dwc3, 0x1010, data);
363ef19b117SAnsuel Smith 	if (ret)
364ef19b117SAnsuel Smith 		goto err_phy_trans;
365ef19b117SAnsuel Smith 
366ef19b117SAnsuel Smith 	/*
367ef19b117SAnsuel Smith 	 * Fix RX Equalization setting as follows
368ef19b117SAnsuel Smith 	 * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
369ef19b117SAnsuel Smith 	 * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
370ef19b117SAnsuel Smith 	 * LANE0.RX_OVRD_IN_HI.RX_EQ set based on SoC version
371ef19b117SAnsuel Smith 	 * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
372ef19b117SAnsuel Smith 	 */
373ef19b117SAnsuel Smith 	ret = usb_ss_read_phycreg(phy_dwc3, SSPHY_CTRL_RX_OVRD_IN_HI(0), &data);
374ef19b117SAnsuel Smith 	if (ret)
375ef19b117SAnsuel Smith 		goto err_phy_trans;
376ef19b117SAnsuel Smith 
377ef19b117SAnsuel Smith 	data &= ~RX_OVRD_IN_HI_RX_EQ_EN;
378ef19b117SAnsuel Smith 	data |= RX_OVRD_IN_HI_RX_EQ_EN_OVRD;
379ef19b117SAnsuel Smith 	data &= ~RX_OVRD_IN_HI_RX_EQ_MASK;
380ef19b117SAnsuel Smith 	data |= RX_OVRD_IN_HI_RX_EQ(phy_dwc3->rx_eq);
381ef19b117SAnsuel Smith 	data |= RX_OVRD_IN_HI_RX_EQ_OVRD;
382ef19b117SAnsuel Smith 	ret = usb_ss_write_phycreg(phy_dwc3,
383ef19b117SAnsuel Smith 				   SSPHY_CTRL_RX_OVRD_IN_HI(0), data);
384ef19b117SAnsuel Smith 	if (ret)
385ef19b117SAnsuel Smith 		goto err_phy_trans;
386ef19b117SAnsuel Smith 
387ef19b117SAnsuel Smith 	/*
388ef19b117SAnsuel Smith 	 * Set EQ and TX launch amplitudes as follows
389ef19b117SAnsuel Smith 	 * LANE0.TX_OVRD_DRV_LO.PREEMPH set based on SoC version
390ef19b117SAnsuel Smith 	 * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 110
391ef19b117SAnsuel Smith 	 * LANE0.TX_OVRD_DRV_LO.EN set to 1.
392ef19b117SAnsuel Smith 	 */
393ef19b117SAnsuel Smith 	ret = usb_ss_read_phycreg(phy_dwc3,
394ef19b117SAnsuel Smith 				  SSPHY_CTRL_TX_OVRD_DRV_LO(0), &data);
395ef19b117SAnsuel Smith 	if (ret)
396ef19b117SAnsuel Smith 		goto err_phy_trans;
397ef19b117SAnsuel Smith 
398ef19b117SAnsuel Smith 	data &= ~TX_OVRD_DRV_LO_PREEMPH_MASK;
399ef19b117SAnsuel Smith 	data |= TX_OVRD_DRV_LO_PREEMPH(phy_dwc3->tx_deamp_3_5db);
400ef19b117SAnsuel Smith 	data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK;
401ef19b117SAnsuel Smith 	data |= 0x6E;
402ef19b117SAnsuel Smith 	data |= TX_OVRD_DRV_LO_EN;
403ef19b117SAnsuel Smith 	ret = usb_ss_write_phycreg(phy_dwc3,
404ef19b117SAnsuel Smith 				   SSPHY_CTRL_TX_OVRD_DRV_LO(0), data);
405ef19b117SAnsuel Smith 	if (ret)
406ef19b117SAnsuel Smith 		goto err_phy_trans;
407ef19b117SAnsuel Smith 
408ef19b117SAnsuel Smith 	data = 0;
409ef19b117SAnsuel Smith 	data &= ~SSPHY_MPLL_MASK;
410ef19b117SAnsuel Smith 	data |= SSPHY_MPLL(phy_dwc3->mpll);
411ef19b117SAnsuel Smith 	usb_ss_write_phycreg(phy_dwc3, 0x30, data);
412ef19b117SAnsuel Smith 
413ef19b117SAnsuel Smith 	/*
414ef19b117SAnsuel Smith 	 * Set the QSCRATCH PHY_PARAM_CTRL1 parameters as follows
415ef19b117SAnsuel Smith 	 * TX_FULL_SWING [26:20] amplitude to 110
416ef19b117SAnsuel Smith 	 * TX_DEEMPH_6DB [19:14] to 32
417ef19b117SAnsuel Smith 	 * TX_DEEMPH_3_5DB [13:8] set based on SoC version
418ef19b117SAnsuel Smith 	 * LOS_BIAS [7:3] to 9
419ef19b117SAnsuel Smith 	 */
420ef19b117SAnsuel Smith 	data = readl(phy_dwc3->base + SSUSB_PHY_PARAM_CTRL_1);
421ef19b117SAnsuel Smith 
422ef19b117SAnsuel Smith 	data &= ~PHY_PARAM_CTRL1_MASK;
423ef19b117SAnsuel Smith 
424ef19b117SAnsuel Smith 	data |= PHY_PARAM_CTRL1_TX_FULL_SWING(0x6e) |
425ef19b117SAnsuel Smith 		PHY_PARAM_CTRL1_TX_DEEMPH_6DB(0x20) |
426ef19b117SAnsuel Smith 		PHY_PARAM_CTRL1_TX_DEEMPH_3_5DB(phy_dwc3->tx_deamp_3_5db) |
427ef19b117SAnsuel Smith 		PHY_PARAM_CTRL1_LOS_BIAS(0x9);
428ef19b117SAnsuel Smith 
429ef19b117SAnsuel Smith 	usb_phy_write_readback(phy_dwc3, SSUSB_PHY_PARAM_CTRL_1,
430ef19b117SAnsuel Smith 			       PHY_PARAM_CTRL1_MASK, data);
431ef19b117SAnsuel Smith 
432ef19b117SAnsuel Smith err_phy_trans:
433ef19b117SAnsuel Smith 	return ret;
434ef19b117SAnsuel Smith }
435ef19b117SAnsuel Smith 
436ef19b117SAnsuel Smith static int qcom_ipq806x_usb_ss_phy_exit(struct phy *phy)
437ef19b117SAnsuel Smith {
438ef19b117SAnsuel Smith 	struct usb_phy *phy_dwc3 = phy_get_drvdata(phy);
439ef19b117SAnsuel Smith 
440ef19b117SAnsuel Smith 	/* Sequence to put SSPHY in low power state:
441ef19b117SAnsuel Smith 	 * 1. Clear REF_PHY_EN in PHY_CTRL_REG
442ef19b117SAnsuel Smith 	 * 2. Clear REF_USE_PAD in PHY_CTRL_REG
443ef19b117SAnsuel Smith 	 * 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention
444ef19b117SAnsuel Smith 	 */
445ef19b117SAnsuel Smith 	usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
446ef19b117SAnsuel Smith 			       SSUSB_CTRL_SS_PHY_EN, 0x0);
447ef19b117SAnsuel Smith 	usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
448ef19b117SAnsuel Smith 			       SSUSB_CTRL_REF_USE_PAD, 0x0);
449ef19b117SAnsuel Smith 	usb_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
450ef19b117SAnsuel Smith 			       SSUSB_CTRL_TEST_POWERDOWN, 0x0);
451ef19b117SAnsuel Smith 
452ef19b117SAnsuel Smith 	clk_disable_unprepare(phy_dwc3->ref_clk);
453ef19b117SAnsuel Smith 	clk_disable_unprepare(phy_dwc3->xo_clk);
454ef19b117SAnsuel Smith 
455ef19b117SAnsuel Smith 	return 0;
456ef19b117SAnsuel Smith }
457ef19b117SAnsuel Smith 
458ef19b117SAnsuel Smith static const struct phy_drvdata qcom_ipq806x_usb_hs_drvdata = {
459ef19b117SAnsuel Smith 	.ops = {
460ef19b117SAnsuel Smith 		.init		= qcom_ipq806x_usb_hs_phy_init,
461ef19b117SAnsuel Smith 		.exit		= qcom_ipq806x_usb_hs_phy_exit,
462ef19b117SAnsuel Smith 		.owner		= THIS_MODULE,
463ef19b117SAnsuel Smith 	},
464ef19b117SAnsuel Smith 	.clk_rate = 60000000,
465ef19b117SAnsuel Smith };
466ef19b117SAnsuel Smith 
467ef19b117SAnsuel Smith static const struct phy_drvdata qcom_ipq806x_usb_ss_drvdata = {
468ef19b117SAnsuel Smith 	.ops = {
469ef19b117SAnsuel Smith 		.init		= qcom_ipq806x_usb_ss_phy_init,
470ef19b117SAnsuel Smith 		.exit		= qcom_ipq806x_usb_ss_phy_exit,
471ef19b117SAnsuel Smith 		.owner		= THIS_MODULE,
472ef19b117SAnsuel Smith 	},
473ef19b117SAnsuel Smith 	.clk_rate = 125000000,
474ef19b117SAnsuel Smith };
475ef19b117SAnsuel Smith 
476ef19b117SAnsuel Smith static const struct of_device_id qcom_ipq806x_usb_phy_table[] = {
477ef19b117SAnsuel Smith 	{ .compatible = "qcom,ipq806x-usb-phy-hs",
478ef19b117SAnsuel Smith 	  .data = &qcom_ipq806x_usb_hs_drvdata },
479ef19b117SAnsuel Smith 	{ .compatible = "qcom,ipq806x-usb-phy-ss",
480ef19b117SAnsuel Smith 	  .data = &qcom_ipq806x_usb_ss_drvdata },
481ef19b117SAnsuel Smith 	{ /* Sentinel */ }
482ef19b117SAnsuel Smith };
483ef19b117SAnsuel Smith MODULE_DEVICE_TABLE(of, qcom_ipq806x_usb_phy_table);
484ef19b117SAnsuel Smith 
485ef19b117SAnsuel Smith static int qcom_ipq806x_usb_phy_probe(struct platform_device *pdev)
486ef19b117SAnsuel Smith {
487ef19b117SAnsuel Smith 	struct resource *res;
488ef19b117SAnsuel Smith 	resource_size_t size;
489ef19b117SAnsuel Smith 	struct phy *generic_phy;
490ef19b117SAnsuel Smith 	struct usb_phy *phy_dwc3;
491ef19b117SAnsuel Smith 	const struct phy_drvdata *data;
492ef19b117SAnsuel Smith 	struct phy_provider *phy_provider;
493ef19b117SAnsuel Smith 
494ef19b117SAnsuel Smith 	phy_dwc3 = devm_kzalloc(&pdev->dev, sizeof(*phy_dwc3), GFP_KERNEL);
495ef19b117SAnsuel Smith 	if (!phy_dwc3)
496ef19b117SAnsuel Smith 		return -ENOMEM;
497ef19b117SAnsuel Smith 
498ef19b117SAnsuel Smith 	data = of_device_get_match_data(&pdev->dev);
499ef19b117SAnsuel Smith 
500ef19b117SAnsuel Smith 	phy_dwc3->dev = &pdev->dev;
501ef19b117SAnsuel Smith 
502ef19b117SAnsuel Smith 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
503ef19b117SAnsuel Smith 	if (!res)
504ef19b117SAnsuel Smith 		return -EINVAL;
505ef19b117SAnsuel Smith 	size = resource_size(res);
506ef19b117SAnsuel Smith 	phy_dwc3->base = devm_ioremap(phy_dwc3->dev, res->start, size);
507ef19b117SAnsuel Smith 
50804db2304SWei Yongjun 	if (!phy_dwc3->base) {
509ef19b117SAnsuel Smith 		dev_err(phy_dwc3->dev, "failed to map reg\n");
51004db2304SWei Yongjun 		return -ENOMEM;
511ef19b117SAnsuel Smith 	}
512ef19b117SAnsuel Smith 
513ef19b117SAnsuel Smith 	phy_dwc3->ref_clk = devm_clk_get(phy_dwc3->dev, "ref");
514ef19b117SAnsuel Smith 	if (IS_ERR(phy_dwc3->ref_clk)) {
515ef19b117SAnsuel Smith 		dev_dbg(phy_dwc3->dev, "cannot get reference clock\n");
516ef19b117SAnsuel Smith 		return PTR_ERR(phy_dwc3->ref_clk);
517ef19b117SAnsuel Smith 	}
518ef19b117SAnsuel Smith 
519ef19b117SAnsuel Smith 	clk_set_rate(phy_dwc3->ref_clk, data->clk_rate);
520ef19b117SAnsuel Smith 
521ef19b117SAnsuel Smith 	phy_dwc3->xo_clk = devm_clk_get(phy_dwc3->dev, "xo");
522ef19b117SAnsuel Smith 	if (IS_ERR(phy_dwc3->xo_clk)) {
523ef19b117SAnsuel Smith 		dev_dbg(phy_dwc3->dev, "cannot get TCXO clock\n");
524ef19b117SAnsuel Smith 		phy_dwc3->xo_clk = NULL;
525ef19b117SAnsuel Smith 	}
526ef19b117SAnsuel Smith 
527ef19b117SAnsuel Smith 	/* Parse device node to probe HSIO settings */
528ef19b117SAnsuel Smith 	if (device_property_read_u32(&pdev->dev, "qcom,rx-eq",
529ef19b117SAnsuel Smith 				     &phy_dwc3->rx_eq))
530ef19b117SAnsuel Smith 		phy_dwc3->rx_eq = SSPHY_RX_EQ_VALUE;
531ef19b117SAnsuel Smith 
532ef19b117SAnsuel Smith 	if (device_property_read_u32(&pdev->dev, "qcom,tx-deamp_3_5db",
533ef19b117SAnsuel Smith 				     &phy_dwc3->tx_deamp_3_5db))
5343d7b0ca5SColin Ian King 		phy_dwc3->tx_deamp_3_5db = SSPHY_TX_DEEMPH_3_5DB;
535ef19b117SAnsuel Smith 
536ef19b117SAnsuel Smith 	if (device_property_read_u32(&pdev->dev, "qcom,mpll", &phy_dwc3->mpll))
537ef19b117SAnsuel Smith 		phy_dwc3->mpll = SSPHY_MPLL_VALUE;
538ef19b117SAnsuel Smith 
539ef19b117SAnsuel Smith 	generic_phy = devm_phy_create(phy_dwc3->dev, pdev->dev.of_node, &data->ops);
540ef19b117SAnsuel Smith 
541ef19b117SAnsuel Smith 	if (IS_ERR(generic_phy))
542ef19b117SAnsuel Smith 		return PTR_ERR(generic_phy);
543ef19b117SAnsuel Smith 
544ef19b117SAnsuel Smith 	phy_set_drvdata(generic_phy, phy_dwc3);
545ef19b117SAnsuel Smith 	platform_set_drvdata(pdev, phy_dwc3);
546ef19b117SAnsuel Smith 
547ef19b117SAnsuel Smith 	phy_provider = devm_of_phy_provider_register(phy_dwc3->dev,
548ef19b117SAnsuel Smith 						     of_phy_simple_xlate);
549ef19b117SAnsuel Smith 
550ef19b117SAnsuel Smith 	if (IS_ERR(phy_provider))
551ef19b117SAnsuel Smith 		return PTR_ERR(phy_provider);
552ef19b117SAnsuel Smith 
553ef19b117SAnsuel Smith 	return 0;
554ef19b117SAnsuel Smith }
555ef19b117SAnsuel Smith 
556ef19b117SAnsuel Smith static struct platform_driver qcom_ipq806x_usb_phy_driver = {
557ef19b117SAnsuel Smith 	.probe		= qcom_ipq806x_usb_phy_probe,
558ef19b117SAnsuel Smith 	.driver		= {
559ef19b117SAnsuel Smith 		.name	= "qcom-ipq806x-usb-phy",
560ef19b117SAnsuel Smith 		.of_match_table = qcom_ipq806x_usb_phy_table,
561ef19b117SAnsuel Smith 	},
562ef19b117SAnsuel Smith };
563ef19b117SAnsuel Smith 
564ef19b117SAnsuel Smith module_platform_driver(qcom_ipq806x_usb_phy_driver);
565ef19b117SAnsuel Smith 
566ef19b117SAnsuel Smith MODULE_ALIAS("platform:phy-qcom-ipq806x-usb");
567ef19b117SAnsuel Smith MODULE_LICENSE("GPL v2");
568ef19b117SAnsuel Smith MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>");
569ef19b117SAnsuel Smith MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");
570ef19b117SAnsuel Smith MODULE_DESCRIPTION("DesignWare USB3 QCOM PHY driver");
571