blob: 5a15e3dd006b98e4c26ea7b6c850952fef1a05da [file] [log] [blame]
Nicolas Ferre501c9c02009-07-27 14:47:40 -07001/*
2 * Driver for EHCI UHP on Atmel chips
3 *
4 * Copyright (C) 2009 Atmel Corporation,
5 * Nicolas Ferre <nicolas.ferre@atmel.com>
6 *
7 * Based on various ehci-*.c drivers
8 *
9 * This file is subject to the terms and conditions of the GNU General Public
10 * License. See the file COPYING in the main directory of this archive for
11 * more details.
12 */
13
14#include <linux/clk.h>
Manjunath Goudar97736962013-04-02 18:24:02 +020015#include <linux/dma-mapping.h>
16#include <linux/io.h>
17#include <linux/kernel.h>
18#include <linux/module.h>
Nicolas Ferre94753752012-03-27 18:23:31 +020019#include <linux/of.h>
Jean-Christophe PLAGNIOL-VILLARD9d843002011-11-22 12:11:13 +080020#include <linux/of_platform.h>
Manjunath Goudar97736962013-04-02 18:24:02 +020021#include <linux/platform_device.h>
22#include <linux/usb.h>
23#include <linux/usb/hcd.h>
24
25#include "ehci.h"
26
27#define DRIVER_DESC "EHCI Atmel driver"
28
29static const char hcd_name[] = "ehci-atmel";
30static struct hc_driver __read_mostly ehci_atmel_hc_driver;
Nicolas Ferre501c9c02009-07-27 14:47:40 -070031
32/* interface and function clocks */
Boris BREZILLON0d768fcf2013-10-18 21:26:51 +020033static struct clk *iclk, *fclk, *uclk;
Nicolas Ferre501c9c02009-07-27 14:47:40 -070034static int clocked;
35
36/*-------------------------------------------------------------------------*/
37
38static void atmel_start_clock(void)
39{
Sylvain Rochetca2c1dc2015-01-20 14:38:59 +010040 if (clocked)
41 return;
Boris BREZILLON0d768fcf2013-10-18 21:26:51 +020042 if (IS_ENABLED(CONFIG_COMMON_CLK)) {
43 clk_set_rate(uclk, 48000000);
44 clk_prepare_enable(uclk);
45 }
Boris BREZILLONcfafb622013-06-19 13:20:09 +020046 clk_prepare_enable(iclk);
47 clk_prepare_enable(fclk);
Nicolas Ferre501c9c02009-07-27 14:47:40 -070048 clocked = 1;
49}
50
51static void atmel_stop_clock(void)
52{
Sylvain Rochetca2c1dc2015-01-20 14:38:59 +010053 if (!clocked)
54 return;
Boris BREZILLONcfafb622013-06-19 13:20:09 +020055 clk_disable_unprepare(fclk);
56 clk_disable_unprepare(iclk);
Boris BREZILLON0d768fcf2013-10-18 21:26:51 +020057 if (IS_ENABLED(CONFIG_COMMON_CLK))
58 clk_disable_unprepare(uclk);
Nicolas Ferre501c9c02009-07-27 14:47:40 -070059 clocked = 0;
60}
61
62static void atmel_start_ehci(struct platform_device *pdev)
63{
64 dev_dbg(&pdev->dev, "start\n");
65 atmel_start_clock();
66}
67
68static void atmel_stop_ehci(struct platform_device *pdev)
69{
70 dev_dbg(&pdev->dev, "stop\n");
71 atmel_stop_clock();
72}
73
74/*-------------------------------------------------------------------------*/
75
Bill Pemberton41ac7b32012-11-19 13:21:48 -050076static int ehci_atmel_drv_probe(struct platform_device *pdev)
Nicolas Ferre501c9c02009-07-27 14:47:40 -070077{
78 struct usb_hcd *hcd;
79 const struct hc_driver *driver = &ehci_atmel_hc_driver;
80 struct resource *res;
Manjunath Goudar97736962013-04-02 18:24:02 +020081 struct ehci_hcd *ehci;
Nicolas Ferre501c9c02009-07-27 14:47:40 -070082 int irq;
83 int retval;
84
85 if (usb_disabled())
86 return -ENODEV;
87
88 pr_debug("Initializing Atmel-SoC USB Host Controller\n");
89
90 irq = platform_get_irq(pdev, 0);
91 if (irq <= 0) {
92 dev_err(&pdev->dev,
93 "Found HC with no IRQ. Check %s setup!\n",
94 dev_name(&pdev->dev));
95 retval = -ENODEV;
96 goto fail_create_hcd;
97 }
98
Jean-Christophe PLAGNIOL-VILLARD9d843002011-11-22 12:11:13 +080099 /* Right now device-tree probed devices don't get dma_mask set.
100 * Since shared usb code relies on it, set it here for now.
101 * Once we have dma capability bindings this can go away.
102 */
Russell Kinge1fd7342013-06-27 12:36:37 +0100103 retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
Russell King22d9d8e2013-06-10 16:28:49 +0100104 if (retval)
105 goto fail_create_hcd;
Jean-Christophe PLAGNIOL-VILLARD9d843002011-11-22 12:11:13 +0800106
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700107 hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
108 if (!hcd) {
109 retval = -ENOMEM;
110 goto fail_create_hcd;
111 }
112
113 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Thierry Reding148e1132013-01-21 11:09:22 +0100114 hcd->regs = devm_ioremap_resource(&pdev->dev, res);
115 if (IS_ERR(hcd->regs)) {
116 retval = PTR_ERR(hcd->regs);
Julia Lawall97983432012-07-29 21:46:05 +0200117 goto fail_request_resource;
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700118 }
119
Varka Bhadram0fb5f702014-11-04 07:51:08 +0530120 hcd->rsrc_start = res->start;
121 hcd->rsrc_len = resource_size(res);
122
Julia Lawall97983432012-07-29 21:46:05 +0200123 iclk = devm_clk_get(&pdev->dev, "ehci_clk");
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700124 if (IS_ERR(iclk)) {
125 dev_err(&pdev->dev, "Error getting interface clock\n");
126 retval = -ENOENT;
Julia Lawall97983432012-07-29 21:46:05 +0200127 goto fail_request_resource;
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700128 }
Julia Lawall97983432012-07-29 21:46:05 +0200129 fclk = devm_clk_get(&pdev->dev, "uhpck");
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700130 if (IS_ERR(fclk)) {
131 dev_err(&pdev->dev, "Error getting function clock\n");
132 retval = -ENOENT;
Julia Lawall97983432012-07-29 21:46:05 +0200133 goto fail_request_resource;
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700134 }
Boris BREZILLON0d768fcf2013-10-18 21:26:51 +0200135 if (IS_ENABLED(CONFIG_COMMON_CLK)) {
136 uclk = devm_clk_get(&pdev->dev, "usb_clk");
137 if (IS_ERR(uclk)) {
138 dev_err(&pdev->dev, "failed to get uclk\n");
139 retval = PTR_ERR(uclk);
140 goto fail_request_resource;
141 }
142 }
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700143
Manjunath Goudar97736962013-04-02 18:24:02 +0200144 ehci = hcd_to_ehci(hcd);
145 /* registers start at offset 0x0 */
146 ehci->caps = hcd->regs;
147
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700148 atmel_start_ehci(pdev);
149
150 retval = usb_add_hcd(hcd, irq, IRQF_SHARED);
151 if (retval)
152 goto fail_add_hcd;
Peter Chen3c9740a2013-11-05 10:46:02 +0800153 device_wakeup_enable(hcd->self.controller);
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700154
155 return retval;
156
157fail_add_hcd:
158 atmel_stop_ehci(pdev);
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700159fail_request_resource:
160 usb_put_hcd(hcd);
161fail_create_hcd:
162 dev_err(&pdev->dev, "init %s fail, %d\n",
163 dev_name(&pdev->dev), retval);
164
165 return retval;
166}
167
Bill Pembertonfb4e98a2012-11-19 13:26:20 -0500168static int ehci_atmel_drv_remove(struct platform_device *pdev)
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700169{
170 struct usb_hcd *hcd = platform_get_drvdata(pdev);
171
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700172 usb_remove_hcd(hcd);
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700173 usb_put_hcd(hcd);
174
175 atmel_stop_ehci(pdev);
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700176 fclk = iclk = NULL;
177
178 return 0;
179}
180
Sylvain Rochetca2c1dc2015-01-20 14:38:59 +0100181#ifdef CONFIG_PM
182static int ehci_atmel_drv_suspend(struct device *dev)
183{
184 struct usb_hcd *hcd = dev_get_drvdata(dev);
185 int ret;
186
187 ret = ehci_suspend(hcd, false);
188 if (ret)
189 return ret;
190
191 atmel_stop_clock();
192 return 0;
193}
194
195static int ehci_atmel_drv_resume(struct device *dev)
196{
197 struct usb_hcd *hcd = dev_get_drvdata(dev);
198
199 atmel_start_clock();
200 return ehci_resume(hcd, false);
201}
202#endif
203
Jean-Christophe PLAGNIOL-VILLARD9d843002011-11-22 12:11:13 +0800204#ifdef CONFIG_OF
205static const struct of_device_id atmel_ehci_dt_ids[] = {
206 { .compatible = "atmel,at91sam9g45-ehci" },
207 { /* sentinel */ }
208};
209
210MODULE_DEVICE_TABLE(of, atmel_ehci_dt_ids);
211#endif
212
Sylvain Rochetca2c1dc2015-01-20 14:38:59 +0100213static SIMPLE_DEV_PM_OPS(ehci_atmel_pm_ops, ehci_atmel_drv_suspend,
214 ehci_atmel_drv_resume);
215
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700216static struct platform_driver ehci_atmel_driver = {
217 .probe = ehci_atmel_drv_probe,
Bill Pemberton76904172012-11-19 13:21:08 -0500218 .remove = ehci_atmel_drv_remove,
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700219 .shutdown = usb_hcd_platform_shutdown,
Jean-Christophe PLAGNIOL-VILLARD9d843002011-11-22 12:11:13 +0800220 .driver = {
221 .name = "atmel-ehci",
Sylvain Rochetca2c1dc2015-01-20 14:38:59 +0100222 .pm = &ehci_atmel_pm_ops,
Jean-Christophe PLAGNIOL-VILLARD9d843002011-11-22 12:11:13 +0800223 .of_match_table = of_match_ptr(atmel_ehci_dt_ids),
224 },
Nicolas Ferre501c9c02009-07-27 14:47:40 -0700225};
Manjunath Goudar97736962013-04-02 18:24:02 +0200226
227static int __init ehci_atmel_init(void)
228{
229 if (usb_disabled())
230 return -ENODEV;
231
232 pr_info("%s: " DRIVER_DESC "\n", hcd_name);
233 ehci_init_driver(&ehci_atmel_hc_driver, NULL);
234 return platform_driver_register(&ehci_atmel_driver);
235}
236module_init(ehci_atmel_init);
237
238static void __exit ehci_atmel_cleanup(void)
239{
240 platform_driver_unregister(&ehci_atmel_driver);
241}
242module_exit(ehci_atmel_cleanup);
243
244MODULE_DESCRIPTION(DRIVER_DESC);
245MODULE_ALIAS("platform:atmel-ehci");
246MODULE_AUTHOR("Nicolas Ferre");
247MODULE_LICENSE("GPL");