1 /*
2  * SAMSUNG S5P USB HOST EHCI Controller
3  *
4  * Copyright (C) 2011 Samsung Electronics Co.Ltd
5  * Author: Jingoo Han <jg1.han@samsung.com>
6  * Author: Joonyoung Shim <jy0922.shim@samsung.com>
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 
15 #include <linux/clk.h>
16 #include <linux/platform_device.h>
17 #include <plat/ehci.h>
18 #include <plat/usb-phy.h>
19 
20 struct s5p_ehci_hcd {
21 	struct device *dev;
22 	struct usb_hcd *hcd;
23 	struct clk *clk;
24 };
25 
26 static const struct hc_driver s5p_ehci_hc_driver = {
27 	.description		= hcd_name,
28 	.product_desc		= "S5P EHCI Host Controller",
29 	.hcd_priv_size		= sizeof(struct ehci_hcd),
30 
31 	.irq			= ehci_irq,
32 	.flags			= HCD_MEMORY | HCD_USB2,
33 
34 	.reset			= ehci_init,
35 	.start			= ehci_run,
36 	.stop			= ehci_stop,
37 	.shutdown		= ehci_shutdown,
38 
39 	.get_frame_number	= ehci_get_frame,
40 
41 	.urb_enqueue		= ehci_urb_enqueue,
42 	.urb_dequeue		= ehci_urb_dequeue,
43 	.endpoint_disable	= ehci_endpoint_disable,
44 	.endpoint_reset		= ehci_endpoint_reset,
45 
46 	.hub_status_data	= ehci_hub_status_data,
47 	.hub_control		= ehci_hub_control,
48 	.bus_suspend		= ehci_bus_suspend,
49 	.bus_resume		= ehci_bus_resume,
50 
51 	.relinquish_port	= ehci_relinquish_port,
52 	.port_handed_over	= ehci_port_handed_over,
53 
54 	.clear_tt_buffer_complete	= ehci_clear_tt_buffer_complete,
55 };
56 
s5p_ehci_probe(struct platform_device * pdev)57 static int __devinit s5p_ehci_probe(struct platform_device *pdev)
58 {
59 	struct s5p_ehci_platdata *pdata;
60 	struct s5p_ehci_hcd *s5p_ehci;
61 	struct usb_hcd *hcd;
62 	struct ehci_hcd *ehci;
63 	struct resource *res;
64 	int irq;
65 	int err;
66 
67 	pdata = pdev->dev.platform_data;
68 	if (!pdata) {
69 		dev_err(&pdev->dev, "No platform data defined\n");
70 		return -EINVAL;
71 	}
72 
73 	s5p_ehci = kzalloc(sizeof(struct s5p_ehci_hcd), GFP_KERNEL);
74 	if (!s5p_ehci)
75 		return -ENOMEM;
76 
77 	s5p_ehci->dev = &pdev->dev;
78 
79 	hcd = usb_create_hcd(&s5p_ehci_hc_driver, &pdev->dev,
80 					dev_name(&pdev->dev));
81 	if (!hcd) {
82 		dev_err(&pdev->dev, "Unable to create HCD\n");
83 		err = -ENOMEM;
84 		goto fail_hcd;
85 	}
86 
87 	s5p_ehci->hcd = hcd;
88 	s5p_ehci->clk = clk_get(&pdev->dev, "usbhost");
89 
90 	if (IS_ERR(s5p_ehci->clk)) {
91 		dev_err(&pdev->dev, "Failed to get usbhost clock\n");
92 		err = PTR_ERR(s5p_ehci->clk);
93 		goto fail_clk;
94 	}
95 
96 	err = clk_enable(s5p_ehci->clk);
97 	if (err)
98 		goto fail_clken;
99 
100 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
101 	if (!res) {
102 		dev_err(&pdev->dev, "Failed to get I/O memory\n");
103 		err = -ENXIO;
104 		goto fail_io;
105 	}
106 
107 	hcd->rsrc_start = res->start;
108 	hcd->rsrc_len = resource_size(res);
109 	hcd->regs = ioremap(res->start, resource_size(res));
110 	if (!hcd->regs) {
111 		dev_err(&pdev->dev, "Failed to remap I/O memory\n");
112 		err = -ENOMEM;
113 		goto fail_io;
114 	}
115 
116 	irq = platform_get_irq(pdev, 0);
117 	if (!irq) {
118 		dev_err(&pdev->dev, "Failed to get IRQ\n");
119 		err = -ENODEV;
120 		goto fail;
121 	}
122 
123 	if (pdata->phy_init)
124 		pdata->phy_init(pdev, S5P_USB_PHY_HOST);
125 
126 	ehci = hcd_to_ehci(hcd);
127 	ehci->caps = hcd->regs;
128 	ehci->regs = hcd->regs +
129 		HC_LENGTH(ehci, readl(&ehci->caps->hc_capbase));
130 
131 	dbg_hcs_params(ehci, "reset");
132 	dbg_hcc_params(ehci, "reset");
133 
134 	/* cache this readonly data; minimize chip reads */
135 	ehci->hcs_params = readl(&ehci->caps->hcs_params);
136 
137 	ehci_reset(ehci);
138 
139 	err = usb_add_hcd(hcd, irq, IRQF_SHARED);
140 	if (err) {
141 		dev_err(&pdev->dev, "Failed to add USB HCD\n");
142 		goto fail;
143 	}
144 
145 	platform_set_drvdata(pdev, s5p_ehci);
146 
147 	return 0;
148 
149 fail:
150 	iounmap(hcd->regs);
151 fail_io:
152 	clk_disable(s5p_ehci->clk);
153 fail_clken:
154 	clk_put(s5p_ehci->clk);
155 fail_clk:
156 	usb_put_hcd(hcd);
157 fail_hcd:
158 	kfree(s5p_ehci);
159 	return err;
160 }
161 
s5p_ehci_remove(struct platform_device * pdev)162 static int __devexit s5p_ehci_remove(struct platform_device *pdev)
163 {
164 	struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
165 	struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
166 	struct usb_hcd *hcd = s5p_ehci->hcd;
167 
168 	usb_remove_hcd(hcd);
169 
170 	if (pdata && pdata->phy_exit)
171 		pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
172 
173 	iounmap(hcd->regs);
174 
175 	clk_disable(s5p_ehci->clk);
176 	clk_put(s5p_ehci->clk);
177 
178 	usb_put_hcd(hcd);
179 	kfree(s5p_ehci);
180 
181 	return 0;
182 }
183 
s5p_ehci_shutdown(struct platform_device * pdev)184 static void s5p_ehci_shutdown(struct platform_device *pdev)
185 {
186 	struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
187 	struct usb_hcd *hcd = s5p_ehci->hcd;
188 
189 	if (hcd->driver->shutdown)
190 		hcd->driver->shutdown(hcd);
191 }
192 
193 #ifdef CONFIG_PM
s5p_ehci_suspend(struct device * dev)194 static int s5p_ehci_suspend(struct device *dev)
195 {
196 	struct s5p_ehci_hcd *s5p_ehci = dev_get_drvdata(dev);
197 	struct usb_hcd *hcd = s5p_ehci->hcd;
198 	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
199 	struct platform_device *pdev = to_platform_device(dev);
200 	struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
201 	unsigned long flags;
202 	int rc = 0;
203 
204 	if (time_before(jiffies, ehci->next_statechange))
205 		msleep(20);
206 
207 	/*
208 	 * Root hub was already suspended. Disable irq emission and
209 	 * mark HW unaccessible.  The PM and USB cores make sure that
210 	 * the root hub is either suspended or stopped.
211 	 */
212 	ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev));
213 	spin_lock_irqsave(&ehci->lock, flags);
214 	ehci_writel(ehci, 0, &ehci->regs->intr_enable);
215 	(void)ehci_readl(ehci, &ehci->regs->intr_enable);
216 
217 	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
218 	spin_unlock_irqrestore(&ehci->lock, flags);
219 
220 	if (pdata && pdata->phy_exit)
221 		pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
222 
223 	return rc;
224 }
225 
s5p_ehci_resume(struct device * dev)226 static int s5p_ehci_resume(struct device *dev)
227 {
228 	struct s5p_ehci_hcd *s5p_ehci = dev_get_drvdata(dev);
229 	struct usb_hcd *hcd = s5p_ehci->hcd;
230 	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
231 	struct platform_device *pdev = to_platform_device(dev);
232 	struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
233 
234 	if (pdata && pdata->phy_init)
235 		pdata->phy_init(pdev, S5P_USB_PHY_HOST);
236 
237 	if (time_before(jiffies, ehci->next_statechange))
238 		msleep(100);
239 
240 	/* Mark hardware accessible again as we are out of D3 state by now */
241 	set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
242 
243 	if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
244 		int	mask = INTR_MASK;
245 
246 		ehci_prepare_ports_for_controller_resume(ehci);
247 		if (!hcd->self.root_hub->do_remote_wakeup)
248 			mask &= ~STS_PCD;
249 		ehci_writel(ehci, mask, &ehci->regs->intr_enable);
250 		ehci_readl(ehci, &ehci->regs->intr_enable);
251 		return 0;
252 	}
253 
254 	usb_root_hub_lost_power(hcd->self.root_hub);
255 
256 	(void) ehci_halt(ehci);
257 	(void) ehci_reset(ehci);
258 
259 	/* emptying the schedule aborts any urbs */
260 	spin_lock_irq(&ehci->lock);
261 	if (ehci->reclaim)
262 		end_unlink_async(ehci);
263 	ehci_work(ehci);
264 	spin_unlock_irq(&ehci->lock);
265 
266 	ehci_writel(ehci, ehci->command, &ehci->regs->command);
267 	ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
268 	ehci_readl(ehci, &ehci->regs->command);	/* unblock posted writes */
269 
270 	/* here we "know" root ports should always stay powered */
271 	ehci_port_power(ehci, 1);
272 
273 	ehci->rh_state = EHCI_RH_SUSPENDED;
274 
275 	return 0;
276 }
277 #else
278 #define s5p_ehci_suspend	NULL
279 #define s5p_ehci_resume		NULL
280 #endif
281 
282 static const struct dev_pm_ops s5p_ehci_pm_ops = {
283 	.suspend	= s5p_ehci_suspend,
284 	.resume		= s5p_ehci_resume,
285 };
286 
287 static struct platform_driver s5p_ehci_driver = {
288 	.probe		= s5p_ehci_probe,
289 	.remove		= __devexit_p(s5p_ehci_remove),
290 	.shutdown	= s5p_ehci_shutdown,
291 	.driver = {
292 		.name	= "s5p-ehci",
293 		.owner	= THIS_MODULE,
294 		.pm	= &s5p_ehci_pm_ops,
295 	}
296 };
297 
298 MODULE_ALIAS("platform:s5p-ehci");
299