Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.9.
  1/* linux/drivers/usb/phy/phy-samsung-usb3.c
  2 *
  3 * Copyright (c) 2013 Samsung Electronics Co., Ltd.
  4 *              http://www.samsung.com
  5 *
  6 * Author: Vivek Gautam <gautam.vivek@samsung.com>
  7 *
  8 * Samsung USB 3.0 PHY transceiver; talks to DWC3 controller.
  9 *
 10 * This program is free software; you can redistribute it and/or modify
 11 * it under the terms of the GNU General Public License version 2 as
 12 * published by the Free Software Foundation.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 */
 19
 20#include <linux/module.h>
 21#include <linux/platform_device.h>
 22#include <linux/clk.h>
 23#include <linux/delay.h>
 24#include <linux/err.h>
 25#include <linux/io.h>
 26#include <linux/of.h>
 27#include <linux/usb/samsung_usb_phy.h>
 28#include <linux/platform_data/samsung-usbphy.h>
 29
 30#include "phy-samsung-usb.h"
 31
 32/*
 33 * Sets the phy clk as EXTREFCLK (XXTI) which is internal clock from clock core.
 34 */
 35static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy)
 36{
 37	u32 reg;
 38	u32 refclk;
 39
 40	refclk = sphy->ref_clk_freq;
 41
 42	reg = PHYCLKRST_REFCLKSEL_EXT_REFCLK |
 43		PHYCLKRST_FSEL(refclk);
 44
 45	switch (refclk) {
 46	case FSEL_CLKSEL_50M:
 47		reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF |
 48			PHYCLKRST_SSC_REFCLKSEL(0x00));
 49		break;
 50	case FSEL_CLKSEL_20M:
 51		reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF |
 52			PHYCLKRST_SSC_REFCLKSEL(0x00));
 53		break;
 54	case FSEL_CLKSEL_19200K:
 55		reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF |
 56			PHYCLKRST_SSC_REFCLKSEL(0x88));
 57		break;
 58	case FSEL_CLKSEL_24M:
 59	default:
 60		reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
 61			PHYCLKRST_SSC_REFCLKSEL(0x88));
 62		break;
 63	}
 64
 65	return reg;
 66}
 67
 68static void samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy)
 69{
 70	void __iomem *regs = sphy->regs;
 71	u32 phyparam0;
 72	u32 phyparam1;
 73	u32 linksystem;
 74	u32 phybatchg;
 75	u32 phytest;
 76	u32 phyclkrst;
 77
 78	/* Reset USB 3.0 PHY */
 79	writel(0x0, regs + EXYNOS5_DRD_PHYREG0);
 80
 81	phyparam0 = readl(regs + EXYNOS5_DRD_PHYPARAM0);
 82	/* Select PHY CLK source */
 83	phyparam0 &= ~PHYPARAM0_REF_USE_PAD;
 84	/* Set Loss-of-Signal Detector sensitivity */
 85	phyparam0 &= ~PHYPARAM0_REF_LOSLEVEL_MASK;
 86	phyparam0 |= PHYPARAM0_REF_LOSLEVEL;
 87	writel(phyparam0, regs + EXYNOS5_DRD_PHYPARAM0);
 88
 89	writel(0x0, regs + EXYNOS5_DRD_PHYRESUME);
 90
 91	/*
 92	 * Setting the Frame length Adj value[6:1] to default 0x20
 93	 * See xHCI 1.0 spec, 5.2.4
 94	 */
 95	linksystem = LINKSYSTEM_XHCI_VERSION_CONTROL |
 96			LINKSYSTEM_FLADJ(0x20);
 97	writel(linksystem, regs + EXYNOS5_DRD_LINKSYSTEM);
 98
 99	phyparam1 = readl(regs + EXYNOS5_DRD_PHYPARAM1);
100	/* Set Tx De-Emphasis level */
101	phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
102	phyparam1 |= PHYPARAM1_PCS_TXDEEMPH;
103	writel(phyparam1, regs + EXYNOS5_DRD_PHYPARAM1);
104
105	phybatchg = readl(regs + EXYNOS5_DRD_PHYBATCHG);
106	phybatchg |= PHYBATCHG_UTMI_CLKSEL;
107	writel(phybatchg, regs + EXYNOS5_DRD_PHYBATCHG);
108
109	/* PHYTEST POWERDOWN Control */
110	phytest = readl(regs + EXYNOS5_DRD_PHYTEST);
111	phytest &= ~(PHYTEST_POWERDOWN_SSP |
112			PHYTEST_POWERDOWN_HSP);
113	writel(phytest, regs + EXYNOS5_DRD_PHYTEST);
114
115	/* UTMI Power Control */
116	writel(PHYUTMI_OTGDISABLE, regs + EXYNOS5_DRD_PHYUTMI);
117
118	phyclkrst = samsung_usb3phy_set_refclk(sphy);
119
120	phyclkrst |= PHYCLKRST_PORTRESET |
121			/* Digital power supply in normal operating mode */
122			PHYCLKRST_RETENABLEN |
123			/* Enable ref clock for SS function */
124			PHYCLKRST_REF_SSP_EN |
125			/* Enable spread spectrum */
126			PHYCLKRST_SSC_EN |
127			/* Power down HS Bias and PLL blocks in suspend mode */
128			PHYCLKRST_COMMONONN;
129
130	writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
131
132	udelay(10);
133
134	phyclkrst &= ~(PHYCLKRST_PORTRESET);
135	writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
136}
137
138static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy)
139{
140	u32 phyutmi;
141	u32 phyclkrst;
142	u32 phytest;
143	void __iomem *regs = sphy->regs;
144
145	phyutmi = PHYUTMI_OTGDISABLE |
146			PHYUTMI_FORCESUSPEND |
147			PHYUTMI_FORCESLEEP;
148	writel(phyutmi, regs + EXYNOS5_DRD_PHYUTMI);
149
150	/* Resetting the PHYCLKRST enable bits to reduce leakage current */
151	phyclkrst = readl(regs + EXYNOS5_DRD_PHYCLKRST);
152	phyclkrst &= ~(PHYCLKRST_REF_SSP_EN |
153			PHYCLKRST_SSC_EN |
154			PHYCLKRST_COMMONONN);
155	writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
156
157	/* Control PHYTEST to remove leakage current */
158	phytest = readl(regs + EXYNOS5_DRD_PHYTEST);
159	phytest |= (PHYTEST_POWERDOWN_SSP |
160			PHYTEST_POWERDOWN_HSP);
161	writel(phytest, regs + EXYNOS5_DRD_PHYTEST);
162}
163
164static int samsung_usb3phy_init(struct usb_phy *phy)
165{
166	struct samsung_usbphy *sphy;
167	unsigned long flags;
168	int ret = 0;
169
170	sphy = phy_to_sphy(phy);
171
172	/* Enable the phy clock */
173	ret = clk_prepare_enable(sphy->clk);
174	if (ret) {
175		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
176		return ret;
177	}
178
179	spin_lock_irqsave(&sphy->lock, flags);
180
181	/* setting default phy-type for USB 3.0 */
182	samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
183
184	/* Disable phy isolation */
185	if (sphy->drv_data->set_isolation)
186		sphy->drv_data->set_isolation(sphy, false);
187
188	/* Initialize usb phy registers */
189	sphy->drv_data->phy_enable(sphy);
190
191	spin_unlock_irqrestore(&sphy->lock, flags);
192
193	/* Disable the phy clock */
194	clk_disable_unprepare(sphy->clk);
195
196	return ret;
197}
198
199/*
200 * The function passed to the usb driver for phy shutdown
201 */
202static void samsung_usb3phy_shutdown(struct usb_phy *phy)
203{
204	struct samsung_usbphy *sphy;
205	unsigned long flags;
206
207	sphy = phy_to_sphy(phy);
208
209	if (clk_prepare_enable(sphy->clk)) {
210		dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
211		return;
212	}
213
214	spin_lock_irqsave(&sphy->lock, flags);
215
216	/* setting default phy-type for USB 3.0 */
217	samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
218
219	/* De-initialize usb phy registers */
220	sphy->drv_data->phy_disable(sphy);
221
222	/* Enable phy isolation */
223	if (sphy->drv_data->set_isolation)
224		sphy->drv_data->set_isolation(sphy, true);
225
226	spin_unlock_irqrestore(&sphy->lock, flags);
227
228	clk_disable_unprepare(sphy->clk);
229}
230
231static int samsung_usb3phy_probe(struct platform_device *pdev)
232{
233	struct samsung_usbphy *sphy;
234	struct samsung_usbphy_data *pdata = dev_get_platdata(&pdev->dev);
235	struct device *dev = &pdev->dev;
236	struct resource *phy_mem;
237	void __iomem	*phy_base;
238	struct clk *clk;
239	int ret;
240
241	phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
242	phy_base = devm_ioremap_resource(dev, phy_mem);
243	if (IS_ERR(phy_base))
244		return PTR_ERR(phy_base);
245
246	sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
247	if (!sphy)
248		return -ENOMEM;
249
250	clk = devm_clk_get(dev, "usbdrd30");
251	if (IS_ERR(clk)) {
252		dev_err(dev, "Failed to get device clock\n");
253		return PTR_ERR(clk);
254	}
255
256	sphy->dev = dev;
257
258	if (dev->of_node) {
259		ret = samsung_usbphy_parse_dt(sphy);
260		if (ret < 0)
261			return ret;
262	} else {
263		if (!pdata) {
264			dev_err(dev, "no platform data specified\n");
265			return -EINVAL;
266		}
267	}
268
269	sphy->plat		= pdata;
270	sphy->regs		= phy_base;
271	sphy->clk		= clk;
272	sphy->phy.dev		= sphy->dev;
273	sphy->phy.label		= "samsung-usb3phy";
274	sphy->phy.type		= USB_PHY_TYPE_USB3;
275	sphy->phy.init		= samsung_usb3phy_init;
276	sphy->phy.shutdown	= samsung_usb3phy_shutdown;
277	sphy->drv_data		= samsung_usbphy_get_driver_data(pdev);
278
279	sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy);
280	if (sphy->ref_clk_freq < 0)
281		return -EINVAL;
282
283	spin_lock_init(&sphy->lock);
284
285	platform_set_drvdata(pdev, sphy);
286
287	return usb_add_phy_dev(&sphy->phy);
288}
289
290static int samsung_usb3phy_remove(struct platform_device *pdev)
291{
292	struct samsung_usbphy *sphy = platform_get_drvdata(pdev);
293
294	usb_remove_phy(&sphy->phy);
295
296	if (sphy->pmuregs)
297		iounmap(sphy->pmuregs);
298	if (sphy->sysreg)
299		iounmap(sphy->sysreg);
300
301	return 0;
302}
303
304static struct samsung_usbphy_drvdata usb3phy_exynos5 = {
305	.cpu_type		= TYPE_EXYNOS5250,
306	.devphy_en_mask		= EXYNOS_USBPHY_ENABLE,
307	.rate_to_clksel		= samsung_usbphy_rate_to_clksel_4x12,
308	.set_isolation		= samsung_usbphy_set_isolation_4210,
309	.phy_enable		= samsung_exynos5_usb3phy_enable,
310	.phy_disable		= samsung_exynos5_usb3phy_disable,
311};
312
313#ifdef CONFIG_OF
314static const struct of_device_id samsung_usbphy_dt_match[] = {
315	{
316		.compatible = "samsung,exynos5250-usb3phy",
317		.data = &usb3phy_exynos5
318	},
319	{},
320};
321MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match);
322#endif
323
324static struct platform_device_id samsung_usbphy_driver_ids[] = {
325	{
326		.name		= "exynos5250-usb3phy",
327		.driver_data	= (unsigned long)&usb3phy_exynos5,
328	},
329	{},
330};
331
332MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids);
333
334static struct platform_driver samsung_usb3phy_driver = {
335	.probe		= samsung_usb3phy_probe,
336	.remove		= samsung_usb3phy_remove,
337	.id_table	= samsung_usbphy_driver_ids,
338	.driver		= {
339		.name	= "samsung-usb3phy",
340		.owner	= THIS_MODULE,
341		.of_match_table = of_match_ptr(samsung_usbphy_dt_match),
342	},
343};
344
345module_platform_driver(samsung_usb3phy_driver);
346
347MODULE_DESCRIPTION("Samsung USB 3.0 phy controller");
348MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>");
349MODULE_LICENSE("GPL");
350MODULE_ALIAS("platform:samsung-usb3phy");