2017-11-03 18:28:30 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2009-10-14 16:44:14 +08:00
|
|
|
/*
|
2010-11-22 01:53:42 +08:00
|
|
|
* ehci-omap.c - driver for USBHOST on OMAP3/4 processors
|
2009-10-14 16:44:14 +08:00
|
|
|
*
|
2010-11-22 01:53:42 +08:00
|
|
|
* Bus Glue for the EHCI controllers in OMAP3/4
|
|
|
|
* Tested on several OMAP3 boards, and OMAP4 Pandaboard
|
2009-10-14 16:44:14 +08:00
|
|
|
*
|
2013-03-12 18:44:41 +08:00
|
|
|
* Copyright (C) 2007-2013 Texas Instruments, Inc.
|
2009-10-14 16:44:14 +08:00
|
|
|
* Author: Vikram Pandita <vikram.pandita@ti.com>
|
2010-11-22 01:53:42 +08:00
|
|
|
* Author: Anand Gadiyar <gadiyar@ti.com>
|
2011-03-01 22:38:21 +08:00
|
|
|
* Author: Keshava Munegowda <keshava_mgowda@ti.com>
|
2013-03-12 18:44:41 +08:00
|
|
|
* Author: Roger Quadros <rogerq@ti.com>
|
2009-10-14 16:44:14 +08:00
|
|
|
*
|
|
|
|
* Copyright (C) 2009 Nokia Corporation
|
|
|
|
* Contact: Felipe Balbi <felipe.balbi@nokia.com>
|
|
|
|
*
|
|
|
|
* Based on "ehci-fsl.c" and "ehci-au1xxx.c" ehci glue layers
|
|
|
|
*/
|
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/io.h>
|
2009-10-14 16:44:14 +08:00
|
|
|
#include <linux/platform_device.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2010-07-08 16:33:02 +08:00
|
|
|
#include <linux/usb/ulpi.h>
|
2011-10-11 15:52:11 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2012-03-19 14:42:47 +08:00
|
|
|
#include <linux/gpio.h>
|
2012-06-05 20:34:27 +08:00
|
|
|
#include <linux/clk.h>
|
2013-03-12 18:44:39 +08:00
|
|
|
#include <linux/usb.h>
|
|
|
|
#include <linux/usb/hcd.h>
|
2013-03-12 18:44:48 +08:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
2013-03-12 18:44:39 +08:00
|
|
|
|
|
|
|
#include "ehci.h"
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2012-10-25 05:26:19 +08:00
|
|
|
#include <linux/platform_data/usb-omap.h>
|
|
|
|
|
2009-10-14 16:44:14 +08:00
|
|
|
/* EHCI Register Set */
|
2010-05-06 22:39:48 +08:00
|
|
|
#define EHCI_INSNREG04 (0xA0)
|
|
|
|
#define EHCI_INSNREG04_DISABLE_UNSUSPEND (1 << 5)
|
2009-10-14 16:44:14 +08:00
|
|
|
#define EHCI_INSNREG05_ULPI (0xA4)
|
|
|
|
#define EHCI_INSNREG05_ULPI_CONTROL_SHIFT 31
|
|
|
|
#define EHCI_INSNREG05_ULPI_PORTSEL_SHIFT 24
|
|
|
|
#define EHCI_INSNREG05_ULPI_OPSEL_SHIFT 22
|
|
|
|
#define EHCI_INSNREG05_ULPI_REGADD_SHIFT 16
|
|
|
|
#define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8
|
|
|
|
#define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0
|
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
#define DRIVER_DESC "OMAP-EHCI Host Controller driver"
|
2010-11-22 01:53:42 +08:00
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
static const char hcd_name[] = "ehci-omap";
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
2010-11-22 01:53:42 +08:00
|
|
|
|
2013-03-12 18:44:41 +08:00
|
|
|
struct omap_hcd {
|
|
|
|
struct usb_phy *phy[OMAP3_HS_USB_PORTS]; /* one PHY for each port */
|
|
|
|
int nports;
|
|
|
|
};
|
2010-11-22 01:53:41 +08:00
|
|
|
|
2011-03-01 22:38:21 +08:00
|
|
|
static inline void ehci_write(void __iomem *base, u32 reg, u32 val)
|
2009-10-14 16:44:14 +08:00
|
|
|
{
|
|
|
|
__raw_writel(val, base + reg);
|
|
|
|
}
|
|
|
|
|
2011-03-01 22:38:21 +08:00
|
|
|
static inline u32 ehci_read(void __iomem *base, u32 reg)
|
2009-10-14 16:44:14 +08:00
|
|
|
{
|
|
|
|
return __raw_readl(base + reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* configure so an HC device and id are always provided */
|
|
|
|
/* always called with process context; sleeping is OK */
|
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
static struct hc_driver __read_mostly ehci_omap_hc_driver;
|
|
|
|
|
2016-04-28 01:28:32 +08:00
|
|
|
static const struct ehci_driver_overrides ehci_omap_overrides __initconst = {
|
2013-03-12 18:44:41 +08:00
|
|
|
.extra_priv_size = sizeof(struct omap_hcd),
|
2013-03-12 18:44:39 +08:00
|
|
|
};
|
|
|
|
|
2009-10-14 16:44:14 +08:00
|
|
|
/**
|
|
|
|
* ehci_hcd_omap_probe - initialize TI-based HCDs
|
|
|
|
*
|
|
|
|
* Allocates basic resources for this USB host controller, and
|
|
|
|
* then invokes the start() method for the HCD associated with it
|
|
|
|
* through the hotplug entry's driver_data.
|
|
|
|
*/
|
|
|
|
static int ehci_hcd_omap_probe(struct platform_device *pdev)
|
|
|
|
{
|
2013-03-12 18:44:41 +08:00
|
|
|
struct device *dev = &pdev->dev;
|
2013-07-30 18:59:40 +08:00
|
|
|
struct usbhs_omap_platform_data *pdata = dev_get_platdata(dev);
|
2013-03-12 18:44:41 +08:00
|
|
|
struct resource *res;
|
|
|
|
struct usb_hcd *hcd;
|
|
|
|
void __iomem *regs;
|
2013-06-10 23:28:49 +08:00
|
|
|
int ret;
|
2013-03-12 18:44:41 +08:00
|
|
|
int irq;
|
|
|
|
int i;
|
|
|
|
struct omap_hcd *omap;
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2011-03-01 22:38:21 +08:00
|
|
|
if (usb_disabled())
|
|
|
|
return -ENODEV;
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2011-03-01 22:38:21 +08:00
|
|
|
if (!dev->parent) {
|
|
|
|
dev_err(dev, "Missing parent device\n");
|
|
|
|
return -ENODEV;
|
2009-10-14 16:44:14 +08:00
|
|
|
}
|
|
|
|
|
2013-03-12 18:44:48 +08:00
|
|
|
/* For DT boot, get platform data from parent. i.e. usbhshost */
|
|
|
|
if (dev->of_node) {
|
2013-07-30 18:59:40 +08:00
|
|
|
pdata = dev_get_platdata(dev->parent);
|
2013-03-12 18:44:48 +08:00
|
|
|
dev->platform_data = pdata;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pdata) {
|
|
|
|
dev_err(dev, "Missing platform data\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2013-03-12 18:44:45 +08:00
|
|
|
irq = platform_get_irq(pdev, 0);
|
2011-03-01 22:38:21 +08:00
|
|
|
if (irq < 0) {
|
2017-08-09 06:26:13 +08:00
|
|
|
dev_err(dev, "EHCI irq failed: %d\n", irq);
|
|
|
|
return irq;
|
2011-03-01 22:38:21 +08:00
|
|
|
}
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:45 +08:00
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2013-03-12 18:44:40 +08:00
|
|
|
regs = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR(regs))
|
|
|
|
return PTR_ERR(regs);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:48 +08:00
|
|
|
/*
|
|
|
|
* Right now device-tree probed devices don't get dma_mask set.
|
|
|
|
* Since shared usb code relies on it, set it here for now.
|
|
|
|
* Once we have dma capability bindings this can go away.
|
|
|
|
*/
|
2013-06-27 19:36:37 +08:00
|
|
|
ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
|
2013-06-10 23:28:49 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2013-03-12 18:44:48 +08:00
|
|
|
|
2013-06-10 23:28:49 +08:00
|
|
|
ret = -ENODEV;
|
2011-03-01 22:38:21 +08:00
|
|
|
hcd = usb_create_hcd(&ehci_omap_hc_driver, dev,
|
|
|
|
dev_name(dev));
|
2009-10-14 16:44:14 +08:00
|
|
|
if (!hcd) {
|
2013-03-12 18:44:40 +08:00
|
|
|
dev_err(dev, "Failed to create HCD\n");
|
|
|
|
return -ENOMEM;
|
2009-10-14 16:44:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
hcd->rsrc_start = res->start;
|
|
|
|
hcd->rsrc_len = resource_size(res);
|
2011-03-01 22:38:21 +08:00
|
|
|
hcd->regs = regs;
|
2013-03-13 21:16:03 +08:00
|
|
|
hcd_to_ehci(hcd)->caps = regs;
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:41 +08:00
|
|
|
omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
|
|
|
|
omap->nports = pdata->nports;
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, hcd);
|
|
|
|
|
|
|
|
/* get the PHY devices if needed */
|
|
|
|
for (i = 0 ; i < omap->nports ; i++) {
|
|
|
|
struct usb_phy *phy;
|
|
|
|
|
|
|
|
/* get the PHY device */
|
2018-04-18 17:26:21 +08:00
|
|
|
phy = devm_usb_get_phy_by_phandle(dev, "phys", i);
|
2013-04-17 16:24:25 +08:00
|
|
|
if (IS_ERR(phy)) {
|
2018-12-14 17:36:15 +08:00
|
|
|
ret = PTR_ERR(phy);
|
|
|
|
if (ret == -ENODEV) { /* no PHY */
|
|
|
|
phy = NULL;
|
2013-03-12 18:44:49 +08:00
|
|
|
continue;
|
2018-12-14 17:36:15 +08:00
|
|
|
}
|
2013-03-12 18:44:49 +08:00
|
|
|
|
2018-01-19 05:13:18 +08:00
|
|
|
if (ret != -EPROBE_DEFER)
|
|
|
|
dev_err(dev, "Can't get PHY for port %d: %d\n",
|
2013-03-12 18:44:41 +08:00
|
|
|
i, ret);
|
|
|
|
goto err_phy;
|
|
|
|
}
|
|
|
|
|
|
|
|
omap->phy[i] = phy;
|
2013-06-14 21:52:07 +08:00
|
|
|
|
|
|
|
if (pdata->port_mode[i] == OMAP_EHCI_PORT_MODE_PHY) {
|
|
|
|
usb_phy_init(omap->phy[i]);
|
|
|
|
/* bring PHY out of suspend */
|
|
|
|
usb_phy_set_suspend(omap->phy[i], 0);
|
|
|
|
}
|
2013-03-12 18:44:41 +08:00
|
|
|
}
|
|
|
|
|
2011-10-11 15:52:11 +08:00
|
|
|
pm_runtime_enable(dev);
|
|
|
|
pm_runtime_get_sync(dev);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2011-03-01 22:38:21 +08:00
|
|
|
/*
|
|
|
|
* An undocumented "feature" in the OMAP3 EHCI controller,
|
|
|
|
* causes suspended ports to be taken out of suspend when
|
|
|
|
* the USBCMD.Run/Stop bit is cleared (for example when
|
|
|
|
* we do ehci_bus_suspend).
|
|
|
|
* This breaks suspend-resume if the root-hub is allowed
|
|
|
|
* to suspend. Writing 1 to this undocumented register bit
|
|
|
|
* disables this feature and restores normal behavior.
|
|
|
|
*/
|
|
|
|
ehci_write(regs, EHCI_INSNREG04,
|
|
|
|
EHCI_INSNREG04_DISABLE_UNSUSPEND);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2012-05-04 19:24:47 +08:00
|
|
|
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "failed to add hcd with err %d\n", ret);
|
2012-06-21 18:44:31 +08:00
|
|
|
goto err_pm_runtime;
|
2012-05-04 19:24:47 +08:00
|
|
|
}
|
2013-11-05 10:46:02 +08:00
|
|
|
device_wakeup_enable(hcd->self.controller);
|
2012-05-04 19:24:47 +08:00
|
|
|
|
2013-03-13 21:14:43 +08:00
|
|
|
/*
|
2013-06-14 21:52:07 +08:00
|
|
|
* Bring PHYs out of reset for non PHY modes.
|
2013-03-13 21:14:43 +08:00
|
|
|
* Even though HSIC mode is a PHY-less mode, the reset
|
|
|
|
* line exists between the chips and can be modelled
|
|
|
|
* as a PHY device for reset control.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < omap->nports; i++) {
|
2013-06-14 21:52:07 +08:00
|
|
|
if (!omap->phy[i] ||
|
|
|
|
pdata->port_mode[i] == OMAP_EHCI_PORT_MODE_PHY)
|
2013-03-13 21:14:43 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
usb_phy_init(omap->phy[i]);
|
|
|
|
/* bring PHY out of suspend */
|
|
|
|
usb_phy_set_suspend(omap->phy[i], 0);
|
|
|
|
}
|
2012-06-05 20:34:27 +08:00
|
|
|
|
2009-10-14 16:44:14 +08:00
|
|
|
return 0;
|
|
|
|
|
2012-06-21 18:44:31 +08:00
|
|
|
err_pm_runtime:
|
2011-10-11 15:52:11 +08:00
|
|
|
pm_runtime_put_sync(dev);
|
2013-03-12 18:44:41 +08:00
|
|
|
|
|
|
|
err_phy:
|
|
|
|
for (i = 0; i < omap->nports; i++) {
|
|
|
|
if (omap->phy[i])
|
|
|
|
usb_phy_shutdown(omap->phy[i]);
|
|
|
|
}
|
|
|
|
|
2012-06-21 18:44:31 +08:00
|
|
|
usb_put_hcd(hcd);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ehci_hcd_omap_remove - shutdown processing for EHCI HCDs
|
|
|
|
* @pdev: USB Host Controller being removed
|
|
|
|
*
|
|
|
|
* Reverses the effect of usb_ehci_hcd_omap_probe(), first invoking
|
|
|
|
* the HCD's stop() method. It is always called from a thread
|
|
|
|
* context, normally "rmmod", "apmd", or something similar.
|
|
|
|
*/
|
|
|
|
static int ehci_hcd_omap_remove(struct platform_device *pdev)
|
|
|
|
{
|
2013-03-12 18:44:41 +08:00
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
|
|
struct omap_hcd *omap = (struct omap_hcd *)hcd_to_ehci(hcd)->priv;
|
|
|
|
int i;
|
2009-10-14 16:44:14 +08:00
|
|
|
|
|
|
|
usb_remove_hcd(hcd);
|
2012-06-05 20:34:27 +08:00
|
|
|
|
2013-03-12 18:44:41 +08:00
|
|
|
for (i = 0; i < omap->nports; i++) {
|
|
|
|
if (omap->phy[i])
|
|
|
|
usb_phy_shutdown(omap->phy[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
usb_put_hcd(hcd);
|
2011-10-11 15:52:11 +08:00
|
|
|
pm_runtime_put_sync(dev);
|
|
|
|
pm_runtime_disable(dev);
|
|
|
|
|
2009-10-14 16:44:14 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-03-12 18:44:48 +08:00
|
|
|
static const struct of_device_id omap_ehci_dt_ids[] = {
|
|
|
|
{ .compatible = "ti,ehci-omap" },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(of, omap_ehci_dt_ids);
|
|
|
|
|
2009-10-14 16:44:14 +08:00
|
|
|
static struct platform_driver ehci_hcd_omap_driver = {
|
|
|
|
.probe = ehci_hcd_omap_probe,
|
|
|
|
.remove = ehci_hcd_omap_remove,
|
2013-07-22 20:04:50 +08:00
|
|
|
.shutdown = usb_hcd_platform_shutdown,
|
2009-10-14 16:44:14 +08:00
|
|
|
/*.suspend = ehci_hcd_omap_suspend, */
|
|
|
|
/*.resume = ehci_hcd_omap_resume, */
|
|
|
|
.driver = {
|
2013-03-12 18:44:39 +08:00
|
|
|
.name = hcd_name,
|
2013-05-21 19:47:15 +08:00
|
|
|
.of_match_table = omap_ehci_dt_ids,
|
2009-10-14 16:44:14 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
static int __init ehci_omap_init(void)
|
|
|
|
{
|
|
|
|
if (usb_disabled())
|
|
|
|
return -ENODEV;
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
pr_info("%s: " DRIVER_DESC "\n", hcd_name);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
ehci_init_driver(&ehci_omap_hc_driver, &ehci_omap_overrides);
|
|
|
|
return platform_driver_register(&ehci_hcd_omap_driver);
|
|
|
|
}
|
|
|
|
module_init(ehci_omap_init);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
static void __exit ehci_omap_cleanup(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&ehci_hcd_omap_driver);
|
|
|
|
}
|
|
|
|
module_exit(ehci_omap_cleanup);
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-02-14 23:08:09 +08:00
|
|
|
MODULE_ALIAS("platform:ehci-omap");
|
2009-10-14 16:44:14 +08:00
|
|
|
MODULE_AUTHOR("Texas Instruments, Inc.");
|
|
|
|
MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>");
|
2013-03-12 18:44:48 +08:00
|
|
|
MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
|
2009-10-14 16:44:14 +08:00
|
|
|
|
2013-03-12 18:44:39 +08:00
|
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
|
|
MODULE_LICENSE("GPL");
|