diff options
author | Praveen Paneri <p.paneri@samsung.com> | 2012-11-23 05:33:06 -0500 |
---|---|---|
committer | Felipe Balbi <balbi@ti.com> | 2013-01-18 08:06:30 -0500 |
commit | 337dc3a7684cf6577b5595b9bb96e1af06baec41 (patch) | |
tree | 6cbab8d202a743645e90f8ae0ce4d786715a71f8 | |
parent | 04a6221c509e90b5f921d408bbf0afcf91147280 (diff) |
usb: phy: samsung: Introducing usb phy driver for hsotg
This driver uses usb_phy interface to interact with s3c-hsotg. Supports
phy_init and phy_shutdown functions to enable/disable usb phy. Support
will be extended to host controllers and more Samsung SoCs.
Signed-off-by: Praveen Paneri <p.paneri@samsung.com>
Acked-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
-rw-r--r-- | Documentation/devicetree/bindings/usb/samsung-usbphy.txt | 11 | ||||
-rw-r--r-- | drivers/usb/phy/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/phy/samsung-usbphy.c | 354 | ||||
-rw-r--r-- | include/linux/platform_data/samsung-usbphy.h | 27 |
5 files changed, 401 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/usb/samsung-usbphy.txt b/Documentation/devicetree/bindings/usb/samsung-usbphy.txt new file mode 100644 index 000000000000..7b26e2d6ea04 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/samsung-usbphy.txt | |||
@@ -0,0 +1,11 @@ | |||
1 | * Samsung's usb phy transceiver | ||
2 | |||
3 | The Samsung's phy transceiver is used for controlling usb otg phy for | ||
4 | s3c-hsotg usb device controller. | ||
5 | TODO: Adding the PHY binding with controller(s) according to the under | ||
6 | developement generic PHY driver. | ||
7 | |||
8 | Required properties: | ||
9 | - compatible : should be "samsung,exynos4210-usbphy" | ||
10 | - reg : base physical address of the phy registers and length of memory mapped | ||
11 | region. | ||
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 5de6e7f39f9c..36a85b675429 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig | |||
@@ -45,3 +45,11 @@ config USB_RCAR_PHY | |||
45 | 45 | ||
46 | To compile this driver as a module, choose M here: the | 46 | To compile this driver as a module, choose M here: the |
47 | module will be called rcar-phy. | 47 | module will be called rcar-phy. |
48 | |||
49 | config SAMSUNG_USBPHY | ||
50 | bool "Samsung USB PHY controller Driver" | ||
51 | depends on USB_S3C_HSOTG | ||
52 | select USB_OTG_UTILS | ||
53 | help | ||
54 | Enable this to support Samsung USB phy controller for samsung | ||
55 | SoCs. | ||
diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 1a579a860a03..ec304f642402 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile | |||
@@ -9,3 +9,4 @@ obj-$(CONFIG_USB_ISP1301) += isp1301.o | |||
9 | obj-$(CONFIG_MV_U3D_PHY) += mv_u3d_phy.o | 9 | obj-$(CONFIG_MV_U3D_PHY) += mv_u3d_phy.o |
10 | obj-$(CONFIG_USB_EHCI_TEGRA) += tegra_usb_phy.o | 10 | obj-$(CONFIG_USB_EHCI_TEGRA) += tegra_usb_phy.o |
11 | obj-$(CONFIG_USB_RCAR_PHY) += rcar-phy.o | 11 | obj-$(CONFIG_USB_RCAR_PHY) += rcar-phy.o |
12 | obj-$(CONFIG_SAMSUNG_USBPHY) += samsung-usbphy.o | ||
diff --git a/drivers/usb/phy/samsung-usbphy.c b/drivers/usb/phy/samsung-usbphy.c new file mode 100644 index 000000000000..5c5e1bb5de7b --- /dev/null +++ b/drivers/usb/phy/samsung-usbphy.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* linux/drivers/usb/phy/samsung-usbphy.c | ||
2 | * | ||
3 | * Copyright (c) 2012 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com | ||
5 | * | ||
6 | * Author: Praveen Paneri <p.paneri@samsung.com> | ||
7 | * | ||
8 | * Samsung USB2.0 High-speed OTG transceiver, talks to S3C HS OTG 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/otg.h> | ||
28 | #include <linux/platform_data/samsung-usbphy.h> | ||
29 | |||
30 | /* Register definitions */ | ||
31 | |||
32 | #define SAMSUNG_PHYPWR (0x00) | ||
33 | |||
34 | #define PHYPWR_NORMAL_MASK (0x19 << 0) | ||
35 | #define PHYPWR_OTG_DISABLE (0x1 << 4) | ||
36 | #define PHYPWR_ANALOG_POWERDOWN (0x1 << 3) | ||
37 | #define PHYPWR_FORCE_SUSPEND (0x1 << 1) | ||
38 | /* For Exynos4 */ | ||
39 | #define PHYPWR_NORMAL_MASK_PHY0 (0x39 << 0) | ||
40 | #define PHYPWR_SLEEP_PHY0 (0x1 << 5) | ||
41 | |||
42 | #define SAMSUNG_PHYCLK (0x04) | ||
43 | |||
44 | #define PHYCLK_MODE_USB11 (0x1 << 6) | ||
45 | #define PHYCLK_EXT_OSC (0x1 << 5) | ||
46 | #define PHYCLK_COMMON_ON_N (0x1 << 4) | ||
47 | #define PHYCLK_ID_PULL (0x1 << 2) | ||
48 | #define PHYCLK_CLKSEL_MASK (0x3 << 0) | ||
49 | #define PHYCLK_CLKSEL_48M (0x0 << 0) | ||
50 | #define PHYCLK_CLKSEL_12M (0x2 << 0) | ||
51 | #define PHYCLK_CLKSEL_24M (0x3 << 0) | ||
52 | |||
53 | #define SAMSUNG_RSTCON (0x08) | ||
54 | |||
55 | #define RSTCON_PHYLINK_SWRST (0x1 << 2) | ||
56 | #define RSTCON_HLINK_SWRST (0x1 << 1) | ||
57 | #define RSTCON_SWRST (0x1 << 0) | ||
58 | |||
59 | #ifndef MHZ | ||
60 | #define MHZ (1000*1000) | ||
61 | #endif | ||
62 | |||
63 | enum samsung_cpu_type { | ||
64 | TYPE_S3C64XX, | ||
65 | TYPE_EXYNOS4210, | ||
66 | }; | ||
67 | |||
68 | /* | ||
69 | * struct samsung_usbphy - transceiver driver state | ||
70 | * @phy: transceiver structure | ||
71 | * @plat: platform data | ||
72 | * @dev: The parent device supplied to the probe function | ||
73 | * @clk: usb phy clock | ||
74 | * @regs: usb phy register memory base | ||
75 | * @ref_clk_freq: reference clock frequency selection | ||
76 | * @cpu_type: machine identifier | ||
77 | */ | ||
78 | struct samsung_usbphy { | ||
79 | struct usb_phy phy; | ||
80 | struct samsung_usbphy_data *plat; | ||
81 | struct device *dev; | ||
82 | struct clk *clk; | ||
83 | void __iomem *regs; | ||
84 | int ref_clk_freq; | ||
85 | int cpu_type; | ||
86 | }; | ||
87 | |||
88 | #define phy_to_sphy(x) container_of((x), struct samsung_usbphy, phy) | ||
89 | |||
90 | /* | ||
91 | * Returns reference clock frequency selection value | ||
92 | */ | ||
93 | static int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) | ||
94 | { | ||
95 | struct clk *ref_clk; | ||
96 | int refclk_freq = 0; | ||
97 | |||
98 | ref_clk = clk_get(sphy->dev, "xusbxti"); | ||
99 | if (IS_ERR(ref_clk)) { | ||
100 | dev_err(sphy->dev, "Failed to get reference clock\n"); | ||
101 | return PTR_ERR(ref_clk); | ||
102 | } | ||
103 | |||
104 | switch (clk_get_rate(ref_clk)) { | ||
105 | case 12 * MHZ: | ||
106 | refclk_freq = PHYCLK_CLKSEL_12M; | ||
107 | break; | ||
108 | case 24 * MHZ: | ||
109 | refclk_freq = PHYCLK_CLKSEL_24M; | ||
110 | break; | ||
111 | case 48 * MHZ: | ||
112 | refclk_freq = PHYCLK_CLKSEL_48M; | ||
113 | break; | ||
114 | default: | ||
115 | if (sphy->cpu_type == TYPE_S3C64XX) | ||
116 | refclk_freq = PHYCLK_CLKSEL_48M; | ||
117 | else | ||
118 | refclk_freq = PHYCLK_CLKSEL_24M; | ||
119 | break; | ||
120 | } | ||
121 | clk_put(ref_clk); | ||
122 | |||
123 | return refclk_freq; | ||
124 | } | ||
125 | |||
126 | static void samsung_usbphy_enable(struct samsung_usbphy *sphy) | ||
127 | { | ||
128 | void __iomem *regs = sphy->regs; | ||
129 | u32 phypwr; | ||
130 | u32 phyclk; | ||
131 | u32 rstcon; | ||
132 | |||
133 | /* set clock frequency for PLL */ | ||
134 | phyclk = sphy->ref_clk_freq; | ||
135 | phypwr = readl(regs + SAMSUNG_PHYPWR); | ||
136 | rstcon = readl(regs + SAMSUNG_RSTCON); | ||
137 | |||
138 | switch (sphy->cpu_type) { | ||
139 | case TYPE_S3C64XX: | ||
140 | phyclk &= ~PHYCLK_COMMON_ON_N; | ||
141 | phypwr &= ~PHYPWR_NORMAL_MASK; | ||
142 | rstcon |= RSTCON_SWRST; | ||
143 | break; | ||
144 | case TYPE_EXYNOS4210: | ||
145 | phypwr &= ~PHYPWR_NORMAL_MASK_PHY0; | ||
146 | rstcon |= RSTCON_SWRST; | ||
147 | default: | ||
148 | break; | ||
149 | } | ||
150 | |||
151 | writel(phyclk, regs + SAMSUNG_PHYCLK); | ||
152 | /* Configure PHY0 for normal operation*/ | ||
153 | writel(phypwr, regs + SAMSUNG_PHYPWR); | ||
154 | /* reset all ports of PHY and Link */ | ||
155 | writel(rstcon, regs + SAMSUNG_RSTCON); | ||
156 | udelay(10); | ||
157 | rstcon &= ~RSTCON_SWRST; | ||
158 | writel(rstcon, regs + SAMSUNG_RSTCON); | ||
159 | } | ||
160 | |||
161 | static void samsung_usbphy_disable(struct samsung_usbphy *sphy) | ||
162 | { | ||
163 | void __iomem *regs = sphy->regs; | ||
164 | u32 phypwr; | ||
165 | |||
166 | phypwr = readl(regs + SAMSUNG_PHYPWR); | ||
167 | |||
168 | switch (sphy->cpu_type) { | ||
169 | case TYPE_S3C64XX: | ||
170 | phypwr |= PHYPWR_NORMAL_MASK; | ||
171 | break; | ||
172 | case TYPE_EXYNOS4210: | ||
173 | phypwr |= PHYPWR_NORMAL_MASK_PHY0; | ||
174 | default: | ||
175 | break; | ||
176 | } | ||
177 | |||
178 | /* Disable analog and otg block power */ | ||
179 | writel(phypwr, regs + SAMSUNG_PHYPWR); | ||
180 | } | ||
181 | |||
182 | /* | ||
183 | * The function passed to the usb driver for phy initialization | ||
184 | */ | ||
185 | static int samsung_usbphy_init(struct usb_phy *phy) | ||
186 | { | ||
187 | struct samsung_usbphy *sphy; | ||
188 | int ret = 0; | ||
189 | |||
190 | sphy = phy_to_sphy(phy); | ||
191 | |||
192 | /* Enable the phy clock */ | ||
193 | ret = clk_prepare_enable(sphy->clk); | ||
194 | if (ret) { | ||
195 | dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); | ||
196 | return ret; | ||
197 | } | ||
198 | |||
199 | /* Disable phy isolation */ | ||
200 | if (sphy->plat && sphy->plat->pmu_isolation) | ||
201 | sphy->plat->pmu_isolation(false); | ||
202 | |||
203 | /* Initialize usb phy registers */ | ||
204 | samsung_usbphy_enable(sphy); | ||
205 | |||
206 | /* Disable the phy clock */ | ||
207 | clk_disable_unprepare(sphy->clk); | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | /* | ||
212 | * The function passed to the usb driver for phy shutdown | ||
213 | */ | ||
214 | static void samsung_usbphy_shutdown(struct usb_phy *phy) | ||
215 | { | ||
216 | struct samsung_usbphy *sphy; | ||
217 | |||
218 | sphy = phy_to_sphy(phy); | ||
219 | |||
220 | if (clk_prepare_enable(sphy->clk)) { | ||
221 | dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); | ||
222 | return; | ||
223 | } | ||
224 | |||
225 | /* De-initialize usb phy registers */ | ||
226 | samsung_usbphy_disable(sphy); | ||
227 | |||
228 | /* Enable phy isolation */ | ||
229 | if (sphy->plat && sphy->plat->pmu_isolation) | ||
230 | sphy->plat->pmu_isolation(true); | ||
231 | |||
232 | clk_disable_unprepare(sphy->clk); | ||
233 | } | ||
234 | |||
235 | static const struct of_device_id samsung_usbphy_dt_match[]; | ||
236 | |||
237 | static inline int samsung_usbphy_get_driver_data(struct platform_device *pdev) | ||
238 | { | ||
239 | if (pdev->dev.of_node) { | ||
240 | const struct of_device_id *match; | ||
241 | match = of_match_node(samsung_usbphy_dt_match, | ||
242 | pdev->dev.of_node); | ||
243 | return (int) match->data; | ||
244 | } | ||
245 | |||
246 | return platform_get_device_id(pdev)->driver_data; | ||
247 | } | ||
248 | |||
249 | static int __devinit samsung_usbphy_probe(struct platform_device *pdev) | ||
250 | { | ||
251 | struct samsung_usbphy *sphy; | ||
252 | struct samsung_usbphy_data *pdata; | ||
253 | struct device *dev = &pdev->dev; | ||
254 | struct resource *phy_mem; | ||
255 | void __iomem *phy_base; | ||
256 | struct clk *clk; | ||
257 | |||
258 | pdata = pdev->dev.platform_data; | ||
259 | if (!pdata) { | ||
260 | dev_err(&pdev->dev, "%s: no platform data defined\n", __func__); | ||
261 | return -EINVAL; | ||
262 | } | ||
263 | |||
264 | phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
265 | if (!phy_mem) { | ||
266 | dev_err(dev, "%s: missing mem resource\n", __func__); | ||
267 | return -ENODEV; | ||
268 | } | ||
269 | |||
270 | phy_base = devm_request_and_ioremap(dev, phy_mem); | ||
271 | if (!phy_base) { | ||
272 | dev_err(dev, "%s: register mapping failed\n", __func__); | ||
273 | return -ENXIO; | ||
274 | } | ||
275 | |||
276 | sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); | ||
277 | if (!sphy) | ||
278 | return -ENOMEM; | ||
279 | |||
280 | clk = devm_clk_get(dev, "otg"); | ||
281 | if (IS_ERR(clk)) { | ||
282 | dev_err(dev, "Failed to get otg clock\n"); | ||
283 | return PTR_ERR(clk); | ||
284 | } | ||
285 | |||
286 | sphy->dev = &pdev->dev; | ||
287 | sphy->plat = pdata; | ||
288 | sphy->regs = phy_base; | ||
289 | sphy->clk = clk; | ||
290 | sphy->phy.dev = sphy->dev; | ||
291 | sphy->phy.label = "samsung-usbphy"; | ||
292 | sphy->phy.init = samsung_usbphy_init; | ||
293 | sphy->phy.shutdown = samsung_usbphy_shutdown; | ||
294 | sphy->cpu_type = samsung_usbphy_get_driver_data(pdev); | ||
295 | sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); | ||
296 | |||
297 | platform_set_drvdata(pdev, sphy); | ||
298 | |||
299 | return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB2); | ||
300 | } | ||
301 | |||
302 | static int __exit samsung_usbphy_remove(struct platform_device *pdev) | ||
303 | { | ||
304 | struct samsung_usbphy *sphy = platform_get_drvdata(pdev); | ||
305 | |||
306 | usb_remove_phy(&sphy->phy); | ||
307 | |||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | #ifdef CONFIG_OF | ||
312 | static const struct of_device_id samsung_usbphy_dt_match[] = { | ||
313 | { | ||
314 | .compatible = "samsung,s3c64xx-usbphy", | ||
315 | .data = (void *)TYPE_S3C64XX, | ||
316 | }, { | ||
317 | .compatible = "samsung,exynos4210-usbphy", | ||
318 | .data = (void *)TYPE_EXYNOS4210, | ||
319 | }, | ||
320 | {}, | ||
321 | }; | ||
322 | MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); | ||
323 | #endif | ||
324 | |||
325 | static struct platform_device_id samsung_usbphy_driver_ids[] = { | ||
326 | { | ||
327 | .name = "s3c64xx-usbphy", | ||
328 | .driver_data = TYPE_S3C64XX, | ||
329 | }, { | ||
330 | .name = "exynos4210-usbphy", | ||
331 | .driver_data = TYPE_EXYNOS4210, | ||
332 | }, | ||
333 | {}, | ||
334 | }; | ||
335 | |||
336 | MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); | ||
337 | |||
338 | static struct platform_driver samsung_usbphy_driver = { | ||
339 | .probe = samsung_usbphy_probe, | ||
340 | .remove = __devexit_p(samsung_usbphy_remove), | ||
341 | .id_table = samsung_usbphy_driver_ids, | ||
342 | .driver = { | ||
343 | .name = "samsung-usbphy", | ||
344 | .owner = THIS_MODULE, | ||
345 | .of_match_table = of_match_ptr(samsung_usbphy_dt_match), | ||
346 | }, | ||
347 | }; | ||
348 | |||
349 | module_platform_driver(samsung_usbphy_driver); | ||
350 | |||
351 | MODULE_DESCRIPTION("Samsung USB phy controller"); | ||
352 | MODULE_AUTHOR("Praveen Paneri <p.paneri@samsung.com>"); | ||
353 | MODULE_LICENSE("GPL"); | ||
354 | MODULE_ALIAS("platform:samsung-usbphy"); | ||
diff --git a/include/linux/platform_data/samsung-usbphy.h b/include/linux/platform_data/samsung-usbphy.h new file mode 100644 index 000000000000..1bd24cba982b --- /dev/null +++ b/include/linux/platform_data/samsung-usbphy.h | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 Samsung Electronics Co.Ltd | ||
3 | * http://www.samsung.com/ | ||
4 | * Author: Praveen Paneri <p.paneri@samsung.com> | ||
5 | * | ||
6 | * Defines platform data for samsung usb phy driver. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the | ||
10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
11 | * option) any later version. | ||
12 | */ | ||
13 | |||
14 | #ifndef __SAMSUNG_USBPHY_PLATFORM_H | ||
15 | #define __SAMSUNG_USBPHY_PLATFORM_H | ||
16 | |||
17 | /** | ||
18 | * samsung_usbphy_data - Platform data for USB PHY driver. | ||
19 | * @pmu_isolation: Function to control usb phy isolation in PMU. | ||
20 | */ | ||
21 | struct samsung_usbphy_data { | ||
22 | void (*pmu_isolation)(int on); | ||
23 | }; | ||
24 | |||
25 | extern void samsung_usbphy_set_pdata(struct samsung_usbphy_data *pd); | ||
26 | |||
27 | #endif /* __SAMSUNG_USBPHY_PLATFORM_H */ | ||