diff options
Diffstat (limited to 'drivers/usb/host/ehci-omap.c')
-rw-r--r-- | drivers/usb/host/ehci-omap.c | 331 |
1 files changed, 144 insertions, 187 deletions
diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 0555ee42d7cb..5de3e43ded50 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c | |||
@@ -4,10 +4,11 @@ | |||
4 | * Bus Glue for the EHCI controllers in OMAP3/4 | 4 | * Bus Glue for the EHCI controllers in OMAP3/4 |
5 | * Tested on several OMAP3 boards, and OMAP4 Pandaboard | 5 | * Tested on several OMAP3 boards, and OMAP4 Pandaboard |
6 | * | 6 | * |
7 | * Copyright (C) 2007-2011 Texas Instruments, Inc. | 7 | * Copyright (C) 2007-2013 Texas Instruments, Inc. |
8 | * Author: Vikram Pandita <vikram.pandita@ti.com> | 8 | * Author: Vikram Pandita <vikram.pandita@ti.com> |
9 | * Author: Anand Gadiyar <gadiyar@ti.com> | 9 | * Author: Anand Gadiyar <gadiyar@ti.com> |
10 | * Author: Keshava Munegowda <keshava_mgowda@ti.com> | 10 | * Author: Keshava Munegowda <keshava_mgowda@ti.com> |
11 | * Author: Roger Quadros <rogerq@ti.com> | ||
11 | * | 12 | * |
12 | * Copyright (C) 2009 Nokia Corporation | 13 | * Copyright (C) 2009 Nokia Corporation |
13 | * Contact: Felipe Balbi <felipe.balbi@nokia.com> | 14 | * Contact: Felipe Balbi <felipe.balbi@nokia.com> |
@@ -28,21 +29,23 @@ | |||
28 | * along with this program; if not, write to the Free Software | 29 | * along with this program; if not, write to the Free Software |
29 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 30 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
30 | * | 31 | * |
31 | * TODO (last updated Feb 27, 2010): | ||
32 | * - add kernel-doc | ||
33 | * - enable AUTOIDLE | ||
34 | * - add suspend/resume | ||
35 | * - add HSIC and TLL support | ||
36 | * - convert to use hwmod and runtime PM | ||
37 | */ | 32 | */ |
38 | 33 | ||
34 | #include <linux/kernel.h> | ||
35 | #include <linux/module.h> | ||
36 | #include <linux/io.h> | ||
39 | #include <linux/platform_device.h> | 37 | #include <linux/platform_device.h> |
40 | #include <linux/slab.h> | 38 | #include <linux/slab.h> |
41 | #include <linux/usb/ulpi.h> | 39 | #include <linux/usb/ulpi.h> |
42 | #include <linux/regulator/consumer.h> | ||
43 | #include <linux/pm_runtime.h> | 40 | #include <linux/pm_runtime.h> |
44 | #include <linux/gpio.h> | 41 | #include <linux/gpio.h> |
45 | #include <linux/clk.h> | 42 | #include <linux/clk.h> |
43 | #include <linux/usb.h> | ||
44 | #include <linux/usb/hcd.h> | ||
45 | #include <linux/of.h> | ||
46 | #include <linux/dma-mapping.h> | ||
47 | |||
48 | #include "ehci.h" | ||
46 | 49 | ||
47 | #include <linux/platform_data/usb-omap.h> | 50 | #include <linux/platform_data/usb-omap.h> |
48 | 51 | ||
@@ -57,10 +60,16 @@ | |||
57 | #define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8 | 60 | #define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8 |
58 | #define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0 | 61 | #define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0 |
59 | 62 | ||
60 | /*-------------------------------------------------------------------------*/ | 63 | #define DRIVER_DESC "OMAP-EHCI Host Controller driver" |
64 | |||
65 | static const char hcd_name[] = "ehci-omap"; | ||
61 | 66 | ||
62 | static const struct hc_driver ehci_omap_hc_driver; | 67 | /*-------------------------------------------------------------------------*/ |
63 | 68 | ||
69 | struct omap_hcd { | ||
70 | struct usb_phy *phy[OMAP3_HS_USB_PORTS]; /* one PHY for each port */ | ||
71 | int nports; | ||
72 | }; | ||
64 | 73 | ||
65 | static inline void ehci_write(void __iomem *base, u32 reg, u32 val) | 74 | static inline void ehci_write(void __iomem *base, u32 reg, u32 val) |
66 | { | 75 | { |
@@ -72,99 +81,16 @@ static inline u32 ehci_read(void __iomem *base, u32 reg) | |||
72 | return __raw_readl(base + reg); | 81 | return __raw_readl(base + reg); |
73 | } | 82 | } |
74 | 83 | ||
84 | /* configure so an HC device and id are always provided */ | ||
85 | /* always called with process context; sleeping is OK */ | ||
75 | 86 | ||
76 | static void omap_ehci_soft_phy_reset(struct usb_hcd *hcd, u8 port) | 87 | static struct hc_driver __read_mostly ehci_omap_hc_driver; |
77 | { | ||
78 | unsigned long timeout = jiffies + msecs_to_jiffies(1000); | ||
79 | unsigned reg = 0; | ||
80 | |||
81 | reg = ULPI_FUNC_CTRL_RESET | ||
82 | /* FUNCTION_CTRL_SET register */ | ||
83 | | (ULPI_SET(ULPI_FUNC_CTRL) << EHCI_INSNREG05_ULPI_REGADD_SHIFT) | ||
84 | /* Write */ | ||
85 | | (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) | ||
86 | /* PORTn */ | ||
87 | | ((port + 1) << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) | ||
88 | /* start ULPI access*/ | ||
89 | | (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT); | ||
90 | |||
91 | ehci_write(hcd->regs, EHCI_INSNREG05_ULPI, reg); | ||
92 | |||
93 | /* Wait for ULPI access completion */ | ||
94 | while ((ehci_read(hcd->regs, EHCI_INSNREG05_ULPI) | ||
95 | & (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT))) { | ||
96 | cpu_relax(); | ||
97 | |||
98 | if (time_after(jiffies, timeout)) { | ||
99 | dev_dbg(hcd->self.controller, | ||
100 | "phy reset operation timed out\n"); | ||
101 | break; | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | static int omap_ehci_init(struct usb_hcd *hcd) | ||
107 | { | ||
108 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); | ||
109 | int rc; | ||
110 | struct usbhs_omap_platform_data *pdata; | ||
111 | |||
112 | pdata = hcd->self.controller->platform_data; | ||
113 | |||
114 | /* Hold PHYs in reset while initializing EHCI controller */ | ||
115 | if (pdata->phy_reset) { | ||
116 | if (gpio_is_valid(pdata->reset_gpio_port[0])) | ||
117 | gpio_set_value_cansleep(pdata->reset_gpio_port[0], 0); | ||
118 | |||
119 | if (gpio_is_valid(pdata->reset_gpio_port[1])) | ||
120 | gpio_set_value_cansleep(pdata->reset_gpio_port[1], 0); | ||
121 | |||
122 | /* Hold the PHY in RESET for enough time till DIR is high */ | ||
123 | udelay(10); | ||
124 | } | ||
125 | |||
126 | /* Soft reset the PHY using PHY reset command over ULPI */ | ||
127 | if (pdata->port_mode[0] == OMAP_EHCI_PORT_MODE_PHY) | ||
128 | omap_ehci_soft_phy_reset(hcd, 0); | ||
129 | if (pdata->port_mode[1] == OMAP_EHCI_PORT_MODE_PHY) | ||
130 | omap_ehci_soft_phy_reset(hcd, 1); | ||
131 | |||
132 | /* we know this is the memory we want, no need to ioremap again */ | ||
133 | ehci->caps = hcd->regs; | ||
134 | |||
135 | rc = ehci_setup(hcd); | ||
136 | |||
137 | if (pdata->phy_reset) { | ||
138 | /* Hold the PHY in RESET for enough time till | ||
139 | * PHY is settled and ready | ||
140 | */ | ||
141 | udelay(10); | ||
142 | |||
143 | if (gpio_is_valid(pdata->reset_gpio_port[0])) | ||
144 | gpio_set_value_cansleep(pdata->reset_gpio_port[0], 1); | ||
145 | |||
146 | if (gpio_is_valid(pdata->reset_gpio_port[1])) | ||
147 | gpio_set_value_cansleep(pdata->reset_gpio_port[1], 1); | ||
148 | } | ||
149 | |||
150 | return rc; | ||
151 | } | ||
152 | 88 | ||
153 | static void disable_put_regulator( | 89 | static const struct ehci_driver_overrides ehci_omap_overrides __initdata = { |
154 | struct usbhs_omap_platform_data *pdata) | 90 | .extra_priv_size = sizeof(struct omap_hcd), |
155 | { | 91 | }; |
156 | int i; | ||
157 | |||
158 | for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) { | ||
159 | if (pdata->regulator[i]) { | ||
160 | regulator_disable(pdata->regulator[i]); | ||
161 | regulator_put(pdata->regulator[i]); | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | 92 | ||
166 | /* configure so an HC device and id are always provided */ | 93 | static u64 omap_ehci_dma_mask = DMA_BIT_MASK(32); |
167 | /* always called with process context; sleeping is OK */ | ||
168 | 94 | ||
169 | /** | 95 | /** |
170 | * ehci_hcd_omap_probe - initialize TI-based HCDs | 96 | * ehci_hcd_omap_probe - initialize TI-based HCDs |
@@ -175,15 +101,15 @@ static void disable_put_regulator( | |||
175 | */ | 101 | */ |
176 | static int ehci_hcd_omap_probe(struct platform_device *pdev) | 102 | static int ehci_hcd_omap_probe(struct platform_device *pdev) |
177 | { | 103 | { |
178 | struct device *dev = &pdev->dev; | 104 | struct device *dev = &pdev->dev; |
179 | struct usbhs_omap_platform_data *pdata = dev->platform_data; | 105 | struct usbhs_omap_platform_data *pdata = dev->platform_data; |
180 | struct resource *res; | 106 | struct resource *res; |
181 | struct usb_hcd *hcd; | 107 | struct usb_hcd *hcd; |
182 | void __iomem *regs; | 108 | void __iomem *regs; |
183 | int ret = -ENODEV; | 109 | int ret = -ENODEV; |
184 | int irq; | 110 | int irq; |
185 | int i; | 111 | int i; |
186 | char supply[7]; | 112 | struct omap_hcd *omap; |
187 | 113 | ||
188 | if (usb_disabled()) | 114 | if (usb_disabled()) |
189 | return -ENODEV; | 115 | return -ENODEV; |
@@ -193,52 +119,74 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) | |||
193 | return -ENODEV; | 119 | return -ENODEV; |
194 | } | 120 | } |
195 | 121 | ||
196 | irq = platform_get_irq_byname(pdev, "ehci-irq"); | 122 | /* For DT boot, get platform data from parent. i.e. usbhshost */ |
197 | if (irq < 0) { | 123 | if (dev->of_node) { |
198 | dev_err(dev, "EHCI irq failed\n"); | 124 | pdata = dev->parent->platform_data; |
199 | return -ENODEV; | 125 | dev->platform_data = pdata; |
200 | } | 126 | } |
201 | 127 | ||
202 | res = platform_get_resource_byname(pdev, | 128 | if (!pdata) { |
203 | IORESOURCE_MEM, "ehci"); | 129 | dev_err(dev, "Missing platform data\n"); |
204 | if (!res) { | ||
205 | dev_err(dev, "UHH EHCI get resource failed\n"); | ||
206 | return -ENODEV; | 130 | return -ENODEV; |
207 | } | 131 | } |
208 | 132 | ||
209 | regs = ioremap(res->start, resource_size(res)); | 133 | irq = platform_get_irq(pdev, 0); |
210 | if (!regs) { | 134 | if (irq < 0) { |
211 | dev_err(dev, "UHH EHCI ioremap failed\n"); | 135 | dev_err(dev, "EHCI irq failed\n"); |
212 | return -ENOMEM; | 136 | return -ENODEV; |
213 | } | 137 | } |
214 | 138 | ||
139 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
140 | regs = devm_ioremap_resource(dev, res); | ||
141 | if (IS_ERR(regs)) | ||
142 | return PTR_ERR(regs); | ||
143 | |||
144 | /* | ||
145 | * Right now device-tree probed devices don't get dma_mask set. | ||
146 | * Since shared usb code relies on it, set it here for now. | ||
147 | * Once we have dma capability bindings this can go away. | ||
148 | */ | ||
149 | if (!pdev->dev.dma_mask) | ||
150 | pdev->dev.dma_mask = &omap_ehci_dma_mask; | ||
151 | |||
215 | hcd = usb_create_hcd(&ehci_omap_hc_driver, dev, | 152 | hcd = usb_create_hcd(&ehci_omap_hc_driver, dev, |
216 | dev_name(dev)); | 153 | dev_name(dev)); |
217 | if (!hcd) { | 154 | if (!hcd) { |
218 | dev_err(dev, "failed to create hcd with err %d\n", ret); | 155 | dev_err(dev, "Failed to create HCD\n"); |
219 | ret = -ENOMEM; | 156 | return -ENOMEM; |
220 | goto err_io; | ||
221 | } | 157 | } |
222 | 158 | ||
223 | hcd->rsrc_start = res->start; | 159 | hcd->rsrc_start = res->start; |
224 | hcd->rsrc_len = resource_size(res); | 160 | hcd->rsrc_len = resource_size(res); |
225 | hcd->regs = regs; | 161 | hcd->regs = regs; |
226 | 162 | hcd_to_ehci(hcd)->caps = regs; | |
227 | /* get ehci regulator and enable */ | 163 | |
228 | for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) { | 164 | omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv; |
229 | if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) { | 165 | omap->nports = pdata->nports; |
230 | pdata->regulator[i] = NULL; | 166 | |
231 | continue; | 167 | platform_set_drvdata(pdev, hcd); |
232 | } | 168 | |
233 | snprintf(supply, sizeof(supply), "hsusb%d", i); | 169 | /* get the PHY devices if needed */ |
234 | pdata->regulator[i] = regulator_get(dev, supply); | 170 | for (i = 0 ; i < omap->nports ; i++) { |
235 | if (IS_ERR(pdata->regulator[i])) { | 171 | struct usb_phy *phy; |
236 | pdata->regulator[i] = NULL; | 172 | |
237 | dev_dbg(dev, | 173 | /* get the PHY device */ |
238 | "failed to get ehci port%d regulator\n", i); | 174 | if (dev->of_node) |
239 | } else { | 175 | phy = devm_usb_get_phy_by_phandle(dev, "phys", i); |
240 | regulator_enable(pdata->regulator[i]); | 176 | else |
177 | phy = devm_usb_get_phy_dev(dev, i); | ||
178 | if (IS_ERR(phy) || !phy) { | ||
179 | /* Don't bail out if PHY is not absolutely necessary */ | ||
180 | if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) | ||
181 | continue; | ||
182 | |||
183 | ret = IS_ERR(phy) ? PTR_ERR(phy) : -ENODEV; | ||
184 | dev_err(dev, "Can't get PHY device for port %d: %d\n", | ||
185 | i, ret); | ||
186 | goto err_phy; | ||
241 | } | 187 | } |
188 | |||
189 | omap->phy[i] = phy; | ||
242 | } | 190 | } |
243 | 191 | ||
244 | pm_runtime_enable(dev); | 192 | pm_runtime_enable(dev); |
@@ -262,16 +210,34 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) | |||
262 | goto err_pm_runtime; | 210 | goto err_pm_runtime; |
263 | } | 211 | } |
264 | 212 | ||
213 | /* | ||
214 | * Bring PHYs out of reset. | ||
215 | * Even though HSIC mode is a PHY-less mode, the reset | ||
216 | * line exists between the chips and can be modelled | ||
217 | * as a PHY device for reset control. | ||
218 | */ | ||
219 | for (i = 0; i < omap->nports; i++) { | ||
220 | if (!omap->phy[i]) | ||
221 | continue; | ||
222 | |||
223 | usb_phy_init(omap->phy[i]); | ||
224 | /* bring PHY out of suspend */ | ||
225 | usb_phy_set_suspend(omap->phy[i], 0); | ||
226 | } | ||
265 | 227 | ||
266 | return 0; | 228 | return 0; |
267 | 229 | ||
268 | err_pm_runtime: | 230 | err_pm_runtime: |
269 | disable_put_regulator(pdata); | ||
270 | pm_runtime_put_sync(dev); | 231 | pm_runtime_put_sync(dev); |
232 | |||
233 | err_phy: | ||
234 | for (i = 0; i < omap->nports; i++) { | ||
235 | if (omap->phy[i]) | ||
236 | usb_phy_shutdown(omap->phy[i]); | ||
237 | } | ||
238 | |||
271 | usb_put_hcd(hcd); | 239 | usb_put_hcd(hcd); |
272 | 240 | ||
273 | err_io: | ||
274 | iounmap(regs); | ||
275 | return ret; | 241 | return ret; |
276 | } | 242 | } |
277 | 243 | ||
@@ -286,14 +252,19 @@ err_io: | |||
286 | */ | 252 | */ |
287 | static int ehci_hcd_omap_remove(struct platform_device *pdev) | 253 | static int ehci_hcd_omap_remove(struct platform_device *pdev) |
288 | { | 254 | { |
289 | struct device *dev = &pdev->dev; | 255 | struct device *dev = &pdev->dev; |
290 | struct usb_hcd *hcd = dev_get_drvdata(dev); | 256 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
257 | struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv; | ||
258 | int i; | ||
291 | 259 | ||
292 | usb_remove_hcd(hcd); | 260 | usb_remove_hcd(hcd); |
293 | disable_put_regulator(dev->platform_data); | ||
294 | iounmap(hcd->regs); | ||
295 | usb_put_hcd(hcd); | ||
296 | 261 | ||
262 | for (i = 0; i < omap->nports; i++) { | ||
263 | if (omap->phy[i]) | ||
264 | usb_phy_shutdown(omap->phy[i]); | ||
265 | } | ||
266 | |||
267 | usb_put_hcd(hcd); | ||
297 | pm_runtime_put_sync(dev); | 268 | pm_runtime_put_sync(dev); |
298 | pm_runtime_disable(dev); | 269 | pm_runtime_disable(dev); |
299 | 270 | ||
@@ -308,6 +279,13 @@ static void ehci_hcd_omap_shutdown(struct platform_device *pdev) | |||
308 | hcd->driver->shutdown(hcd); | 279 | hcd->driver->shutdown(hcd); |
309 | } | 280 | } |
310 | 281 | ||
282 | static const struct of_device_id omap_ehci_dt_ids[] = { | ||
283 | { .compatible = "ti,ehci-omap" }, | ||
284 | { } | ||
285 | }; | ||
286 | |||
287 | MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids); | ||
288 | |||
311 | static struct platform_driver ehci_hcd_omap_driver = { | 289 | static struct platform_driver ehci_hcd_omap_driver = { |
312 | .probe = ehci_hcd_omap_probe, | 290 | .probe = ehci_hcd_omap_probe, |
313 | .remove = ehci_hcd_omap_remove, | 291 | .remove = ehci_hcd_omap_remove, |
@@ -315,56 +293,35 @@ static struct platform_driver ehci_hcd_omap_driver = { | |||
315 | /*.suspend = ehci_hcd_omap_suspend, */ | 293 | /*.suspend = ehci_hcd_omap_suspend, */ |
316 | /*.resume = ehci_hcd_omap_resume, */ | 294 | /*.resume = ehci_hcd_omap_resume, */ |
317 | .driver = { | 295 | .driver = { |
318 | .name = "ehci-omap", | 296 | .name = hcd_name, |
297 | .of_match_table = of_match_ptr(omap_ehci_dt_ids), | ||
319 | } | 298 | } |
320 | }; | 299 | }; |
321 | 300 | ||
322 | /*-------------------------------------------------------------------------*/ | 301 | /*-------------------------------------------------------------------------*/ |
323 | 302 | ||
324 | static const struct hc_driver ehci_omap_hc_driver = { | 303 | static int __init ehci_omap_init(void) |
325 | .description = hcd_name, | 304 | { |
326 | .product_desc = "OMAP-EHCI Host Controller", | 305 | if (usb_disabled()) |
327 | .hcd_priv_size = sizeof(struct ehci_hcd), | 306 | return -ENODEV; |
328 | |||
329 | /* | ||
330 | * generic hardware linkage | ||
331 | */ | ||
332 | .irq = ehci_irq, | ||
333 | .flags = HCD_MEMORY | HCD_USB2, | ||
334 | |||
335 | /* | ||
336 | * basic lifecycle operations | ||
337 | */ | ||
338 | .reset = omap_ehci_init, | ||
339 | .start = ehci_run, | ||
340 | .stop = ehci_stop, | ||
341 | .shutdown = ehci_shutdown, | ||
342 | |||
343 | /* | ||
344 | * managing i/o requests and associated device resources | ||
345 | */ | ||
346 | .urb_enqueue = ehci_urb_enqueue, | ||
347 | .urb_dequeue = ehci_urb_dequeue, | ||
348 | .endpoint_disable = ehci_endpoint_disable, | ||
349 | .endpoint_reset = ehci_endpoint_reset, | ||
350 | 307 | ||
351 | /* | 308 | pr_info("%s: " DRIVER_DESC "\n", hcd_name); |
352 | * scheduling support | ||
353 | */ | ||
354 | .get_frame_number = ehci_get_frame, | ||
355 | 309 | ||
356 | /* | 310 | ehci_init_driver(&ehci_omap_hc_driver, &ehci_omap_overrides); |
357 | * root hub support | 311 | return platform_driver_register(&ehci_hcd_omap_driver); |
358 | */ | 312 | } |
359 | .hub_status_data = ehci_hub_status_data, | 313 | module_init(ehci_omap_init); |
360 | .hub_control = ehci_hub_control, | ||
361 | .bus_suspend = ehci_bus_suspend, | ||
362 | .bus_resume = ehci_bus_resume, | ||
363 | 314 | ||
364 | .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, | 315 | static void __exit ehci_omap_cleanup(void) |
365 | }; | 316 | { |
317 | platform_driver_unregister(&ehci_hcd_omap_driver); | ||
318 | } | ||
319 | module_exit(ehci_omap_cleanup); | ||
366 | 320 | ||
367 | MODULE_ALIAS("platform:ehci-omap"); | 321 | MODULE_ALIAS("platform:ehci-omap"); |
368 | MODULE_AUTHOR("Texas Instruments, Inc."); | 322 | MODULE_AUTHOR("Texas Instruments, Inc."); |
369 | MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>"); | 323 | MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>"); |
324 | MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); | ||
370 | 325 | ||
326 | MODULE_DESCRIPTION(DRIVER_DESC); | ||
327 | MODULE_LICENSE("GPL"); | ||