From: Cristian Birsan <cristian.bir...@microchip.com> This driver adds support of PIC32 MUSB OTG controller as dual role device. It implements platform specific glue to reuse musb core.
Signed-off-by: Cristian Birsan <cristian.bir...@microchip.com> Signed-off-by: Purna Chandra Mandal <purna.man...@microchip.com> In-reply-to: 460027775-20729-2-git-send-email-purna.man...@microchip.com --- Changes in v2: - fix i386 build - fix indentation drivers/usb/musb/Kconfig | 9 +- drivers/usb/musb/Makefile | 1 + drivers/usb/musb/pic32.c | 608 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/musb/pic32.c diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 886526b..1970c1e 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -112,6 +112,13 @@ config USB_MUSB_BLACKFIN depends on (BF54x && !BF544) || (BF52x && ! BF522 && !BF523) depends on NOP_USB_XCEIV +config USB_MUSB_PIC32 + tristate "Microchip PIC32 USB platforms" + depends on MACH_PIC32 + depends on NOP_USB_XCEIV + help + Say y to enable PIC32 USB DRC controller support + config USB_MUSB_UX500 tristate "Ux500 platforms" depends on ARCH_U8500 || COMPILE_TEST @@ -149,7 +156,7 @@ config USB_UX500_DMA config USB_INVENTRA_DMA bool 'Inventra' - depends on USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN + depends on USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN || USB_MUSB_PIC32 help Enable DMA transfers using Mentor's engine. diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index f95befe..a38ebb7 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_USB_MUSB_DA8XX) += da8xx.o obj-$(CONFIG_USB_MUSB_BLACKFIN) += blackfin.o obj-$(CONFIG_USB_MUSB_UX500) += ux500.o obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o +obj-$(CONFIG_USB_MUSB_PIC32) += pic32.o obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o diff --git a/drivers/usb/musb/pic32.c b/drivers/usb/musb/pic32.c new file mode 100644 index 0000000..be36236 --- /dev/null +++ b/drivers/usb/musb/pic32.c @@ -0,0 +1,608 @@ +/* + * Microchip PIC32 MUSB Dual-Role Controller "glue layer". + * + * Cristian Birsan <cristian.bir...@microchip.com> + * Purna Chandra Mandal <purna.man...@microchip.com> + * Copyright (C) 2015 Microchip Technology Inc. All rights reserved. + * + * Based on the am35x and dsps "glue layer" code. + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/usb/of.h> + +#include "musb_core.h" + +#define MUSB_SOFTRST 0x7f +#define MUSB_SOFTRST_NRST BIT(0) +#define MUSB_SOFTRST_NRSTX BIT(1) + +/* USB Clock & Reset Control */ +#define USBCRCON 0x0 +#define USBCRCON_USBWKUPEN BIT(0) /* Enable remote wakeup interrupt */ +#define USBCRCON_USBRIE BIT(1) /* Enable Remote resume interrupt */ +#define USBCRCON_USBIE BIT(2) /* Enable USB General interrupt */ +#define USBCRCON_SENDMONEN BIT(3) /* Enable Session End VBUS monitoring */ +#define USBCRCON_BSVALMONEN BIT(4) /* Enable B-Device VBUS monitoring */ +#define USBCRCON_ASVALMONEN BIT(5) /* Enable A-Device VBUS monitoring */ +#define USBCRCON_VBUSMONEN BIT(6) /* Enable VBUS monitoring */ +#define USBCRCON_PHYIDEN BIT(7) /* Enabale USBPhy USBID monitoring */ +#define USBCRCON_USBIDVAL BIT(8) /* USBID override value */ +#define USBCRCON_USBIDOVEN BIT(9) /* Enable USBID override */ +#define USBCRCON_USBWKUP BIT(24) /* Remote wakeup status */ +#define USBCRCON_USBRF BIT(25) /* USB Remote resume status */ +#define USBCRCON_USBIF BIT(26) /* USB General interrupt status */ + +#define PIC32_TX_EP_MASK 0xffff /* EP0 + 15 Tx EPs */ +#define PIC32_RX_EP_MASK 0xfffe /* 15 Rx EPs */ + +#define POLL_SECONDS 2 + +struct pic32_musb { + void __iomem *cru; + struct clk *clk; + int oc_irq; + struct platform_device *platdev; + struct timer_list timer; /* otg_workaround timer */ + unsigned long last_timer; /* last timer data for */ +}; + +static irqreturn_t pic32_over_current(int irq, void *d) +{ + struct device *dev = d; + + dev_err(dev, "USB Host over-current detected !\n"); + + return IRQ_HANDLED; +} + +static void pic32_musb_enable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct pic32_musb *glue = dev_get_drvdata(dev->parent); + + /* Enable additional interrupts */ + enable_irq(glue->oc_irq); +} + +static void pic32_musb_disable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct pic32_musb *glue = dev_get_drvdata(dev->parent); + + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + + /* Disable additional interrupts */ + disable_irq(glue->oc_irq); +} + +static void pic32_musb_set_vbus(struct musb *musb, int is_on) +{ + WARN_ON(is_on && is_peripheral_active(musb)); +} + +static void otg_timer(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + struct device *dev = musb->controller; + struct pic32_musb *glue; + int skip_session = 0; + unsigned long flags; + u8 devctl; + + glue = dev_get_drvdata(dev->parent); + /* + * We poll because IP's won't expose several OTG-critical + * status change events (from the transceiver) otherwise. + */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + dev_dbg(dev, "Poll devctl %02x (%s)\n", devctl, + usb_otg_state_string(musb->xceiv->otg->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->otg->state) { + case OTG_STATE_A_WAIT_BCON: + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + skip_session = 1; + /* fall */ + case OTG_STATE_A_IDLE: + case OTG_STATE_B_IDLE: + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + if (!(devctl & MUSB_DEVCTL_SESSION) && !skip_session) + musb_writeb(musb->mregs, + MUSB_DEVCTL, MUSB_DEVCTL_SESSION); + mod_timer(&glue->timer, jiffies + POLL_SECONDS * HZ); + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static void pic32_musb_try_idle(struct musb *musb, unsigned long timeout) +{ + struct device *dev = musb->controller; + struct pic32_musb *glue = dev_get_drvdata(dev); + + if (timeout == 0) + timeout = jiffies + msecs_to_jiffies(3); + + /* Never idle if active, or when VBUS timeout is not set as host */ + if (musb->is_active || + (musb->a_wait_bcon == 0 && + musb->xceiv->otg->state == OTG_STATE_A_WAIT_BCON)) { + dev_dbg(dev, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->otg->state)); + del_timer(&glue->timer); + glue->last_timer = jiffies; + return; + } + + if (musb->port_mode != MUSB_PORT_MODE_DUAL_ROLE) + return; + + if (!musb->g.dev.driver) + return; + + if (time_after(glue->last_timer, timeout) && + timer_pending(&glue->timer)) { + dev_dbg(dev, "Longer idle timer already pending, ignoring\n"); + return; + } + glue->last_timer = timeout; + + dev_dbg(dev, "%s inactive, starting idle timer for %u ms\n", + usb_otg_state_string(musb->xceiv->otg->state), + jiffies_to_msecs(timeout - jiffies)); + mod_timer(&glue->timer, timeout); +} + +static irqreturn_t pic32_musb_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + struct device *dev = musb->controller; + struct pic32_musb *glue; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + + glue = dev_get_drvdata(dev->parent); + spin_lock_irqsave(&musb->lock, flags); + + /* Get endpoint interrupts */ + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX) & PIC32_RX_EP_MASK; + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX) & PIC32_TX_EP_MASK; + + /* Get usb core interrupts */ + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + if (!musb->int_usb && !(musb->int_rx || musb->int_tx)) { + dev_err(dev, "Got USB spurious interrupt !\n"); + goto irq_done; + } + + if (is_host_active(musb) && musb->int_usb & MUSB_INTR_BABBLE) + dev_err(dev, "CAUTION: Babble interrupt occurred!\n"); + + /* Drop spurious RX and TX if device is disconnected */ + if (musb->int_usb & MUSB_INTR_DISCONNECT) { + musb->int_tx = 0; + musb->int_rx = 0; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) + ret |= musb_interrupt(musb); + +irq_done: + /* Poll for ID change in OTG port mode */ + if (musb->xceiv->otg->state == OTG_STATE_B_IDLE && + musb->port_mode == MUSB_PORT_MODE_DUAL_ROLE) + mod_timer(&glue->timer, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int pic32_musb_set_mode(struct musb *musb, u8 mode) +{ + struct device *dev = musb->controller; + struct pic32_musb *glue; + u32 crcon; + + glue = dev_get_drvdata(dev->parent); + crcon = musb_readl(glue->cru, USBCRCON); + switch (mode) { + case MUSB_HOST: + crcon &= ~USBCRCON_USBIDVAL; + crcon |= USBCRCON_USBIDOVEN; + musb_writel(glue->cru, USBCRCON, crcon); + dev_dbg(dev, "MUSB Host mode enabled\n"); + break; + + case MUSB_PERIPHERAL: + crcon |= USBCRCON_USBIDVAL; + crcon |= USBCRCON_USBIDOVEN; + musb_writel(glue->cru, USBCRCON, crcon); + dev_dbg(dev, "MUSB Device mode enabled\n"); + break; + + case MUSB_OTG: + /* enable OTG mode using usb_id irq */ + dev_warn(dev, "MUSB OTG mode enabled\n"); + break; + + default: + dev_err(dev, "unsupported mode %d\n", mode); + return -EINVAL; + } + + return 0; +} + +static int pic32_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + struct pic32_musb *glue; + u16 hwvers; + int ret; + + glue = dev_get_drvdata(dev->parent); + + /* Returns zero if e.g. not clocked */ + hwvers = musb_read_hwvers(musb->mregs); + if (!hwvers) + return -ENODEV; + + /* The PHY transceiver is registered using device tree */ + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) + return -EPROBE_DEFER; + + setup_timer(&glue->timer, otg_timer, (unsigned long)musb); + + /* On-chip PHY and PLL is enabled by default */ + musb->isr = pic32_musb_interrupt; + + /* Request other interrupts */ + irq_set_status_flags(glue->oc_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(dev, glue->oc_irq, pic32_over_current, + 0, dev_name(dev), dev); + if (ret) { + dev_err(dev, "failed to request irq: %d\n", ret); + return ret; + } + + switch (musb->port_mode) { + case MUSB_PORT_MODE_DUAL_ROLE: + case MUSB_PORT_MODE_HOST: + case MUSB_PORT_MODE_GADGET: + break; + default: + dev_err(dev, "unsupported mode %d\n", musb->port_mode); + return -EINVAL; + } + + musb_writel(glue->cru, USBCRCON, + USBCRCON_USBIDOVEN | USBCRCON_PHYIDEN | + USBCRCON_USBIE | USBCRCON_USBRIE | + USBCRCON_USBWKUPEN | USBCRCON_VBUSMONEN); + + /* soft reset */ + musb_writeb(musb->mregs, MUSB_SOFTRST, MUSB_SOFTRST_NRSTX); + + return 0; +} + +static int pic32_musb_exit(struct musb *musb) +{ + struct device *dev = musb->controller; + struct pic32_musb *glue; + + glue = dev_get_drvdata(dev->parent); + del_timer_sync(&glue->timer); + /* no way to shutdown on-chip PHY and its PLL */ + usb_put_phy(musb->xceiv); + + return 0; +} + +/* PIC32 supports only 32bit read operation */ +static void pic32_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + void __iomem *fifo = hw_ep->fifo; + u32 val, rem = len % 4; + + /* USB stack ensures dst is always 32bit aligned. */ + readsl(fifo, dst, len / 4); + + if (rem) { + dst += len & ~0x03; + val = musb_readl(fifo, 0); + memcpy(dst, &val, rem); + } +} + +static const struct musb_platform_ops pic32_musb_ops = { + .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, + .init = pic32_musb_init, + .exit = pic32_musb_exit, + .read_fifo = pic32_read_fifo, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create, + .dma_exit = musbhs_dma_controller_destroy, +#endif + .enable = pic32_musb_enable, + .disable = pic32_musb_disable, + .set_mode = pic32_musb_set_mode, + .try_idle = pic32_musb_try_idle, + .set_vbus = pic32_musb_set_vbus, +}; + +static u64 musb_dmamask = DMA_BIT_MASK(32); + +static int get_musb_port_mode(struct device *dev) +{ + enum usb_dr_mode mode; + + mode = usb_get_dr_mode(dev); + switch (mode) { + case USB_DR_MODE_HOST: + return MUSB_PORT_MODE_HOST; + case USB_DR_MODE_PERIPHERAL: + return MUSB_PORT_MODE_GADGET; + case USB_DR_MODE_UNKNOWN: + case USB_DR_MODE_OTG: + default: + return MUSB_PORT_MODE_DUAL_ROLE; + } +} + +/* Microchip FIFO config 0 - fits in 8KB */ +static struct musb_fifo_cfg pic32_musb_fifo_cfg0[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 6, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 512, }, +}; + +/* Microchip FIFO config 1 - fits in 8KB */ +static struct musb_fifo_cfg pic32_musb_fifo_cfg1[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 4096, }, +}; + +static int pic32_probe_musb_device(struct pic32_musb *glue, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct musb_hdrc_platform_data pdata; + struct musb_hdrc_config *mconfig; + struct platform_device *platdev; + struct resource resources[3]; + struct resource *res; + int fifo_mode = 0; + int ret; + + memset(resources, 0, sizeof(resources)); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mc"); + if (!res) { + dev_err(&pdev->dev, "failed to get memory.\n"); + return -EINVAL; + } + resources[0] = *res; + resources[0].name = "mc"; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "mc"); + if (!res) { + dev_err(&pdev->dev, "failed to get irq.\n"); + return -EINVAL; + } + resources[1] = *res; + resources[1].name = "mc"; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "dma"); + if (!res) { + dev_warn(&pdev->dev, "No MUSB DMA irq provided. Assuming PIO mode.\n"); + } else { + resources[2] = *res; + resources[2].name = "dma"; + } + + /* allocate child platform device for musb platform driver */ + platdev = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!platdev) + return -ENOMEM; + + glue->platdev = platdev; + + platdev->dev.parent = &pdev->dev; + platdev->dev.dma_mask = &musb_dmamask; + platdev->dev.coherent_dma_mask = 0; + + ret = platform_device_add_resources(platdev, resources, + ARRAY_SIZE(resources)); + if (ret) { + dev_err(&pdev->dev, "failed to add resources\n"); + goto err_pdev; + } + + mconfig = devm_kzalloc(&pdev->dev, sizeof(*mconfig), GFP_KERNEL); + if (!mconfig) { + ret = -ENOMEM; + goto err_pdev; + } + + mconfig->host_port_deassert_reset_at_resume = 1; + mconfig->multipoint = of_property_read_bool(np, "mentor,multipoint"); + of_property_read_u32(np, "mentor,num-eps", (u32 *)&mconfig->num_eps); + of_property_read_u32(np, "mentor,ram-bits", (u32 *)&mconfig->ram_bits); + + /* FiFo configuration */ + of_property_read_u32(np, "microchip,fifo-mode", (u32 *)&fifo_mode); + dev_info(&pdev->dev, "using fifo mode %d\n", fifo_mode); + switch (fifo_mode) { + case 1: + mconfig->fifo_cfg = pic32_musb_fifo_cfg1; + mconfig->fifo_cfg_size = ARRAY_SIZE(pic32_musb_fifo_cfg1); + break; + default: + mconfig->fifo_cfg = pic32_musb_fifo_cfg0; + mconfig->fifo_cfg_size = ARRAY_SIZE(pic32_musb_fifo_cfg0); + break; + } + + /* add platform data */ + pdata.config = mconfig; + pdata.platform_ops = &pic32_musb_ops; + pdata.mode = get_musb_port_mode(&pdev->dev); + + /* DT keeps this entry in mA, but musb expects it as per USB spec */ + of_property_read_u32(np, "mentor,power", (u32 *)&pdata.power); + pdata.power /= 2; + + ret = platform_device_add_data(platdev, &pdata, sizeof(pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err_pdev; + } + + ret = platform_device_add(platdev); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err_pdev; + } + + return 0; + +err_pdev: + platform_device_put(platdev); + return ret; +} + +static int pic32_musb_probe(struct platform_device *pdev) +{ + struct device_node *dev_oc; + struct pic32_musb *glue; + struct resource *cru; + int ret; + + /* allocate glue */ + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + dev_oc = of_parse_phandle(pdev->dev.of_node, "usb_overcurrent", 0); + if (!dev_oc) { + dev_err(&pdev->dev, "error usb_overcurrent property missing\n"); + return -EINVAL; + } + + glue->oc_irq = irq_of_parse_and_map(dev_oc, 0); + if (!glue->oc_irq) { + dev_err(&pdev->dev, "cannot get over current irq!\n"); + ret = -EINVAL; + goto err_put_oc; + } + + /* clock-reset module */ + cru = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usbcr"); + glue->cru = devm_ioremap_resource(&pdev->dev, cru); + if (IS_ERR(glue->cru)) { + ret = PTR_ERR(glue->cru); + goto err_put_oc; + } + + glue->clk = devm_clk_get(&pdev->dev, "usb_clk"); + if (IS_ERR(glue->clk)) { + dev_err(&pdev->dev, "failed to get usb_clk %d\n", ret); + ret = PTR_ERR(glue->clk); + goto err_put_oc; + } + + ret = clk_prepare_enable(glue->clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable usb_clk %d\n", ret); + goto err_put_oc; + } + + platform_set_drvdata(pdev, glue); + + ret = pic32_probe_musb_device(glue, pdev); + if (ret) { + clk_disable_unprepare(glue->clk); + goto err_put_oc; + } + +err_put_oc: + of_node_put(dev_oc); + return ret; +} + +static int pic32_remove(struct platform_device *pdev) +{ + struct pic32_musb *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->platdev); + clk_disable_unprepare(glue->clk); + + return 0; +} + +static const struct of_device_id pic32_musb_of_match[] = { + { .compatible = "microchip,pic32mzda-usb" }, + { } +}; +MODULE_DEVICE_TABLE(of, pic32_musb_of_match); + +static struct platform_driver pic32_musb_driver = { + .probe = pic32_musb_probe, + .remove = pic32_remove, + .driver = { + .name = "musb-pic32mz", + .of_match_table = pic32_musb_of_match, + }, +}; + +MODULE_DESCRIPTION("Microchip PIC32 MUSB Glue Layer"); +MODULE_AUTHOR("Cristian Birsan <cristian.bir...@microchip.com>"); +MODULE_LICENSE("GPL v2"); + +module_platform_driver(pic32_musb_driver); -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html