1/*
2 * Copyright 2012-2014 Freescale Semiconductor, Inc.
3 * Copyright (C) 2012 Marek Vasut <marex@denx.de>
4 * on behalf of DENX Software Engineering GmbH
5 *
6 * The code contained herein is licensed under the GNU General Public
7 * License. You may obtain a copy of the GNU General Public License
8 * Version 2 or later at the following locations:
9 *
10 * http://www.opensource.org/licenses/gpl-license.html
11 * http://www.gnu.org/copyleft/gpl.html
12 */
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/platform_device.h>
17#include <linux/clk.h>
18#include <linux/usb/otg.h>
19#include <linux/stmp_device.h>
20#include <linux/delay.h>
21#include <linux/err.h>
22#include <linux/io.h>
23#include <linux/of_device.h>
24#include <linux/regmap.h>
25#include <linux/mfd/syscon.h>
26
27#define DRIVER_NAME "mxs_phy"
28
29#define HW_USBPHY_PWD				0x00
30#define HW_USBPHY_CTRL				0x30
31#define HW_USBPHY_CTRL_SET			0x34
32#define HW_USBPHY_CTRL_CLR			0x38
33
34#define HW_USBPHY_DEBUG_SET			0x54
35#define HW_USBPHY_DEBUG_CLR			0x58
36
37#define HW_USBPHY_IP				0x90
38#define HW_USBPHY_IP_SET			0x94
39#define HW_USBPHY_IP_CLR			0x98
40
41#define BM_USBPHY_CTRL_SFTRST			BIT(31)
42#define BM_USBPHY_CTRL_CLKGATE			BIT(30)
43#define BM_USBPHY_CTRL_OTG_ID_VALUE		BIT(27)
44#define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS	BIT(26)
45#define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE	BIT(25)
46#define BM_USBPHY_CTRL_ENVBUSCHG_WKUP		BIT(23)
47#define BM_USBPHY_CTRL_ENIDCHG_WKUP		BIT(22)
48#define BM_USBPHY_CTRL_ENDPDMCHG_WKUP		BIT(21)
49#define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD	BIT(20)
50#define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE	BIT(19)
51#define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL		BIT(18)
52#define BM_USBPHY_CTRL_ENUTMILEVEL3		BIT(15)
53#define BM_USBPHY_CTRL_ENUTMILEVEL2		BIT(14)
54#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT	BIT(1)
55
56#define BM_USBPHY_IP_FIX                       (BIT(17) | BIT(18))
57
58#define BM_USBPHY_DEBUG_CLKGATE			BIT(30)
59
60/* Anatop Registers */
61#define ANADIG_ANA_MISC0			0x150
62#define ANADIG_ANA_MISC0_SET			0x154
63#define ANADIG_ANA_MISC0_CLR			0x158
64
65#define ANADIG_USB1_VBUS_DET_STAT		0x1c0
66#define ANADIG_USB2_VBUS_DET_STAT		0x220
67
68#define ANADIG_USB1_LOOPBACK_SET		0x1e4
69#define ANADIG_USB1_LOOPBACK_CLR		0x1e8
70#define ANADIG_USB2_LOOPBACK_SET		0x244
71#define ANADIG_USB2_LOOPBACK_CLR		0x248
72
73#define ANADIG_USB1_MISC			0x1f0
74#define ANADIG_USB2_MISC			0x250
75
76#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG	BIT(12)
77#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11)
78
79#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID	BIT(3)
80#define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID	BIT(3)
81
82#define BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1	BIT(2)
83#define BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN	BIT(5)
84#define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1	BIT(2)
85#define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN	BIT(5)
86
87#define BM_ANADIG_USB1_MISC_RX_VPIN_FS		BIT(29)
88#define BM_ANADIG_USB1_MISC_RX_VMIN_FS		BIT(28)
89#define BM_ANADIG_USB2_MISC_RX_VPIN_FS		BIT(29)
90#define BM_ANADIG_USB2_MISC_RX_VMIN_FS		BIT(28)
91
92#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
93
94/* Do disconnection between PHY and controller without vbus */
95#define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS	BIT(0)
96
97/*
98 * The PHY will be in messy if there is a wakeup after putting
99 * bus to suspend (set portsc.suspendM) but before setting PHY to low
100 * power mode (set portsc.phcd).
101 */
102#define MXS_PHY_ABNORMAL_IN_SUSPEND		BIT(1)
103
104/*
105 * The SOF sends too fast after resuming, it will cause disconnection
106 * between host and high speed device.
107 */
108#define MXS_PHY_SENDING_SOF_TOO_FAST		BIT(2)
109
110/*
111 * IC has bug fixes logic, they include
112 * MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST
113 * which are described at above flags, the RTL will handle it
114 * according to different versions.
115 */
116#define MXS_PHY_NEED_IP_FIX			BIT(3)
117
118struct mxs_phy_data {
119	unsigned int flags;
120};
121
122static const struct mxs_phy_data imx23_phy_data = {
123	.flags = MXS_PHY_ABNORMAL_IN_SUSPEND | MXS_PHY_SENDING_SOF_TOO_FAST,
124};
125
126static const struct mxs_phy_data imx6q_phy_data = {
127	.flags = MXS_PHY_SENDING_SOF_TOO_FAST |
128		MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
129		MXS_PHY_NEED_IP_FIX,
130};
131
132static const struct mxs_phy_data imx6sl_phy_data = {
133	.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
134		MXS_PHY_NEED_IP_FIX,
135};
136
137static const struct mxs_phy_data vf610_phy_data = {
138	.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
139		MXS_PHY_NEED_IP_FIX,
140};
141
142static const struct mxs_phy_data imx6sx_phy_data = {
143	.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
144};
145
146static const struct of_device_id mxs_phy_dt_ids[] = {
147	{ .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, },
148	{ .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, },
149	{ .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, },
150	{ .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, },
151	{ .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
152	{ /* sentinel */ }
153};
154MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
155
156struct mxs_phy {
157	struct usb_phy phy;
158	struct clk *clk;
159	const struct mxs_phy_data *data;
160	struct regmap *regmap_anatop;
161	int port_id;
162};
163
164static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
165{
166	return mxs_phy->data == &imx6q_phy_data;
167}
168
169static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy)
170{
171	return mxs_phy->data == &imx6sl_phy_data;
172}
173
174/*
175 * PHY needs some 32K cycles to switch from 32K clock to
176 * bus (such as AHB/AXI, etc) clock.
177 */
178static void mxs_phy_clock_switch_delay(void)
179{
180	usleep_range(300, 400);
181}
182
183static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
184{
185	int ret;
186	void __iomem *base = mxs_phy->phy.io_priv;
187
188	ret = stmp_reset_block(base + HW_USBPHY_CTRL);
189	if (ret)
190		return ret;
191
192	/* Power up the PHY */
193	writel(0, base + HW_USBPHY_PWD);
194
195	/*
196	 * USB PHY Ctrl Setting
197	 * - Auto clock/power on
198	 * - Enable full/low speed support
199	 */
200	writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
201		BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
202		BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
203		BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
204		BM_USBPHY_CTRL_ENAUTO_PWRON_PLL |
205		BM_USBPHY_CTRL_ENUTMILEVEL2 |
206		BM_USBPHY_CTRL_ENUTMILEVEL3,
207	       base + HW_USBPHY_CTRL_SET);
208
209	if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX)
210		writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET);
211
212	return 0;
213}
214
215/* Return true if the vbus is there */
216static bool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy)
217{
218	unsigned int vbus_value;
219
220	if (!mxs_phy->regmap_anatop)
221		return false;
222
223	if (mxs_phy->port_id == 0)
224		regmap_read(mxs_phy->regmap_anatop,
225			ANADIG_USB1_VBUS_DET_STAT,
226			&vbus_value);
227	else if (mxs_phy->port_id == 1)
228		regmap_read(mxs_phy->regmap_anatop,
229			ANADIG_USB2_VBUS_DET_STAT,
230			&vbus_value);
231
232	if (vbus_value & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)
233		return true;
234	else
235		return false;
236}
237
238static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
239{
240	void __iomem *base = mxs_phy->phy.io_priv;
241	u32 reg;
242
243	if (disconnect)
244		writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
245			base + HW_USBPHY_DEBUG_CLR);
246
247	if (mxs_phy->port_id == 0) {
248		reg = disconnect ? ANADIG_USB1_LOOPBACK_SET
249			: ANADIG_USB1_LOOPBACK_CLR;
250		regmap_write(mxs_phy->regmap_anatop, reg,
251			BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 |
252			BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN);
253	} else if (mxs_phy->port_id == 1) {
254		reg = disconnect ? ANADIG_USB2_LOOPBACK_SET
255			: ANADIG_USB2_LOOPBACK_CLR;
256		regmap_write(mxs_phy->regmap_anatop, reg,
257			BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 |
258			BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN);
259	}
260
261	if (!disconnect)
262		writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
263			base + HW_USBPHY_DEBUG_SET);
264
265	/* Delay some time, and let Linestate be SE0 for controller */
266	if (disconnect)
267		usleep_range(500, 1000);
268}
269
270static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy)
271{
272	void __iomem *base = mxs_phy->phy.io_priv;
273	u32 phyctrl = readl(base + HW_USBPHY_CTRL);
274
275	if (IS_ENABLED(CONFIG_USB_OTG) &&
276			!(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE))
277		return true;
278
279	return false;
280}
281
282static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
283{
284	bool vbus_is_on = false;
285
286	/* If the SoCs don't need to disconnect line without vbus, quit */
287	if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS))
288		return;
289
290	/* If the SoCs don't have anatop, quit */
291	if (!mxs_phy->regmap_anatop)
292		return;
293
294	vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
295
296	if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy))
297		__mxs_phy_disconnect_line(mxs_phy, true);
298	else
299		__mxs_phy_disconnect_line(mxs_phy, false);
300
301}
302
303static int mxs_phy_init(struct usb_phy *phy)
304{
305	int ret;
306	struct mxs_phy *mxs_phy = to_mxs_phy(phy);
307
308	mxs_phy_clock_switch_delay();
309	ret = clk_prepare_enable(mxs_phy->clk);
310	if (ret)
311		return ret;
312
313	return mxs_phy_hw_init(mxs_phy);
314}
315
316static void mxs_phy_shutdown(struct usb_phy *phy)
317{
318	struct mxs_phy *mxs_phy = to_mxs_phy(phy);
319	u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP |
320			BM_USBPHY_CTRL_ENDPDMCHG_WKUP |
321			BM_USBPHY_CTRL_ENIDCHG_WKUP |
322			BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
323			BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
324			BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
325			BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
326			BM_USBPHY_CTRL_ENAUTO_PWRON_PLL;
327
328	writel(value, phy->io_priv + HW_USBPHY_CTRL_CLR);
329	writel(0xffffffff, phy->io_priv + HW_USBPHY_PWD);
330
331	writel(BM_USBPHY_CTRL_CLKGATE,
332	       phy->io_priv + HW_USBPHY_CTRL_SET);
333
334	clk_disable_unprepare(mxs_phy->clk);
335}
336
337static bool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy)
338{
339	unsigned int line_state;
340	/* bit definition is the same for all controllers */
341	unsigned int dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS,
342		     dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS;
343	unsigned int reg = ANADIG_USB1_MISC;
344
345	/* If the SoCs don't have anatop, quit */
346	if (!mxs_phy->regmap_anatop)
347		return false;
348
349	if (mxs_phy->port_id == 0)
350		reg = ANADIG_USB1_MISC;
351	else if (mxs_phy->port_id == 1)
352		reg = ANADIG_USB2_MISC;
353
354	regmap_read(mxs_phy->regmap_anatop, reg, &line_state);
355
356	if ((line_state & (dp_bit | dm_bit)) ==  dm_bit)
357		return true;
358	else
359		return false;
360}
361
362static int mxs_phy_suspend(struct usb_phy *x, int suspend)
363{
364	int ret;
365	struct mxs_phy *mxs_phy = to_mxs_phy(x);
366	bool low_speed_connection, vbus_is_on;
367
368	low_speed_connection = mxs_phy_is_low_speed_connection(mxs_phy);
369	vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
370
371	if (suspend) {
372		/*
373		 * FIXME: Do not power down RXPWD1PT1 bit for low speed
374		 * connect. The low speed connection will have problem at
375		 * very rare cases during usb suspend and resume process.
376		 */
377		if (low_speed_connection & vbus_is_on) {
378			/*
379			 * If value to be set as pwd value is not 0xffffffff,
380			 * several 32Khz cycles are needed.
381			 */
382			mxs_phy_clock_switch_delay();
383			writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD);
384		} else {
385			writel(0xffffffff, x->io_priv + HW_USBPHY_PWD);
386		}
387		writel(BM_USBPHY_CTRL_CLKGATE,
388		       x->io_priv + HW_USBPHY_CTRL_SET);
389		clk_disable_unprepare(mxs_phy->clk);
390	} else {
391		mxs_phy_clock_switch_delay();
392		ret = clk_prepare_enable(mxs_phy->clk);
393		if (ret)
394			return ret;
395		writel(BM_USBPHY_CTRL_CLKGATE,
396		       x->io_priv + HW_USBPHY_CTRL_CLR);
397		writel(0, x->io_priv + HW_USBPHY_PWD);
398	}
399
400	return 0;
401}
402
403static int mxs_phy_set_wakeup(struct usb_phy *x, bool enabled)
404{
405	struct mxs_phy *mxs_phy = to_mxs_phy(x);
406	u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP |
407			BM_USBPHY_CTRL_ENDPDMCHG_WKUP |
408				BM_USBPHY_CTRL_ENIDCHG_WKUP;
409	if (enabled) {
410		mxs_phy_disconnect_line(mxs_phy, true);
411		writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_SET);
412	} else {
413		writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_CLR);
414		mxs_phy_disconnect_line(mxs_phy, false);
415	}
416
417	return 0;
418}
419
420static int mxs_phy_on_connect(struct usb_phy *phy,
421		enum usb_device_speed speed)
422{
423	dev_dbg(phy->dev, "%s device has connected\n",
424		(speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
425
426	if (speed == USB_SPEED_HIGH)
427		writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
428		       phy->io_priv + HW_USBPHY_CTRL_SET);
429
430	return 0;
431}
432
433static int mxs_phy_on_disconnect(struct usb_phy *phy,
434		enum usb_device_speed speed)
435{
436	dev_dbg(phy->dev, "%s device has disconnected\n",
437		(speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
438
439	/* Sometimes, the speed is not high speed when the error occurs */
440	if (readl(phy->io_priv + HW_USBPHY_CTRL) &
441			BM_USBPHY_CTRL_ENHOSTDISCONDETECT)
442		writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
443		       phy->io_priv + HW_USBPHY_CTRL_CLR);
444
445	return 0;
446}
447
448static int mxs_phy_probe(struct platform_device *pdev)
449{
450	struct resource *res;
451	void __iomem *base;
452	struct clk *clk;
453	struct mxs_phy *mxs_phy;
454	int ret;
455	const struct of_device_id *of_id =
456			of_match_device(mxs_phy_dt_ids, &pdev->dev);
457	struct device_node *np = pdev->dev.of_node;
458
459	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
460	base = devm_ioremap_resource(&pdev->dev, res);
461	if (IS_ERR(base))
462		return PTR_ERR(base);
463
464	clk = devm_clk_get(&pdev->dev, NULL);
465	if (IS_ERR(clk)) {
466		dev_err(&pdev->dev,
467			"can't get the clock, err=%ld", PTR_ERR(clk));
468		return PTR_ERR(clk);
469	}
470
471	mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL);
472	if (!mxs_phy)
473		return -ENOMEM;
474
475	/* Some SoCs don't have anatop registers */
476	if (of_get_property(np, "fsl,anatop", NULL)) {
477		mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
478			(np, "fsl,anatop");
479		if (IS_ERR(mxs_phy->regmap_anatop)) {
480			dev_dbg(&pdev->dev,
481				"failed to find regmap for anatop\n");
482			return PTR_ERR(mxs_phy->regmap_anatop);
483		}
484	}
485
486	ret = of_alias_get_id(np, "usbphy");
487	if (ret < 0)
488		dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret);
489	mxs_phy->port_id = ret;
490
491	mxs_phy->phy.io_priv		= base;
492	mxs_phy->phy.dev		= &pdev->dev;
493	mxs_phy->phy.label		= DRIVER_NAME;
494	mxs_phy->phy.init		= mxs_phy_init;
495	mxs_phy->phy.shutdown		= mxs_phy_shutdown;
496	mxs_phy->phy.set_suspend	= mxs_phy_suspend;
497	mxs_phy->phy.notify_connect	= mxs_phy_on_connect;
498	mxs_phy->phy.notify_disconnect	= mxs_phy_on_disconnect;
499	mxs_phy->phy.type		= USB_PHY_TYPE_USB2;
500	mxs_phy->phy.set_wakeup		= mxs_phy_set_wakeup;
501
502	mxs_phy->clk = clk;
503	mxs_phy->data = of_id->data;
504
505	platform_set_drvdata(pdev, mxs_phy);
506
507	device_set_wakeup_capable(&pdev->dev, true);
508
509	ret = usb_add_phy_dev(&mxs_phy->phy);
510	if (ret)
511		return ret;
512
513	return 0;
514}
515
516static int mxs_phy_remove(struct platform_device *pdev)
517{
518	struct mxs_phy *mxs_phy = platform_get_drvdata(pdev);
519
520	usb_remove_phy(&mxs_phy->phy);
521
522	return 0;
523}
524
525#ifdef CONFIG_PM_SLEEP
526static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on)
527{
528	unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
529
530	/* If the SoCs don't have anatop, quit */
531	if (!mxs_phy->regmap_anatop)
532		return;
533
534	if (is_imx6q_phy(mxs_phy))
535		regmap_write(mxs_phy->regmap_anatop, reg,
536			BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG);
537	else if (is_imx6sl_phy(mxs_phy))
538		regmap_write(mxs_phy->regmap_anatop,
539			reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL);
540}
541
542static int mxs_phy_system_suspend(struct device *dev)
543{
544	struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
545
546	if (device_may_wakeup(dev))
547		mxs_phy_enable_ldo_in_suspend(mxs_phy, true);
548
549	return 0;
550}
551
552static int mxs_phy_system_resume(struct device *dev)
553{
554	struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
555
556	if (device_may_wakeup(dev))
557		mxs_phy_enable_ldo_in_suspend(mxs_phy, false);
558
559	return 0;
560}
561#endif /* CONFIG_PM_SLEEP */
562
563static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend,
564		mxs_phy_system_resume);
565
566static struct platform_driver mxs_phy_driver = {
567	.probe = mxs_phy_probe,
568	.remove = mxs_phy_remove,
569	.driver = {
570		.name = DRIVER_NAME,
571		.of_match_table = mxs_phy_dt_ids,
572		.pm = &mxs_phy_pm,
573	 },
574};
575
576static int __init mxs_phy_module_init(void)
577{
578	return platform_driver_register(&mxs_phy_driver);
579}
580postcore_initcall(mxs_phy_module_init);
581
582static void __exit mxs_phy_module_exit(void)
583{
584	platform_driver_unregister(&mxs_phy_driver);
585}
586module_exit(mxs_phy_module_exit);
587
588MODULE_ALIAS("platform:mxs-usb-phy");
589MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
590MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>");
591MODULE_DESCRIPTION("Freescale MXS USB PHY driver");
592MODULE_LICENSE("GPL");
593