]> git.openfabrics.org - ~shefty/rdma-dev.git/blob - drivers/usb/host/ohci-exynos.c
USB: ohci-exynos: add clock gating to suspend/resume
[~shefty/rdma-dev.git] / drivers / usb / host / ohci-exynos.c
1 /*
2  * SAMSUNG EXYNOS USB HOST OHCI Controller
3  *
4  * Copyright (C) 2011 Samsung Electronics Co.Ltd
5  * Author: Jingoo Han <jg1.han@samsung.com>
6  *
7  * This program is free software; you can redistribute  it and/or modify it
8  * under  the terms of  the GNU General  Public License as published by the
9  * Free Software Foundation;  either version 2 of the  License, or (at your
10  * option) any later version.
11  *
12  */
13
14 #include <linux/clk.h>
15 #include <linux/platform_device.h>
16 #include <mach/ohci.h>
17 #include <plat/usb-phy.h>
18
19 struct exynos_ohci_hcd {
20         struct device *dev;
21         struct usb_hcd *hcd;
22         struct clk *clk;
23 };
24
25 static int ohci_exynos_start(struct usb_hcd *hcd)
26 {
27         struct ohci_hcd *ohci = hcd_to_ohci(hcd);
28         int ret;
29
30         ohci_dbg(ohci, "ohci_exynos_start, ohci:%p", ohci);
31
32         ret = ohci_init(ohci);
33         if (ret < 0)
34                 return ret;
35
36         ret = ohci_run(ohci);
37         if (ret < 0) {
38                 dev_err(hcd->self.controller, "can't start %s\n",
39                         hcd->self.bus_name);
40                 ohci_stop(hcd);
41                 return ret;
42         }
43
44         return 0;
45 }
46
47 static const struct hc_driver exynos_ohci_hc_driver = {
48         .description            = hcd_name,
49         .product_desc           = "EXYNOS OHCI Host Controller",
50         .hcd_priv_size          = sizeof(struct ohci_hcd),
51
52         .irq                    = ohci_irq,
53         .flags                  = HCD_MEMORY|HCD_USB11,
54
55         .start                  = ohci_exynos_start,
56         .stop                   = ohci_stop,
57         .shutdown               = ohci_shutdown,
58
59         .get_frame_number       = ohci_get_frame,
60
61         .urb_enqueue            = ohci_urb_enqueue,
62         .urb_dequeue            = ohci_urb_dequeue,
63         .endpoint_disable       = ohci_endpoint_disable,
64
65         .hub_status_data        = ohci_hub_status_data,
66         .hub_control            = ohci_hub_control,
67 #ifdef  CONFIG_PM
68         .bus_suspend            = ohci_bus_suspend,
69         .bus_resume             = ohci_bus_resume,
70 #endif
71         .start_port_reset       = ohci_start_port_reset,
72 };
73
74 static int __devinit exynos_ohci_probe(struct platform_device *pdev)
75 {
76         struct exynos4_ohci_platdata *pdata;
77         struct exynos_ohci_hcd *exynos_ohci;
78         struct usb_hcd *hcd;
79         struct ohci_hcd *ohci;
80         struct resource *res;
81         int irq;
82         int err;
83
84         pdata = pdev->dev.platform_data;
85         if (!pdata) {
86                 dev_err(&pdev->dev, "No platform data defined\n");
87                 return -EINVAL;
88         }
89
90         exynos_ohci = kzalloc(sizeof(struct exynos_ohci_hcd), GFP_KERNEL);
91         if (!exynos_ohci)
92                 return -ENOMEM;
93
94         exynos_ohci->dev = &pdev->dev;
95
96         hcd = usb_create_hcd(&exynos_ohci_hc_driver, &pdev->dev,
97                                         dev_name(&pdev->dev));
98         if (!hcd) {
99                 dev_err(&pdev->dev, "Unable to create HCD\n");
100                 err = -ENOMEM;
101                 goto fail_hcd;
102         }
103
104         exynos_ohci->hcd = hcd;
105         exynos_ohci->clk = clk_get(&pdev->dev, "usbhost");
106
107         if (IS_ERR(exynos_ohci->clk)) {
108                 dev_err(&pdev->dev, "Failed to get usbhost clock\n");
109                 err = PTR_ERR(exynos_ohci->clk);
110                 goto fail_clk;
111         }
112
113         err = clk_enable(exynos_ohci->clk);
114         if (err)
115                 goto fail_clken;
116
117         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
118         if (!res) {
119                 dev_err(&pdev->dev, "Failed to get I/O memory\n");
120                 err = -ENXIO;
121                 goto fail_io;
122         }
123
124         hcd->rsrc_start = res->start;
125         hcd->rsrc_len = resource_size(res);
126         hcd->regs = ioremap(res->start, resource_size(res));
127         if (!hcd->regs) {
128                 dev_err(&pdev->dev, "Failed to remap I/O memory\n");
129                 err = -ENOMEM;
130                 goto fail_io;
131         }
132
133         irq = platform_get_irq(pdev, 0);
134         if (!irq) {
135                 dev_err(&pdev->dev, "Failed to get IRQ\n");
136                 err = -ENODEV;
137                 goto fail;
138         }
139
140         if (pdata->phy_init)
141                 pdata->phy_init(pdev, S5P_USB_PHY_HOST);
142
143         ohci = hcd_to_ohci(hcd);
144         ohci_hcd_init(ohci);
145
146         err = usb_add_hcd(hcd, irq, IRQF_SHARED);
147         if (err) {
148                 dev_err(&pdev->dev, "Failed to add USB HCD\n");
149                 goto fail;
150         }
151
152         platform_set_drvdata(pdev, exynos_ohci);
153
154         return 0;
155
156 fail:
157         iounmap(hcd->regs);
158 fail_io:
159         clk_disable(exynos_ohci->clk);
160 fail_clken:
161         clk_put(exynos_ohci->clk);
162 fail_clk:
163         usb_put_hcd(hcd);
164 fail_hcd:
165         kfree(exynos_ohci);
166         return err;
167 }
168
169 static int __devexit exynos_ohci_remove(struct platform_device *pdev)
170 {
171         struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
172         struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev);
173         struct usb_hcd *hcd = exynos_ohci->hcd;
174
175         usb_remove_hcd(hcd);
176
177         if (pdata && pdata->phy_exit)
178                 pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
179
180         iounmap(hcd->regs);
181
182         clk_disable(exynos_ohci->clk);
183         clk_put(exynos_ohci->clk);
184
185         usb_put_hcd(hcd);
186         kfree(exynos_ohci);
187
188         return 0;
189 }
190
191 static void exynos_ohci_shutdown(struct platform_device *pdev)
192 {
193         struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev);
194         struct usb_hcd *hcd = exynos_ohci->hcd;
195
196         if (hcd->driver->shutdown)
197                 hcd->driver->shutdown(hcd);
198 }
199
200 #ifdef CONFIG_PM
201 static int exynos_ohci_suspend(struct device *dev)
202 {
203         struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev);
204         struct usb_hcd *hcd = exynos_ohci->hcd;
205         struct ohci_hcd *ohci = hcd_to_ohci(hcd);
206         struct platform_device *pdev = to_platform_device(dev);
207         struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
208         unsigned long flags;
209         int rc = 0;
210
211         /*
212          * Root hub was already suspended. Disable irq emission and
213          * mark HW unaccessible, bail out if RH has been resumed. Use
214          * the spinlock to properly synchronize with possible pending
215          * RH suspend or resume activity.
216          */
217         spin_lock_irqsave(&ohci->lock, flags);
218         if (ohci->rh_state != OHCI_RH_SUSPENDED &&
219                         ohci->rh_state != OHCI_RH_HALTED) {
220                 rc = -EINVAL;
221                 goto fail;
222         }
223
224         clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
225
226         if (pdata && pdata->phy_exit)
227                 pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
228
229         clk_disable(exynos_ohci->clk);
230
231 fail:
232         spin_unlock_irqrestore(&ohci->lock, flags);
233
234         return rc;
235 }
236
237 static int exynos_ohci_resume(struct device *dev)
238 {
239         struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev);
240         struct usb_hcd *hcd = exynos_ohci->hcd;
241         struct platform_device *pdev = to_platform_device(dev);
242         struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data;
243
244         clk_enable(exynos_ohci->clk);
245
246         if (pdata && pdata->phy_init)
247                 pdata->phy_init(pdev, S5P_USB_PHY_HOST);
248
249         /* Mark hardware accessible again as we are out of D3 state by now */
250         set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
251
252         ohci_finish_controller_resume(hcd);
253
254         return 0;
255 }
256 #else
257 #define exynos_ohci_suspend     NULL
258 #define exynos_ohci_resume      NULL
259 #endif
260
261 static const struct dev_pm_ops exynos_ohci_pm_ops = {
262         .suspend        = exynos_ohci_suspend,
263         .resume         = exynos_ohci_resume,
264 };
265
266 static struct platform_driver exynos_ohci_driver = {
267         .probe          = exynos_ohci_probe,
268         .remove         = __devexit_p(exynos_ohci_remove),
269         .shutdown       = exynos_ohci_shutdown,
270         .driver = {
271                 .name   = "exynos-ohci",
272                 .owner  = THIS_MODULE,
273                 .pm     = &exynos_ohci_pm_ops,
274         }
275 };
276
277 MODULE_ALIAS("platform:exynos-ohci");
278 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");