📄 at91_udc.c
字号:
| USB_REQ_CLEAR_FEATURE: tmp = w_index & USB_ENDPOINT_NUMBER_MASK; ep = &udc->ep[tmp]; if (w_value != USB_ENDPOINT_HALT || tmp > NUM_ENDPOINTS) goto stall; if (tmp == 0) goto succeed; if (!ep->desc || ep->is_iso) goto stall; if ((w_index & USB_DIR_IN)) { if (!ep->is_in) goto stall; } else if (ep->is_in) goto stall; at91_udp_write(AT91_UDP_RST_EP, ep->int_mask); at91_udp_write(AT91_UDP_RST_EP, 0); tmp = __raw_readl(ep->creg); tmp |= CLR_FX; tmp &= ~(SET_FX | AT91_UDP_FORCESTALL); __raw_writel(tmp, ep->creg); if (!list_empty(&ep->queue)) handle_ep(ep); goto succeed; }#undef w_value#undef w_index#undef w_length /* pass request up to the gadget driver */ status = udc->driver->setup(&udc->gadget, &pkt.r); if (status < 0) {stall: VDBG("req %02x.%02x protocol STALL; stat %d\n", pkt.r.bRequestType, pkt.r.bRequest, status); csr |= AT91_UDP_FORCESTALL; __raw_writel(csr, creg); udc->req_pending = 0; } return;succeed: /* immediate successful (IN) STATUS after zero length DATA */ PACKET("ep0 in/status\n");write_in: csr |= AT91_UDP_TXPKTRDY; __raw_writel(csr, creg); udc->req_pending = 0; return;}static void handle_ep0(struct at91_udc *udc){ struct at91_ep *ep0 = &udc->ep[0]; u32 __iomem *creg = ep0->creg; u32 csr = __raw_readl(creg); struct at91_request *req; if (unlikely(csr & AT91_UDP_STALLSENT)) { nuke(ep0, -EPROTO); udc->req_pending = 0; csr |= CLR_FX; csr &= ~(SET_FX | AT91_UDP_STALLSENT | AT91_UDP_FORCESTALL); __raw_writel(csr, creg); VDBG("ep0 stalled\n"); csr = __raw_readl(creg); } if (csr & AT91_UDP_RXSETUP) { nuke(ep0, 0); udc->req_pending = 0; handle_setup(udc, ep0, csr); return; } if (list_empty(&ep0->queue)) req = NULL; else req = list_entry(ep0->queue.next, struct at91_request, queue); /* host ACKed an IN packet that we sent */ if (csr & AT91_UDP_TXCOMP) { csr |= CLR_FX; csr &= ~(SET_FX | AT91_UDP_TXCOMP); /* write more IN DATA? */ if (req && ep0->is_in) { if (handle_ep(ep0)) udc->req_pending = 0; /* * Ack after: * - last IN DATA packet (including GET_STATUS) * - IN/STATUS for OUT DATA * - IN/STATUS for any zero-length DATA stage * except for the IN DATA case, the host should send * an OUT status later, which we'll ack. */ } else { udc->req_pending = 0; __raw_writel(csr, creg); /* * SET_ADDRESS takes effect only after the STATUS * (to the original address) gets acked. */ if (udc->wait_for_addr_ack) { u32 tmp; at91_udp_write(AT91_UDP_FADDR, AT91_UDP_FEN | udc->addr); tmp = at91_udp_read(AT91_UDP_GLB_STAT); tmp &= ~AT91_UDP_FADDEN; if (udc->addr) tmp |= AT91_UDP_FADDEN; at91_udp_write(AT91_UDP_GLB_STAT, tmp); udc->wait_for_addr_ack = 0; VDBG("address %d\n", udc->addr); } } } /* OUT packet arrived ... */ else if (csr & AT91_UDP_RX_DATA_BK0) { csr |= CLR_FX; csr &= ~(SET_FX | AT91_UDP_RX_DATA_BK0); /* OUT DATA stage */ if (!ep0->is_in) { if (req) { if (handle_ep(ep0)) { /* send IN/STATUS */ PACKET("ep0 in/status\n"); csr = __raw_readl(creg); csr &= ~SET_FX; csr |= CLR_FX | AT91_UDP_TXPKTRDY; __raw_writel(csr, creg); udc->req_pending = 0; } } else if (udc->req_pending) { /* * AT91 hardware has a hard time with this * "deferred response" mode for control-OUT * transfers. (For control-IN it's fine.) * * The normal solution leaves OUT data in the * fifo until the gadget driver is ready. * We couldn't do that here without disabling * the IRQ that tells about SETUP packets, * e.g. when the host gets impatient... * * Working around it by copying into a buffer * would almost be a non-deferred response, * except that it wouldn't permit reliable * stalling of the request. Instead, demand * that gadget drivers not use this mode. */ DBG("no control-OUT deferred responses!\n"); __raw_writel(csr | AT91_UDP_FORCESTALL, creg); udc->req_pending = 0; } /* STATUS stage for control-IN; ack. */ } else { PACKET("ep0 out/status ACK\n"); __raw_writel(csr, creg); /* "early" status stage */ if (req) done(ep0, req, 0); } }}static irqreturn_t at91_udc_irq (int irq, void *_udc, struct pt_regs *r){ struct at91_udc *udc = _udc; u32 rescans = 5; while (rescans--) { u32 status = at91_udp_read(AT91_UDP_ISR); status &= at91_udp_read(AT91_UDP_IMR); if (!status) break; /* USB reset irq: not maskable */ if (status & AT91_UDP_ENDBUSRES) { at91_udp_write(AT91_UDP_IDR, ~MINIMUS_INTERRUPTUS); at91_udp_write(AT91_UDP_IER, MINIMUS_INTERRUPTUS); /* Atmel code clears this irq twice */ at91_udp_write(AT91_UDP_ICR, AT91_UDP_ENDBUSRES); at91_udp_write(AT91_UDP_ICR, AT91_UDP_ENDBUSRES); VDBG("end bus reset\n"); udc->addr = 0; stop_activity(udc); /* enable ep0 */ at91_udp_write(AT91_UDP_CSR(0), AT91_UDP_EPEDS | AT91_UDP_EPTYPE_CTRL); udc->gadget.speed = USB_SPEED_FULL; udc->suspended = 0; at91_udp_write(AT91_UDP_IER, AT91_UDP_EP(0)); /* * NOTE: this driver keeps clocks off unless the * USB host is present. That saves power, and also * eliminates IRQs (reset, resume, suspend) that can * otherwise flood from the controller. If your * board doesn't support VBUS detection, suspend and * resume irq logic may need more attention... */ /* host initiated suspend (3+ms bus idle) */ } else if (status & AT91_UDP_RXSUSP) { at91_udp_write(AT91_UDP_IDR, AT91_UDP_RXSUSP); at91_udp_write(AT91_UDP_IER, AT91_UDP_RXRSM); at91_udp_write(AT91_UDP_ICR, AT91_UDP_RXSUSP); // VDBG("bus suspend\n"); if (udc->suspended) continue; udc->suspended = 1; /* * NOTE: when suspending a VBUS-powered device, the * gadget driver should switch into slow clock mode * and then into standby to avoid drawing more than * 500uA power (2500uA for some high-power configs). */ if (udc->driver && udc->driver->suspend) udc->driver->suspend(&udc->gadget); /* host initiated resume */ } else if (status & AT91_UDP_RXRSM) { at91_udp_write(AT91_UDP_IDR, AT91_UDP_RXRSM); at91_udp_write(AT91_UDP_IER, AT91_UDP_RXSUSP); at91_udp_write(AT91_UDP_ICR, AT91_UDP_RXRSM); // VDBG("bus resume\n"); if (!udc->suspended) continue; udc->suspended = 0; /* * NOTE: for a VBUS-powered device, the gadget driver * would normally want to switch out of slow clock * mode into normal mode. */ if (udc->driver && udc->driver->resume) udc->driver->resume(&udc->gadget); /* endpoint IRQs are cleared by handling them */ } else { int i; unsigned mask = 1; struct at91_ep *ep = &udc->ep[1]; if (status & mask) handle_ep0(udc); for (i = 1; i < NUM_ENDPOINTS; i++) { mask <<= 1; if (status & mask) handle_ep(ep); ep++; } } } return IRQ_HANDLED;}/*-------------------------------------------------------------------------*/static struct at91_udc controller = { .gadget = { .ops = &at91_udc_ops, .ep0 = &controller.ep[0].ep, .name = driver_name, .dev = { .bus_id = "gadget" } }, .ep[0] = { .ep = { .name = ep0name, .ops = &at91_ep_ops, }, .udc = &controller, .maxpacket = 8, .creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(0)), .int_mask = 1 << 0, }, .ep[1] = { .ep = { .name = "ep1", .ops = &at91_ep_ops, }, .udc = &controller, .is_pingpong = 1, .maxpacket = 64, .creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(1)), .int_mask = 1 << 1, }, .ep[2] = { .ep = { .name = "ep2", .ops = &at91_ep_ops, }, .udc = &controller, .is_pingpong = 1, .maxpacket = 64, .creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(2)), .int_mask = 1 << 2, }, .ep[3] = { .ep = { /* could actually do bulk too */ .name = "ep3-int", .ops = &at91_ep_ops, }, .udc = &controller, .maxpacket = 8, .creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(3)), .int_mask = 1 << 3, }, .ep[4] = { .ep = { .name = "ep4", .ops = &at91_ep_ops, }, .udc = &controller, .is_pingpong = 1, .maxpacket = 256, .creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(4)), .int_mask = 1 << 4, }, .ep[5] = { .ep = { .name = "ep5", .ops = &at91_ep_ops, }, .udc = &controller, .is_pingpong = 1, .maxpacket = 256, .creg = (void __iomem *)(AT91_VA_BASE_UDP + AT91_UDP_CSR(5)), .int_mask = 1 << 5, }, /* ep6 and ep7 are also reserved */};static irqreturn_t at91_vbus_irq(int irq, void *_udc, struct pt_regs *r){ struct at91_udc *udc = _udc; unsigned value; /* vbus needs at least brief debouncing */ udelay(10); value = at91_get_gpio_value(udc->board.vbus_pin); if (value != udc->vbus) at91_vbus_session(&udc->gadget, value); return IRQ_HANDLED;}int usb_gadget_register_driver (struct usb_gadget_driver *driver){ struct at91_udc *udc = &controller; int retval; if (!driver || driver->speed != USB_SPEED_FULL || !driver->bind || !driver->unbind || !driver->setup) { DBG("bad parameter.\n"); return -EINVAL; } if (udc->driver) { DBG("UDC already has a gadget driver\n"); return -EBUSY; } udc->driver = driver; udc->gadget.dev.driver = &driver->driver; udc->gadget.dev.driver_data = &driver->driver; udc->enabled = 1; udc->selfpowered = 1; retval = driver->bind(&udc->gadget); if (retval) { DBG("driver->bind() returned %d\n", retval); udc->driver = NULL; return retval; } local_irq_disable(); pullup(udc, 1); local_irq_enable(); DBG("bound to %s\n", driver->driver.name); return 0;}EXPORT_SYMBOL (usb_gadget_register_driver);int usb_gadget_unregister_driver (struct usb_gadget_driver *driver){ struct at91_udc *udc = &controller; if (!driver || driver != udc->driver) return -EINVAL; local_irq_disable(); udc->enabled = 0; pullup(udc, 0); local_irq_enable(); driver->unbind(&udc->gadget); udc->driver = NULL; DBG("unbound from %s\n", driver->driver.name); return 0;}EXPORT_SYMBOL (usb_gadget_unregister_driver);/*-------------------------------------------------------------------------*/static void at91udc_shutdown(struct platform_device *dev){ /* force disconnect on reboot */ pullup(platform_get_drvdata(dev), 0);}static int __devinit at91udc_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; struct at91_udc *udc; int retval; if (!dev->platform_data) { /* small (so we copy it) but critical! */ DBG("missing platform_data\n"); return -ENODEV; } if (!request_mem_region(AT91_BASE_UDP, SZ_16K, driver_name)) { DBG("someone's using UDC memory\n"); return -EBUSY; } /* init software state */ udc = &controller; udc->gadget.dev.parent = dev; udc->board = *(struct at91_udc_data *) dev->platform_data; udc->pdev = pdev; udc_reinit(udc); udc->enabled = 0; /* get interface and function clocks */ udc->iclk = clk_get(dev, "udc_clk"); udc->fclk = clk_get(dev, "udpck"); if (IS_ERR(udc->iclk) || IS_ERR(udc->fclk)) { DBG("clocks missing\n"); return -ENODEV; } retval = device_register(&udc->gadget.dev); if (retval < 0) goto fail0; /* disable everything until there's a gadget driver and vbus */ pullup(udc, 0); /* request UDC and maybe VBUS irqs */ if (request_irq(AT91_ID_UDP, at91_udc_irq, SA_INTERRUPT, driver_name, udc)) { DBG("request irq %d failed\n", AT91_ID_UDP); retval = -EBUSY; goto fail1; } if (udc->board.vbus_pin > 0) { if (request_irq(udc->board.vbus_pin, at91_vbus_irq, SA_INTERRUPT, driver_name, udc)) { DBG("request vbus irq %d failed\n", udc->board.vbus_pin); free_irq(AT91_ID_UDP, udc); retval = -EBUSY; goto fail1; } } else { DBG("no VBUS detection, assuming always-on\n"); udc->vbus = 1; } dev_set_drvdata(dev, udc); create_debug_file(udc); INFO("%s version %s\n", driver_name, DRIVER_VERSION); return 0;fail1: device_unregister(&udc->gadget.dev);fail0: release_mem_region(AT91_VA_BASE_UDP, SZ_16K); DBG("%s probe failed, %d\n", driver_name, retval); return retval;}static int __devexit at91udc_remove(struct platform_device *dev){ struct at91_udc *udc = platform_get_drvdata(dev); DBG("remove\n"); pullup(udc, 0); if (udc->driver != 0) usb_gadget_unregister_driver(udc->driver); remove_debug_file(udc); if (udc->board.vbus_pin > 0) free_irq(udc->board.vbus_pin, udc); free_irq(AT91_ID_UDP, udc); device_unregister(&udc->gadget.dev); release_mem_region(AT91_BASE_UDP, SZ_16K); clk_put(udc->iclk); clk_put(udc->fclk); return 0;}#ifdef CONFIG_PMstatic int at91udc_suspend(struct platform_device *dev, pm_message_t mesg){ struct at91_udc *udc = platform_get_drvdata(dev); /* * The "safe" suspend transitions are opportunistic ... e.g. when * the USB link is suspended (48MHz clock autogated off), or when * it's disconnected (programmatically gated off, elsewhere). * Then we can suspend, and the chip can enter slow clock mode. * * The problem case is some component (user mode?) suspending this * device while it's active, with the 48 MHz clock in use. There * are two basic approaches: (a) veto suspend levels involving slow * clock mode, (b) disconnect, so 48 MHz will no longer be in use * and we can enter slow clock mode. This uses (b) for now, since * it's simplest until AT91 PM exists and supports the other option. */ if (udc->vbus && !udc->suspended) pullup(udc, 0); return 0;}static int at91udc_resume(struct platform_device *dev){ struct at91_udc *udc = platform_get_drvdata(dev); /* maybe reconnect to host; if so, clocks on */ pullup(udc, 1); return 0;}#else#define at91udc_suspend NULL#define at91udc_resume NULL#endifstatic struct platform_driver at91_udc = { .probe = at91udc_probe, .remove = __devexit_p(at91udc_remove), .shutdown = at91udc_shutdown, .suspend = at91udc_suspend, .resume = at91udc_resume, .driver = { .name = (char *) driver_name, .owner = THIS_MODULE, },};static int __devinit udc_init_module(void){ return platform_driver_register(&at91_udc);}module_init(udc_init_module);static void __devexit udc_exit_module(void){ platform_driver_unregister(&at91_udc);}module_exit(udc_exit_module);MODULE_DESCRIPTION("AT91RM9200 udc driver");MODULE_AUTHOR("Thomas Rathbone, David Brownell");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -